How would I go about monitoring Kernel callbacks? I am specifically interested in monitoring the callback functions from the Kernel callback table. I am trying to figure out which user32 API call triggers which callback function.
I do not believe I can see these calls using a debugger, so would an option be ETW tracing?
I did some quick tests (on Windows 10) using the kernel debugger since it's pretty much needed to get which user calls ends up in which callback function.
I'm using notepad.exe as a target since it has a 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
the _EPROCESS
structure is at 0xffffb987185d9080 and its _PEB
is at 0xc21923b000. The first one will be useful to put a breakpoint just for notepad.exe and the second one to have a view on the kernel call back table.
Setting a BP on nt!KeUserModeCallback
2: kd> bp /p ffffb987185d9080 nt!KeUserModeCallback; g
We know that the first parameter for this function is an index into the kernel callback table:
NTSTATUS KeUserModeCallback (
IN ULONG ApiNumber,
IN PVOID InputBuffer,
IN ULONG InputLength,
OUT PVOID *OutputBuffer,
IN PULONG OutputLength
);
The kernel callback table is accesssible directly from the _PEB
structure, using the KernelCallbackTable
field:
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
It's interesting to see that this table also have a symbolic name, namely USER32!apfnDispatch
:
1: kd> ln 0x00007ffc`12831070
(0x00007ffc`12831070) USER32!apfnDispatch
1: kd> ? USER32!apfnDispatch
Evaluate expression: 140720619065456 = 00007ffc`12831070
With all this we can set a logging breakpoint on nt!KeUserModeCallback
:
1: kd> bp /p ffffb987185d9080 nt!KeUserModeCallback ".printf \"RCX: %p --> %y\\n\", @rcx, poi(user32!apfnDispatch + (@rcx * 8)); k; g"
This prints the ApiNumber (in RCX) and the name of the function associated to the number from the kernel callback table, followed by a stack trace. Dump example:
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
It get a bit more complicated if you have a function like user32!GetMessageW
where the callback function depends on the argument passed to GetMessageW
.
In this case you need to get back to the frame where it is called:
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
Le'ts get back the the frame where the function is called (frame 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]
Here's the disassembly:
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
The 1st argument to GetMessageW
is a pointer to a MSG structure so you can see it comes from RBP+0x0f in this case:
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 ................
In this case the WM_ message is 0x400.
Beside the kernel debugger, it's interesting to see that all calls to nt!KeUserModeCallback
are surrounded by calls to nt!EtwTraceBeginCallback
and nt!EtwTraceEndCallback
(below an example from win32Kfull.sys
in the fnHkINLPMSG
function)
.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
So using ETW is definitely a possibility. Although I haven't tested it, I suspect the output from the event is quite "raw" without any mention of function names, so it probably requires more work after getting the output from ETW.