作用
当系统中创建一个新进程时,内核会通知所有注册的回调函数。这是实现进程监控、白名单控制、恶意软件检测的核心机制。
注册方式
通过 PsSetCreateProcessNotifyRoutine(旧版)或 PsSetCreateProcessNotifyRoutineEx(推荐,支持更多参数如父进程 ID、命令行等)注册。
1 2 3 4
| NTSTATUS PsSetCreateProcessNotifyRoutineEx( PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, BOOLEAN Remove );
|
回调函数原型
PsSetCreateProcessNotifyRoutineEx (Ex版本):
1 2 3 4 5
| VOID ProcessNotifyCallback( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo );
|
- 可以阻止进程创建。通过修改
CreateInfo->Status 为错误码(如 STATUS_ACCESS_DENIED)。
- 可以修改命令行。通过修改
CreateInfo->CommandLine。
- 这是目前最推荐的进程监控和拦截方式,因为它提供了比旧版
PsSetCreateProcessNotifyRoutine 更多的信息(如父进程ID、命令行等)。
旧版 PsSetCreateProcessNotifyRoutine:
- 回调原型是
VOID CreateProcessNotifyRoutine(HANDLE ParentId, HANDLE ProcessId, BOOLEAN Create)。
- 无法阻止进程创建,只能事后通知。
PPS_CREATE_NOTIFY_INFO
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;
|
CreateInfo 非空时表示进程正在创建,里面包含父进程ID、命令行、映像文件名等。
可以通过设置 CreateInfo->CreationStatus = STATUS_ACCESS_DENIED 来阻止进程启动。
例子
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
| #include <ntifs.h>
NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process); NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);
PCHAR GetProcessNameByProcessId(HANDLE ProcessId) { NTSTATUS st = STATUS_UNSUCCESSFUL; PEPROCESS ProcessObj = NULL; PCHAR string = NULL; st = PsLookupProcessByProcessId(ProcessId, &ProcessObj); if (NT_SUCCESS(st)) { string = PsGetProcessImageFileName(ProcessObj); ObfDereferenceObject(ProcessObj); } return string; }
BOOLEAN BypassCheckSign(PDRIVER_OBJECT pDriverObject) { #ifdef _WIN64 typedef struct _KLDR_DATA_TABLE_ENTRY { LIST_ENTRY listEntry; ULONG64 __Undefined1; ULONG64 __Undefined2; ULONG64 __Undefined3; ULONG64 NonPagedDebugInfo; ULONG64 DllBase; ULONG64 EntryPoint; ULONG SizeOfImage; UNICODE_STRING path; UNICODE_STRING name; ULONG Flags; USHORT LoadCount; USHORT __Undefined5; ULONG64 __Undefined6; ULONG CheckSum; ULONG __padding1; ULONG TimeDateStamp; ULONG __padding2; } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY; #else typedef struct _KLDR_DATA_TABLE_ENTRY { LIST_ENTRY listEntry; ULONG unknown1; ULONG unknown2; ULONG unknown3; ULONG unknown4; ULONG unknown5; ULONG unknown6; ULONG unknown7; UNICODE_STRING path; UNICODE_STRING name; ULONG Flags; } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY; #endif
PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection; pLdrData->Flags = pLdrData->Flags | 0x20;
return TRUE; }
VOID My_LyShark_Com_CreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) { char ProcName[16] = { 0 }; if (CreateInfo != NULL) { strcpy_s(ProcName, 16, PsGetProcessImageFileName(Process)); DbgPrint("[LyShark] 父进程ID: %ld | 父进程名: %s | 进程名: %s | 进程路径:%wZ \n", CreateInfo->ParentProcessId, GetProcessNameByProcessId(CreateInfo->ParentProcessId), PsGetProcessImageFileName(Process), CreateInfo->ImageFileName);
if (0 == _stricmp(ProcName, "lyshark.exe")) { CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL; } } else { strcpy_s(ProcName, 16, PsGetProcessImageFileName(Process)); DbgPrint("[LyShark] 进程[ %s ] 退出了, 程序被关闭", ProcName); } }
VOID UnDriver(PDRIVER_OBJECT driver) { DWORD32 ref = 0;
ref = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)My_LyShark_Com_CreateProcessNotifyEx, TRUE); DbgPrint("[lyshark.com] 注销进程回调: %d \n", ref); }
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) { NTSTATUS status;
BypassCheckSign(Driver);
DbgPrint("hello lyshark.com \n");
status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)My_LyShark_Com_CreateProcessNotifyEx, FALSE); if (!NT_SUCCESS(status)) { DbgPrint("[lyshark.com] 创建进程回调错误"); } Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }
|
编译并运行这个驱动程序,我们可以在ARK工具中看到这个驱动所加载的CreateProcess的回调事件。

windbg查看结构手动致盲
dq nt!PspCreateProcessNotifyRoutine

还不是直接的回调地址,需要去掉最低位的标志位再解引用一次。
结构说明
每个条目是一个 EX_CALLBACK_ROUTINE_BLOCK 指针,最低4位用作标志,真正的指针需实际指针 = 值 & 0xFFFFFFFFFFFFFFF0 然后解引用得到 EX_CALLBACK_ROUTINE_BLOCK,其中偏移 +0x8 才是真正的回调函数地址。

看到了是我们加载的驱动,然后直接把这个指针置为零

致盲后:

打开进程也不打印了。
脚本寻找结构进行内核进程监控致盲思路
1.找到函数
1 2 3 4 5 6 7 8 9
| nt!PspSetCreateProcessNotifyRoutine+0x54: fffff807`23c1b2dc 488bf8 mov rdi,rax fffff807`23c1b2df 4885c0 test rax,rax fffff807`23c1b2e2 0f84ae630f00 je nt!PspSetCreateProcessNotifyRoutine+0xf640e (fffff807`23d11696) fffff807`23c1b2e8 33db xor ebx,ebx fffff807`23c1b2ea 4c8d2d0f124f00 lea r13,[nt!PspCreateProcessNotifyRoutine (fffff807`2410c500)] fffff807`23c1b2f1 488d0cdd00000000 lea rcx,[rbx*8] fffff807`23c1b2f9 4533c0 xor r8d,r8d fffff807`23c1b2fc 4903cd add rcx,r13
|
现在我们可以看到回调数组。
1
| lea r13,[nt!PspCreateProcessNotifyRoutine (fffff807`2410c500)]
|
2.定位函数
这个就特征码硬搜就行了,比如别的脚本是定位4533c0 xor r8d,r8d
3.根据偏移拿到真实地址
找到了 xor r8d, r8d 的地址,然后用它反推 PspCreateProcessNotifyRoutine 的地址。
代码逻辑拆解
1
| xor r8d, r8d 地址 = patternaddress
|
内存布局回顾
1 2 3 4
| fffff803`1b399ada lea r13, [PspCreateProcessNotifyRoutine] ← 7字节 机器码: 4C 8D 2D [FF 2C 55 00] ← 方括号内是32bit offset fffff803`1b399ae1 lea rcx, [rbx*8] ← 8字节 fffff803`1b399ae9 xor r8d, r8d ← patternaddress
|
读取 offset
1
| VirtualRead(patternaddress - 0x0f, &offset, sizeof(DWORD));
|
patternaddress - 0x0F = 0xae9 - 0xF = 0xada + 3 = 指向 lea r13 指令中的 4字节相对偏移 FF 2C 55 00
计算地址
1 2 3 4
| DWORD64 result = (((patternaddress) >> 32) << 32) // 取高32位,保留基址高半部分 + ((DWORD)(patternaddress) + offset) // 低32位 + 相对偏移 - 0x0f // 回退到 lea r13 指令起始 + 0x04; // 跳过 lea 的前4字节,指向offset字段后(即下一条指令地址)
|
本质就是 RIP 相对寻址公式:
1 2 3
| PspCreateProcessNotifyRoutine = 下一条指令地址(0xae1) + offset(0x552CFF) = fffff803`1b399ae1 + 00552CFF = fffff803`1b8ec7e0 ✓
|