This is a quick walk through on the basics of Windows Kernel debugging. You will need two Windows Virtual Machines (VM) for this scenario. I used two Windows 11 25H2 VMs.

You can use which ever Hyper Visor you prefer. I used Hyper-V because it’s easy to use and allows you to run Virtualization Based Security (VBS) on your host and the VMs at the same time.

You can download the latest version of Windows installer ISO from Microsoft Download

I recommend setting up two Network Interface Cards (NIC) per VM. One will be used for debugging the kernel and the other will be used for accessing the internet. If you want a completely isolated environment you could get by with one NIC. The debugging NIC must be the first NIC on the system.

This is a screenshot of my debugger VM settings showing the network setup. The debugging network is on it’s own private Virtual Switch. Network_config

It is important to ensure that Secure Boot is disabled on the Debugee machine. Secure Boot can be enabled on the Debugger machine.

Set static IPs on the NIC assigned to the debug network. I used 10.10.10.10 for the Debugger machine and 10.10.10.11 for the Debugee machine. Debugger ipconfig

The Debugger machine needs WinDbg installed. You can download it as part of the Windows Software Development Kit (SDK) downlaoded from Windows SDK or you can install WinDbg from a PowerShell terminal with:

winget install Microsoft.WinDbg

I also use the Debugger machine as my development platform and install Visual Studio Visual Studio Download and the Windows Driver Kit Windows Driver Kit Download. This is optional and not required if you are not doing any development.

You can go ahead and start WinDbg and configure a Kernel Debugging connection listener by clicking File->Stat Debugging-> Attach to Kernel. Then set a port. You can use the default port 50000 or any other unused UDP port. You will also need a 256-bit authentication key that must be in the form of four 64-bit chunks separated by dots i.e. x.x.x.x. You can use lowercase a-z and 0-9 for each chunk. In my example I use kernel.dbg.lab.2025

debugger connection 1 debugger connection 2 debugger connection 3

You can pre-download symbols (function names, etc.) by opening a command prompt and creating a symbols folder and running the symchk tool. Kernel debugging will be a lot more confusing if you don’t have symbols. As long as you have internet connection on the Debugger machine you can download symbols on the fly, but it saves time when debugging to pre-download them.

mkdir symbols

cd "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64"

symchk /r C:\Windows\System32\*.dll /s srv*C:\symbols*https://msdl.microsoft.com/download/symbols

symchk /r C:\Windows\System32\drivers\*.sys /s srv*C:\symbols*https://msdl.microsoft.com/download/symbols

Now switch over to the Debugee machine to setup the IP information and configure kernel debugging. I used 10.10.10.11 for the Debugee IP on the debugging network. The following commands on an administrator command prompt will enable network kernel debugging to connect to 10.10.10.10 on UDP port 50000 with key kernel.dbg.lab.2025:

bcdedit /debug on

bcdedit /dbgsettings net hostip:10.10.10.10 port:50000 key:kernel.dbg.lab.2025

bcdedit /set "{dbgsettings} dhcp no"

enable debug debug settings Reboot the Debugee machine and you will see the connection on the Debugger machine. debugger connected Click the pause button to break. This will cause the Debugee machine to become unresponsive to any input since you have initiated a break into the kernel. This happens anytime you hit a break point or manually break. Now is a good time to set your symbol path and force a reload of the symbols. The command bar changes from BUSY Debuggee is running… to 0:kd>. You will be able to type commands into the command bar and hit enter.

.sympath srv*C:\symbols*https://msdl.microsoft.com/download/symbols

.reload /f

set symbols The .reload /f will force a reload of the symbols and attempt to download any missing symbols. Type g in the command bar and hit enter or click the green play button to resume the kernel and allow the Debugee to continue processing.

Here is a table of some of the useful WinDbg commands to use with kernel debugging:

Command Description
dq address or register Display qwords at specified address or address in register
dd address or register Display dwords at specified address or address in register
db address or register Display byte at specified address or address in register
dqs address or register Same as dq but also display symbols
dds address or register Same as dd but also display symbols
? expression Perform math operations
u address or register Disassemble code at specified address or address in register
.formats data Display the data in several different useful formats. Helps with converting hex to binary or decimal
!process blank or 0 0 process_name Search for a process or display information about current process
.process /i EPROCESS address Interact with the specified process
bp address Set a breakpoint at specified address, you can use symbols instead of address
bu address Set an unresolved breakpoint. Resolves once symbols are loaded
ba e1 /p process address Set a hardware breakpoint on execution of a specified address from within a specified process. You can change e to r or w for read/write
bl List breakpoints
bc * or number Clear or delete all breakpoints or specified breakpoint by number retrieved with bl
bd * or number Disable but not delete all breakpoints or specified breakpoint by number retrieved with bl
.reload /f Force reload of symbols and download missing symbols
!address address Display information about specified address
!pte address Display the page table entry for the specified virtual address. Can also use with a PTE address to retrieve the associated virtual address
lm blank or m module_name List loaded modules with base and end address or list only specified module
!drvobj driver_name 2 List information about specified driver
dt -v module!symbol Display table of a Windows data structure

Here is an example of listing the currently loaded modules and then just the nt kernel itself:

lm m

lm m nt

lm lm m nt

Here is an example of dumping the HalDispatchTable in the nt kernel using dqs:

dqs nt!HalDispatchTable

HalDispatchTable

Start cmd.exe and launch calc.exe from the command prompt on the Debugee machine. You can then search for the calc.exe process, interact with it, and display it’s process info with:

!process 0 0 calc.exe

.process /i `EPROCESS address from above command`

g

!process

calc process calc process details The address following THREAD in the above image is the address to the KTHREAD structure for the process. Process in Windows are a management and control mechanism for executing code. Processes do not execute code themselves. All code is executed under the process as a thread or fiber. When the process has multiple threads the one marked as ‘executive’ will have the KTHREAD. You can highlight the address and ctrl+c to copy the address and dump the KTHREAD with:

dt -v nt!_KTHREAD `kthread address`

calc kthread

You can then take the StackBase, which is the address following the last address of the stack, and subtract from it to display the end of the stack. You can subtract 0x100 bytes and see a nice code cave no the Kernel Stack for the process with:

dqs `StackBase address`-100

kthread stack base

If you want to see where the Kernel Stack is currently point and what is on it you can use:

dt -v nt!_KTHREAD `KTHREAD address` KernelStack

dqs `address displayed from above command`

kernel stack

This was a quick run down on how to setup Windows Kernel Debugging and some quick familiarization on using WinDbg to help you get your bearings to follow along in future tutorials.

I highly recommend book marking the Vergilius Project to enumerate Windows Kernel Structures if you are going to be diving in to Windows Kernel Debugging Vergilius Project