Windows驱动开发入门-进程和线程通知

进程通知实战

数据结构

某个进程无论何时被创建或销毁时,感兴趣的驱动都能从内核得到通知。用PsGetCreateProcessNotifyRoutineEx注册进程通知,最多注册64个通知。

1
2
3
4
5
6
7
8
9
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
_In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
_In_ BOOLEAN Remove //FALSE注册 TRUE取消
);
typedef void (*PCREATE_PROCESS_NOTIFY_ROUTINE_EX)( //回调原型
_Inout_ PEPROCESS Process, //新创建或即将销毁的进程对象
_In_ HANDLE ProcessId, //进程唯一标识 实际不是HANDLE是ID
_Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo //被创建进程详细信息 被销毁进程为NULL
);

还有PsSetCreateProcessNotifyRoutineEx2,但该回调在微进程中也会被调用。微进程用于WSL中容纳Linux进程。

用到进程通知回调的驱动程序必须在PE映像头中设置IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY标志,否则调用注册函数返回STATUS_ACCESS_DENIED。需要通过链接器命令行中添加参数/integritycheck。

线程通知用PsSetCreateThreadNotifyRoutine注册,用PsRemoveCreateThreadNotifyRoutine注销。

当一个EXE、DLL或驱动映像文件装入时,驱动都能收到通知。用PsSetLoadImageNotifyRoutine注册映像载入通知回调,用PsRemoveImageNotifyRoutine注销。但并没有映像卸载回调机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
typedef void (*PLOAD_IMAGE_NOTIFY_ROUTINE)( //映像载入通知回调函数原型
_In_opt_ PUNICODE_STRING FullImageName, //可能为NULL 不为NULL也可能不对 为内部NT格式路径 以\Device\HarddiskVolumeX\...开头
_In_ HANDLE ProcessId, //载入映像的进程ID 驱动内核映像为0
_In_ PIMAGE_INFO ImageInfo //映像附加信息
);
#define IMAGE_ADDRESSING_MODE_32BIT 3
typedef struct _IMAGE_INFO {
union {
ULONG Properties;
struct {
ULONG ImageAddressingMode : 8; // Code addressing mode
ULONG SystemModeImage : 1; // System mode image 内核映像设置这个标志
ULONG ImageMappedToAllPids : 1; // Image mapped into all processes
ULONG ExtendedInfoPresent : 1; // IMAGE_INFO_EX available 设置该标志则启用IMAGE_INFO_EX结构
ULONG MachineTypeMismatch : 1; // Architecture type mismatch
ULONG ImageSignatureLevel : 4; // Signature level 签名级别 见SE_SIGNING_LEVEL
ULONG ImageSignatureType : 3; // Signature type 签名类型 见SE_IMAGE_SIGNATURE_TYPE
ULONG ImagePartialMap : 1; // Nonzero if entire image is not mapped
ULONG Reserved : 12;
};
};
PVOID ImageBase; //映像装入基地址
ULONG ImageSelector;
SIZE_T ImageSize; //映像大小
ULONG ImageSectionNumber;
} IMAGE_INFO, *PIMAGE_INFO;
typedef struct _IMAGE_INFO_EX {
SIZE_T Size;
IMAGE_INFO ImageInfo;
struct _FILE_OBJECT *FileObject;
} IMAGE_INFO_EX, *PIMAGE_INFO_EX;
typedef UCHAR SE_SIGNING_LEVEL, *PSE_SIGNING_LEVEL;
#define SE_SIGNING_LEVEL_UNCHECKED 0x00000000
#define SE_SIGNING_LEVEL_UNSIGNED 0x00000001
#define SE_SIGNING_LEVEL_ENTERPRISE 0x00000002
#define SE_SIGNING_LEVEL_CUSTOM_1 0x00000003
#define SE_SIGNING_LEVEL_DEVELOPER SE_SIGNING_LEVEL_CUSTOM_1
#define SE_SIGNING_LEVEL_AUTHENTICODE 0x00000004
#define SE_SIGNING_LEVEL_CUSTOM_2 0x00000005
#define SE_SIGNING_LEVEL_STORE 0x00000006
#define SE_SIGNING_LEVEL_CUSTOM_3 0x00000007
#define SE_SIGNING_LEVEL_ANTIMALWARE SE_SIGNING_LEVEL_CUSTOM_3
#define SE_SIGNING_LEVEL_MICROSOFT 0x00000008
#define SE_SIGNING_LEVEL_CUSTOM_4 0x00000009
#define SE_SIGNING_LEVEL_CUSTOM_5 0x0000000A
#define SE_SIGNING_LEVEL_DYNAMIC_CODEGEN 0x0000000B
#define SE_SIGNING_LEVEL_WINDOWS 0x0000000C
#define SE_SIGNING_LEVEL_CUSTOM_7 0x0000000D
#define SE_SIGNING_LEVEL_WINDOWS_TCB 0x0000000E
#define SE_SIGNING_LEVEL_CUSTOM_6 0x0000000F
typedef enum _SE_IMAGE_SIGNATURE_TYPE {
SeImageSignatureNone = 0,
SeImageSignatureEmbedded,
SeImageSignatureCache,
SeImageSignatureCatalogCached,
SeImageSignatureCatalogNotCached,
SeImageSignatureCatalogHint,
SeImageSignaturePackageCatalog,
SeImageSignaturePplMitigated
} SE_IMAGE_SIGNATURE_TYPE, *PSE_IMAGE_SIGNATURE_TYPE;

//IMAGE_INFO_EX结构可用CONTAINING_RECORD访问:
if(ImageInfo->ExtendedInfoPresent){
auto exinfo=CONTAINING_RECORD(ImageInfo,IMAGE_INFO_EX,ImageInfo);
//access FileObject
};

进程详细信息结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct _PS_CREATE_NOTIFY_INFO {
_In_ SIZE_T Size;
union {
_In_ ULONG Flags;
struct {
_In_ ULONG FileOpenNameAvailable : 1;
_In_ ULONG IsSubsystemProcess : 1; //如果为微进程
_In_ ULONG Reserved : 30;
};
};
_In_ HANDLE ParentProcessId; //父进程ID
_In_ CLIENT_ID CreatingThreadId; //进程创建函数调用者的线程ID和进程ID合并值
_Inout_ struct _FILE_OBJECT *FileObject;
_In_ PCUNICODE_STRING ImageFileName; //可执行映像文件名 FileOpenNameAvailable被设置时有效
_In_opt_ PCUNICODE_STRING CommandLine; //创建进程的命令行
_Inout_ NTSTATUS CreationStatus; //返回给调用者的状态 例如可返回STATUS_ACCESS_DENIED等失败状态阻止进程创建
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
typedef struct _CLIENT_ID {
HANDLE UniqueProcess; //与ParentProcessId字段相同
HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;

例如,我们把所有进程创建和销毁信息存在一个LIST_ENTRY链表中,并用快速互斥量进行保护。用户模式公用结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum class ItemType : short {
None,
ProcessCreate,
ProcessExit,
ThreadCreate,
ThreadExit,
ImageLoad
};
struct ItemHeader{
ItemTYpe Type; //事件类型
USHORT Size; //负荷大小
LARGE_ITNEGER Time; //事件事件
};
struct ProcessExitInfo : ItemHeader{
ULONG ProcessId; //退出进程ID
};
struct ProcessCreateInfo : ItemHeader { //创建进程
ULONG ProcessId; //进程ID
ULONG ParentProcessId; //父进程ID
USHORT CommandLineLength; //命令行长度
USHORT CommandLineOffset; //命令行从结构起始处开始偏移量
};
struct ThreadCreateExitInfo : ItemHeader {
ULONG ThreadId;
ULONG ProcessId;
};

对于ProcessCreateInfo结构中,为了存储命令行字符串。若字符串存储简单用WCHAR CommandLine[1024]可能导致内存浪费或截断,用UNICODE_STRING CommandLine时该类型不会在用户模式中定义,且内部指向系统空间,用户模式无法访问。所以这里将命令行字符串存储在该结构后面,并用偏移量指示。

对于需要存储的链表,不应暴露给用户模式,这里另建一个头文件:

1
2
3
4
5
6
7
8
9
10
template<typename T>
struct FullItem {
LIST_ENTRY Entry;
T Data;
};
struct Globals { //链表头部
LIST_ENTRY ItemsHead;
int ItemCount;
FastMutex Mutex;
};

各例程实现

DriverEntry例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Globals g_Globals;
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING) {
auto status = STATUS_SUCCESS;
InitializeListHead(&g_Globals.ItemsHead);
g_Globals.Mutex.Init();
PDEVICE_OBJECT DeviceObject = nullptr;
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\sysmon");
bool symLinkCreated = false;
bool processCallbacks = false, threadCallbacks = false;
do {
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\sysmon");
status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &DeviceObject);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to create device (0x%08X)\n", status));
break;
}
DeviceObject->Flags |= DO_DIRECT_IO;
status = IoCreateSymbolicLink(&symLink, &devName);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to create sym link (0x%08X)\n", status));
break;
}
symLinkCreated = true;
status = PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, FALSE);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to register process callback (0x%08X)\n", status));
break;
}
processCallbacks = true;
status = PsSetCreateThreadNotifyRoutine(OnThreadNotify); //注册线程通知
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to set thread callback (status=%08X)\n", status));
break;
}
threadCallbacks = true;
status = PsSetLoadImageNotifyRoutine(OnImageLoadNotify);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to set image load callback (status=%08X)\n", status));
break;
}
} while (false);
if (!NT_SUCCESS(status)) {
if (threadCallbacks)
PsRemoveCreateThreadNotifyRoutine(OnThreadNotify);
if (processCallbacks)
PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, TRUE);
if (symLinkCreated)
IoDeleteSymbolicLink(&symLink);
if (DeviceObject)
IoDeleteDevice(DeviceObject);
}
DriverObject->DriverUnload = SysMonUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = SysMonCreateClose;
DriverObject->MajorFunction[IRP_MJ_READ] = SysMonRead;
return status;
}

进程通知例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void OnProcessNotify(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) {
UNREFERENCED_PARAMETER(Process);
if (CreateInfo) {
// process created
USHORT allocSize = sizeof(FullItem<ProcessCreateInfo>);
USHORT commandLineSize = 0;
if (CreateInfo->CommandLine) {
commandLineSize = CreateInfo->CommandLine->Length;
allocSize += commandLineSize;
}
auto info = (FullItem<ProcessCreateInfo>*)ExAllocatePoolWithTag(PagedPool, allocSize, DRIVER_TAG);
if (info == nullptr) {
KdPrint((DRIVER_PREFIX "failed allocation\n"));
return;
}
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Type = ItemType::ProcessCreate;
item.Size = sizeof(ProcessCreateInfo) + commandLineSize;
item.ProcessId = HandleToULong(ProcessId);
item.ParentProcessId = HandleToULong(CreateInfo->ParentProcessId);
if (commandLineSize > 0) {
::memcpy((UCHAR*)&item + sizeof(item), CreateInfo->CommandLine->Buffer, commandLineSize);
item.CommandLineLength = commandLineSize / sizeof(WCHAR); // length in WCHARs
item.CommandLineOffset = sizeof(item);
}
else
item.CommandLineLength = 0;
PushItem(&info->Entry);
}
else {
// process exited
auto info = (FullItem<ProcessExitInfo>*)ExAllocatePoolWithTag(PagedPool, sizeof(FullItem<ProcessExitInfo>), DRIVER_TAG); //分配内存
if (info == nullptr) {
KdPrint((DRIVER_PREFIX "failed allocation\n"));
return;
}
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time); //返回当前系统UTC时间 从1601年1月1日以来计数值
item.Type = ItemType::ProcessExit; //类型
item.ProcessId = HandleToULong(ProcessId); //HANDLE转无符号32位整数
item.Size = sizeof(ProcessExitInfo); //大小
PushItem(&info->Entry);
}
}
void PushItem(LIST_ENTRY * entry) { //将新数据项加入链表尾部
AutoLock<FastMutex> lock(g_Globals.Mutex); //获取快速互斥量
if (g_Globals.ItemCount > 1024) {
// too many items, remove oldest one
auto head = RemoveHeadList(&g_Globals.ItemsHead);
g_Globals.ItemCount--;
auto item = CONTAINING_RECORD(head, FullItem<ItemHeader>, Entry);
ExFreePool(item);
}
InsertTailList(&g_Globals.ItemsHead, entry);
g_Globals.ItemCount++;
}

数据提供给用户模式

这里打算让用户模式用读请求对驱动进行轮询以获取信息。驱动会向用户提供的缓冲区填充尽可能多的事件,直到缓冲区用完或队列里没有事件为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
NTSTATUS SysMonRead(PDEVICE_OBJECT, PIRP Irp) {
auto stack = IoGetCurrentIrpStackLocation(Irp);
auto len = stack->Parameters.Read.Length;
auto status = STATUS_SUCCESS;
auto count = 0;
NT_ASSERT(Irp->MdlAddress); // we're using Direct I/O
auto buffer = (UCHAR*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if (!buffer)
status = STATUS_INSUFFICIENT_RESOURCES;
else {
//访问列表头获取数据项
AutoLock lock(g_Globals.Mutex);
while (true) {
if (IsListEmpty(&g_Globals.ItemsHead)) // can also check g_Globals.ItemCount
break;
auto entry = RemoveHeadList(&g_Globals.ItemsHead);
auto info = CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry);
auto size = info->Data.Size;
if (len < size) {
// user's buffer full, insert item back
InsertHeadList(&g_Globals.ItemsHead, entry);
break;
}
g_Globals.ItemCount--;
::memcpy(buffer, &info->Data, size);
len -= size;
buffer += size;
count += size;
ExFreePool(info);
}
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = count;
IoCompleteRequest(Irp, 0);
return status;
}

对于卸载例程,若链表中还有数据项,则需要将他们显式释放:

1
2
3
4
5
6
7
8
9
10
11
12
void SysMonUnload(PDRIVER_OBJECT DriverObject) {
PsRemoveLoadImageNotifyRoutine(OnImageLoadNotify);
PsRemoveCreateThreadNotifyRoutine(OnThreadNotify);
PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, TRUE);
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\sysmon");
IoDeleteSymbolicLink(&symLink);
IoDeleteDevice(DriverObject->DeviceObject);
while (!IsListEmpty(&g_Globals.ItemsHead)) {
auto entry = RemoveHeadList(&g_Globals.ItemsHead);
ExFreePool(CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry));
}
}

用户模式客户程序

main函数中轮循:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
auto hFile = ::CreateFile(L"\\\\.\\SysMon", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
return Error("Failed to open file");
BYTE buffer[1 << 16];
while (true) {
DWORD bytes;
if (!::ReadFile(hFile, buffer, sizeof(buffer), &bytes, nullptr))
return Error("Failed to read");
if (bytes != 0)
DisplayInfo(buffer, bytes);
::Sleep(200);
}
}

其中DisplayInfo解析收到的缓冲区结构。所有事件都从一个公共头部开始,基于ItemType区分各种事件类型。处理完当前事件后,头部Size字段指明下一个事件从哪里开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void DisplayInfo(BYTE* buffer, DWORD size) {
auto count = size;
while (count > 0) {
auto header = (ItemHeader*)buffer;
switch (header->Type) {
case ItemType::ProcessExit: {
DisplayTime(header->Time);
auto info = (ProcessExitInfo*)buffer;
printf("Process %d Exited\n", info->ProcessId);
break;
}
case ItemType::ProcessCreate: {
DisplayTime(header->Time);
auto info = (ProcessCreateInfo*)buffer;
std::wstring commandline((WCHAR*)(buffer + info->CommandLineOffset), info->CommandLineLength);
printf("Process %d Created. Command line: %ws\n", info->ProcessId, commandline.c_str());
break;
}
case ItemType::ThreadCreate: {
DisplayTime(header->Time);
auto info = (ThreadCreateExitInfo*)buffer;
printf("Thread %d Created in process %d\n", info->ThreadId, info->ProcessId);
break;
}
case ItemType::ThreadExit: {
DisplayTime(header->Time);
auto info = (ThreadCreateExitInfo*)buffer;
printf("Thread %d Exited from process %d\n", info->ThreadId, info->ProcessId);
break;
}
case ItemType::ImageLoad: {
DisplayTime(header->Time);
auto info = (ImageLoadInfo*)buffer;
printf("Image loaded into process %d at address 0x%p (%ws)\n", info->ProcessId, info->LoadAddress, info->ImageFileName);
break;
}
default:
break;
}
buffer += header->Size;
count -= header->Size;
}
}
void DisplayTime(const LARGE_INTEGER& time) {
SYSTEMTIME st;
::FileTimeToSystemTime((FILETIME*)&time, &st);
printf("%02d:%02d:%02d.%03d: ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
}

线程通知回调例程

事件大小结构固定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void OnThreadNotify(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create) {
auto size = sizeof(FullItem<ThreadCreateExitInfo>);
auto info = (FullItem<ThreadCreateExitInfo>*)ExAllocatePoolWithTag(PagedPool, size, DRIVER_TAG);
if (info == nullptr) {
KdPrint((DRIVER_PREFIX "Failed to allocate memory\n"));
return;
}
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Size = sizeof(item);
item.Type = Create ? ItemType::ThreadCreate : ItemType::ThreadExit;
item.ProcessId = HandleToULong(ProcessId);
item.ThreadId = HandleToULong(ThreadId);
PushItem(&info->Entry);
}

源码

驱动

AutoLock.h:

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once
template<typename TLock>
struct AutoLock {
AutoLock(TLock& lock) : _lock(lock) {
_lock.Lock();
}
~AutoLock() {
_lock.Unlock();
}
private:
TLock& _lock;
};

FastMutex.cpp:

1
2
3
4
5
6
7
8
9
10
11
#include "pch.h"
#include "FastMutex.h"
void FastMutex::Init() {
ExInitializeFastMutex(&_mutex);
}
void FastMutex::Lock() {
ExAcquireFastMutex(&_mutex);
}
void FastMutex::Unlock() {
ExReleaseFastMutex(&_mutex);
}

FastMutex.h:

1
2
3
4
5
6
7
8
9
#pragma once
class FastMutex {
public:
void Init();
void Lock();
void Unlock();
private:
FAST_MUTEX _mutex;
};

pch.cpp:

1
#include "pch.h"

pch.h:

1
2
#pragma once
#include <ntddk.h>

SysMon.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include "pch.h"
#include "SysMon.h"
#include "SysMonCommon.h"
#include "AutoLock.h"
DRIVER_UNLOAD SysMonUnload;
DRIVER_DISPATCH SysMonCreateClose, SysMonRead;
void OnProcessNotify(_Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);
void OnThreadNotify(_In_ HANDLE ProcessId, _In_ HANDLE ThreadId, _In_ BOOLEAN Create);
void OnImageLoadNotify(_In_opt_ PUNICODE_STRING FullImageName, _In_ HANDLE ProcessId, _In_ PIMAGE_INFO ImageInfo);
void PushItem(LIST_ENTRY* entry);
Globals g_Globals;
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING) {
auto status = STATUS_SUCCESS;
InitializeListHead(&g_Globals.ItemsHead);
g_Globals.Mutex.Init();
PDEVICE_OBJECT DeviceObject = nullptr;
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\sysmon");
bool symLinkCreated = false;
bool processCallbacks = false, threadCallbacks = false;
do {
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\sysmon");
status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &DeviceObject);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to create device (0x%08X)\n", status));
break;
}
DeviceObject->Flags |= DO_DIRECT_IO;
status = IoCreateSymbolicLink(&symLink, &devName);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to create sym link (0x%08X)\n", status));
break;
}
symLinkCreated = true;
status = PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, FALSE);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to register process callback (0x%08X)\n", status));
break;
}
processCallbacks = true;
status = PsSetCreateThreadNotifyRoutine(OnThreadNotify);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to set thread callback (status=%08X)\n", status));
break;
}
threadCallbacks = true;
status = PsSetLoadImageNotifyRoutine(OnImageLoadNotify);
if (!NT_SUCCESS(status)) {
KdPrint((DRIVER_PREFIX "failed to set image load callback (status=%08X)\n", status));
break;
}
} while (false);
if (!NT_SUCCESS(status)) {
if (threadCallbacks)
PsRemoveCreateThreadNotifyRoutine(OnThreadNotify);
if (processCallbacks)
PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, TRUE);
if (symLinkCreated)
IoDeleteSymbolicLink(&symLink);
if (DeviceObject)
IoDeleteDevice(DeviceObject);
}
DriverObject->DriverUnload = SysMonUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = SysMonCreateClose;
DriverObject->MajorFunction[IRP_MJ_READ] = SysMonRead;
return status;
}
NTSTATUS SysMonCreateClose(PDEVICE_OBJECT, PIRP Irp) {
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, 0);
return STATUS_SUCCESS;
}
NTSTATUS SysMonRead(PDEVICE_OBJECT, PIRP Irp) {
auto stack = IoGetCurrentIrpStackLocation(Irp);
auto len = stack->Parameters.Read.Length;
auto status = STATUS_SUCCESS;
auto count = 0;
NT_ASSERT(Irp->MdlAddress); // we're using Direct I/O
auto buffer = (UCHAR*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if (!buffer)
status = STATUS_INSUFFICIENT_RESOURCES;
else {
AutoLock lock(g_Globals.Mutex);
while (true) {
if (IsListEmpty(&g_Globals.ItemsHead)) // can also check g_Globals.ItemCount
break;
auto entry = RemoveHeadList(&g_Globals.ItemsHead);
auto info = CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry);
auto size = info->Data.Size;
if (len < size) {
// user's buffer full, insert item back
InsertHeadList(&g_Globals.ItemsHead, entry);
break;
}
g_Globals.ItemCount--;
::memcpy(buffer, &info->Data, size);
len -= size;
buffer += size;
count += size;
ExFreePool(info);
}
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = count;
IoCompleteRequest(Irp, 0);
return status;
}
void SysMonUnload(PDRIVER_OBJECT DriverObject) {
PsRemoveLoadImageNotifyRoutine(OnImageLoadNotify);
PsRemoveCreateThreadNotifyRoutine(OnThreadNotify);
PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, TRUE);
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\sysmon");
IoDeleteSymbolicLink(&symLink);
IoDeleteDevice(DriverObject->DeviceObject);
while (!IsListEmpty(&g_Globals.ItemsHead)) {
auto entry = RemoveHeadList(&g_Globals.ItemsHead);
ExFreePool(CONTAINING_RECORD(entry, FullItem<ItemHeader>, Entry));
}
}
void OnProcessNotify(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) {
UNREFERENCED_PARAMETER(Process);
if (CreateInfo) {
// process created
USHORT allocSize = sizeof(FullItem<ProcessCreateInfo>);
USHORT commandLineSize = 0;
if (CreateInfo->CommandLine) {
commandLineSize = CreateInfo->CommandLine->Length;
allocSize += commandLineSize;
}
auto info = (FullItem<ProcessCreateInfo>*)ExAllocatePoolWithTag(PagedPool, allocSize, DRIVER_TAG);
if (info == nullptr) {
KdPrint((DRIVER_PREFIX "failed allocation\n"));
return;
}
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Type = ItemType::ProcessCreate;
item.Size = sizeof(ProcessCreateInfo) + commandLineSize;
item.ProcessId = HandleToULong(ProcessId);
item.ParentProcessId = HandleToULong(CreateInfo->ParentProcessId);
if (commandLineSize > 0) {
::memcpy((UCHAR*)&item + sizeof(item), CreateInfo->CommandLine->Buffer, commandLineSize);
item.CommandLineLength = commandLineSize / sizeof(WCHAR); // length in WCHARs
item.CommandLineOffset = sizeof(item);
}
else
item.CommandLineLength = 0;
PushItem(&info->Entry);
}
else {
// process exited
auto info = (FullItem<ProcessExitInfo>*)ExAllocatePoolWithTag(PagedPool, sizeof(FullItem<ProcessExitInfo>), DRIVER_TAG);
if (info == nullptr) {
KdPrint((DRIVER_PREFIX "failed allocation\n"));
return;
}
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Type = ItemType::ProcessExit;
item.ProcessId = HandleToULong(ProcessId);
item.Size = sizeof(ProcessExitInfo);
PushItem(&info->Entry);
}
}
void OnThreadNotify(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create) {
auto size = sizeof(FullItem<ThreadCreateExitInfo>);
auto info = (FullItem<ThreadCreateExitInfo>*)ExAllocatePoolWithTag(PagedPool, size, DRIVER_TAG);
if (info == nullptr) {
KdPrint((DRIVER_PREFIX "Failed to allocate memory\n"));
return;
}
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Size = sizeof(item);
item.Type = Create ? ItemType::ThreadCreate : ItemType::ThreadExit;
item.ProcessId = HandleToULong(ProcessId);
item.ThreadId = HandleToULong(ThreadId);
PushItem(&info->Entry);
}
void OnImageLoadNotify(PUNICODE_STRING FullImageName, HANDLE ProcessId, PIMAGE_INFO ImageInfo) {
if (ProcessId == nullptr) {
// system image, ignore
return;
}
auto size = sizeof(FullItem<ImageLoadInfo>);
auto info = (FullItem<ImageLoadInfo>*)ExAllocatePoolWithTag(PagedPool, size, DRIVER_TAG);
if (info == nullptr) {
KdPrint((DRIVER_PREFIX "Failed to allocate memory\n"));
return;
}
::memset(info, 0, size);
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Size = sizeof(item);
item.Type = ItemType::ImageLoad;
item.ProcessId = HandleToULong(ProcessId);
item.ImageSize = ImageInfo->ImageSize;
item.LoadAddress = ImageInfo->ImageBase;
if (FullImageName)
::memcpy(item.ImageFileName, FullImageName->Buffer, min(FullImageName->Length, MaxImageFileSize * sizeof(WCHAR)));
else
::wcscpy_s(item.ImageFileName, L"(unknown)");
//if (ImageInfo->ExtendedInfoPresent)
// auto exinfo = CONTAINING_RECORD(ImageInfo, IMAGE_INFO_EX, ImageInfo);
PushItem(&info->Entry);
}
void PushItem(LIST_ENTRY * entry) {
AutoLock<FastMutex> lock(g_Globals.Mutex);
if (g_Globals.ItemCount > 1024) {
// too many items, remove oldest one
auto head = RemoveHeadList(&g_Globals.ItemsHead);
g_Globals.ItemCount--;
auto item = CONTAINING_RECORD(head, FullItem<ItemHeader>, Entry);
ExFreePool(item);
}
InsertTailList(&g_Globals.ItemsHead, entry);
g_Globals.ItemCount++;
}

SysMon.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once
#include "FastMutex.h"
#define DRIVER_PREFIX "SysMon: "
#define DRIVER_TAG 'nmys'
struct Globals {
LIST_ENTRY ItemsHead;
int ItemCount;
FastMutex Mutex;
};
template<typename T>
struct FullItem {
LIST_ENTRY Entry;
T Data;
};

SysMon.inf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
; SysMon.inf
[Version]
Signature="$WINDOWS NT$"
Class=System
ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}
Provider=%ManufacturerName%
DriverVer=
CatalogFile=SysMon.cat
[DestinationDirs]
DefaultDestDir = 12
[SourceDisksNames]
1 = %DiskName%,,,""
[SourceDisksFiles]
[Manufacturer]
%ManufacturerName%=Standard,NT$ARCH$
[Standard.NT$ARCH$]
[Strings]
ManufacturerName="<Your manufacturer name>" ;TODO: Replace with your manufacturer name
ClassName=""
DiskName="SysMon Source Disk"

SysMonCommon.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#pragma once
enum class ItemType : short {
None,
ProcessCreate,
ProcessExit,
ThreadCreate,
ThreadExit,
ImageLoad
};
struct ItemHeader {
ItemType Type;
USHORT Size;
LARGE_INTEGER Time;
};
struct ProcessExitInfo : ItemHeader {
ULONG ProcessId;
};
struct ProcessCreateInfo : ItemHeader {
ULONG ProcessId;
ULONG ParentProcessId;
USHORT CommandLineLength;
USHORT CommandLineOffset;
};
struct ThreadCreateExitInfo : ItemHeader {
ULONG ThreadId;
ULONG ProcessId;
};
const int MaxImageFileSize = 300;
struct ImageLoadInfo : ItemHeader {
ULONG ProcessId;
void* LoadAddress;
ULONG_PTR ImageSize;
WCHAR ImageFileName[MaxImageFileSize + 1];
};

客户端

pch.cpp:

1
#include "pch.h"

pch.h:

1
2
3
4
5
#ifndef PCH_H
#define PCH_H
#include <Windows.h>
#include <stdio.h>
#endif //PCH_H

SysMonClient.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include "pch.h"
#include "..\SysMon\SysMonCommon.h"
#include <string>
int Error(const char* text) {
printf("%s (%d)\n", text, ::GetLastError());
return 1;
}
void DisplayTime(const LARGE_INTEGER& time) {
SYSTEMTIME st;
::FileTimeToSystemTime((FILETIME*)&time, &st);
printf("%02d:%02d:%02d.%03d: ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
}
void DisplayInfo(BYTE* buffer, DWORD size) {
auto count = size;
while (count > 0) {
auto header = (ItemHeader*)buffer;
switch (header->Type) {
case ItemType::ProcessExit: {
DisplayTime(header->Time);
auto info = (ProcessExitInfo*)buffer;
printf("Process %d Exited\n", info->ProcessId);
break;
}
case ItemType::ProcessCreate: {
DisplayTime(header->Time);
auto info = (ProcessCreateInfo*)buffer;
std::wstring commandline((WCHAR*)(buffer + info->CommandLineOffset), info->CommandLineLength);
printf("Process %d Created. Command line: %ws\n", info->ProcessId, commandline.c_str());
break;
}
case ItemType::ThreadCreate: {
DisplayTime(header->Time);
auto info = (ThreadCreateExitInfo*)buffer;
printf("Thread %d Created in process %d\n", info->ThreadId, info->ProcessId);
break;
}
case ItemType::ThreadExit: {
DisplayTime(header->Time);
auto info = (ThreadCreateExitInfo*)buffer;
printf("Thread %d Exited from process %d\n", info->ThreadId, info->ProcessId);
break;
}
case ItemType::ImageLoad: {
DisplayTime(header->Time);
auto info = (ImageLoadInfo*)buffer;
printf("Image loaded into process %d at address 0x%p (%ws)\n", info->ProcessId, info->LoadAddress, info->ImageFileName);
break;
}
default:
break;
}
buffer += header->Size;
count -= header->Size;
}
}
int main() {
auto hFile = ::CreateFile(L"\\\\.\\SysMon", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
return Error("Failed to open file");
BYTE buffer[1 << 16];
while (true) {
DWORD bytes;
if (!::ReadFile(hFile, buffer, sizeof(buffer), &bytes, nullptr))
return Error("Failed to read");
if (bytes != 0)
DisplayInfo(buffer, bytes);
::Sleep(200);
}
}