Kernel debugging in seconds with Vagrant

All Windows kernel hackers, from beginners to professionals, know how time-consuming it can be to set up and manage virtual machines for kernel debugging. Vagrant is a free and open-source tool to automate the creation and automation of VMs. This post will introduce you to Vagrant and how to leverage its power to automate our kernel debugging setup.

Vagrant allows you to consider virtual machines as “disposable”, since they can be deleted and recreated automatically. You can then easily set up consistent VMs on multiple machines, share them with other people, or even deploy your virtual machines to a cloud provider such as AWS.

This guide will assume you already have Vagrant and a hypervisor installed. You can download Vagrant here.

Hypervisors supported by Vagrant include:

The Hyper-V provider is substantially gimped in terms of networking capability, so it is recommended to use any other hypervisor platform if possible. I’ll be using VirtualBox in this guide.

Preparing a base image (box)

Vagrant has a concept of “boxes”, which are essentially bare-minimum disk images, ideally containing just the absolute minimum to boot with no extra programs installed. These boxes form virtual machine templates that we can build off of via shell scripting.

You can save a lot of time by using a box from Vagrant Cloud instead. But if you can’t find a pre-made box that meets your specification, continue on.

A Ubuntu box is provided by Vagrant by default, but since we’re intersted in Windows it’s not really of any use to us. Vagrant Cloud is a web service of publicly-available boxes that other users have created, and there are a few Windows boxes available. If those are good enough for your use case, by all means go ahead and use a prepared box from the cloud. But if you want more control, creating your own box is your only option. Thankfully, it’s not that hard.

Start by creating a new virtual machine. Choose the minimum amount of RAM needed to install the OS and allocate a decent amount of disk space – about 50GB should be enough, but make sure it is dynamically allocated. You’ll also need to attach a virtual NIC configured in NAT mode. I’ve chosen Windows 10 LTSC 2019 Evaluation as my guest operating system, so once the VM is created, insert a virtual disk of the installation ISO. Boot up the VM and run through the normal installation process, but once you get to the user account creation, make sure the username is specified as “vagrant” with a password of “vagrant”.

Once you’ve installed the operating system and have booted to desktop, there are a couple more tasks to complete. According to the Vagrant documentation, you must:

Additionally, you must enable remote management using WinRM to allow Vagrant to execute scripts within the VM. To do so, execute the following in an elevated command prompt:

winrm quickconfig -q
winrm set winrm/config/winrs @{MaxMemoryPerShellMB="512"}
winrm set winrm/config @{MaxTimeoutms="1800000"}
winrm set winrm/config/service @{AllowUnencrypted="true"}
winrm set winrm/config/service/auth @{Basic="true"}
sc config WinRM start= auto

Once you’ve completed these tasks, you’re done setting up the box, so you should power it off. Note that you should not configure kernel debugging at this point, since boxes are supposed to be reusable base images, not for a specific purpose.

Now back on your host computer, let’s actually create the box.

vagrant package --base Win10LTSCBase --output
vagrant box add invokestatic/win10ltsc

You’ll need to change the arguments to fit your environment, including changing the VM name (“Win10LTSCBase”) and also the output box name (“invokestatic/win10ltsc”). This will create a “snapshot” of the virtual machine we just created and package into a box named invokestatic/win10ltsc.

Vagrant Up

Now that our box is set up, let’s put it to good use. First, create a Vagrantfile in an empty directory with the following contents:

You’ll have to edit this file a bit to suit your needs, particularly the name and possibly the kernel debugging port.

Vagrant.configure("2") do |config|
  config.vm.guest = :windows		# tell Vagrant this is a Windows-based guest
  config.vm.communicator = "winrm"	# use winrm for management instead of ssh

  config.winrm.password = "vagrant"	# the credentials specified during OS install
  config.winrm.username = "vagrant"	
  config.vm.define "win10" do |win10| = "invokestatic/win10ltsc"	# edit this to be the name of the box you created
    win10.vm.provision "shell", path: "guest/kdbg.bat"			# this batch file will be run inside the VM :forwarded_port, guest: 49152, host: 49152		# expose kernel debugging port to host

Create a new directory named guest, which will store scripts that will run inside the guest. Inside this directory, create the file kdbg.bat with the following contents. This allows us to configure the kernel debugging options inside the guest when the VM is created for the first time.

bcdedit /debug on
bcdedit /dbgsettings net hostip: port:49152 key:
copy C:\vagrant\guest\onboot.bat C:\onboot.bat
schtasks /create /sc onstart /tr "C:\onboot.bat" /tn vagrantonboot /ru SYSTEM /f
shutdown /r /t 0

You can change these settings as you like, but generally only use network debugging if you can help it. You can change the port and key, but note if you change the port you’ll also need to update the port forwarding in the Vagrantfile. If you need to debug Windows 7 or earlier, you’re going to need to configure COM debugging, which is possible with Vagrant but not covered by this guide.

Once everything is setup, go ahead and run vagrant up in your project directory. This will create a new VM with all of the settings specified in the Vagrantfile.

Attaching the debugger

After a few moments, your VM should be created and running, fully set up with kernel debugging enabled. We can attach in WinDbg on our host computer by pressing Ctrl + K and specifying port 49152 with key

If all went to plan, you should be greeted with a connected kernel debugging session!

Automate driver deployment

If all you want to do is step through Windows code, you’re all good to go. But chances are you’re looking to debug a kernel driver. Fortunately, Vagrant automatically maps all files in the project directory into C:\vagrant, so you can drop your driver file into the directory and it will automatically be available to the guest.

Note that since this mapping is implemented as a network share, the Windows kernel can’t load drivers from it, so prior to loading, it must be copied to somewhere on the C: drive. This too can be automated.

In the guest directory, create the file onboot.bat with the following contents:

MyDriver.sys is the name of the driver that will be deployed. It should be located in the root of the project directory.

sc stop MyDriver
sc delete MyDriver
sc create MyDriver binPath= "C:\Windows\System32\drivers\MyDriver.sys" type= kernel
copy C:\vagrant\MyDriver.sys C:\Windows\System32\drivers
sc start MyDriver

This will copy the driver file from the project directory into the system driver directory, create a new service, and load it. Our kdbg.bat creates a Windows task scheduler task that will run on boot to execute this task.

The “in seconds” part

Finally, we create a batch file to automate VM creation, driver deployment, and debugger attachment. In the root directory, create a batch file named start-debugger.bat with the following contents:

start vagrant up & vagrant powershell --command "schtasks /run /tn vagrantonboot"
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe" -k net:port=49152,key=
vagrant halt -f

When executed, this script will, in order:

  1. Start the VM (creating it if it doesn’t exist)
  2. Deploy and start the driver
  3. Attach WinDbg
  4. Stop the VM when WinDbg closes

Pretty slick! We’ve now completely automated our kernel debugging setup, getting to a debugger in just a couple of seconds.

GitHub project

View the GitHub project with all of the project files here. It contains some additional useful scripts that aren’t described in this guide.