😄
0 回调与通知 回调与通知必须要区分开,否则会造成很大的麻烦。
回调对象是 CALLBACK_OBJECT,而通知对象是 EX_CALLBACK。
枚举通知例程的函数为 PspEnumerateCallback,枚举回调的函数为 ExpEnumerateCallback。
本文分析系统版本:
1 进程通知 进程通知整体结构:
1.1 创建/移除进程通知 创建进程通知可以通过以下说那个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 NTSTATUS PsSetCreateProcessNotifyRoutine ( [in] PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, [in] BOOLEAN Remove ) ;NTSTATUS PsSetCreateProcessNotifyRoutineEx ( [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, [in] BOOLEAN Remove ) ;NTSTATUS PsSetCreateProcessNotifyRoutineEx2 ( [in] PSCREATEPROCESSNOTIFYTYPE NotifyType, [in] PVOID NotifyInformation, [in] BOOLEAN Remove ) ;
需要说明:
PsSetCreateProcessNotifyRoutine 只能记录进程创建或消亡,不能阻止进程的创建。
PsSetCreateProcessNotifyRoutineEx 可以阻止进程的创建。
通知回调例程中的 Create 参数:
对于创建进程通知:是在被创建的进程第一个主线程中调用的进程通知。
对于删除进程通知:是在进程最后一个主线程终止时调用的进程通知。
三个函数都是要调用 PspSetCreateProcessNotifyRoutine:
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 PAGE:0000000140784B B0 PsSetCreateProcessNotifyRoutine proc near PAGE:0000000140784B B0 ; CODE XREF: VfFaultsSetParameters+4 D↓p PAGE:0000000140784B B0 ; ViInitSystemPhase1+1 A↓p PAGE:0000000140784B B0 ; DATA XREF: ... PAGE:0000000140784B B0 sub rsp, 28 h PAGE:0000000140784B B4 mov al, dl PAGE:0000000140784B B6 xor edx, edx PAGE:0000000140784B B8 test al, al PAGE:0000000140784B BA setnz dl PAGE:0000000140784B BD call PspSetCreateProcessNotifyRoutine PAGE:0000000140784B C2 add rsp, 28 h PAGE:0000000140784B C6 retn PAGE:0000000140784B C6 ; --------------------------------------------------------------------------- PAGE:0000000140784B C7 db 0 CCh PAGE:0000000140784B C7 PsSetCreateProcessNotifyRoutine endp PAGE:0000000140784 A00 PsSetCreateProcessNotifyRoutineEx proc near PAGE:0000000140784 A00 ; DATA XREF: .pdata:000000014010 C1A0↑o PAGE:0000000140784 A00 sub rsp, 28 h PAGE:0000000140784 A04 neg dl PAGE:0000000140784 A06 sbb edx, edx PAGE:0000000140784 A08 neg edx PAGE:0000000140784 A0A add edx, 2 PAGE:0000000140784 A0D call PspSetCreateProcessNotifyRoutine PAGE:0000000140784 A12 add rsp, 28 h PAGE:0000000140784 A16 retn PAGE:0000000140784 A16 ; --------------------------------------------------------------------------- PAGE:0000000140784 A17 db 0 CCh PAGE:0000000140784 A17 PsSetCreateProcessNotifyRoutineEx endp PAGE:0000000140784 A60 PsSetCreateProcessNotifyRoutineEx2 proc near PAGE:0000000140784 A60 ; DATA XREF: .pdata:000000014010 C1C4↑o PAGE:0000000140784 A60 sub rsp, 28 h PAGE:0000000140784 A64 mov rax, rdx PAGE:0000000140784 A67 test ecx, ecx PAGE:0000000140784 A69 jnz short loc_140784A83 PAGE:0000000140784 A6B neg r8b PAGE:0000000140784 A6E mov rcx, rax PAGE:0000000140784 A71 sbb edx, edx PAGE:0000000140784 A73 neg edx PAGE:0000000140784 A75 add edx, 6 PAGE:0000000140784 A78 call PspSetCreateProcessNotifyRoutine PAGE:0000000140784 A7D PAGE:0000000140784 A7D loc_140784A7D: ; CODE XREF: PsSetCreateProcessNotifyRoutineEx2+28 ↓j PAGE:0000000140784 A7D add rsp, 28 h PAGE:0000000140784 A81 retn PAGE:0000000140784 A81 ; --------------------------------------------------------------------------- PAGE:0000000140784 A82 db 0 CCh PAGE:0000000140784 A83 ; --------------------------------------------------------------------------- PAGE:0000000140784 A83 PAGE:0000000140784 A83 loc_140784A83: ; CODE XREF: PsSetCreateProcessNotifyRoutineEx2+9 ↑j PAGE:0000000140784 A83 mov eax, 0 C000000Dh PAGE:0000000140784 A88 jmp short loc_140784A7D PAGE:0000000140784 A88 PsSetCreateProcessNotifyRoutineEx2 endp
判断使用哪个函数:
1 2 3 4 5 6 7 8 9 PsSetCreateProcessNotifyRoutine: - 0 , False - 1 , True PsSetCreateProcessNotifyRoutineEx: - 2 , False - 3 , True PsSetCreateProcessNotifyRoutineEx2: - 6 , False - 7 , True
PspSetCreateProcessNotifyRoutine:
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 signed __int64 __fastcall PspSetCreateProcessNotifyRoutine (__int64 a1, unsigned int a2) { KSPIN_LOCK v2; unsigned int v3; __int64 v4; int v5; KSPIN_LOCK *v6; __int64 v7; struct _KTHREAD *v8 ; __int64 v9; __int64 v10; volatile signed __int64 *v11; volatile signed __int32 *v12; v2 = a2; v3 = a2 & 2 ; v4 = a1; if ( a2 & 1 ) { v8 = KeGetCurrentThread(); --LOWORD(v8->_union_108.WaitBlock[3 ].SpareLong); v9 = 0 i64; while ( 1 ) { v10 = ExReferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v9]); v11 = (volatile signed __int64 *)v10; if ( v10 ) { LODWORD(v2) = v2 & 0xFFFFFFFE ; if ( *(_QWORD *)(v10 + 8 ) == v4 && *(_DWORD *)(v10 + 16 ) == (_DWORD)v2 && ExCompareExchangeCallBack(&PspCreateProcessNotifyRoutine[v9], 0 i64, v10) ) { v12 = &PspCreateProcessNotifyRoutineCount; if ( v3 ) v12 = &PspCreateProcessNotifyRoutineExCount; _InterlockedDecrement(v12); ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v9], (unsigned __int64)v11); KeLeaveCriticalRegionThread((__int64)v8); ExWaitForRundownProtectionRelease(v11); ExFreePoolWithTag((PVOID)v11, 0 ); return 0 i64; } ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v9], (unsigned __int64)v11); } v9 = (unsigned int )(v9 + 1 ); if ( (unsigned int )v9 >= 0x40 ) { KeLeaveCriticalRegionThread((__int64)v8); return 3221225594 i64; } } } if ( a2 & 2 ) v5 = 32 ; else v5 = 0 ; if ( !(unsigned int )MmVerifyCallbackFunctionCheckFlags(a1, v5) ) return 3221225506 i64; v6 = ExAllocateCallBack(v4, v2); if ( !v6 ) return 3221225626 i64; v7 = 0 i64; while ( !ExCompareExchangeCallBack(&PspCreateProcessNotifyRoutine[v7], (volatile signed __int64 *)v6, 0 i64) ) { v7 = (unsigned int )(v7 + 1 ); if ( (unsigned int )v7 >= 0x40 ) { ExFreePoolWithTag(v6, 0 ); return 3221225485 i64; } } if ( v3 ) { _InterlockedIncrement(&PspCreateProcessNotifyRoutineExCount); if ( !(PspNotifyEnableMask & 4 ) ) _interlockedbittestandset(&PspNotifyEnableMask, 2u ); } else { _InterlockedIncrement(&PspCreateProcessNotifyRoutineCount); if ( !(PspNotifyEnableMask & 2 ) ) _interlockedbittestandset(&PspNotifyEnableMask, 1u ); } return 0 i64; }
首先判断 if ( a2 & 1 ) 测试 bit 0 是否为 1,也就是测试 Remove == 1?。如果是则为将通知函数从通知数组中移除。 如果是移除通知数组的成员:
指定 PspCreateProcessNotifyRoutine[i],然后调用 ExReferenceCallBackBlock 函数。在该函数内,先将通知函数数组目标元素 EX_FAST_REF.RefCnt -= 1,然后在获取 EX_CALLBACK_ROUTINE_BLOCK。并对 EX_CALLBACK_ROUTINE_BLOCK.RundownProtect 运行保护进行校验检查。最后该函数返回通知函数所在的 EX_CALLBACK_ROUTINE_BLOCK 结构。
然后调用 ExCompareExchangeCallBack(arg1,arg2,arg3) 将目标通知函数从数组中摘除。
最后是 PspCreateProcessNotifyRoutineCount/PspCreateProcessNotifyRoutineExCount -= 1。该全局变量表示数组元素的个数 。
前者对应 PsSetCreateProcessNotifyRoutine 创建的通知函数。
后者对应 PsSetCreateProcessNotifyRoutineEx/PsSetCreateProcessNotifyRoutineEx2 创建的通知函数。
如果是创建通知函数。
调用 MmVerifyCallbackFunctionCheckFlags 判断通知例程所在模块是否在 PE 头中指定 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 标志,可通过链接器命令行参数处指定:/integritycheck。如果未启用完整性检查标志,则返回 0xC0000022。
关于 MmVerifyCallbackFunctionCheckFlags 函数的分析,请看本文 2.1.2 节。
调用 ExAllocateCallBack 函数创建通知对象 EX_CALLBACK_ROUTINE_BLOCK 结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 _EX_CALLBACK_ROUTINE_BLOCK *__fastcall ExAllocateCallBack (PEX_CALLBACK_FUNCTION Function, PVOID Context) { _EX_CALLBACK_ROUTINE_BLOCK *CallbackBlock; CallbackBlock = (_EX_CALLBACK_ROUTINE_BLOCK *)ExAllocatePoolWithTag( NonPagedPoolNx, sizeof (_EX_CALLBACK_ROUTINE_BLOCK), 'brbC'); if ( CallbackBlock ) { CallbackBlock->Function = Function; CallbackBlock->Context = Context; ExInitializePushLock(&CallbackBlock->RundownProtect); } return CallbackBlock; }
最后调用 ExCompareExchangeCallBack(arg1,arg2,arg3) 将新创建的 EX_CALLBACK_ROUTINE_BLOCK 通知对象插入到 PsSetCreateProcessNotifyRoutine 数组中中元素为 NULL 的位置。
1 2 3 4 if ([arg1] == arg3){ [arg1] = arg2; }
对通知数组数量的全局变量进行加 1 操作。PspCreateProcessNotifyRoutineCount/PspCreateProcessNotifyRoutineExCount += 1。
对 PspNotifyEnableMask 进行操作。具体见 2.1.3。
1.1.1 通知例程数组 不管是进程通知,还是线程、模块通知,都有对应的通知函数数组,每个数组最多有 64 个元素 。也就是说每个类型的通知函数最多有 64 个。以下三个全局指针即为数组首地址:
1 2 3 4 5 6 7 8 9 10 11 PspCreateProcessNotifyRoutine PspCreateThreadNotifyRoutine PspLoadImageNotifyRoutine kd> dq PspCreateProcessNotifyRoutine fffff804`74f 01420 ffff858b`b885006f ffff858b`b88feb1f fffff804`74f 01430 ffff858b`bb29b26f ffff858b`bb29baaf fffff804`74f 01440 ffff858b`bb29bc5f ffff858b`bb721cdf fffff804`74f 01450 ffff858b`bb721a9f ffff858b`bb02231f fffff804`74f 01460 ffff858b`be3fb8ff ffff858b`bfbfc95f fffff804`74f 01470 00000000 `00000000 00000000 `00000000
说是通知例程数组实际上并不准确,实际上该数组的每一个元素都是 EX_CALLBACK 结构。
关于 EX_FAST_REF 结构的解释可看《x86 内存管理(一)虚拟地址空间布局-ASLR-VAD 4.9 》。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct _EX_CALLBACK { EX_FAST_REF RoutineBlock; } EX_CALLBACK, *PEX_CALLBACK; typedef struct _EX_FAST_REF { union { VOID* Object; ULONGLONG RefCnt:4 ; ULONGLONG Value; }; }EX_FAST_REF;
RoutineBlock 即是 EX_FAST_REF 结构,又是 EX_CALLBACK_ROUTINE_BLOCK 结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct _EX_CALLBACK_ROUTINE_BLOCK { +0x0 EX_RUNDOWN_REF RundownProtect; +0x8 PEX_CALLBACK_FUNCTION Function; +0x10 PVOID Context; } EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK; struct _EX_RUNDOWN_REF { union { ULONGLONG Count; VOID* Ptr; }; };
示例:解析 PspCreateProcessNotifyRoutine 数组。
查看获取数组的元素。
1 2 3 4 5 6 7 kd> dq PspCreateProcessNotifyRoutine fffff804`74f 01420 ffff858b`b885006f ffff858b`b88feb1f fffff804`74f 01430 ffff858b`bb29b26f ffff858b`bb29baaf fffff804`74f 01440 ffff858b`bb29bc5f ffff858b`bb721cdf fffff804`74f 01450 ffff858b`bb721a9f ffff858b`bb02231f fffff804`74f 01460 ffff858b`be3fb8ff ffff858b`bfbfc95f fffff804`74f 01470 00000000 `00000000 00000000 `00000000
下面以第一个元素 0xffff858bb885006f 进行解析。
解析 RoutineBlock。
得到 0xffff858bb885006f 对应于 EX_FAST_REF 结构(RoutineBlock),将低 4 位清零以获取 EX_CALLBACK_ROUTINE_BLOCK 结构。
1 2 3 4 kd> dq (0xffff858b `b885006f & 0xFFFFFFFF `FFFFFFF0) ffff858b`b8850060 00000000 `00000020 fffff804`74571050 ffff858b`b8850070 00000000 `00000000 fffff804`72f 878f0 ffff858b`b8850080 62676e43 `02030000 00000000 `00000000
可以看到 EX_CALLBACK_ROUTINE_BLOCK.Function 对应的函数地址是 0xfffff80474571050:
1 2 3 4 5 kd> u fffff804`74571050 nt!ViCreateProcessCallback: fffff804`74571050 4883 ec28 sub rsp,28 h fffff804`74571054 833 d850e8c0000 cmp dword ptr [nt!ViVerifierEnabled (fffff804`74e31 ee0)],0 fffff804`7457105b 488b c2 mov rax,rdx
最后需要说明的是通知数组元素个数:
PspCreateProcessNotifyRoutineCount :表示 PsSetCreateProcessNotifyRoutine 函数创建的通知函数个数。
PspCreateProcessNotifyRoutineExCount :表示 PsSetCreateProcessNotifyRoutineEx/2 函数创建的通知函数个数。
1.1.2 验证通知例程模块完整性 MmVerifyCallbackFunctionCheckFlags 函数功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 BOOL __fastcall (PVOID NotifyRoutine, DWORD Flags) { struct _KTHREAD *CurrentThread ; BOOL bHasValidFlags; _KLDR_DATA_TABLE_ENTRY *Entry; if ( MiGetSystemRegionType(NotifyRoutine) == 1 ) return 0 ; CurrentThread = KeGetCurrentThread(); bHasValidFlags = 0 ; --CurrentThread->KernelApcDisable; ExAcquireResourceSharedLite(&PsLoadedModuleResource, 1u ); Entry = MiLookupDataTableEntry(NotifyRoutine, 1u ); if ( Entry && (!Flags || Entry->Flags & Flags) ) bHasValidFlags = 1 ; ExReleaseResourceLite(&PsLoadedModuleResource); KeLeaveCriticalRegionThread(CurrentThread); return bHasValidFlags; }
首先调用 ExAcquireResourceSharedLite 函数,该函数请求 PsLoadedModuleResource 资源的读访问权限。即根据 PsLoadedModuleResource 加载模块参数,去获得模块资源的共享/独占权限。
然后调用 MiLookupDataTableEntry 函数取得通知函数所在模块的 KLDR_DATA_TABLE_ENTRY 链。
对通知函数进行如下验证,才表示通过,返回 TRUE:
该函数主要是判断通知例程所在模块是否在 PE 头中指定 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 标志,可通过链接器命令行参数处指定:/integritycheck。如果是通过 Visual Studio 自身编译器作为交叉编译工具链,则需在“项目-属性-链接器-命令行”位置添加 /INTEGRITYCHECK 即可。
1 2 3 4 PLDR_DATA_TABLE_ENTRY NTAPI MiLookupDataTableEntry ( IN PVOID Address, IN BOOLEAN bAcquiredResource ) ;
参看:
1.1.3 PspNotifyEnableMask PspNotifyEnableMask 表示是否开启进程/线程/模块通知回调,此变量不受 PatchGuard 保护 :
Bit 0:是否开启模块通知。
Bit 1:是否开启 PsSetCreateProcessNotifyRoutine 创建的进程通知。
Bit 2:是否开启 PsSetCreateProcessNotifyRoutineEx/2 创建的进程通知。
Bit 3:是否开启 PsSetCreateThreadNotifyRoutine 创建的线程通知。
Bit 4:是否开启 PsSetCreateThreadNotifyRoutineEx 创建的线程通知。
PspNotifyEnableMask 是 ULONG 类型。
在 PspSetCreateProcessNotifyRoutine 函数最后会对进程这两个标志位进行设置。
1 2 3 4 5 6 7 8 9 10 11 12 if ( v3 ) { _InterlockedIncrement(&PspCreateProcessNotifyRoutineExCount); if ( !(PspNotifyEnableMask & 4 ) ) _interlockedbittestandset(&PspNotifyEnableMask, 2u ); } else { _InterlockedIncrement(&PspCreateProcessNotifyRoutineCount); if ( !(PspNotifyEnableMask & 2 ) ) _interlockedbittestandset(&PspNotifyEnableMask, 1u ); }
参考:
1.1.4 总结 要分析回调函数,就先要分析相关的数据结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 PspCreateProcessNotifyRoutine[i] 对应于 EX_CALLBACK typedef struct _EX_CALLBACK { EX_FAST_REF RoutineBlock; } EX_CALLBACK, *PEX_CALLBACK; RoutineBlock 对应于通知对象 EX_CALLBACK_ROUTINE_BLOCK typedef struct _EX_CALLBACK_ROUTINE_BLOCK { EX_RUNDOWN_REF RundownProtect; PEX_CALLBACK_FUNCTION Function; PVOID Context; } EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;
通知对象结构:
1 2 3 4 5 6 typedef struct _EX_CALLBACK_ROUTINE_BLOCK { EX_RUNDOWN_REF RundownProtect; PEX_CALLBACK_FUNCTION Function; PVOID Context; } EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;
进程通知整体结构:
参考:
1.2 进程通知函数 创建进程通知有以下三个例程:
PsSetCreateProcessNotifyRoutine
PsSetCreateProcessNotifyRoutineEx
PsSetCreateProcessNotifyRoutineEx2
每个函数对应的通知例程不同。
CREATE_PROCESS_NOTIFY_ROUTINE 通过函数 PsSetCreateProcessNotifyRoutine 创建的通知例程为 PCREATE_PROCESS_NOTIFY_ROUTINE 类型。
1 2 3 4 NTSTATUS PsSetCreateProcessNotifyRoutine ( [in] PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, [in] BOOLEAN Remove ) ;
PCREATE_PROCESS_NOTIFY_ROUTINE :
1 2 3 4 5 6 7 8 PCREATE_PROCESS_NOTIFY_ROUTINE PcreateProcessNotifyRoutine; void PcreateProcessNotifyRoutine ( [in] HANDLE ParentId, [in] HANDLE ProcessId, [in] BOOLEAN Create ) {...}
Create:TRUE,当前是创建进程的通知。FALSE,当前是进程结束通知。
ProcessId:引发该通知的进程 ID。被创建的进程ID 。
ParentId:表示 ProcessId 进程的父进程。
通知例程没有返回值 ,也就是不能通过返回值来改变进程创建或者结束的行为。
需要说明:
PsSetCreateProcessNotifyRoutine 只能记录进程创建或消亡,不能阻止进程的创建。
PsSetCreateProcessNotifyRoutineEx 可以阻止进程的创建。
通知回调例程中的 Create 参数:
对于创建进程通知:是在被创建的进程第一个主线程中调用的进程通知。
对于删除进程通知:是在进程最后一个主线程终止时调用的进程通知。
注意以下两点,否则会蓝屏:
调用 pPsLookupProcessByProcessId((HANDLE)ProcessId, &pEPOCESS);,系统会对该进程的引用计数累加1,需要在不使用该结构时,及时调用 ObDereferenceObject(pEPOCESS) 解除引用计数。
A driver must remove any callbacks that it registers before it unloads. 在卸载一个驱动之前,必须卸载该驱动创建的所有通知函数,这个操作可以放在 DriverUnload 来完成 。
CREATE_PROCESS_NOTIFY_ROUTINE_EX 通过函数 PsSetCreateProcessNotifyRoutineEx 创建的通知例程为 PCREATE_PROCESS_NOTIFY_ROUTINE_EX 类型。
1 2 3 4 NTSTATUS PsSetCreateProcessNotifyRoutineEx ( [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, [in] BOOLEAN Remove ) ;
PCREATE_PROCESS_NOTIFY_ROUTINE_EX :
1 2 3 4 5 6 7 8 PCREATE_PROCESS_NOTIFY_ROUTINE_EX PcreateProcessNotifyRoutineEx; void PcreateProcessNotifyRoutineEx ( [_Inout_] PEPROCESS Process, [in] HANDLE ProcessId, [in, out, optional] PPS_CREATE_NOTIFY_INFO CreateInfo ) {...}
ProcessId:被创建进程的 ID。
PEPROCESS:被创建进程的 EPROCESS。
PPS_CREATE_NOTIFY_INFO:如果为 NULL,表示进程正在退出。如果不为 NULL,则用来表示被创建进程的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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;
代码如下:
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 #include <ntddk.h> typedef NTSTATUS (*PSLOOKUPPROCESSBYPROCESSID) ( HANDLE ProcessId, PEPROCESS* Process ) ;VOID MyProcessNotify ( PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo ) { UNICODE_STRING uProcName; RtlInitUnicodeString(&uProcName, L"notepad.exe" ); __try { if ((CreateInfo != NULL ) && (wcswcs(CreateInfo->ImageFileName->Buffer, L"notepad.exe" ) != NULL )) { DbgPrint("ParentProcessId= %d, UniqueProcess = %d\n" , (ULONG)(CreateInfo->ParentProcessId), (ULONG)(CreateInfo->CreatingThreadId.UniqueProcess)); DbgPrint("ImageFileName = %wZ\n" , CreateInfo->ImageFileName); } } __except (1 ) { DbgPrint("MyProcessNotify have a fault!\n" ); } } VOID DriverUnload (PDRIVER_OBJECT pDriver) { PsSetCreateProcessNotifyRoutineEx(MyProcessNotify, TRUE); DbgPrint("Goodbye~\n" ); } NTSTATUS DriverEntry (PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) { pDriver->DriverUnload = DriverUnload; NTSTATUS Status = STATUS_SUCCESS; Status = PsSetCreateProcessNotifyRoutineEx(MyProcessNotify, FALSE); if (Status != STATUS_SUCCESS) { DbgPrint("Faild to Create Process Notify!\n" ); DbgPrint("Status = %x!\n" , Status); } return STATUS_SUCCESS; }
https://www.cnblogs.com/priarieNew/p/9758980.html
https://xz.aliyun.com/t/12129
PsSetCreateProcessNotifyRoutineEx2 函数 PsSetCreateProcessNotifyRoutineEx2 创建的通知例程由 PSCREATEPROCESSNOTIFYTYPE 决定。
1 2 3 4 5 6 7 8 9 NTSTATUS PsSetCreateProcessNotifyRoutineEx2 ( [in] PSCREATEPROCESSNOTIFYTYPE NotifyType, [in] PVOID NotifyInformation, [in] BOOLEAN Remove ) ;typedef enum _PSCREATEPROCESSNOTIFYTYPE { PsCreateProcessNotifySubsystems } PSCREATEPROCESSNOTIFYTYPE;
如果 NotifyType = PsCreateProcessNotifySubsystems,则 NotifyInformation 为 PCREATE_PROCESS_NOTIFY_ROUTINE_EX。这样就和 PsSetCreateProcessNotifyRoutineEx 一样了。
1.3 调用进程通知 当进程对象被创建时,系统并不会调用进程通知回调,只有当第一个线程插入进程中时才会调用该回调 。
当对通知数组 PspCreateProcessNotifyRoutine 进行交叉引用时,会发现第一个是 PspCallProcessNotifyRoutines 函数。实际上触发进程通知例程执行的就是该函数,其功能类似于 ExNotifyCallback 函数。
调用 PspCallProcessNotifyRoutines 函数主要有两个地方:
PspInsertThread
PspExitProcess
参考《Windows内核回调实现原理与逆向调试分析 》上面两个函数调用过程如下:
通过逆向分析,该函数 PspCallProcessNotifyRoutines(PEPROCESS pEprocess, __int64 a2, BOOLEAN Create) 第二个参数暂时还没有逆向出来具体含义。
该函数的功能非常简单:
在一个最多可循环 63 次的循环里,根据 PspCreateProcessNotifyRoutine[i] 过去到通知例程所在的 EX_CALLBACK_ROUTINE_BLOCK 结构。这个过程由 ExReferenceCallBackBlock 函数来实现。
只要 PspNotifyEnableMask 的 Bit 1、Bit 2 已经置位,就可以调用数组中每一个不为 NULL 的通知例程。
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 __int64 __fastcall PspCallProcessNotifyRoutines (_QWORD *a1, __int64 a2, char a3) { __int64 v3; char v4; _QWORD *v5; __int64 v6; void (__fastcall **v7)(_QWORD *); __int64 v8; __int64 v9; unsigned int v10; unsigned int v11; signed int v12; __m128i v13; __int64 v14; signed __int64 v15; __int64 v16; signed __int64 v17; __int64 *v18; int v19; unsigned __int64 v20; unsigned __int64 v21; __int64 v22; int v24; __int64 v25; unsigned int v26; __int64 v27; __int64 *v28; __int64 v29; void (__fastcall **v30)(_QWORD *); __int64 v31; void (__fastcall **v32)(_QWORD *); __int64 Dst; int v34; __int64 v35; __int128 v36; __int64 v37; __int128 v38; int v39; char v40; __int64 v41; v3 = a2; v4 = a3; v5 = a1; memset (&Dst, 0 , 0x48 ui64); v6 = ExGetExtensionTable(PspDamExtensionHost); v7 = (void (__fastcall **)(_QWORD *))v6; v30 = (void (__fastcall **)(_QWORD *))v6; v8 = ExGetExtensionTable(PspBamExtensionHost); v9 = v5[280 ]; v10 = 0 ; v11 = ((unsigned int )PspNotifyEnableMask >> 2 ) & 1 ; v32 = (void (__fastcall **)(_QWORD *))v8; v41 = 0 i64; v40 = 0 ; v31 = v9; if ( !v4 || !(_BYTE)v11 && !v7 && !v8 ) { v18 = 0 i64; goto LABEL_17; } v12 = 0 ; Dst = 72 i64; if ( v9 ) v12 = 2 ; v34 = v12; v35 = v5[168 ]; v13 = *(__m128i *)&KeGetCurrentThread()[1 ].CycleTime; v39 = 0 ; _mm_storeu_si128((__m128i *)&v36, v13); if ( v9 ) { v37 = 0 i64; _mm_store_si128((__m128i *)&v38, (__m128i)0 i64); if ( !v3 ) goto LABEL_16; v28 = *(__int64 **)(v3 + 456 ); if ( !v28 ) goto LABEL_16; v37 = *v28; *(_QWORD *)&v38 = v28[1 ]; v17 = v28[2 ]; } else { if ( v3 && (v14 = *(_QWORD *)(v3 + 176 )) != 0 ) { v41 = *(_QWORD *)(v3 + 176 ); } else { v40 = 1 ; PsReferenceProcessFilePointer(v5, &v41); v14 = v41; } v37 = v14; if ( v3 && *(_DWORD *)(v3 + 4 ) & 0x20 ) { v34 |= 1u ; v15 = v3 + 232 ; } else { v15 = v14 + 88 ; } *(_QWORD *)&v38 = v15; if ( !v3 || (v16 = *(_QWORD *)(v3 + 208 )) == 0 ) { *((_QWORD *)&v38 + 1 ) = 0 i64; goto LABEL_16; } v17 = v16 + 112 ; } *((_QWORD *)&v38 + 1 ) = v17; LABEL_16: v18 = &Dst; LABEL_17: if ( PspNotifyEnableMask & 2 || (_BYTE)v11 ) { v19 = 0 ; while ( 1 ) { ExReferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v19]); v21 = v20; if ( v20 ) { v24 = *(_DWORD *)(v20 + 16 ); if ( !v31 || v24 & 4 ) { if ( v24 & 2 ) { if ( (_BYTE)v11 ) { v25 = v5[136 ]; (*(void (__fastcall **)(_QWORD *))(v21 + 8 ))(v5); } } else { v27 = v5[136 ]; (*(void (__fastcall **)(_QWORD))(v21 + 8 ))(v5[168 ]); } } ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v19], v21); if ( v18 ) { v26 = *((_DWORD *)v18 + 16 ); if ( (v26 & 0x80000000 ) != 0 ) break ; } } if ( (unsigned int )++v19 >= 0x40 ) goto LABEL_21; } v10 = *((_DWORD *)v18 + 16 ); PsTerminateProcess(v5, v26); LABEL_21: v7 = v30; } if ( v32 && (!v18 || *((_DWORD *)v18 + 16 ) >= 0 ) ) { v22 = v5[136 ]; (*v32)(v5); ExReleaseExtensionTable(PspBamExtensionHost); } if ( v7 && (!v18 || *((_DWORD *)v18 + 16 ) >= 0 ) ) { v29 = v5[136 ]; (*v7)(v5); ExReleaseExtensionTable(PspDamExtensionHost); } if ( v40 ) HalPutDmaAdapter(v41); return v10; }
注意,该函数中还涉及到 ExGetExtensionTable(PspDamExtensionHost),这与内核扩展机制有关。可以参考:
1.4 枚举通知例程 可参考:Enumerating process, thread, and image load notification callback routines in Windows 。
PspEnumerateCallback/CmpEnumerateCallback/DbgEnumerateCallback/ExpEnumerateCallback/KiEnumerateCallback
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 signed __int64 __fastcall PspEnumerateCallback (int a1, unsigned int *RoutineIndex, PEX_CALLBACK *a3) { unsigned int *v3; int v4; signed __int64 *v5; __int64 v6; v3 = RoutineIndex; if ( a1 ) { v4 = a1 - 1 ; if ( v4 ) { if ( v4 != 1 ) return 0 i64; v5 = (signed __int64 *)&PspLoadImageNotifyRoutine; } else { v5 = PspCreateProcessNotifyRoutine; } } else { v5 = (signed __int64 *)&PspCreateThreadNotifyRoutine; } v6 = *RoutineIndex; if ( (unsigned int )v6 < 0x40 ) { *a3 = &v5[v6]; *v3 = v6 + 1 ; return 1 i64; } return 0 i64; }
绿盟那个手册有将虚拟化
2 线程通知
2.1 创建/移除线程通知 创建线程通知的函数主要有两个:PsSetCreateThreadNotifyRoutine、PsSetCreateThreadNotifyRoutineEx。
PsSetCreateThreadNotifyRoutine :
1 2 3 4 5 6 7 8 9 10 11 12 NTSTATUS PsSetCreateThreadNotifyRoutine ( [in] PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine ) ;PCREATE_THREAD_NOTIFY_ROUTINE PcreateThreadNotifyRoutine; void PcreateThreadNotifyRoutine ( [in] HANDLE ProcessId, [in] HANDLE ThreadId, [in] BOOLEAN Create ) {...}
PsSetCreateThreadNotifyRoutineEx :
1 2 3 4 5 6 7 8 9 NTSTATUS PsSetCreateThreadNotifyRoutineEx ( [in] PSCREATETHREADNOTIFYTYPE NotifyType, [in] PVOID NotifyInformation ) ;typedef enum _PSCREATETHREADNOTIFYTYPE { PsCreateThreadNotifyNonSystem, PsCreateThreadNotifySubsystems } PSCREATETHREADNOTIFYTYPE;
移除通知函数的例程为 PsRemoveCreateThreadNotifyRoutine:
1 2 3 NTSTATUS PsRemoveCreateThreadNotifyRoutine ( [in] PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine ) ;
PsSetCreateThreadNotifyRoutine/PsSetCreateThreadNotifyRoutineEx 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 signed __int64 __fastcall PsSetCreateThreadNotifyRoutine (LPVOID a1) { return PspSetCreateThreadNotifyRoutine(a1, 0 ); } signed __int64 __fastcall PsSetCreateThreadNotifyRoutineEx (int a1, LPVOID a2) { KSPIN_LOCK v2; unsigned int v3; signed __int64 result; v2 = a2; if ( a1 ) { if ( a1 != 1 ) return 3221225485 i64; v3 = 2 ; } else { v3 = 1 ; } if ( (unsigned int )MmVerifyCallbackFunctionCheckFlags(a2, 32 ) ) result = PspSetCreateThreadNotifyRoutine(v2, v3); else result = 3221225506 i64; return result; }
PspSetCreateThreadNotifyRoutine 函数原型如下:
1 PspSetCreateThreadNotifyRoutine(PVOID NotifyRoutine, DWORD Flags)
分析得如下:
PsSetCreateThreadNotifyRoutine:
PsSetCreateThreadNotifyRoutineEx:
NotifyType == PsCreateThreadNotifyNonSystem(0),Flags = 1
NotifyType == PsCreateThreadNotifySubsystems(1),Flags = 2
同样也是需要调用 MmVerifyCallbackFunctionCheckFlags(PVOID NotifyRoutine, DWORD Flags) 验证通知函数所在模块的完整性。
PspSetCreateThreadNotifyRoutine 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 signed __int64 __fastcall PspSetCreateThreadNotifyRoutine (PVOID a1, unsigned int a2) { char v2; KSPIN_LOCK *v3; __int64 v4; v2 = a2; v3 = ExAllocateCallBack(a1, a2); if ( !v3 ) return 3221225626 i64; v4 = 0 i64; while ( !ExCompareExchangeCallBack(&PspCreateThreadNotifyRoutine[v4], (volatile signed __int64 *)v3, 0 i64) ) { v4 = (unsigned int )(v4 + 1 ); if ( (unsigned int )v4 >= 0x40 ) { ExFreePoolWithTag(v3, 0 ); return 3221225626 i64; } } if ( v2 & 1 ) { _InterlockedIncrement(&PspCreateThreadNotifyRoutineNonSystemCount); if ( !(PspNotifyEnableMask & 0x10 ) ) _interlockedbittestandset(&PspNotifyEnableMask, 4u ); } else { _InterlockedIncrement(&PspCreateThreadNotifyRoutineCount); if ( !(PspNotifyEnableMask & 8 ) ) _interlockedbittestandset(&PspNotifyEnableMask, 3u ); } return 0 i64; }
PspNotifyEnableMask 和通知函数个数:
Bit 3,PsCreateThreadNotifyNonSystem 型线程通知函数,对应于 PspCreateThreadNotifyRoutineCount。
Bit 4,PsCreateThreadNotifyNonSystem 型线程通知函数,对应于 PspCreateThreadNotifyRoutineNonSystemCount。
移除线程通知:
PsRemoveCreateThreadNotifyRoutine 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 signed __int64 __fastcall PsRemoveCreateThreadNotifyRoutine (__int64 a1) { struct _KTHREAD *v1 ; __int64 v2; __int64 v3; __int64 v4; unsigned __int64 v5; volatile signed __int32 *v7; v1 = KeGetCurrentThread(); v2 = a1; --LOWORD(v1->_union_108.WaitBlock[3 ].SpareLong); v3 = 0 i64; while ( 1 ) { v4 = ExReferenceCallBackBlock(&PspCreateThreadNotifyRoutine[v3]); v5 = v4; if ( v4 ) break ; LABEL_6: v3 = (unsigned int )(v3 + 1 ); if ( (unsigned int )v3 >= 0x40 ) { KeLeaveCriticalRegionThread((__int64)v1); return 3221225594 i64; } } if ( *(_QWORD *)(v4 + 8 ) != v2 || !ExCompareExchangeCallBack(&PspCreateThreadNotifyRoutine[v3], 0 i64, v4) ) { ExDereferenceCallBackBlock(&PspCreateThreadNotifyRoutine[v3], v5); goto LABEL_6; } v7 = &PspCreateThreadNotifyRoutineNonSystemCount; if ( !*(_QWORD *)(v5 + 16 ) ) v7 = &PspCreateThreadNotifyRoutineCount; _InterlockedAdd(v7, 0xFFFFFFFF ); ExDereferenceCallBackBlock(&PspCreateThreadNotifyRoutine[v3], v5); KeLeaveCriticalRegionThread((__int64)v1); ExWaitForRundownProtectionRelease((volatile signed __int64 *)v5); ExFreePoolWithTag((PVOID)v5, 0 ); return 0 i64; }
PspCallThreadNotifyRoutines
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 void __fastcall PspCallThreadNotifyRoutines (__int64 a1, char a2, char a3) { __int64 v3; bool v4; volatile signed __int64 *v5; signed __int64 v6; unsigned __int64 v7; unsigned __int64 v8; int v9; __int64 v10; volatile signed __int64 *v11; signed __int64 v12; unsigned __int64 v13; unsigned __int64 v14; __int64 v15; volatile signed __int64 *v16; signed __int64 v17; unsigned __int64 v18; unsigned __int64 v19; __int64 v20; v3 = a1; v4 = *(_QWORD *)(a1 + 1504 ) != 0 i64; if ( a2 ) { if ( a3 ) { if ( PspNotifyEnableMask & 0x10 ) { v11 = PspCreateThreadNotifyRoutine; v12 = 64 i64; do { ExReferenceCallBackBlock(v11); v14 = v13; if ( v13 ) { if ( *(_DWORD *)(v13 + 16 ) & 1 ) { v15 = *(_QWORD *)(v3 + 1152 ); (*(void (__fastcall **)(_QWORD))(v13 + 8 ))(*(_QWORD *)(*(_QWORD *)(v3 + 544 ) + 1088 i64)); } ExDereferenceCallBackBlock(v11, v14); } ++v11; --v12; } while ( v12 ); } } else if ( PspNotifyEnableMask & 8 ) { v5 = PspCreateThreadNotifyRoutine; v6 = 64 i64; do { ExReferenceCallBackBlock(v5); v8 = v7; if ( v7 ) { v9 = *(_DWORD *)(v7 + 16 ); if ( !(v9 & 1 ) && (!v4 || v9 & 2 ) ) { v10 = *(_QWORD *)(v3 + 1152 ); (*(void (__fastcall **)(_QWORD))(v8 + 8 ))(*(_QWORD *)(*(_QWORD *)(v3 + 544 ) + 1088 i64)); } ExDereferenceCallBackBlock(v5, v8); } ++v5; --v6; } while ( v6 ); } } else if ( PspNotifyEnableMask & 0x10 || PspNotifyEnableMask & 8 ) { v16 = PspCreateThreadNotifyRoutine; v17 = 64 i64; do { ExReferenceCallBackBlock(v16); v19 = v18; if ( v18 ) { JUMPOUT(v4, 0 , sub_140811BB0); v20 = *(_QWORD *)(v3 + 1152 ); (*(void (__fastcall **)(_QWORD))(v18 + 8 ))(*(_QWORD *)(*(_QWORD *)(v3 + 544 ) + 1088 i64)); ExDereferenceCallBackBlock(v16, v19); } ++v16; --v17; } while ( v17 ); } }
总结
2.2 线程通知函数 创建线程通知函数的 PsSetCreateThreadNotifyRoutine/PsSetCreateThreadNotifyRoutineEx 线程通知函数都是 CREATE_THREAD_NOTIFY_ROUTINE。
1 2 3 4 5 6 7 8 PCREATE_THREAD_NOTIFY_ROUTINE PcreateThreadNotifyRoutine; void PcreateThreadNotifyRoutine ( [in] HANDLE ProcessId, [in] HANDLE ThreadId, [in] BOOLEAN Create ) {...}
无返回值 。
需要说明 :
对于 PsSetCreateThreadNotifyRoutine 函数:
Create == TRUE,ThreadId 表示创建者线程的 TID,而不是被创建的线程 TID。
Create == FALSE,表示当前即将要结束的线程 TID。
对于 PsSetCreateThreadNotifyRoutineEx 函数:
3 模块通知
3.1 创建/摘除模块通知 当一个内核模块(驱动)或者应用层模块(DLL, EXE)被加载时,会触发一个加载模块的通知,当通知发生时,模块己经被映射到内存中 ,但是模块内的代 码还没有开始执行。模块在进行卸载时也会发送通知。
创建模块通知函数的函数是 PsSetLoadImageNotifyRoutine,该函数会直接调用 PsSetLoadImageNotifyRoutineEx 函数。摘除模块通知函数的例程是 PsCallImageNotifyRoutines。
1 2 3 4 5 6 7 8 NTSTATUS PsSetLoadImageNotifyRoutine ( [in] PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine ) ;NTSTATUS PsSetLoadImageNotifyRoutineEx ( [in] PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine, [in] ULONG_PTR Flags ) ;
PsSetLoadImageNotifyRoutine 1 2 3 4 __int64 __fastcall PsSetLoadImageNotifyRoutine (__int64 a1) { return PsSetLoadImageNotifyRoutineEx(a1, 0 i64); }
PsSetLoadImageNotifyRoutineEx 需要说明:
ExAllocateCallBack、ExCompareExchangeCallBack 函数返回类型都是 EX_CALLBACK_ROUTINE_BLOCK。
指向模块通知例程的指针是 PspLoadImageNotifyRoutine,每个元素都是 EX_CALLBACK 结构。
模块通知数组中元素的个数使用 PspLoadImageNotifyRoutine 全局变量表示。
是否启用模块通知,由 PspNotifyEnableMask 的 Bit 0 是否为 1 来决定。
每次创建模块通知例程时都会调用 EtwWrite 写入 ETW 事件。
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 signed __int64 __fastcall PsSetLoadImageNotifyRoutineEx (KSPIN_LOCK a1, KSPIN_LOCK a2) { __int64 v2; KSPIN_LOCK *v3; __int64 v4; unsigned int v5; unsigned int v7; __int64 v8; __int64 *v9; int v10; int v11; int *v12; int v13; int v14; v2 = a1; if ( a2 & 0xFFFFFFFFFFFFFFFE ui64 ) return 3221225712 i64; v3 = ExAllocateCallBack(a1, a2); if ( v3 ) { v4 = 0 i64; while ( !ExCompareExchangeCallBack(&PspLoadImageNotifyRoutine[v4], (volatile signed __int64 *)v3, 0 i64) ) { v4 = (unsigned int )(v4 + 1 ); if ( (unsigned int )v4 >= 0x40 ) { ExFreePoolWithTag(v3, 0 ); goto LABEL_15; } } _InterlockedIncrement(&PspLoadImageNotifyRoutineCount); if ( !(PspNotifyEnableMask & 1 ) ) _interlockedbittestandset(&PspNotifyEnableMask, 0 ); v5 = 0 ; } else { LABEL_15: v5 = -1073741670 ; } v7 = v5; v8 = v2; if ( EtwApiCallsProvRegHandle ) { v11 = 0 ; v14 = 0 ; v9 = &v8; v10 = 8 ; v12 = (int *)&v7; v13 = 4 ; EtwWrite(EtwApiCallsProvRegHandle); } return v5; }
PsRemoveLoadImageNotifyRoutine 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 signed __int64 __fastcall PsRemoveLoadImageNotifyRoutine (__int64 a1) { struct _KTHREAD *v1 ; __int64 v2; __int64 v3; __int64 v4; volatile signed __int64 *v5; v1 = KeGetCurrentThread(); v2 = a1; --LOWORD(v1->_union_108.WaitBlock[3 ].SpareLong); v3 = 0 i64; while ( 1 ) { ExReferenceCallBackBlock(&PspLoadImageNotifyRoutine[v3]); v5 = (volatile signed __int64 *)v4; if ( v4 ) break ; LABEL_6: v3 = (unsigned int )(v3 + 1 ); if ( (unsigned int )v3 >= 0x40 ) { KeLeaveCriticalRegionThread((__int64)v1); return 3221225594 i64; } } if ( *(_QWORD *)(v4 + 8 ) != v2 || !ExCompareExchangeCallBack(&PspLoadImageNotifyRoutine[v3], 0 i64, v4) ) { ExDereferenceCallBackBlock(&PspLoadImageNotifyRoutine[v3], (unsigned __int64)v5); goto LABEL_6; } _InterlockedAdd(&PspLoadImageNotifyRoutineCount, 0xFFFFFFFF ); ExDereferenceCallBackBlock(&PspLoadImageNotifyRoutine[v3], (unsigned __int64)v5); KeLeaveCriticalRegionThread((__int64)v1); ExWaitForRundownProtectionRelease(v5); ExFreePoolWithTag((PVOID)v5, 0 ); return 0 i64; }
3.2 模块通知例程 模块通知例程原型为 LOAD_IMAGE_NOTIFY_ROUTINE:
1 2 3 4 5 6 7 8 PLOAD_IMAGE_NOTIFY_ROUTINE PloadImageNotifyRoutine; void PloadImageNotifyRoutine ( [in, optional] PUNICODE_STRING FullImageName, [in] HANDLE ProcessId, [in] PIMAGE_INFO ImageInfo ) {...}
参数说明:
FullImageName:被加载模块的全路径。注意,如果进程在创建期间无法获取模块的全路径时, FullImageName = NULL。
ProcessId:加载该模块的进程 PID(不是表示该模块所属的进程 PID)。如果是驱动程序,则 ProcessId = NULL 。
ImageInfo:表示被加载模块详细的信息。注意该结构在 Vista 前后有变化,本文只关注 Vista 之后。
无返回值 。
IMAGE_INFO 结构如下,重点关注 ExtendedInfoPresent 成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8 ; ULONG SystemModeImage : 1 ; ULONG ImageMappedToAllPids : 1 ; ULONG ExtendedInfoPresent : 1 ; ULONG MachineTypeMismatch : 1 ; ULONG ImageSignatureLevel : 4 ; ULONG ImageSignatureType : 3 ; ULONG ImagePartialMap : 1 ; ULONG Reserved : 12 ; }; }; PVOID ImageBase; ULONG ImageSelector; SIZE_T ImageSize; ULONG ImageSectionNumber; } IMAGE_INFO, *PIMAGE_INFO;
如果 ExtendedInfoPresent == 1,则将 IMAGE_INFO 扩展为 IMAGE_INFO_EX 结构:
1 2 3 4 5 typedef struct _IMAGE_INFO_EX { SIZE_T Size; IMAGE_INFO ImageInfo; struct _FILE_OBJECT *FileObject ; } IMAGE_INFO_EX, *PIMAGE_INFO_EX;
提示:可以根据已有的 ImageInfo 成员,使用 CONTAINING_RECORD 宏来获取 IMAGE_INFO_EX 地址。
关于 FileObject 可以参见《x86 内存管理(二)私有内存-共享内存-物理内存 》:
3.3 调用模块通知例程 由 PsCallImageNotifyRoutines 来调用所有模块通知例程。
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 _QWORD *__fastcall PsCallImageNotifyRoutines (__int128 *a1, __int64 a2, __int64 a3, __int64 a4) { __int64 v4; __int128 *v5; struct _KTHREAD *v6 ; __int64 v7; __int64 v8; __int64 v9; signed __int64 v10; _DWORD *v11; volatile signed __int64 *v12; unsigned __int64 v13; unsigned __int64 v14; void (__fastcall *v16)(__int128 *); __int128 v17; v4 = a3; v5 = a1; v17 = 0 i64; v6 = KeGetCurrentThread(); v7 = a4; v8 = a2; --LOWORD(v6->_union_108.WaitBlock[3 ].SpareLong); if ( (unsigned __int8)KeAreAllApcsDisabled() ) __int2c(); if ( FltMgrCallbacks && (*(signed int (__fastcall **)(__int64))(FltMgrCallbacks + 24 ))(v7) >= 0 ) v5 = &v17; if ( v8 ) v9 = *(_QWORD *)(v8 + 1088 ); if ( PerfGlobalGroupMask & 4 ) PerfLogImageLoad(v5, v8, v4 + 8 ); if ( PspNotifyEnableMask & 1 ) { *(_DWORD *)(v4 + 8 ) |= 0x400 u; v10 = 64 i64; v11 = (_DWORD *)(v4 + 8 ); *(_QWORD *)v4 = 56 i64; *(_QWORD *)(v4 + 48 ) = v7; v12 = PspLoadImageNotifyRoutine; do { v13 = ExReferenceCallBackBlock(v12); v14 = v13; if ( v13 ) { v16 = *(void (__fastcall **)(__int128 *))(v13 + 8 ); if ( !(*v11 & 0x800 ) || *(_BYTE *)(v14 + 16 ) & 1 ) { if ( v16 ) v16(v5); } ExDereferenceCallBackBlock(v12, v14); } ++v12; --v10; } while ( v10 ); } return KeLeaveCriticalRegionThread((__int64)v6); }
3.4 Bypass Image Load 本节主要是在模块通知例程执行时,去阻止模块的执行,而不是加载 。因为调用 PsCallImageNotifyRoutines 前模块已经加载到内存了。阻止模块的加载主要分为:
内存映射镜像时:使用 NtCreateFile/NtOpenFile --> NtCreateSection 来创建一个 SEC_IMAGE 的内存区对象,然后调用 NtMapViewOfSection 将视图映射到自己的的地址空间。
调用 PsCallImageNotifyRoutines 的路径:
1 NtMapViewOfSection --> MiMapViewOfSection --> MiMapViewOfImageSection --> PsCallImageNotifyRoutines
参考: