Wane
812 words
4 minutes
Debugging Windows Kernel Drivers with IDA

Post is also uploaded at HackMD. Check it out.

Prerequisite#

  • Windows 10 or later
  • IDA Pro 7.x or later
  • VMware Player 17.x or later, or virt-manager with QEMU/KVM

For unsigned drivers#

Before we start, let’s talk about unsigned drivers. We cannot load unsigned drivers into our Windows kernel, because of Driver Signing Enforcement (DSE). So, we should sign a driver before we talk about the topic.

Of course, there is no problem if driver don’t uses their DriverObject parameter on DriverEntry. The popular project kdmapper uses vulnerable driver for loading unsigned drivers, bypassing DSE. However, if you load your driver with the project, DriverObject parameter is set to NULL. So in many cases, your driver should crash and end up loading BSOD.

Luckily you can make a test sign for any drivers, you can load the driver if OS has booted in test mode.

Creating Root Certificate#

First, let’s make a TestCert test sign and store it in Localmachine\My.

New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=TestCert" -CertStoreLocation "Cert:\LocalMachine\My"

Next, we’ll load our sign to CTLs, which is in LocalMachine\Root.

$cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -eq "CN=TestCert" }
Export-Certificate -Cert $cert -FilePath TestCert.cer
Import-Certificate -FilePath TestCert.cer -CertStoreLocation Cert:\LocalMachine\Root

This will generate TestCert.cer in your current directory, and will register into LocalMachine\Root.

Lastly, we sign our driver with TestCert with signtool.

signtool sign /v /n "TestCert" /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 Driver.sys

Boom! We can now load Driver.sys with test sign.

Opening GDBStub#

VMware#

We can debug entire Windows on VMware, using GDBStub. VMware supports GDBStub debugging, of course they work for other OS!

Add these two lines to your .vmx files:

debugStub.listen.guest64.remote = "TRUE"
monitor.debugOnStartGuest64 = "TRUE"

So you can debug on boot time. You can now load your IDA by connecting default port :8864. You can use Remote GDB Debugger option in IDA.

virt-manager#

I recommend this method, as this uses qemu, and it is more developer-friendly. The debugging process is going to be more comfortable.

modify the first:

+ <domain xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0" type="kvm">
- <domain type="kvm">

and add these to end of settings:

+ <qemu:commandline>
+   <qemu:arg value="-s"/>
+ </qemu:commandline>

now launch with this shell script (the -S option is somewhat not working, so we need to do this way):

virsh -c qemu:///system start win11
virsh -c qemu:///system qemu-monitor-command win11 --hmp 'stop'

You can now connect to the VM with default port :1234. You can do as same as VMware.

Finding NT kernel base address#

You should now locate NT kernel’s base address. Unfortunately your debugging is starting from boot time, you should use clever approach.

First, map the memory area so we can locate or jump to specific address.

Next, we exploit KUSER_SHARED_DATA. there are references in very beginning on kernel initialization, so we add hardware breakpoint at the structure.

import ida_dbg
import ida_idd

if ida_dbg.add_bpt(0xFFFFF78000000000, 8, ida_idd.BPT_RDWR) == False:
    raise RuntimeError("Failed to set breakpoint")

ida_dbg.continue_process()

Our RIP is on somewhere in kernel, so now we can use backwalking technique to find NT kernel base.

import idaapi
import ida_dbg

# ida_dbg.del_bpt(0xFFFFF78000000000) # Optional

def page_align(address):
    return (address&~(0x1000-1))

MODE = "QEMU"
if MODE == "VMWARE":
    monitor_result = send_dbg_command("r idtr")
    base_pos = monitor_result.find("base=")
    limit_pos = monitor_result.rfind(" limit")
    idt_base = monitor_result[base_pos+5:limit_pos]
    idt_base = int(idt_base, 16)
elif MODE == "QEMU":
    kgs = idaapi.get_reg_val("k_gs_base")
    pcr = read_dbg_qword(kgs+0x18) # gs:[0x18] = KeGetPcr
    idt_base = read_dbg_qword(pcr+0x38) # pcr[0x38] = Idt entries
else:
    raise ValueError("invalid mode")

i0e_low = read_dbg_word(idt_base)
i0e_mid = read_dbg_word(idt_base+0x6)
i0e_high = read_dbg_dword(idt_base+0x8)
idt_zero_entry = (i0e_low) | (i0e_mid << 16) | (i0e_high << 32)
print(f"IDT 0th handler: {hex(idt_zero_entry)}")

DosHeader = page_align(idt_zero_entry)
while(True):
    e_magic = read_dbg_word(DosHeader+0)
    if e_magic == 0x5A4D:
        print("Base address located at {}".format(hex(DosHeader)))
        break
    DosHeader -= 0x1000
    
e_lfanew = read_dbg_dword(DosHeader+0x3c)
if read_dbg_dword(DosHeader+e_lfanew) != 0x4550:
    raise ValueError("couldnt verify pe")

opt_offset = DosHeader+e_lfanew+0x18
if read_dbg_word(opt_offset) != 0x20b:
    raise ValueError("couldnt verify pe+")

We could find our base address.

IDT 0th handler: 0xfffff80585caf700
Base address located at 0xfffff80585600000

Load Symbol#

We can now load our symbol into our kernel:

Analyze#

Now we should set breakpoint on our driver’s entry when we load it. It is well known that you run sc start, it calls NtLoadDriver, subsequently IopLoadDriver, and finally PnpCallDriverEntry. We’ll put a breakpoint on PnpCallDriverEntry.

Now go in through KscpCfgDispatchUserCallTargetEsSmep, (…or some related to _guard_dispatch_icall_no_overrides.) After some step in execution, you can find driver entry!

After this, you can now calculate base, and place breakpoint, etc, yeah.

Used kernel driver#

In this post, I used a Dreamhack challenge named Windows Surprise Event, which is made by Zoodasa. I recommend you to see this challenge!

Exercise: kdmapper#

For some drivers, they are required to loaded with kdmapper. Of course you can debug them with this method.

This is an appendix for this post, as your exercise.

Conclusion#

It has some advantages, like it do not trigger cli or sti instruction, Kd flags, etc because it is working with VM-exit tricks. (DR movement hypervisor handling) So you can debug any windows kernel stuff stealthily unless they have hypervisor detections.

The debugging method is very effective to many situations, such as you don’t have any helpers but only one kernel driver, analyzing boot-time loading drivers, or even analyzing the kernel itself! (It is very useful to analyze PatchGuard yes.)

Debugging Windows Kernel Drivers with IDA
https://blog.wane.im/posts/winkd_ida/
Author
Wane
Published at
2025-09-04