Local Kernel Debugger (LKD) is a python wrapper around dbgengine.dll to
perform local kernel debugging of a Windows kernel booted in DEBUG mode.
Local Kernel Debugging is the ability to perform kernel-mode debugging on a
single computer. In other words, the debugger runs on the same computer
that is being debugged.
Windows offers this functionality through WinDbg and KD binaries which allow to
read/write the kernel memory, perform in/out and access MSRs.
WinDbg and KD use dbgengine.dll to offer this functionality, but dbgengine only
allows binary with name windbg.exe or kd.exe and also requires that the said
binary embeds a driver used to perform the local kernel debugging actions.
To bypass these restrictions we used IAT hook against dbgengine.dll to emulate the
resource (that is read from a file) and also change the result of
GetModuleFileNameW to C:\\windbg.exe.
With that we are able to retrieve some COM interfaces offered by dbgengine and
used to perform Local Kernel Debugging.
Last problem is that some functionalities we find useful are missing in the
LKD driver (memory allocation, custom kernel call, ...) so we
use the LKD driver to rewrite some part of it in memory to upgrade its features.
The LKD try to use the usual environment variable _NT_SYMBOL_PATH to retrieve the symbol path.
If this variable does not exist it will use the .\symbols directory in the current path
of dbginterface.py
Some part of LKD need (for now) to have access to the ntoskrnl symbols so the
debugged computer must be connected to Internet at least once.
LKD cannot be done in a SysWow64 process, if you try to debug a 64bits kernel you will need a 64bits python.
You can find every MSI you need at https://www.python.org/downloads/windows/
See the example directory.
-
dbginterface.pyThe main file of the project, LKD objects that setup the IAT hooks for the WinDbg imposture, attach to the local kernel, retrieve the COM interfaces and wrap them. -
resource_emulation.pyIAT hooks that allow to emulate a resource from a file in the File System. -
simple_com.pySimple wrapper to COM interface (used in example\output_demo.py). -
driver_upgrade.pyCode that rewrite part of the LKD driver in memory to upgrade its features -
bin\windbg_driver_x86.sysLKD (signed) driver for 32bits kernel extracted from WinDbg's resources -
bin\windbg_driver_x64.sysLKD (signed) driver for 64bits kernel extracted from WinDbg's resources -
bin\DBGDLL\dbgengine + symbol engine for windows 32bits -
bin\DBGDLL64\dbgengine + symbol engine for windows 64bits -
test\Our test, need to be launched on 32bits and 64bits kernels to be complete -
example\Our demos code of the use and interest of LKD -
doc\Sphinx documentation (Available online) -
windows\Side project with some windows helpers, some part of this are not directly used by LKD.
The features used are:- IAT Hooks
- Windows API proxy
- Native execution
- simple_x86 / simple_x64 assembler
There is a COM API to perform I/O: WriteIo and ReadIo but it seems that
these API check (nt!KdpSysReadIoSpace & nt!KdpSysWriteIoSpace) for some alignment in the port and the size.
A ReadIo(size=4) must be on a port aligned on 4.
For performing any I/O, LKD offers two function: do_in, do_out.
do_in(self, port, size)
<require upgraded driver>
Perform IN instruction in kernel mode
do_out(self, port, value, size)
<require upgraded driver>
Perform OUT instruction in kernel mode
Used to convert a symbol address to an ULONG64 requested by the API and vice versa. Problem is that in a 32bits kernel the kernel addresses are bit expended.
ntoskrnl base in 32bits kernel would not be 0x8XXXXXXX but 0xffffffff8XXXXXXX.