The Windows kernel is a rich environment in which hundreds of drivers execute on a typical system, and where thousands of variables containing global state are present. For advanced troubleshooting, IT experts will typically use tools such as the Windows Debugger (WinDbg), SysInternals Tools, or write their own. Unfortunately, usage of these tools is getting increasingly hard, and they are themselves limited by their own access to Windows APIs and exposed features.
Some of today’s challenges include:
- Windows 8 and later support Secure Boot, which prevents kernel debugging (including local debugging) and loading of test-signed driver code. This restricts troubleshooting tools to those that have a signed kernel-mode driver.
- Even on systems without Secure Boot enabled, enabling local debugging or changing boot options which ease debugging capabilities will often trigger BitLocker’s recovery mode.
- Windows 10 Anniversary Update and later include much stricter driver signature requirements, which now enforce Microsoft EV Attestation Signing. This restricts the freedom of software developers as generic “read-write-everything” drivers are frowned upon.
- Windows 10 Spring Update now includes customer-facing options for enabling HyperVisor Code Integrity (HVCI) which further restricts allowable drivers and blacklists multiple 3rd party drivers that had “read-write-everything” capabilities due to poorly written interfaces and security risks.
- Technologies like Supervisor Mode Execution Prevention (SMEP), Kernel Control Flow Guard (KCFG) and HVCI with Second Level Address Translation (SLAT) are making traditional Ring 0 execution ‘tricks’ obsoleted, so a new approach is needed.
In such an environment, it was clear that a simple tool which can be used as an emergency band-aid/hotfix and to quickly troubleshoot kernel/system-level issues which may be apparent by analyzing kernel state might be valuable for the community.
How it Works
r0ak works by redirecting the execution flow of the window manager’s trusted font validation checks when attempting to load a new font, by replacing the trusted font table’s comparator routine with an alternate function which schedules an executive work item (
WORK_QUEUE_ITEM) stored in the input node. Then, the trusted font table’s right child (which serves as the root node) is overwritten with a named pipe’s write buffer (
NP_DATA_ENTRY) in which a custom work item is stored. This item’s underlying worker function and its parameter are what will eventually be executed by a dedicated
PASSIVE_LEVELonce a font load is attempted and the comparator routine executes, receiving the name pipe-backed parent node as its input. A real-time Event Tracing for Windows (ETW) trace event is used to receive an asynchronous notification that the work item has finished executing, which makes it safe to tear down the structures, free the kernel-mode buffers, and restore normal operation.
When using the
--execute option, this function and parameter are supplied by the user.
--write, a custom gadget is used to modify arbitrary 32-bit values anywhere in kernel memory.
--read, the write gadget is used to modify the system’s HSTI buffer pointer and size (N.B.: This is destructive behavior in terms of any other applications that will request the HSTI data. As this is optional Windows behavior, and this tool is meant for emergency debugging/experimentation, this loss of data was considered acceptable). Then, the HSTI Query API is used to copy back into the tool’s user-mode address space, and a hex dump is shown.
Because only built-in, Microsoft-signed, Windows functionality is used, and all called functions are part of the KCFG bitmap, there is no violation of any security checks, and no debugging flags are required, or usage of 3rd party poorly-written drivers.
Due to the usage of the Windows Symbol Engine, you must have either the Windows Software Development Kit (SDK) or Windows Driver Kit (WDK) installed with the Debugging Tools for Windows. The tool will lookup your installation path automatically, and leverage the
SymSrv.dll that are present in that directory. As these files are not re-distributable, they cannot be included with the release of the tool.
Alternatively, if you obtain these libraries on your own, you can modify the source-code to use them.
Usage of symbols requires an Internet connection, unless you have pre-cached them locally. Additionally, you should setup the
_NT_SYMBOL_PATH variable pointing to an appropriate symbol server and cached location.
It is assumed that an IT Expert or other troubleshooter which apparently has a need to read/write/execute kernel memory (and has knowledge of the appropriate kernel variables to access) is already more than intimately familiar with the above setup requirements. Please do not file issues asking what the SDK is or how to set an environment variable.
- Some driver leaked kernel pool? Why not call
ntoskrnl.exe!ExFreePooland pass in the kernel address that’s leaking? What about an object reference? Go call
ntoskrnl.exe!ObfDereferenceObjectand have that cleaned up.
- Want to dump the kernel DbgPrint log? Why not dump the internal circular buffer at
- Wondering how big the kernel stacks are on your machine? Try looking at
- Want to dump the system call table to look for hooks? Go print out
These are only a few examples — all Ring 0 addresses are accepted, either by
module!symbol syntax or directly passing the kernel pointer if known. The Windows Symbol Engine is used to look these up.
The tool requires certain kernel variables and functions that are only known to exist in modern versions of Windows 10, and was only meant to work on 64-bit systems. These limitations are due to the fact that on older systems (or x86 systems), these stricter security requirements don’t exist, and as such, more traditional approaches can be used instead. This is a personal tool which I am making available, and I had no need for these older systems, where I could use a simple driver instead. That being said, this repository accepts pull requests, if anyone is interested in porting it.
Secondly, due to the use cases and my own needs, the following restrictions apply:
- Reads — Limited to 4 GB of data at a time
- Writes — Limited to 32-bits of data at a time
- Executes — Limited to functions which only take 1 scalar parameter
Obviously, these limitations could be fixed by programmatically choosing a different approach, but they fit the needs of a command line tool and my use cases. Again, pull requests are accepted if others wish to contribute their own additions.
Note that all execution (including execution of the
--write commands) occurs in the context of a System Worker Thread at
PASSIVE_LEVEL. Therefore, user-mode addresses should not be passed in as parameters/arguments.
r0ak v1.0.0 -- Ring 0 Army Knife http://www.github.com/ionescu007/r0ak Copyright (c) 2018 Alex Ionescu [@aionescu] USAGE: r0ak.exe [--execute <Address | module.ext!function> <Argument>] [--write <Address | module.ext!function> <Value>] [--read <Address | module.ext!function> <Size>]