Windows 通知机制

😄

0 回调与通知

回调与通知必须要区分开,否则会造成很大的麻烦。

  • 回调对象是 CALLBACK_OBJECT,而通知对象是 EX_CALLBACK。
  • 枚举通知例程的函数为 PspEnumerateCallback,枚举回调的函数为 ExpEnumerateCallback。

本文分析系统版本:

10.png

1 进程通知

进程通知整体结构:

3.png

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
// PsSetCreateProcessNotifyRoutine
PAGE:0000000140784BB0 PsSetCreateProcessNotifyRoutine proc near
PAGE:0000000140784BB0 ; CODE XREF: VfFaultsSetParameters+4D↓p
PAGE:0000000140784BB0 ; ViInitSystemPhase1+1A↓p
PAGE:0000000140784BB0 ; DATA XREF: ...
PAGE:0000000140784BB0 sub rsp, 28h
PAGE:0000000140784BB4 mov al, dl
PAGE:0000000140784BB6 xor edx, edx
PAGE:0000000140784BB8 test al, al
PAGE:0000000140784BBA setnz dl // edx == 0/1 (Remove = False/True)
PAGE:0000000140784BBD call PspSetCreateProcessNotifyRoutine
PAGE:0000000140784BC2 add rsp, 28h
PAGE:0000000140784BC6 retn
PAGE:0000000140784BC6 ; ---------------------------------------------------------------------------
PAGE:0000000140784BC7 db 0CCh
PAGE:0000000140784BC7 PsSetCreateProcessNotifyRoutine endp

// PsSetCreateProcessNotifyRoutineEx
PAGE:0000000140784A00 PsSetCreateProcessNotifyRoutineEx proc near
PAGE:0000000140784A00 ; DATA XREF: .pdata:000000014010C1A0↑o
PAGE:0000000140784A00 sub rsp, 28h
PAGE:0000000140784A04 neg dl // edx == 0/-1
PAGE:0000000140784A06 sbb edx, edx // edx = edx - edx - Rflags.CF == 0/-1
PAGE:0000000140784A08 neg edx // edx == 0/1
PAGE:0000000140784A0A add edx, 2 // edx == 2/3 (Remove = False/True)
PAGE:0000000140784A0D call PspSetCreateProcessNotifyRoutine
PAGE:0000000140784A12 add rsp, 28h
PAGE:0000000140784A16 retn
PAGE:0000000140784A16 ; ---------------------------------------------------------------------------
PAGE:0000000140784A17 db 0CCh
PAGE:0000000140784A17 PsSetCreateProcessNotifyRoutineEx endp

// PsSetCreateProcessNotifyRoutineEx2
PAGE:0000000140784A60 PsSetCreateProcessNotifyRoutineEx2 proc near
PAGE:0000000140784A60 ; DATA XREF: .pdata:000000014010C1C4↑o
PAGE:0000000140784A60 sub rsp, 28h
PAGE:0000000140784A64 mov rax, rdx
PAGE:0000000140784A67 test ecx, ecx
PAGE:0000000140784A69 jnz short loc_140784A83
PAGE:0000000140784A6B neg r8b // Remove
PAGE:0000000140784A6E mov rcx, rax
PAGE:0000000140784A71 sbb edx, edx // edx = 0/-1
PAGE:0000000140784A73 neg edx // edx = 0/1
PAGE:0000000140784A75 add edx, 6 // edx = 6/7
PAGE:0000000140784A78 call PspSetCreateProcessNotifyRoutine
PAGE:0000000140784A7D
PAGE:0000000140784A7D loc_140784A7D: ; CODE XREF: PsSetCreateProcessNotifyRoutineEx2+28↓j
PAGE:0000000140784A7D add rsp, 28h
PAGE:0000000140784A81 retn
PAGE:0000000140784A81 ; ---------------------------------------------------------------------------
PAGE:0000000140784A82 db 0CCh
PAGE:0000000140784A83 ; ---------------------------------------------------------------------------
PAGE:0000000140784A83
PAGE:0000000140784A83 loc_140784A83: ; CODE XREF: PsSetCreateProcessNotifyRoutineEx2+9↑j
PAGE:0000000140784A83 mov eax, 0C000000Dh
PAGE:0000000140784A88 jmp short loc_140784A7D
PAGE:0000000140784A88 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
// PspSetCreateProcessNotifyRoutine(LPVOID NotifyRoutine, ULONG Flags)
signed __int64 __fastcall PspSetCreateProcessNotifyRoutine(__int64 a1, unsigned int a2)
{
KSPIN_LOCK v2; // rbx
unsigned int v3; // esi
__int64 v4; // r14
int v5; // edx
KSPIN_LOCK *v6; // rdi
__int64 v7; // rbx
struct _KTHREAD *v8; // rbp
__int64 v9; // r15
__int64 v10; // rax
volatile signed __int64 *v11; // rdi
volatile signed __int32 *v12; // rax

v2 = a2;
v3 = a2 & 2;
v4 = a1;

// Remove == 1,下面是摘除通知函数的逻辑
if ( a2 & 1 )
{
v8 = KeGetCurrentThread();
--LOWORD(v8->_union_108.WaitBlock[3].SpareLong);
v9 = 0i64;
while ( 1 )
{
v10 = ExReferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v9]); // 返回EX_CALLBACK_ROUTINE_BLOCK结构
v11 = (volatile signed __int64 *)v10;
if ( v10 )
{
LODWORD(v2) = v2 & 0xFFFFFFFE; // Remove & 0xFFFFFFFE
if ( *(_QWORD *)(v10 + 8) == v4 // 判断是否是目标通知函数
&& *(_DWORD *)(v10 + 16) == (_DWORD)v2 // EX_CALLBACK_ROUTINE_BLOCK.Context
&& ExCompareExchangeCallBack(&PspCreateProcessNotifyRoutine[v9], 0i64, v10) )
// 将 PspCreateProcessNotifyRoutine[v9] = 0,摘除指定通知函数
{
v12 = &PspCreateProcessNotifyRoutineCount;
if ( v3 )
v12 = &PspCreateProcessNotifyRoutineExCount; // Ex、Ex2 函数
_InterlockedDecrement(v12);
ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v9], (unsigned __int64)v11);
KeLeaveCriticalRegionThread((__int64)v8);
ExWaitForRundownProtectionRelease(v11);
ExFreePoolWithTag((PVOID)v11, 0);
return 0i64;
}
ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v9], (unsigned __int64)v11);
}
v9 = (unsigned int)(v9 + 1);
if ( (unsigned int)v9 >= 0x40 )
{
KeLeaveCriticalRegionThread((__int64)v8);
return 3221225594i64;
}
}
}

// 下面是创建通知函数的逻辑
if ( a2 & 2 ) // Ex、Ex2 函数
v5 = 32;
else
v5 = 0;
if ( !(unsigned int)MmVerifyCallbackFunctionCheckFlags(a1, v5) )
return 3221225506i64;
v6 = ExAllocateCallBack(v4, v2);
if ( !v6 )
return 3221225626i64;
v7 = 0i64;
while ( !ExCompareExchangeCallBack(&PspCreateProcessNotifyRoutine[v7], (volatile signed __int64 *)v6, 0i64) )
{
v7 = (unsigned int)(v7 + 1);
if ( (unsigned int)v7 >= 0x40 )
{
ExFreePoolWithTag(v6, 0);
return 3221225485i64;
}
}
if ( v3 )
{
_InterlockedIncrement(&PspCreateProcessNotifyRoutineExCount);
if ( !(PspNotifyEnableMask & 4) )
_interlockedbittestandset(&PspNotifyEnableMask, 2u);
}
else
{
_InterlockedIncrement(&PspCreateProcessNotifyRoutineCount);
if ( !(PspNotifyEnableMask & 2) )
_interlockedbittestandset(&PspNotifyEnableMask, 1u);
}
return 0i64;
}
  1. 首先判断 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 创建的通知函数。
  2. 如果是创建通知函数。

    1. 调用 MmVerifyCallbackFunctionCheckFlags 判断通知例程所在模块是否在 PE 头中指定 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 标志,可通过链接器命令行参数处指定:/integritycheck。如果未启用完整性检查标志,则返回 0xC0000022。

      关于 MmVerifyCallbackFunctionCheckFlags 函数的分析,请看本文 2.1.2 节。

    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;
      }
    3. 最后调用 ExCompareExchangeCallBack(arg1,arg2,arg3) 将新创建的 EX_CALLBACK_ROUTINE_BLOCK 通知对象插入到 PsSetCreateProcessNotifyRoutine 数组中中元素为 NULL 的位置。

      1
      2
      3
      4
      if([arg1] == arg3)
      {
      [arg1] = arg2;
      }
    4. 对通知数组数量的全局变量进行加 1 操作。PspCreateProcessNotifyRoutineCount/PspCreateProcessNotifyRoutineExCount += 1。

    5. 对 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`74f01420 ffff858b`b885006f ffff858b`b88feb1f
fffff804`74f01430 ffff858b`bb29b26f ffff858b`bb29baaf
fffff804`74f01440 ffff858b`bb29bc5f ffff858b`bb721cdf
fffff804`74f01450 ffff858b`bb721a9f ffff858b`bb02231f
fffff804`74f01460 ffff858b`be3fb8ff ffff858b`bfbfc95f
fffff804`74f01470 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;

//0x8 bytes (sizeof)
typedef struct _EX_FAST_REF
{
union
{
VOID* Object; //0x0
ULONGLONG RefCnt:4; //0x0
ULONGLONG Value; //0x0
};
}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; // 通知函数的 Remove 参数
} EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;

// 当停运保护生效时,除对象的所有者外,其他驱动可以访问该对象而不用担心在访问结束前该对象会被其所有者删除。
//0x8 bytes (sizeof)
struct _EX_RUNDOWN_REF
{
union
{
ULONGLONG Count; //0x0
VOID* Ptr; // 共享对象 //0x0
};
};

示例:解析 PspCreateProcessNotifyRoutine 数组。

  1. 查看获取数组的元素。

    1
    2
    3
    4
    5
    6
    7
    kd> dq PspCreateProcessNotifyRoutine
    fffff804`74f01420 ffff858b`b885006f ffff858b`b88feb1f
    fffff804`74f01430 ffff858b`bb29b26f ffff858b`bb29baaf
    fffff804`74f01440 ffff858b`bb29bc5f ffff858b`bb721cdf
    fffff804`74f01450 ffff858b`bb721a9f ffff858b`bb02231f
    fffff804`74f01460 ffff858b`be3fb8ff ffff858b`bfbfc95f
    fffff804`74f01470 00000000`00000000 00000000`00000000

    下面以第一个元素 0xffff858bb885006f 进行解析。

  2. 解析 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`72f878f0
    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 4883ec28 sub rsp,28h
    fffff804`74571054 833d850e8c0000 cmp dword ptr [nt!ViVerifierEnabled (fffff804`74e31ee0)],0
    fffff804`7457105b 488bc2 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
// Flags == 0x20(32)/0
// (Remove & 2) != 0,则 Flags == 32,表示 Ex、Ex2 函数创建通知函数
// (Remove & 2) == 0,则 Flags == 32,表示非 Ex、Ex2 函数创建通知函数
BOOL __fastcall (PVOID NotifyRoutine, DWORD Flags)
{
struct _KTHREAD *CurrentThread; // rbp
BOOL bHasValidFlags; // ebx
_KLDR_DATA_TABLE_ENTRY *Entry; // rax

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;
}
  1. 首先调用 ExAcquireResourceSharedLite 函数,该函数请求 PsLoadedModuleResource 资源的读访问权限。即根据 PsLoadedModuleResource 加载模块参数,去获得模块资源的共享/独占权限。

  2. 然后调用 MiLookupDataTableEntry 函数取得通知函数所在模块的 KLDR_DATA_TABLE_ENTRY 链。

  3. 对通知函数进行如下验证,才表示通过,返回 TRUE:

    • 通知函数所在模块存在。

    • Flags == 0。

    • KLDR_DATA_TABLE_ENTRY.Flags = 0x20。

该函数主要是判断通知例程所在模块是否在 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 )	// v3 = a2 & 2;表示 Ex/Ex2 函数创建的通知函数
{
_InterlockedIncrement(&PspCreateProcessNotifyRoutineExCount);
if ( !(PspNotifyEnableMask & 4) ) // 没有开启 PsSetCreateProcessNotifyRoutineEx/Ex2 创建的通知函数标志
_interlockedbittestandset(&PspNotifyEnableMask, 2u); // 将 PspNotifyEnableMask Bit2 置 1
}
else
{
_InterlockedIncrement(&PspCreateProcessNotifyRoutineCount);
if ( !(PspNotifyEnableMask & 2) ) // 没有开启 PsSetCreateProcessNotifyRoutine 创建的通知函数标志
_interlockedbittestandset(&PspNotifyEnableMask, 1u); // 将 PspNotifyEnableMask Bit1 置 1
}

参考:

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;

进程通知整体结构:

3.png

参考:

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 参数:
    • 对于创建进程通知:是在被创建的进程第一个主线程中调用的进程通知。
    • 对于删除进程通知:是在进程最后一个主线程终止时调用的进程通知。

注意以下两点,否则会蓝屏:

  1. 调用 pPsLookupProcessByProcessId((HANDLE)ProcessId, &pEPOCESS);,系统会对该进程的引用计数累加1,需要在不使用该结构时,及时调用 ObDereferenceObject(pEPOCESS) 解除引用计数。
  2. 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; // 此结构的大小(以字节为单位) = sizeof(PS_CREATE_NOTIFY_INFO)
union {
ULONG Flags; // 保留。 请改用 FileOpenNameAvailable 成员
struct {
ULONG FileOpenNameAvailable : 1; // 一个布尔值,指定 ImageFileName 成员是否包含用于打开进程可执行文件的确切文件名
ULONG IsSubsystemProcess : 1; // 一个布尔值,指示进程子系统的类型是 Win32 以外的子系统
ULONG Reserved : 30;
};
};
HANDLE ParentProcessId; // 新进程的父进程的进程 ID,请注意,父进程不一定与创建新进程的进程相同。
CLIENT_ID CreatingThreadId; // CreatingThreadId.UniqueProcess 父进程ID
// CreatingThreadId.UniqueProcess 父进程中创建新进程的那个线程ID
struct _FILE_OBJECT *FileObject; // 进程对应的可执行文件的文件对象指针
PCUNICODE_STRING ImageFileName; // 进程的名字,UNICODE_STRING指针类型
PCUNICODE_STRING CommandLine; // 进程的参数(UNICODE_STRING),使用前需要做 NULL 判断
NTSTATUS CreationStatus; // 非常重要的成员
// 要为进程创建操作返回的 NTSTATUS 值。 驱动程序可以将此值更改为错误代码,以防止创建进程
// 常用的是 0xC0000022L(STATUS_ACCESS_DENIED),表示操作拒绝
} 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>
//#include <windows.h>
//http://www.bytekits.com/blog/blog-ntifs-confilict.html
//wdm.h一般用于即插即用驱动。
//ntddk.h 一般用于非即插即用驱动,如我们常说的NT架构驱动。这类驱动一般用于无硬件支持的系统内核服务。
//Ntifs.h 包含 Ntddk.h,而 Ntddk.h 又包含 Wdm.h。


typedef NTSTATUS (*PSLOOKUPPROCESSBYPROCESSID)(
HANDLE ProcessId,
PEPROCESS* Process
);
/*
VOID MyProcessNotify(
_In_ HANDLE ParentId,
_In_ HANDLE ProcessId,
_In_ BOOLEAN Create
)
{
UNICODE_STRING uStrFunc;
RtlInitUnicodeString(&uStrFunc, L"PsLookupProcessByProcessId");
PSLOOKUPPROCESSBYPROCESSID pPsLookupProcessByProcessId = NULL;
PEPROCESS pEPOCESS = NULL;


DbgPrint("Success to Create Process Notify!\n");

pPsLookupProcessByProcessId = (PSLOOKUPPROCESSBYPROCESSID)MmGetSystemRoutineAddress(&uStrFunc);
pPsLookupProcessByProcessId((HANDLE)ProcessId, &pEPOCESS);

DbgPrint("ProcessId = %d, ParentId = %d, Create = %d, Name = %s\n", (ULONG)ProcessId, (ULONG)ParentId, (ULONG)Create, ((ULONG64)pEPOCESS + 0x5a8));

ObDereferenceObject(pEPOCESS);

}
*/

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))
//if (!RtlCompareUnicodeString(&uProcName, CreateInfo->ImageFileName, TRUE))
{
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)
{
//UNREFERENCED_PARAMETER(pDriver);
PsSetCreateProcessNotifyRoutineEx(MyProcessNotify, TRUE); // Remove Process Notify.
DbgPrint("Goodbye~\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)
{
//UNREFERENCED_PARAMETER(pRegPath);
pDriver->DriverUnload = DriverUnload;

NTSTATUS Status = STATUS_SUCCESS;

Status = PsSetCreateProcessNotifyRoutineEx(MyProcessNotify, FALSE); // Create Process Notify.
if (Status != STATUS_SUCCESS)
{
DbgPrint("Faild to Create Process Notify!\n");
DbgPrint("Status = %x!\n", Status);
}

//DbgPrint("Hello Driver!\n");

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 // 为所有子系统(包括 Win32 子系统)的进程调用驱动程序注册的回调
} PSCREATEPROCESSNOTIFYTYPE;

如果 NotifyType = PsCreateProcessNotifySubsystems,则 NotifyInformation 为 PCREATE_PROCESS_NOTIFY_ROUTINE_EX。这样就和 PsSetCreateProcessNotifyRoutineEx 一样了。

1.3 调用进程通知

当进程对象被创建时,系统并不会调用进程通知回调,只有当第一个线程插入进程中时才会调用该回调。

当对通知数组 PspCreateProcessNotifyRoutine 进行交叉引用时,会发现第一个是 PspCallProcessNotifyRoutines 函数。实际上触发进程通知例程执行的就是该函数,其功能类似于 ExNotifyCallback 函数。

1.png

调用 PspCallProcessNotifyRoutines 函数主要有两个地方:

  • PspInsertThread
  • PspExitProcess

4.png

参考《Windows内核回调实现原理与逆向调试分析》上面两个函数调用过程如下:

5.png

通过逆向分析,该函数 PspCallProcessNotifyRoutines(PEPROCESS pEprocess, __int64 a2, BOOLEAN Create) 第二个参数暂时还没有逆向出来具体含义。

该函数的功能非常简单:

  1. 在一个最多可循环 63 次的循环里,根据 PspCreateProcessNotifyRoutine[i] 过去到通知例程所在的 EX_CALLBACK_ROUTINE_BLOCK 结构。这个过程由 ExReferenceCallBackBlock 函数来实现。
  2. 只要 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; // rbx
char v4; // r14
_QWORD *v5; // rdi
__int64 v6; // rax
void (__fastcall **v7)(_QWORD *); // rsi
__int64 v8; // rax
__int64 v9; // rcx
unsigned int v10; // er13
unsigned int v11; // er12
signed int v12; // eax
__m128i v13; // xmm0
__int64 v14; // rcx
signed __int64 v15; // rax
__int64 v16; // rax
signed __int64 v17; // rax
__int64 *v18; // rbx
int v19; // er14
unsigned __int64 v20; // rax
unsigned __int64 v21; // rsi
__int64 v22; // rdx
int v24; // eax
__int64 v25; // rdx
unsigned int v26; // eax
__int64 v27; // rdx
__int64 *v28; // rcx
__int64 v29; // rdx
void (__fastcall **v30)(_QWORD *); // [rsp+20h] [rbp-49h]
__int64 v31; // [rsp+28h] [rbp-41h]
void (__fastcall **v32)(_QWORD *); // [rsp+30h] [rbp-39h]
__int64 Dst; // [rsp+40h] [rbp-29h]
int v34; // [rsp+48h] [rbp-21h]
__int64 v35; // [rsp+50h] [rbp-19h]
__int128 v36; // [rsp+58h] [rbp-11h]
__int64 v37; // [rsp+68h] [rbp-1h]
__int128 v38; // [rsp+70h] [rbp+7h]
int v39; // [rsp+80h] [rbp+17h]
char v40; // [rsp+D0h] [rbp+67h]
__int64 v41; // [rsp+E8h] [rbp+7Fh]

v3 = a2;
v4 = a3; // Create
v5 = a1; // PEPROCESS
memset(&Dst, 0, 0x48ui64);
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;// Bit 2
// 是否开启 PsSetCreateProcessNotifyRoutineEx 创建的进程通知
v32 = (void (__fastcall **)(_QWORD *))v8;
v41 = 0i64;
v40 = 0;
v31 = v9;
if ( !v4 || !(_BYTE)v11 && !v7 && !v8 )
{
v18 = 0i64;
goto LABEL_17; // 进程创建通知
}
v12 = 0;
Dst = 72i64;
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 = 0i64;
_mm_store_si128((__m128i *)&v38, (__m128i)0i64);
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) = 0i64;
goto LABEL_16;
}
v17 = v16 + 112;
}
*((_QWORD *)&v38 + 1) = v17;
LABEL_16:
v18 = &Dst;
LABEL_17: // 进程创建通知
if ( PspNotifyEnableMask & 2 || (_BYTE)v11 ) // 已经开启 Bit1、Bit2(NotifyRoutine、NotifyRoutineEx/2进程创建通知)
{
v19 = 0;
while ( 1 )
{
ExReferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v19]);// 取到 EX_CALLBACK_ROUTINE_BLOCK 结构
v21 = v20; // v20 为返回值
if ( v20 )
{
v24 = *(_DWORD *)(v20 + 16); // v24 = EX_CALLBACK_ROUTINE_BLOCK.Context(Remove)
if ( !v31 || v24 & 4 ) // Remove = 7
{
if ( v24 & 2 ) // Ex函数创建的进程通知例程
{ // Ex函数创建的进程通知例程
if ( (_BYTE)v11 )
{
v25 = v5[136]; // UniqueProcessId
(*(void (__fastcall **)(_QWORD *))(v21 + 8))(v5);// 调用通知例程
//
// PCREATE_PROCESS_NOTIFY_ROUTINE PcreateProcessNotifyRoutine;
//
// void PcreateProcessNotifyRoutine(
// [in] HANDLE ParentId,
// [in] HANDLE ProcessId,
// [in] BOOLEAN Create
// )
// {...}
}
}
else
{
v27 = v5[136]; // UniqueProcessId
(*(void (__fastcall **)(_QWORD))(v21 + 8))(v5[168]);// v5[168] = InheritedFromUniqueProcessId 父进程ID
}
}
ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[v19], v21);
if ( v18 )
{
v26 = *((_DWORD *)v18 + 16);
if ( (v26 & 0x80000000) != 0 )
break;
}
}
if ( (unsigned int)++v19 >= 0x40 ) // 最多 64 个元素
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
// a3 为输出参数,类型为 PEX_CALLBACK
signed __int64 __fastcall PspEnumerateCallback(int a1, unsigned int *RoutineIndex, PEX_CALLBACK *a3)
{
unsigned int *v3; // r9
int v4; // ecx
signed __int64 *v5; // rcx
__int64 v6; // rdx

v3 = RoutineIndex;
if ( a1 )
{
v4 = a1 - 1;
if ( v4 )
{
if ( v4 != 1 )
return 0i64;
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 1i64;
}
return 0i64;
}

2.png

绿盟那个手册有将虚拟化

2 线程通知

6.png

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 // 不管NotifyType取何值,都指向通知函数PcreateThreadNotifyRoutine
);

typedef enum _PSCREATETHREADNOTIFYTYPE {
PsCreateThreadNotifyNonSystem, // 0,通知函数在新的非系统线程上执行
PsCreateThreadNotifySubsystems // 1,通知函数可以被所有子系统的线程调用
} 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
// PsSetCreateThreadNotifyRoutine(LPVOID NotifyRoutine)
signed __int64 __fastcall PsSetCreateThreadNotifyRoutine(LPVOID a1)
{
return PspSetCreateThreadNotifyRoutine(a1, 0);
}

// PsSetCreateThreadNotifyRoutineEx(int NotifyType, LPVOID NotifyInformation)
signed __int64 __fastcall PsSetCreateThreadNotifyRoutineEx(int a1, LPVOID a2)
{
KSPIN_LOCK v2; // rdi
unsigned int v3; // ebx
signed __int64 result; // rax

v2 = a2;
if ( a1 )
{
if ( a1 != 1 ) // NotifyType == PsCreateThreadNotifySubsystems
return 3221225485i64; // 0xC000000D, STATUS_INVALID_PARAMETER
v3 = 2;
}
else
{
v3 = 1; // NotifyType == PsCreateThreadNotifyNonSystem
}
// MmVerifyCallbackFunctionCheckFlags(PVOID NotifyRoutine, DWORD Flags)
// Flags = 0x20, 在 PE 头中指定 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 标志
if ( (unsigned int)MmVerifyCallbackFunctionCheckFlags(a2, 32) )
result = PspSetCreateThreadNotifyRoutine(v2, v3);
else
result = 3221225506i64;
return result;
}

PspSetCreateThreadNotifyRoutine 函数原型如下:

1
PspSetCreateThreadNotifyRoutine(PVOID NotifyRoutine, DWORD Flags)

分析得如下:

  • PsSetCreateThreadNotifyRoutine:
    • Flags = 0
  • 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
// PspSetCreateThreadNotifyRoutine(PVOID NotifyRoutine, DWORD Flags)
signed __int64 __fastcall PspSetCreateThreadNotifyRoutine(PVOID a1, unsigned int a2)
{
char v2; // si
KSPIN_LOCK *v3; // rdi
__int64 v4; // rbx

v2 = a2;
v3 = ExAllocateCallBack(a1, a2); // v3 = EX_CALLBACK_ROUTINE_BLOCK
if ( !v3 )
return 3221225626i64;
v4 = 0i64;
// 遍历 PspCreateThreadNotifyRoutine 数组,将新建的元素插入到数组中为0的位置
while ( !ExCompareExchangeCallBack(&PspCreateThreadNotifyRoutine[v4], (volatile signed __int64 *)v3, 0i64) )
{
v4 = (unsigned int)(v4 + 1);
if ( (unsigned int)v4 >= 0x40 ) // 数组最多有 64 个元素
{
ExFreePoolWithTag(v3, 0);
return 3221225626i64;
}
}
if ( v2 & 1 ) // Flags = 1,即 NotifyType == PsCreateThreadNotifyNonSystem(0)
{
_InterlockedIncrement(&PspCreateThreadNotifyRoutineNonSystemCount);
if ( !(PspNotifyEnableMask & 0x10) ) // PspNotifyEnableMask 的 Bit4,PsCreateThreadNotifyNonSystem型线程通知函数
_interlockedbittestandset(&PspNotifyEnableMask, 4u);
}
else
{
_InterlockedIncrement(&PspCreateThreadNotifyRoutineCount);
if ( !(PspNotifyEnableMask & 8) ) // PspNotifyEnableMask 的 Bit3,PsCreateThreadNotifyNonSystem型线程通知函数
_interlockedbittestandset(&PspNotifyEnableMask, 3u);
}
return 0i64;
}
  1. 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
// PsRemoveCreateThreadNotifyRoutine(LPVOID NotifyRoutine)
signed __int64 __fastcall PsRemoveCreateThreadNotifyRoutine(__int64 a1)
{
struct _KTHREAD *v1; // rsi
__int64 v2; // r14
__int64 v3; // rdi
__int64 v4; // rax
unsigned __int64 v5; // rbx
volatile signed __int32 *v7; // rdx

v1 = KeGetCurrentThread();
v2 = a1;
--LOWORD(v1->_union_108.WaitBlock[3].SpareLong);
v3 = 0i64;
while ( 1 )
{
v4 = ExReferenceCallBackBlock(&PspCreateThreadNotifyRoutine[v3]); // 返回EX_CALLBACK_ROUTINE_BLOCK结构
v5 = v4;
if ( v4 )
break;
LABEL_6:
v3 = (unsigned int)(v3 + 1);
if ( (unsigned int)v3 >= 0x40 )
{
KeLeaveCriticalRegionThread((__int64)v1);
return 3221225594i64;
}
}
if ( *(_QWORD *)(v4 + 8) != v2 || !ExCompareExchangeCallBack(&PspCreateThreadNotifyRoutine[v3], 0i64, 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 0i64;
}

PspCallThreadNotifyRoutines

7.png

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; // r15
bool v4; // bp
volatile signed __int64 *v5; // rbx
signed __int64 v6; // rdi
unsigned __int64 v7; // rax
unsigned __int64 v8; // rsi
int v9; // eax
__int64 v10; // rdx
volatile signed __int64 *v11; // rbx
signed __int64 v12; // rdi
unsigned __int64 v13; // rax
unsigned __int64 v14; // rsi
__int64 v15; // rdx
volatile signed __int64 *v16; // rbx
signed __int64 v17; // rdi
unsigned __int64 v18; // rax
unsigned __int64 v19; // rsi
__int64 v20; // rdx

v3 = a1;
v4 = *(_QWORD *)(a1 + 1504) != 0i64;
if ( a2 )
{
if ( a3 )
{
if ( PspNotifyEnableMask & 0x10 )
{
v11 = PspCreateThreadNotifyRoutine;
v12 = 64i64;
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) + 1088i64));
}
ExDereferenceCallBackBlock(v11, v14);
}
++v11;
--v12;
}
while ( v12 );
}
}
else if ( PspNotifyEnableMask & 8 )
{
v5 = PspCreateThreadNotifyRoutine;
v6 = 64i64;
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) + 1088i64));
}
ExDereferenceCallBackBlock(v5, v8);
}
++v5;
--v6;
}
while ( v6 );
}
}
else if ( PspNotifyEnableMask & 0x10 || PspNotifyEnableMask & 8 )
{
v16 = PspCreateThreadNotifyRoutine;
v17 = 64i64;
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) + 1088i64));
ExDereferenceCallBackBlock(v16, v19);
}
++v16;
--v17;
}
while ( v17 );
}
}

总结

6.png

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, // ThreadId 所属的进程 ID
[in] HANDLE ThreadId, // 引发线程通知的线程 ID
[in] BOOLEAN Create // 是否是创建
)
{...}

无返回值。

需要说明:

  1. 对于 PsSetCreateThreadNotifyRoutine 函数:
    • Create == TRUE,ThreadId 表示创建者线程的 TID,而不是被创建的线程 TID。
    • Create == FALSE,表示当前即将要结束的线程 TID。
  2. 对于 PsSetCreateThreadNotifyRoutineEx 函数:
    • ThreadId 都是被创建线程 TID。

特别说明:驱动程序必须在卸载之前删除它注册的任何回调函数。 可以通过调用 PsRemoveCreateThreadNotifyRoutine 例程来删除回调。

3 模块通知

8.png

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, 0i64);
}

PsSetLoadImageNotifyRoutineEx

需要说明:

  1. ExAllocateCallBack、ExCompareExchangeCallBack 函数返回类型都是 EX_CALLBACK_ROUTINE_BLOCK。
  2. 指向模块通知例程的指针是 PspLoadImageNotifyRoutine,每个元素都是 EX_CALLBACK 结构。
  3. 模块通知数组中元素的个数使用 PspLoadImageNotifyRoutine 全局变量表示。
  4. 是否启用模块通知,由 PspNotifyEnableMask 的 Bit 0 是否为 1 来决定。
  5. 每次创建模块通知例程时都会调用 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; // rsi
KSPIN_LOCK *v3; // rdi
__int64 v4; // rbx
unsigned int v5; // ebx
unsigned int v7; // [rsp+30h] [rbp-48h]
__int64 v8; // [rsp+38h] [rbp-40h]
__int64 *v9; // [rsp+40h] [rbp-38h]
int v10; // [rsp+48h] [rbp-30h]
int v11; // [rsp+4Ch] [rbp-2Ch]
int *v12; // [rsp+50h] [rbp-28h]
int v13; // [rsp+58h] [rbp-20h]
int v14; // [rsp+5Ch] [rbp-1Ch]

v2 = a1;
if ( a2 & 0xFFFFFFFFFFFFFFFEui64 )
return 3221225712i64;
v3 = ExAllocateCallBack(a1, a2);
if ( v3 )
{
v4 = 0i64;
while ( !ExCompareExchangeCallBack(&PspLoadImageNotifyRoutine[v4], (volatile signed __int64 *)v3, 0i64) )
{
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; // rsi
__int64 v2; // r14
__int64 v3; // rdi
__int64 v4; // rax
volatile signed __int64 *v5; // rbx

v1 = KeGetCurrentThread();
v2 = a1;
--LOWORD(v1->_union_108.WaitBlock[3].SpareLong);
v3 = 0i64;
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 3221225594i64;
}
}
if ( *(_QWORD *)(v4 + 8) != v2 || !ExCompareExchangeCallBack(&PspLoadImageNotifyRoutine[v3], 0i64, 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 0i64;
}

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; // 始终设置为 IMAGE_ADDRESSING_MODE_32BIT
ULONG SystemModeImage : 1; // 驱动程序:1,应用层模块(exe, dll):0
ULONG ImageMappedToAllPids : 1; // 始终设置为零
ULONG ExtendedInfoPresent : 1; // 若为1,则 IMAGE_INFO 是 IMAGE_INFO_EX 的一部分(从Vista开始)
ULONG MachineTypeMismatch : 1; // 始终设置为零(在 Windows 8/Windows Server 2012 中添加)
ULONG ImageSignatureLevel : 4; // 代码完整性,标记映像的签名级别(在 Windows 8.1/Windows Server 2012 R2 中添加)
ULONG ImageSignatureType : 3; // 代码完整性,用于标记映像的签名类型
ULONG ImagePartialMap : 1; // 如果将整个模块镜像进行mapping映射:0,否则为1(在Windows 10/Windows Server 2016中添加)
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_EX结构的大小(以字节为单位), sizeof(IMAGE_INFO_EX)
IMAGE_INFO ImageInfo; // IMAGE_INFO 结构
struct _FILE_OBJECT *FileObject; // 当前加载的模块所对应的文件对象指针
} IMAGE_INFO_EX, *PIMAGE_INFO_EX;

提示:可以根据已有的 ImageInfo 成员,使用 CONTAINING_RECORD 宏来获取 IMAGE_INFO_EX 地址。

关于 FileObject 可以参见《x86 内存管理(二)私有内存-共享内存-物理内存》:

40.png

3.3 调用模块通知例程

由 PsCallImageNotifyRoutines 来调用所有模块通知例程。

9.png

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; // rbx
__int128 *v5; // rbp
struct _KTHREAD *v6; // r15
__int64 v7; // rsi
__int64 v8; // rdi
__int64 v9; // r12
signed __int64 v10; // rdi
_DWORD *v11; // r14
volatile signed __int64 *v12; // rbx
unsigned __int64 v13; // rax
unsigned __int64 v14; // rsi
void (__fastcall *v16)(__int128 *); // rax
__int128 v17; // [rsp+30h] [rbp-38h]

v4 = a3;
v5 = a1;
v17 = 0i64;
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) |= 0x400u;
v10 = 64i64;
v11 = (_DWORD *)(v4 + 8);
*(_QWORD *)v4 = 56i64;
*(_QWORD *)(v4 + 48) = v7;
v12 = PspLoadImageNotifyRoutine;
do
{
v13 = ExReferenceCallBackBlock(v12); // v13: EX_CALLBACK_ROUTINE_BLOCK
v14 = v13;
if ( v13 )
{
v16 = *(void (__fastcall **)(__int128 *))(v13 + 8); // NotifyRoutine
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 前模块已经加载到内存了。阻止模块的加载主要分为:

  • 内核层:.sys 。

    主要有以下方法:只需要找到驱动入口点,并且直接返回 STATUS_ACCESS_DENIED(0xC0000022) 错误码就好。这样,已经加载的驱动就会在执行的时候出错,导致驱动启动失败。

  • 应用层:.dll、.exe。

    由于驱动中 DriverEntry 可以直接返回 NTSTATUS,但是 DLL 入口点函数 DllMain 的返回值并不能决定 DLL 是否成功加载,所以主要有以下两种方法:

内存映射镜像时:使用 NtCreateFile/NtOpenFile --> NtCreateSection 来创建一个 SEC_IMAGE 的内存区对象,然后调用 NtMapViewOfSection 将视图映射到自己的的地址空间。

调用 PsCallImageNotifyRoutines 的路径:

1
NtMapViewOfSection --> MiMapViewOfSection --> MiMapViewOfImageSection --> PsCallImageNotifyRoutines

参考: