Last changed: 05.05.2021
Windows Kernel Debugging with WinDbg
The underlying concepts can be read in the official documentation of the Microsoft WDK and of the distiction between miniport and port drivers
More windbg
commands can be found in
microsofts official windbg documentation
The Vergilius Project offers a browsable collection of windows kernel structures.
setup kernel debugging
create boot entry for target machine
The tool bcdedit.exe
can be used to create a boot entry.
bcdedit /copy {current} /d "Kernel Debugging"
bcdedit /default {<GENERATED_UUID>}
bcdedit /set {default} debug yes
The different connection methods can be configured as follows
bcdedit /dbgsettings serial debugport:1 baudrate:115200
bcdedit /dbgsettings usb targetname:<NAME>
bcdedit /dbgsettings net hostip:10.0.0.1 port:50000 newkey
configure serial connection in virtualbox
Setup serial port in host and target. E.g. select COM1
on both machines and
configure them as Host-Pipe
with a common filename /tmp/pipe1
. In the
machine which will get booted first uncheck the option connect to pipe
.
disable driver signature checks and patchguard
bcdedit /set testsigning on
bcdedit /set nointegritychecks on
If you want to get rid of the watermark you have to patch the correnponding
strings in shell32.dll.mui
und basebrd.dll.mui
.
connect kernel debugger
The kernel debugger has to be invoked according to the chosen connection method.
kd -k com:port=1,baud=115200
kd -k usb:targetname=<NAME>
kd -k net:port=12345,key=<GENERATED_KEY>,target=<TARGET_IP>
detect kernel debugger
If you use network debugging you can detect the kernel debugger by its interface
systeminfo
wmic nic get caption
connect windbg to ida
If you have IDA Pro
running on the same machine as the kernel debugger you
can configure IDA Pro
to use windbg for kernel debugging. Make sure the
folder with the windbg binaries is in the PATH
.
Select Kernel mode debugging
in the menu Debugger -> Debugger options... -> Set specific options
. Then configure the Connection string
in Debugger -> Process options...
(e.g.
net:port=50000,key=<GENERATED_KEY>,target=<TARGET_IP>
or com:port=1,baud=115200
).
Now you should be able to connect with Debugger -> Attach to process...
.
ret-sync
Alternatively you can connect IDA Pro
with ret-sync
. Download the
ret-sync plugin and
the prebuild binaries for windbg
Check python path in SyncPlugin.py
. Probably C:\Python27-x64
on x64.
IDA -> Script file ... -> SyncPlugin.py
In kd
load the dll with
.load path\to\sync.dll
!sync
analyse processes
!process 0 0 list all processes
!dml_proc browsable process list
!process 0 0 calc.exe show info of calc.exe
To analyse a specific process you will have to switch the context.
!process -1 0 show current process
.process /i /r /p <address> switch context to process
.reload /user reload symbols
!peb show peb of current process
lmu list loaded user modules (dlls)
The current thread is also stored in the KPRCB
of the corresponding cpu.
!running show current cpu
rdmsr c0000101 get current KPCR address
!pcr 0 print KPCR of cpu 0
dt _KPCR <addr>
dt _KPCR <addr> Prcb.CurrentThread->ApcState.Process
dt _EPROCESS <proc_addr>
process privileges
dt _EPROCESS poi(nt!PSInitialSystemProcess) UniqueProcessId ImageFileName Token.
!token @@C++(<token_addr> & ~f)
dt _TOKEN @@C++(<token_addr> & ~f)
system calls (64bit)
uf ntdll!NtCreateFile
When the syscall
instruction (x64) is executed the value from the
IA32_LSTAR
MSR (System Call Target Address, 0xc0000082
) gets loaded into
RIP
.
rdmsr c0000082
ln <address>
uf nt!KiSystemCall64Shadow
uf nt!KiSystemServiceUser
u nt!KiSystemServiceRepeat L10
dqs nt!KeServiceDesctiptorTable
Calculate function pointer address
ln nt!KiServiceTable + ((poi(nt!KiServiceTable + (0x55*4)) & 0xffffffff) >>4)
ln @@C++(*(void **)@@(nt!KeServiceDescriptorTable)) + (@@C++((*(int **)@@(nt!KeServiceDescriptorTable))[0x55]) >>>4)
You can do this dynamically
bp /p $proc nt!KiSystemServiceRepeat
ln nt!KiServiceTable + ((poi(nt!KiServiceTable + (@eax*4)) & 0xffffffff) >>4)
page table entries
You can list all allocated pages and translate their page table entries to virtual addresses.
!pte <address>
!ptetree
!pte2va <pte>
loading kernel drivers
To load a driver from cmd.exe
create a driver service.
sc create driver_debug binPath= c:\path\to\driver.sys type= kernel
sc query driver_debug
sc qc driver_debug
sc start driver_debug
In windbg
break on the unresolved driver entry.
bu $iment(driver_name) set unresolved breakpoint
If this does not work you can create a debug driver which breaks on entry and look at the stacktrace.
#include <ntifs.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT drv, PUNICODE_STRING reg) {
UNREFERENCED_PARAMETER(drv);
UNREFERENCED_PARAMETER(reg);
__debugbreak();
return STATUS_UNSUCCESSFUL;
}
driver info
lm list modules (drivers)
!dh -f <driver_name> show driver headers (incl. entry point)
lm m nt show kernel base
!object \Driver
dt nt!_DRIVER_OBJECT <address>
!drvobj <DRIVER>
!devobj <DEVICE>
kdfiles
If you are developing a driver on the debugging machine you can create a mapping to automatically copy the most recent file to the debuggee.
map
\??\c:\path\to\driver.sys
C:\Users\user\Source\Repos\test\Release\test.sys
driver I/O
Normally a driver creates a device in the appropriate device stack and
registers the needed major functions. To be accessable from user space the
driver needs to create a symbolic link (e.g. in the GLOBAL
/??
) namespace.
major functions
Major functions have the following prototype
NTSTATUS DriverDispatch(_DEVICE_OBJECT *DeviceObject, _IRP *Irp)
The names of the 28 different major functions can be resolved in windbg.
da @@(((char**)@@(nt!IrpMajorNames))[0])
I/O request packets (IRP)
Input and output to the major function is contained in the _IRP
. The function
code and the parameters can be found in the Tail.Overlay.CurrentStackLocation (0xb8)
.
Depending on the configured method the output gets copied to the UserBuffer (0x70)
(METHOD_NEITHER
) or AssociatedIrp.SystemBuffer (0x18)
(METHOD_BUFFERED
).
dt nt!_IRP @rdx
dt nt!_IRP @rdx AssiciatedIrp->*
dt nt!_IRP @rdx Tail.Overlay.CurrentStackLocation->*
dt nt!_IRP @rdx Tail.Overlay.CurrentStackLocation->Parameters.DeviceIoControl.
IOCTLs
Comands to the device itself get send to the major functions
IRP_MJ_DEVICE_CONTROL
or IRP_MJ_INTERNAL_DEVICE_CONTROL
. The
_IO_STACK_LOCATION
contains the IoControlCode (0x18)
.
#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)
driver signing
signtool sign /f Verisign.pfx /p t-span /ac MSCV-VSClass3.cer driver.sys
bypass
The Kernel Driver Utility exploits vulnerable drivers to load unsigned code.