Kernel debugging always seemed a little arcane to me. It was something that techno-wizards partook in from their turbid lairs, whispering incantations over keyboards while us lesser mortals confine ourselves to the safety of userland.
This post however aims to pull aside the curtain and shed the sheen of mysticism from kernel debugging and demonstrate how straightforward it really is. In fact, this will be little more than a regurgitation of the MDSN post on the matter, with a few extra steps, details and opinions along the way.
To begin with, we'll need a debugger and debugee. While technically possible to debug the kernel on the host machine (with some pretty strong caveats) it's far easier and more productive to debug the kernel on one host from another, as otherwise when you hit a breakpoint and the kernel stops, well so does everything else...
The easiest way to do this nowadays is with the use of a Virtual Machine (the debuggee) performing any actions that we wish to investigate, communicating with its host (the debugger), which will be doing the investigating.
For the debugee we'll use the classic free Windows developer VM and run it from within VMWare.
For the host we'll use Windows 10 with WinDbg Preview from the Microsoft Store. This is the latest generation of the venerable WinDbg debugger from Microsoft. We'll also need the Debugging Tools for Windows 10 to be installed on the host.
The next step is to ensure that the debugee can communicate with the host. There are multiple ways to do this, such as via a serial port or a named pipe, but the easiest and most stable is simply a network socket.
For this to work, the host must be able to communicate with the VM on a specified IP and port. To this end, we'll set up the VM in Host-only mode. In VMWare, for the debugee VM, navigate to VM -> Settings and choose Host-only for the Network Adapter configuration.
Whilst we're on the host, we might as well take note of the host IP. In this case, I'm noting the host's IP on my home network by running
ipconfig from a PowerShell prompt on the host.
Next, we'll navigate to the Windows Debugging Tools directory on the host, by default at C:\Program Files (x86)\Windows Kits\10\Debuggers\x64, and copy the kdnet.exe and VerifiedNICList.xml files onto the debugee VM. We'll place these at a convenient location, for example C:\kdnet.
From an Administrator PowerShell prompt we then run `kdnet.exe`, which informs us that Network debugging is supported on the main Network Interface (NIC).
This NIC is the correct one for communicating with the host, so we can move forwards and enable network debugging between the two by running
kdnet.exe <HostIP> <DebugPort>. The debug port needs to be unique per debugee (in the event we want to debug more than one at a time) and it is recommended by Microsoft that we choose a port between 50000-500039.
In our case then we'll run
kdnet.exe 192.168.1.105 50007.
The output of
kdnet.exe provides us with a key and instructs to reboot the computer, however hold off on that for now and instead copy the key and move to the debugger.
From WinDbg Preview, choose File -> Start Debugging -> Attach to Kernel
Under the Net tab, enter the port number and the key from the output of
kdnet.exe. We don't need a Target IP (as it says), so instead just hit ok. WinDbg will note it's using NET debugging and we see a prompt saying "Waiting to reconnect..."
Then, reboot the VM (using the shutdown command
kdnet.exe provided or otherwise) and wait for the connection to the debugger. Shortly after hitting the splash screen as the VM boots, we should see the connection in the debugger.
We'll wait a little longer (the VM will probably be a fair bit slower while being debugged) and let the machine finish booting to ensure that all the system structures have been successfully set up and populated, and then we'll hit the 'break' button in the debugger to pause execution and allow us to start poking around the innards of Windows 10.
For example, issuing the
lm k command (loaded kernel modules) will list the various modules and drivers loaded into the kernel.
This brings us to the conclusion of this post. Microsoft have actually made it very accessible and straightforward to set up and get going with kernel debugging, and this post really only hopes to highlight this and act as a trampoline for what comes next.
Next time we'll be using the debugger to explore the kernel itself as we try to get a handle on what works where in Windows kernel land and explore the transition from userland to the kernel through the use of syscalls and interrupts.