Uninformed: Informative Information for the Uninformed

Vol 8» 2007.Sep


The Windows kernel provides an extensive debugging interface to allow the kernel itself (and third party drivers) to be debugged in a live, interactive environment (as opposed to after-the-fact, post-mortem crash dump debugging). This debugging interface is used by a kernel debugger program (kd.exe, or WinDbg.exe) in order to perform tasks such as the inspecting the running state (including memory, registers, kernel state such as processes and threads, and the like) of the kernel on-demand. The debugging interface also provides facilities for the kernel to report various events of interest to a kernel debugger, such as exceptions, module load events, debug print output, and a handful of other state transitions. As a result, the kernel debugger interface has ``hooks'' built-in to various parts of the kernel for the purpose of notifying the kernel debugger of these events.

The far-reaching capabilities of the kernel debugger in combination with the fact that the kernel debugger interface is (in general) present in a compatible fashion across all OS builds provides an attractive mechanism that can be used to gain control of a system. By subverting KiDebugRoutine to instead point to a custom callback function, it becomes possible to surepticiously gain control at key moments (debug prints, exception dispatching, kernel module loading are the primary candidates).

The architecture of the kernel debugger event notification interface can be summed up in terms of a global function pointer (KiDebugRoutine) in the kernel. A number distinct pieces of code, such as the exception dispatcher, module loader, and so on are designed to call through KiDebugRoutine in order to notify the kernel debugger of events. In order to minimize overhead in scenarios where the kernel debugger is inactive, KiDebugRoutine is typically set to point to a dummy function, KdpStub, which performs almost no actions and, for the most part, simply returns immediately to the caller. However, when the system is booted with the kernel debugger enabled, KiDebugRoutine may be set to an alternate function, KdpTrap, which passes the information supplied by the caller to the remote debugger.

Although enabling or disabling the kernel debugger has traditionally been a boot-time-only decision, newer OS builds such as Windows Server 2003 and beyond have some support for transitioning a system from a ``kernel debugger inactive'' state to a ``kernel debugger active'' state. As a result, there is some additional logic now baked into the dummy routine (KdpStub) which can under some circumstances result in the debugger being activated on-demand. This results in control being passed to the actual debugger communication routine (KdpTrap) after an on-demand kernel debugger initialization. Thus, in some circumstances, KdpStub will pass control through to KdpTrap.

Additionally, in Windows Server 2003 and later, it is possible to disable the kernel debugger on the fly. This may result in KiDebugRoutine being changed to refer to KdpStub instead of the boot-time-assigned KdpTrap. This behavior, combined with the previous points, is meant to show that provided a system is booted with the kernel debugger enabled it may not be enough to just enforce a policy that KiDebugRoutine must not change throughout the lifetime of the system.

Aside from exception dispatching notifiations, most debug events find their way to KiDebugRoutine via interrupt 0x2d, otherwise known as ``DebugService''. This includes user-mode debug print events as well as kernel mode originated events (such as kernel module load events). The trap handler for interrupt 0x2d packages the information supplied to the debug service interrupt into the format of a special exception that is then dispatched via KiExceptionDispatch (the normal exception dispatcher path for interrupt-generated exceptions). This in turn leads to KiDebugRoutine being called as a normal part of the exception dispatcher's operation.

Category: Type IIa, varies. Although on previous OS versions KiDebugRoutine was essentially write-once, recent versions allow limited changes of this value on the fly while the system is booted.

Origin: At the time of this writing, the authors are not aware of existing malware using KiDebugRoutine.

Capabilities: Redirecting KiDebugRoutine to point to a caller-controlled location allows control to be gained during exception dispatching (a very common occurrence), as well as certain other circumstances (such as module loading and debug print output). As an added bonus, because KiDebugRoutine is integral to the operation of the kernel debugger facility as a whole, it should be possible to ``filter'' the events received by the kernel debugger by manipulation of which events are actually passed on to KdpTrap, if a kernel debugger is enabled. However, it should be noted that other steps would need to be taken to prevent a kernel debugger from detecting the presence of code, such as the interception of the kernel debugger read-memory facilities.

Considerations: Depending on how the system global flags (NtGlobalFlag) are configured, and whether the system was booted in such a way as to suppress notification of user mode exceptions to the kernel debugger, exception events may not always be delivered to KiDebugRoutine. Also, as KiDebugRoutine is not exported, it would be necessary to locate it in order to intercept it. Furthermore, many of the debugger events occur in an arbitrary context, such that pointing KiDebugRoutine to user mode (except within ntdll space) may be considered dangerous. Even while pointing KiDebugRoutine to ntdll, there is the risk that the system may be brought down as some debugger events may be reported while the system cannot tolerate paging (e.g. debug prints). From a thread-safety perspective, an interlocked exchange on KiDebugRoutine should be a relatively synchronization-safe operation (however the new callback routine may never be unmapped from the address space without some means of ensuring that no callbacks are active).

Covertness: As KiDebugRoutine is a non-exported, writable kernel global, it has some inherent defenses against simple detection techniques. However, in legitimate system operation, there are only two legal values for KiDebugRoutine: KdpStub, and KdpTrap. Though both of these routines are not exported, a combination of detection techniques (such as verifying the integrity of read only kernel code, and a verification that KiDebugRoutine refers to a location within an expected code region of the kernel memory image) may make it easier to locate blatant attacks on KiDebugRoutine. For example, simply setting KiDebugRoutine to point to an out-of-kernel location could be detected with such an approach, as could pointing it elsewhere in the kernel and then writing to it (either the target location would need to be outside the normal code region, easily detectable, or normally read-only code would have to be overwritten, also relatively easily detectable). Also, all versions of PatchGuard protect KiDebugRoutine in x64 versions of Windows. This means that effective exploitation of KiDebugRoutine in the long term on such systems would require an attacker to deal with PatchGuard. This is considered a relatively minor difficulty by the authors.