如何监控内核回调

How to monitor Kernel callbacks

我将如何监控内核回调?我特别感兴趣的是监视来自内核回调 table 的回调函数。我试图找出哪个 user32 API 调用触发了哪个回调函数。

我不相信我可以使用调试器看到这些调用,所以可以选择 ETW 跟踪吗?

我使用内核调试器进行了一些快速测试(在 Windows 10 上),因为它非常需要获取哪些用户调用在哪个回调函数中结束。

我使用 notepad.exe 作为目标,因为它有 GUI。

2: kd> !process 0 0 notepad.exe
PROCESS ffffb987185d9080
    SessionId: 1  Cid: 20ec    Peb: c21923b000  ParentCid: 141c
    DirBase: 5510e002  ObjectTable: ffffa80636fcf340  HandleCount: 239.
    Image: notepad.exe

_EPROCESS 结构位于 0xffffb987185d9080,其 _PEB 位于 0xc21923b000。第一个用于为 notepad.exe 设置断点,第二个用于查看内核回调 table.

nt!KeUserModeCallback

上设置 BP
2: kd> bp /p ffffb987185d9080 nt!KeUserModeCallback; g

我们知道这个函数的 first parameter 是内核回调的索引 table:

NTSTATUS KeUserModeCallback (
    IN ULONG ApiNumber,
    IN PVOID InputBuffer,
    IN ULONG InputLength,
    OUT PVOID *OutputBuffer,
    IN PULONG OutputLength
    );

内核回调 table 可直接从 _PEB 结构访问,使用 KernelCallbackTable 字段:

1: kd> dt _peb c21923b000 KernelC*
wintypes!_PEB
   +0x058 KernelCallbackTable : 0x00007ffc`12831070 Void

1: kd> dps 0x00007ffc`12831070
00007ffc`12831070  00007ffc`127c2710 USER32!_fnCOPYDATA
00007ffc`12831078  00007ffc`128299f0 USER32!_fnCOPYGLOBALDATA
00007ffc`12831080  00007ffc`127c0b90 USER32!_fnDWORD
00007ffc`12831088  00007ffc`127c69f0 USER32!_fnNCDESTROY
00007ffc`12831090  00007ffc`127cda60 USER32!_fnDWORDOPTINLPMSG
00007ffc`12831098  00007ffc`1282a220 USER32!_fnINOUTDRAG
00007ffc`128310a0  00007ffc`127c7f20 USER32!_fnGETTEXTLENGTHS
00007ffc`128310a8  00007ffc`12829ec0 USER32!_fnINCNTOUTSTRING
00007ffc`128310b0  00007ffc`12829f80 USER32!_fnINCNTOUTSTRINGNULL
00007ffc`128310b8  00007ffc`127c9690 USER32!_fnINLPCOMPAREITEMSTRUCT
00007ffc`128310c0  00007ffc`127c2b70 USER32!__fnINLPCREATESTRUCT
00007ffc`128310c8  00007ffc`1282a040 USER32!_fnINLPDELETEITEMSTRUCT
00007ffc`128310d0  00007ffc`127cfdf0 USER32!__fnINLPDRAWITEMSTRUCT
00007ffc`128310d8  00007ffc`1282a0a0 USER32!_fnINLPHELPINFOSTRUCT
00007ffc`128310e0  00007ffc`1282a0a0 USER32!_fnINLPHELPINFOSTRUCT
00007ffc`128310e8  00007ffc`1282a1a0 USER32!_fnINLPMDICREATESTRUCT

有趣的是,这个table也有一个象征性的名字,即USER32!apfnDispatch:

1: kd> ln 0x00007ffc`12831070
(0x00007ffc`12831070)   USER32!apfnDispatch 

1: kd> ? USER32!apfnDispatch
Evaluate expression: 140720619065456 = 00007ffc`12831070

通过所有这些我们可以在 nt!KeUserModeCallback:

上设置一个日志记录断点
1: kd> bp /p ffffb987185d9080 nt!KeUserModeCallback ".printf \"RCX: %p --> %y\n\", @rcx, poi(user32!apfnDispatch + (@rcx * 8)); k; g"

这会打印 ApiNumber(在 RCX 中)和与内核回调中的数字关联的函数名称 table,后跟堆栈跟踪。转储示例:

RCX: 0000000000000016 --> USER32!_fnINOUTLPPOINT5 (00007ffc`127c3d30)
 # Child-SP          RetAddr           Call Site
00 ffffb80f`ac76dd08 fffffe32`37a3d2ef nt!KeUserModeCallback
01 ffffb80f`ac76dd10 fffffe32`37a095d4 win32kfull!SfnINOUTLPWINDOWPOS+0x29f
02 ffffb80f`ac76de50 fffffe32`37a091c2 win32kfull!xxxSendMessageToClient+0x114
03 ffffb80f`ac76df10 fffffe32`379e0ed9 win32kfull!xxxSendTransformableMessageTimeout+0x282
04 ffffb80f`ac76e060 fffffe32`379df20c win32kfull!xxxCalcValidRects+0x32d
05 ffffb80f`ac76e200 fffffe32`379ac9c5 win32kfull!xxxEndDeferWindowPosEx+0x1ac
06 ffffb80f`ac76e2e0 fffffe32`37a25033 win32kfull!xxxProcessDesktopRecalc+0x221
07 ffffb80f`ac76e3b0 fffffe32`37a261e8 win32kfull!xxxProcessEventMessage+0x39b
08 ffffb80f`ac76e6e0 fffffe32`37a06211 win32kfull!xxxScanSysQueue+0xd48
09 ffffb80f`ac76ef20 fffffe32`37a050e2 win32kfull!xxxRealInternalGetMessage+0xef1
0a ffffb80f`ac76f3f0 fffffe32`373e6276 win32kfull!NtUserGetMessage+0x92
0b ffffb80f`ac76f480 fffff805`28c08bb5 win32k!NtUserGetMessage+0x16
0c ffffb80f`ac76f4c0 00007ffc`11b11104 nt!KiSystemServiceCopyEnd+0x25
0d 000000c2`1947fa08 00007ffc`127c1b3e win32u!NtUserGetMessage+0x14
0e 000000c2`1947fa10 00007ff6`0803c3ac USER32!GetMessageW+0x2e
0f 000000c2`1947fa70 00007ff6`080559b6 notepad!wWinMain+0x2b4
10 000000c2`1947fb20 00007ffc`131b7034 notepad!__scrt_common_main_seh+0x106
11 000000c2`1947fb60 00007ffc`14022651 KERNEL32!BaseThreadInitThunk+0x14
12 000000c2`1947fb90 00000000`00000000 ntdll!RtlUserThreadStart+0x21

RCX: 000000000000001b --> USER32!_fnINSTRING (00007ffc`127c1ce0)
 # Child-SP          RetAddr           Call Site
00 ffffb80f`ac76dda8 fffffe32`37997895 nt!KeUserModeCallback
01 ffffb80f`ac76ddb0 fffffe32`37a095d4 win32kfull!SfnINSTRINGNULL+0x2b5
02 ffffb80f`ac76e140 fffffe32`37a091c2 win32kfull!xxxSendMessageToClient+0x114
03 ffffb80f`ac76e200 fffffe32`37a0cc10 win32kfull!xxxSendTransformableMessageTimeout+0x282
04 ffffb80f`ac76e350 fffffe32`37a24ebd win32kfull!xxxSendMessage+0x2c
05 ffffb80f`ac76e3b0 fffffe32`37a261e8 win32kfull!xxxProcessEventMessage+0x225
06 ffffb80f`ac76e6e0 fffffe32`37a06211 win32kfull!xxxScanSysQueue+0xd48
07 ffffb80f`ac76ef20 fffffe32`37a050e2 win32kfull!xxxRealInternalGetMessage+0xef1
08 ffffb80f`ac76f3f0 fffffe32`373e6276 win32kfull!NtUserGetMessage+0x92
09 ffffb80f`ac76f480 fffff805`28c08bb5 win32k!NtUserGetMessage+0x16
0a ffffb80f`ac76f4c0 00007ffc`11b11104 nt!KiSystemServiceCopyEnd+0x25
0b 000000c2`1947fa08 00007ffc`127c1b3e win32u!NtUserGetMessage+0x14
0c 000000c2`1947fa10 00007ff6`0803c3ac USER32!GetMessageW+0x2e
0d 000000c2`1947fa70 00007ff6`080559b6 notepad!wWinMain+0x2b4
0e 000000c2`1947fb20 00007ffc`131b7034 notepad!__scrt_common_main_seh+0x106
0f 000000c2`1947fb60 00007ffc`14022651 KERNEL32!BaseThreadInitThunk+0x14
10 000000c2`1947fb90 00000000`00000000 ntdll!RtlUserThreadStart+0x21

RCX: 000000000000006a --> USER32!_fnINLPUAHDRAWMENU (00007ffc`127c61b0)
 # Child-SP          RetAddr           Call Site
00 ffffb80f`ad457888 fffffe32`37a4e22d nt!KeUserModeCallback
01 ffffb80f`ad457890 fffffe32`37a095d4 win32kfull!SfnINLPUAHDRAWMENU+0x20d
02 ffffb80f`ad4579c0 fffffe32`37a091c2 win32kfull!xxxSendMessageToClient+0x114
03 ffffb80f`ad457a80 fffffe32`37a0cc10 win32kfull!xxxSendTransformableMessageTimeout+0x282
04 ffffb80f`ad457bd0 fffffe32`379a852f win32kfull!xxxSendMessage+0x2c
05 ffffb80f`ad457c30 fffffe32`379a81b8 win32kfull!xxxSendUAHMenuMessage+0x3f
06 ffffb80f`ad457c80 fffffe32`379a6a45 win32kfull!xxxPaintMenuBar+0x174
07 ffffb80f`ad457d20 fffffe32`373e7152 win32kfull!NtUserPaintMenuBar+0xe5
08 ffffb80f`ad457d80 fffff805`28c08bb5 win32k!NtUserPaintMenuBar+0x2a
09 ffffb80f`ad457dd0 00007ffc`11b12c64 nt!KiSystemServiceCopyEnd+0x25
0a 000000c2`1947efd8 00007ffc`0f12a919 win32u!NtUserPaintMenuBar+0x14
0b 000000c2`1947efe0 00007ffc`0f1271f4 uxtheme!CThemeWnd::NcPaint+0x239
0c 000000c2`1947f130 00007ffc`0f12b809 uxtheme!OnDwpNcActivate+0x54
0d 000000c2`1947f170 00007ffc`0f12b271 uxtheme!_ThemeDefWindowProc+0x589
0e 000000c2`1947f350 00007ffc`127ac7e3 uxtheme!ThemeDefWindowProcW+0x11
0f 000000c2`1947f390 00007ff6`0803bb57 USER32!DefWindowProcW+0x1a3
10 000000c2`1947f3f0 00007ffc`127ae858 notepad!NPWndProc+0x557
11 000000c2`1947f730 00007ffc`127ae3dc USER32!UserCallWinProcCheckWow+0x2f8
12 000000c2`1947f8c0 00007ffc`127c0bc3 USER32!DispatchClientMessage+0x9c
13 000000c2`1947f920 00007ffc`14070c54 USER32!_fnDWORD+0x33
14 000000c2`1947f980 00007ffc`11b11104 ntdll!KiUserCallbackDispatcherContinue
15 000000c2`1947fa08 00007ffc`127c1b3e win32u!NtUserGetMessage+0x14
16 000000c2`1947fa10 00007ff6`0803c3ac USER32!GetMessageW+0x2e
17 000000c2`1947fa70 00007ff6`080559b6 notepad!wWinMain+0x2b4
18 000000c2`1947fb20 00007ffc`131b7034 notepad!__scrt_common_main_seh+0x106
19 000000c2`1947fb60 00007ffc`14022651 KERNEL32!BaseThreadInitThunk+0x14
1a 000000c2`1947fb90 00000000`00000000 ntdll!RtlUserThreadStart+0x21

RCX: 000000000000006b --> USER32!__fnINLPUAHDRAWMENUITEM (00007ffc`127c4190)
 # Child-SP          RetAddr           Call Site
00 ffffb80f`ad457538 fffffe32`37a4e835 nt!KeUserModeCallback
01 ffffb80f`ad457540 fffffe32`37a095d4 win32kfull!SfnINLPUAHDRAWMENUITEM+0x255
02 ffffb80f`ad457700 fffffe32`37a091c2 win32kfull!xxxSendMessageToClient+0x114
03 ffffb80f`ad4577c0 fffffe32`37a0cc10 win32kfull!xxxSendTransformableMessageTimeout+0x282
04 ffffb80f`ad457910 fffffe32`379ab397 win32kfull!xxxSendMessage+0x2c
05 ffffb80f`ad457970 fffffe32`379ab118 win32kfull!xxxSendMenuDrawItemMessage+0x17f
06 ffffb80f`ad457ab0 fffffe32`379aaf34 win32kfull!xxxDrawMenuItem+0x130
07 ffffb80f`ad457b80 fffffe32`379a8203 win32kfull!xxxMenuDraw+0x224
08 ffffb80f`ad457c80 fffffe32`379a6a45 win32kfull!xxxPaintMenuBar+0x1bf
09 ffffb80f`ad457d20 fffffe32`373e7152 win32kfull!NtUserPaintMenuBar+0xe5
0a ffffb80f`ad457d80 fffff805`28c08bb5 win32k!NtUserPaintMenuBar+0x2a
0b ffffb80f`ad457dd0 00007ffc`11b12c64 nt!KiSystemServiceCopyEnd+0x25
0c 000000c2`1947efd8 00007ffc`0f12a919 win32u!NtUserPaintMenuBar+0x14
0d 000000c2`1947efe0 00007ffc`0f1271f4 uxtheme!CThemeWnd::NcPaint+0x239
0e 000000c2`1947f130 00007ffc`0f12b809 uxtheme!OnDwpNcActivate+0x54
0f 000000c2`1947f170 00007ffc`0f12b271 uxtheme!_ThemeDefWindowProc+0x589
10 000000c2`1947f350 00007ffc`127ac7e3 uxtheme!ThemeDefWindowProcW+0x11
11 000000c2`1947f390 00007ff6`0803bb57 USER32!DefWindowProcW+0x1a3
12 000000c2`1947f3f0 00007ffc`127ae858 notepad!NPWndProc+0x557
13 000000c2`1947f730 00007ffc`127ae3dc USER32!UserCallWinProcCheckWow+0x2f8
14 000000c2`1947f8c0 00007ffc`127c0bc3 USER32!DispatchClientMessage+0x9c
15 000000c2`1947f920 00007ffc`14070c54 USER32!_fnDWORD+0x33
16 000000c2`1947f980 00007ffc`11b11104 ntdll!KiUserCallbackDispatcherContinue
17 000000c2`1947fa08 00007ffc`127c1b3e win32u!NtUserGetMessage+0x14
18 000000c2`1947fa10 00007ff6`0803c3ac USER32!GetMessageW+0x2e
19 000000c2`1947fa70 00007ff6`080559b6 notepad!wWinMain+0x2b4
1a 000000c2`1947fb20 00007ffc`131b7034 notepad!__scrt_common_main_seh+0x106
1b 000000c2`1947fb60 00007ffc`14022651 KERNEL32!BaseThreadInitThunk+0x14
1c 000000c2`1947fb90 00000000`00000000 ntdll!RtlUserThreadStart+0x21

如果你有一个像 user32!GetMessageW 这样的函数,它会变得有点复杂,回调函数取决于传递给 GetMessageW 的参数。

在这种情况下,您需要返回调用它的框架:

RCX: 000000000000001c --> USER32!__fnINDEVICECHANGE (00007ffc`127cdd70)
 # Child-SP          RetAddr           Call Site
00 ffffb80f`ac76e8a8 fffffe32`37999cab nt!KeUserModeCallback
01 ffffb80f`ac76e8b0 fffffe32`37a095d4 win32kfull!SfnINDEVICECHANGE+0x28b
02 ffffb80f`ac76ec40 fffffe32`37a08634 win32kfull!xxxSendMessageToClient+0x114
03 ffffb80f`ac76ed00 fffffe32`37a06078 win32kfull!xxxReceiveMessage+0x3b4
04 ffffb80f`ac76ef20 fffffe32`37a050e2 win32kfull!xxxRealInternalGetMessage+0xd58
05 ffffb80f`ac76f3f0 fffffe32`373e6276 win32kfull!NtUserGetMessage+0x92
06 ffffb80f`ac76f480 fffff805`28c08bb5 win32k!NtUserGetMessage+0x16
07 ffffb80f`ac76f4c0 00007ffc`11b11104 nt!KiSystemServiceCopyEnd+0x25
08 000000c2`1947fa08 00007ffc`127c1b3e win32u!NtUserGetMessage+0x14
09 000000c2`1947fa10 00007ff6`0803c3ac USER32!GetMessageW+0x2e
0a 000000c2`1947fa70 00007ff6`080559b6 notepad!wWinMain+0x2b4
0b 000000c2`1947fb20 00007ffc`131b7034 notepad!__scrt_common_main_seh+0x106
0c 000000c2`1947fb60 00007ffc`14022651 KERNEL32!BaseThreadInitThunk+0x14
0d 000000c2`1947fb90 00000000`00000000 ntdll!RtlUserThreadStart+0x21

让我们回到调用函数的帧(帧 0xa):

1: kd> .frame /r 0xa
0a 000000c2`1947fa70 00007ff6`080559b6 notepad!wWinMain+0x2b4
rax=ffffb80fac76e8e4 rbx=000002870361237c rcx=000000000000001c
rdx=ffffb80fac76ea00 rsi=00007ff608030000 rdi=0000000000000000
rip=00007ff60803c3ac rsp=000000c21947fa70 rbp=000000c21947fab9
 r8=0000000000000068  r9=ffffb80fac76e908 r10=0000000000000000
r11=ffffb80fac76e800 r12=0000000000000000 r13=0000000000000000
r14=0000000000020375 r15=00007ff608030000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040246
notepad!wWinMain+0x2b4:
00007ff6`0803c3ac 0f1f440000      nop     dword ptr [rax+rax]

这是反汇编:

00007ff6`0803c399 4533c9          xor     r9d,r9d
00007ff6`0803c39c 488d4d0f        lea     rcx,[rbp+0Fh]
00007ff6`0803c3a0 4533c0          xor     r8d,r8d
00007ff6`0803c3a3 33d2            xor     edx,edx
00007ff6`0803c3a5 48ff15f4c80100  call    qword ptr [notepad!_imp_GetMessageW (00007ff6`08058ca0)]
00007ff6`0803c3ac 0f1f440000      nop     dword ptr [rax+rax]  ; frame pointer here

GetMessageW 的第一个参数是一个指向 MSG 结构的指针,因此在这种情况下您可以看到它来自 RBP+0x0f:

1: kd> db 000000c21947fab9 + f
000000c2`1947fac8  10 03 03 00 00 00 00 00-00 04 00 00 00 00 00 00  ................
000000c2`1947fad8  be ba 00 00 00 00 00 00-c0 2f 67 03 87 02 00 00  ........./g.....
000000c2`1947fae8  96 6e 07 00 ab 02 00 00-80 01 00 00 00 00 00 00  .n..............
000000c2`1947faf8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000000c2`1947fb08  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000000c2`1947fb18  b6 59 05 08 f6 7f 00 00-01 00 00 00 00 00 00 00  .Y..............
000000c2`1947fb28  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000000c2`1947fb38  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

在这种情况下,WM_ 消息是 0x400。

除了内核调试器,有趣的是所有对 nt!KeUserModeCallback 的调用都被对 nt!EtwTraceBeginCallbacknt!EtwTraceEndCallback 的调用包围(下面是来自 win32Kfull.sys 的示例fnHkINLPMSG 函数)

.text:00000001C009D429                 call    cs:__imp_EtwTraceBeginCallback
.text:00000001C009D430                 nop     dword ptr [rax+rax+00h]
.text:00000001C009D435                 lea     rax, [rsp+158h+arg_10]
.text:00000001C009D43D                 mov     [rsp+158h+BugCheckParameter4], rax
.text:00000001C009D442                 lea     r9, [rsp+158h+var_110]
.text:00000001C009D447                 mov     r8d, 58h ; 'X'
.text:00000001C009D44D                 lea     rdx, [rsp+158h+var_E8]
.text:00000001C009D452                 lea     ecx, [r8-29h]
.text:00000001C009D456                 call    cs:__imp_KeUserModeCallback
.text:00000001C009D45D                 nop     dword ptr [rax+rax+00h]
.text:00000001C009D462                 mov     r12d, eax
.text:00000001C009D465                 mov     ecx, 2Fh ; '/'
.text:00000001C009D46A                 call    cs:__imp_EtwTraceEndCallback

所以使用 ETW 绝对是可能的。虽然我没有测试过,但我怀疑事件的输出非常“原始”,没有提到任何函数名称,因此在从 ETW 获得输出后可能需要做更多的工作。