windowswinapicallbackx64dbg

How to monitor Kernel callbacks


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?


Solution

  • 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.