Last time, I briefly outlined the operation of KiRaiseUserExceptionDispatcher, and how it is used by the NtClose system service to report certain classes of handle misuse under the debugger.
All of the NTDLL kernel mode to user mode “callbacks” that I have covered thus far have been, for the most part fairly “passive” in nature. By this, I mean that the kernel does not explicitly call any of these callbacks, at least in the usual nature of making a function call. Instead, all of the routines that we have discussed thus far are only invoked instead of the normal return procedure for a system call or interrupt, under certain conditions. (Conceptually, this is similar in some respects to returning to a different location using longjmp.)
In contrast to the other routines that we have discussed thus far, KiUserCallbackDispatcher breaks completely out of the passive callback model. The user mode callback dispatcher is, as the name implies, a trampoline that is used to make full-fledged calls to user mode, from kernel mode. (It is complemented by the NtCallbackReturn system service, which resumes execution in kernel mode following a user mode callback’s completion. Note that this means that a user mode callback can make auxiliary calls into the kernel without “returning” back to the original kernel mode caller.)
Calling user mode to kernel mode is a very non-traditional approach in the Windows world, and for good reason. Such calls are typically dangerous and need to be implemented very carefully in order to avoid creating any number of system reliability or integrity issues. Beyond simply validating any data returned to kernel mode from user mode, there are a far greater number of concerns with a direct kernel mode to user mode call model as supported by KiUserCallbackDispatcher. For example, a thread running in user mode can be freely suspended, delayed for a very long period of time due to a high priority user thread, or even terminated. These actions mean that any code spanning a call out to user mode must not hold locks, have acquired memory or other resources that might need to be released, or soforth.
From a kernel mode perspective, the way a user mode callback using KiUserCallbackDispatcher works is that the kernel saves the current processor state on the current kernel stack, alters the view of the top of the current kernel stack to point after the saved register state, sets a field in the current thread (CallbackStack) to point to the stack frame containing the saved register state (the previous CallbackStack value is saved to allow for recursive callbacks), and then executes a return to user mode using the standard return mechanism.
The user mode return address is, of course, set to the feature NTDLL routine of this article, KiUserCallbackDispatcher. The way the user mode callback dispatcher operates is fairly simple. First, it indexes into an array stored in the PEB with an argument to the callback dispatcher that is used to select the function to be invoked. Then, the callback routine located in the array is invoked, and provided with a single pointer-sized argument from kernel mode (this argument is typically a structure pointer containing several parameters packaged up into one contiguous block of memory). The actual implementation of KiUserCallbackDispatcher is fairly simple, and I have posted a C representation of it.
In Win32, kernel mode to user mode callbacks are used exclusively by User32 for windowing related aspects, such as calling a window procedure to send a WM_NCCREATE message during the creation of a new window on behalf of a user mode caller that has invoked NtUserCreateWindowEx. For example, during window creation processing, if we set a breakpoint on KiUserCallbackDispatcher, we might see the following:
Breakpoint 1 hit ntdll!KiUserCallbackDispatch: 00000000`77691ff7 488b4c2420 mov rcx,qword ptr [rsp+20h] 0:000> k RetAddr Call Site 00000000`775851ca ntdll!KiUserCallbackDispatch 00000000`7758514a USER32!ZwUserCreateWindowEx+0xa 00000000`775853f4 USER32!VerNtUserCreateWindowEx+0x27c 00000000`77585550 USER32!CreateWindowEx+0x3fe 000007fe`fddfa5b5 USER32!CreateWindowExW+0x70 000007fe`fde221d3 ole32!InitMainThreadWnd+0x65 000007fe`fde2150c ole32!wCoInitializeEx+0xfa 00000000`ff7e6db0 ole32!CoInitializeEx+0x18c 00000000`ff7ecf8b notepad!WinMain+0x5c 00000000`7746cdcd notepad!IsTextUTF8+0x24f 00000000`7768c6e1 kernel32!BaseThreadInitThunk+0xd 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
If we step through this call a bit more, we’ll see that it eventually ends up in a function by the name of user32!_fnINLPCREATESTRUCT, which eventually calls user32!DispatchClientMessage with the WM_NCCREATE window message, allowing the window procedure of the new window to participate in the window creation process, despite the fact that win32k.sys handles the creation of a window in kernel mode.
Callbacks are, as previously mentioned, permitted to be nested (or even recursively made) as well. For example, after watching calls to KiUserCallbackDispatcher for a time, we’ll probably see something akin to the following:
Breakpoint 1 hit ntdll!KiUserCallbackDispatch: 00000000`77691ff7 488b4c2420 mov rcx,qword ptr [rsp+20h] 0:000> k RetAddr Call Site 00000000`7758b45a ntdll!KiUserCallbackDispatch 00000000`7758b4a4 USER32!NtUserMessageCall+0xa 00000000`7758e55a USER32!RealDefWindowProcWorker+0xb1 000007fe`fca62118 USER32!RealDefWindowProcW+0x5a 000007fe`fca61fa1 uxtheme!_ThemeDefWindowProc+0x298 00000000`7758b992 uxtheme!ThemeDefWindowProcW+0x11 00000000`ff7e69ef USER32!DefWindowProcW+0xe6 00000000`7758e25a notepad!NPWndProc+0x217 00000000`7758cbaf USER32!UserCallWinProcCheckWow+0x1ad 00000000`77584e1c USER32!DispatchClientMessage+0xc3 00000000`77692016 USER32!_fnINOUTNCCALCSIZE+0x3c 00000000`775851ca ntdll!KiUserCallbackDispatcherContinue 00000000`7758514a USER32!ZwUserCreateWindowEx+0xa 00000000`775853f4 USER32!VerNtUserCreateWindowEx+0x27c 00000000`77585550 USER32!CreateWindowEx+0x3fe 00000000`ff7e9525 USER32!CreateWindowExW+0x70 00000000`ff7e6e12 notepad!NPInit+0x1f9 00000000`ff7ecf8b notepad!WinMain+0xbe 00000000`7746cdcd notepad!IsTextUTF8+0x24f 00000000`7768c6e1 kernel32!BaseThreadInitThunk+0xd
This support for recursive callbacks is a large factor in why threads that talk to win32k.sys often have so-called “large kernel stacks”. The kernel mode dispatcher for user mode calls will attempt to convert the thread to a large kernel stack when a call is made, as the typical sized kernel stack is not large enough to support the number of recursive kernel mode to user mode calls present in a many complicated window messaging calls.
If the process is a Wow64 process, then the callback array in the PEB is prepointed to an array of conversion functions inside the Wow64 layer, which map the callback argument to a version compatible with the 32-bit user32.dll, as appropriate.
Next up: Taking a look at LdrInitializeThunk, where all user mode threads really begin their execution.