利用Windows内核提供的事件通知机制,可以对系统内某一类事件的操作进行监控。比如,可以通过PsSetCreateProcessNotifyRoutineEx函数注册一个创建进程的通知,从而实现对进程创建的监控。
本文主要参考《Windows内核编程》第21章。
PsSetCreateProcessNotifyRoutineEx的用法
PsSetCreateProcessNotifyRoutineEx的函数原型为:
1 2 3 4
| NTSTATUS PsSetCreateProcessNotifyRoutineEx( PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, BOOLEAN Remove );
|
其中NotifyRoutine为一个函数指针,这个函数的原型被规定为:
1 2 3 4 5 6 7 8
| PCREATE_PROCESS_NOTIFY_ROUTINE_EX PcreateProcessNotifyRoutineEx;
void PcreateProcessNotifyRoutineEx( PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo ) {...}
|
这个函数被称为通知例程,只要发生了“进程创建或销毁”这一事件,这个PcreateProcessNotifyRoutineEx就会被调用一次,这样就实现了对进程创建的监控。
另一个参数为Remove,Remove为FALSE时,表示要进行注册一个通知;Remove为TRUE,表示要移除这个通知。因此,一般会在DriverEntry,即驱动的入口函数中调用PsSetCreateProcessNotifyRoutineEx时将Remove设置为FALSE;在DriverUnload,即驱动的卸载函数中再调用一次PsSetCreateProcessNotifyRoutineEx,此时需要将Remove设置为FALSE。
通知例程函数的参数说明
通知例程函数有三个形参,通过这三个形参,就可以知道要创建(或销毁)的进程的一些基本信息。
ProcessId为要创建的进程对应的进程ID。
根据CreateInfo可以判断当前进行的是进程的创建还是销毁操作。如果要进行进程销毁操作,则CreateInfo的值会是NULL。因此,通过判断CreateInfo是否为NULL,可以得知要进行的操作是进程创建还是进程销毁。
进行进程创建操作时,CreateInfo的结构体定义为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| typedef struct _PS_CREATE_NOTIFY_INFO { SIZE_T Size; union { ULONG Flags; struct { ULONG FileOpenNameAvailable : 1; ULONG IsSubsystemProcess : 1; ULONG Reserved : 30; }; }; HANDLE ParentProcessId; CLIENT_ID CreatingThreadId; struct _FILE_OBJECT *FileObject; PCUNICODE_STRING ImageFileName; PCUNICODE_STRING CommandLine; NTSTATUS CreationStatus; } PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
|
从中可以得到进程的名字、参数、父进程ID等信息。
进程监控驱动程序编写
函数和全局变量声明
在程序的最开始先声明几个需要用到的函数和全局变量:
1 2 3 4 5 6 7
| #include <ntddk.h>
VOID DriverUnload(__in struct _DRIVER_OBJECT* DriverObject); VOID ProcessNotify(__inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo);
BOOLEAN g_bSuccRegister = FALSE;
|
DriverEntry
1 2 3 4 5 6 7 8 9 10 11 12 13
| NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT* DriverObject, __in PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); NTSTATUS nStatus = STATUS_UNSUCCESSFUL; do { DriverObject->DriverUnload = DriverUnload; if (STATUS_SUCCESS != PsSetCreateProcessNotifyRoutineEx(ProcessNotify, FALSE)) { break; } g_bSuccRegister = TRUE; nStatus = STATUS_SUCCESS; } while (FALSE); return nStatus; }
|
DriverEntry的主要功能是调用PsSetCreateProcessNotifyRoutineEx,将ProcessNotify函数注册为进程创建的通知例程。
DriverUnload
1 2 3 4 5 6 7
| VOID DriverUnload(__in struct _DRIVER_OBJECT* DriverObject) { UNREFERENCED_PARAMETER(DriverObject); if (g_bSuccRegister) { PsSetCreateProcessNotifyRoutineEx(ProcessNotify, TRUE); } return; }
|
在卸载驱动模块时,需要再调用一次PsSetCreateProcessNotifyRoutineEx,将之前注册的事件通知移除。
ProcessNotify
这一函数为进程创建或销毁的通知例程,每当发生进程创建或进程销毁时,这一函数都会被调用,内容为:
1 2 3 4 5 6 7 8 9 10 11
| VOID ProcessNotify(__inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo) { UNREFERENCED_PARAMETER(Process); if (NULL == CreateInfo) { DbgPrint("[Destroy] [PID = 0x%x] [CurrentPID = 0x%x]\n", ProcessId, PsGetCurrentProcessId()); return; } DbgPrint("[Create] [PID = 0x%x, Name=%wZ] [CurrentPID = 0x%x] [PPID = 0x%x]\n", ProcessId, CreateInfo->ImageFileName, PsGetCurrentProcessId(), CreateInfo->ParentProcessId); return; }
|
函数会打印和进程有关的一些信息。
完整代码
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
| #include <ntddk.h>
VOID DriverUnload(__in struct _DRIVER_OBJECT* DriverObject); VOID ProcessNotify(__inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo);
BOOLEAN g_bSuccRegister = FALSE;
NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT* DriverObject, __in PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); NTSTATUS nStatus = STATUS_UNSUCCESSFUL; do { DriverObject->DriverUnload = DriverUnload; if (STATUS_SUCCESS != PsSetCreateProcessNotifyRoutineEx(ProcessNotify, FALSE)) { break; } g_bSuccRegister = TRUE; nStatus = STATUS_SUCCESS; } while (FALSE); return nStatus; }
VOID DriverUnload(__in struct _DRIVER_OBJECT* DriverObject) { UNREFERENCED_PARAMETER(DriverObject); if (g_bSuccRegister) { PsSetCreateProcessNotifyRoutineEx(ProcessNotify, TRUE); } return; }
VOID ProcessNotify(__inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo) { UNREFERENCED_PARAMETER(Process); if (NULL == CreateInfo) { DbgPrint("[Destroy] [PID = 0x%x] [CurrentPID = 0x%x]\n", ProcessId, PsGetCurrentProcessId()); return; } DbgPrint("[Create] [PID = 0x%x, Name=%wZ] [CurrentPID = 0x%x] [PPID = 0x%x]\n", ProcessId, CreateInfo->ImageFileName, PsGetCurrentProcessId(), CreateInfo->ParentProcessId); return; }
|
编译时需要加上/INTEGRITYCHECK
使用Visual Studio对驱动进行编译时,需要加上/INTEGRITYCHECK选项,否则PsSetCreateProcessNotifyRoutineEx会返回STATUS_ACCESS_DENIED错误码。
运行结果
在虚拟机中禁用驱动程序强制签名,然后运行驱动,打开DbgView查看打印的结果:
上图为部分打印结果,驱动成功监测到notepad进程的创建和销毁。
通知例程的上下文
在创建和销毁进程时,通知例程都会被调用,那么通知例程是在哪个进程中被调用的呢?根据《Windows内核编程》的说法:
对于进程创建通知来说,通知例程运行在创建该进程的线程上下文中,如果线程A调用应用层CreateProcess函数创建子进程B,那么通知例程就运行在A线程的上下文中。对于进程结束通知来说,通知例程运行在该进程中最后一个退出的线程的上下文中(一般是主线程)。
根据上面的运行结果也可以看出,当进程创建时,调用PsGetCurrentProcessId得到的进程ID(CurrentPID)是和父进程的ID相同的;而当进程结束时,PsGetCurrentProcessId得到的进程ID是和要销毁的进程ID相同的。不过也有进程创建时,CurrentPID和PPID不相等的情况。
进程是32位还是64位
在内核驱动中,要想知道进程是32位还是64位,可以使用ZwQueryInformationProcess函数,但这个函数需要传入进程的句柄,所以在这之前要先想办法获取进程的句柄。现在已经有了指向进程对象的指针,类型为PEPROCESS,变量名为Process,因此可以使用ObOpenObjectByPointer得到这个进程对象的一个句柄:
1 2 3 4 5
| status = ObOpenObjectByPointer(Process, OBJ_KERNEL_HANDLE, NULL, 0, NULL, KernelMode, &hProcess); if (STATUS_SUCCESS != status) { DbgPrint("ObOpenObjectByPointer Failed:0x%x", status); break; }
|
接下来使用ZwQueryInformationProcess,但发现头文件中并没有这个函数,因此要使用MmGetSystemRoutineAddress找到这个函数。首先在文件开头定义全局变量存储ZwQueryInformationProcess的地址:
1 2 3 4 5 6 7 8
| typedef NTSTATUS(*ZWQUERYINFORMATIONPROCESS) ( __in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, __out_bcount(ProcessInformationLength) PVOID ProcessInformation, __in ULONG ProcessInformationLength, __out_opt PULONG ReturnLength);
ZWQUERYINFORMATIONPROCESS g_pZwQueryInformationProcess = NULL;
|
在DriverEntry中添加代码,对g_pZwQueryInformationProcess变量赋值:
1 2 3 4 5 6 7
| UNICODE_STRING uFuncName = { 0 }; DriverObject->DriverUnload = DriverUnload; RtlInitUnicodeString(&uFuncName, L"ZwQueryInformationProcess"); g_pZwQueryInformationProcess = (ZWQUERYINFORMATIONPROCESS)MmGetSystemRoutineAddress(&uFuncName); if (NULL == g_pZwQueryInformationProcess) { break; }
|
最后在通知例程中添加代码,使用ZwQueryInformationProcess判断其是32位进程还是64位进程:
1 2 3 4 5 6 7 8 9 10 11
| status = g_pZwQueryInformationProcess(hProcess, ProcessWow64Information, &isWOW64, sizeof(isWOW64), NULL); if (STATUS_SUCCESS != status) { DbgPrint("ZwQueryInformationProcess Failed:0x%x", status); break; } if (isWOW64) { DbgPrint("[Detail 0x%x] 32bit", ProcessId); } else { DbgPrint("[Detail 0x%x] 64bit", ProcessId); }
|
添加了上面这些代码后,源代码为:
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
| #include <ntifs.h> #include <ntddk.h>
VOID DriverUnload(__in struct _DRIVER_OBJECT* DriverObject); VOID ProcessNotify(__inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo); typedef NTSTATUS(*ZWQUERYINFORMATIONPROCESS) ( __in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, __out_bcount(ProcessInformationLength) PVOID ProcessInformation, __in ULONG ProcessInformationLength, __out_opt PULONG ReturnLength);
ZWQUERYINFORMATIONPROCESS g_pZwQueryInformationProcess = NULL; BOOLEAN g_bSuccRegister = FALSE;
NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT* DriverObject, __in PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); NTSTATUS nStatus = STATUS_UNSUCCESSFUL; do { UNICODE_STRING uFuncName = { 0 }; DriverObject->DriverUnload = DriverUnload; RtlInitUnicodeString(&uFuncName, L"ZwQueryInformationProcess"); g_pZwQueryInformationProcess = (ZWQUERYINFORMATIONPROCESS)MmGetSystemRoutineAddress(&uFuncName); if (NULL == g_pZwQueryInformationProcess) { break; } if (STATUS_SUCCESS != PsSetCreateProcessNotifyRoutineEx(ProcessNotify, FALSE)) { break; } g_bSuccRegister = TRUE; nStatus = STATUS_SUCCESS; } while (FALSE); return nStatus; }
VOID DriverUnload(__in struct _DRIVER_OBJECT* DriverObject) { UNREFERENCED_PARAMETER(DriverObject); if (g_bSuccRegister) { PsSetCreateProcessNotifyRoutineEx(ProcessNotify, TRUE); } return; }
VOID ProcessNotify(__inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo) { UNREFERENCED_PARAMETER(Process); if (NULL == CreateInfo) { DbgPrint("[Destroy] [PID = 0x%x] [CurrentPID = 0x%x]\n", ProcessId, PsGetCurrentProcessId()); return; } DbgPrint("[Create] [PID = 0x%x, Name=%wZ] [CurrentPID = 0x%x] [PPID = 0x%x]\n", ProcessId, CreateInfo->ImageFileName, PsGetCurrentProcessId(), CreateInfo->ParentProcessId); HANDLE hProcess = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; ULONG_PTR isWOW64 = 0; do { status = ObOpenObjectByPointer(Process, OBJ_KERNEL_HANDLE, NULL, 0, NULL, KernelMode, &hProcess); if (STATUS_SUCCESS != status) { DbgPrint("ObOpenObjectByPointer Failed:0x%x", status); break; } status = g_pZwQueryInformationProcess(hProcess, ProcessWow64Information, &isWOW64, sizeof(isWOW64), NULL); if (STATUS_SUCCESS != status) { DbgPrint("ZwQueryInformationProcess Failed:0x%x", status); break; } if (isWOW64) { DbgPrint("[Detail 0x%x] 32bit", ProcessId); } else { DbgPrint("[Detail 0x%x] 64bit", ProcessId); } } while (FALSE);
if (NULL != hProcess) { ZwClose(hProcess); hProcess = NULL; } return; }
|
运行结果如下:
说明驱动程序可以分辨出32位和64位进程。