# Win32 Kernel Chronicles: Building and Debugging a Windows Driver.

Alright internet, welcome to another blog post. As the title suggests, we're going to delve into building a Windows Driver. I want to make a quick disclaimer that there are multiple blog posts, videos, and resources for getting started with Windows Driver Development. This post just represents my take on the process, updated for 2023/2024 -- and *hopefully* a spring board into more advance topics down the road. I've recently transitioned from Full Stack Development to Windows Kernel Engineering, and this blog series will serve as a guide for that learning journey. The field of kernel development is 'quite niche', so if you find this post helpful, I'm glad to be of assistance!

## Prerequisites

If you hope to follow along with this article, we will need to set up a '***virtual development lab.***'

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">A 'virtual development lab' is a requirement for kernel development. Why? Well, due to the nature of the Operating Systems Kernel, a logical/runtime error in our code will almost certainly <strong>result in a blue-screen-of-death (BSOD).</strong> Additionally, having a virtual computer also allows us to set breakpoints and effectively debug the driver.</div>
</div>

*LeTs StArt CoOkiNg*, these will be our ingredients:

* A 64bit CPU architecture
    
* [Visual Studio Community Edition](https://visualstudio.microsoft.com/vs/community/)
    
* [Windows Software Development Kit](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/) (Windows SDK)
    
* [Windows Driver Kit](https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) (Windows WDK)
    
* Virtualization software (I'll be using [VirtualBox](https://www.virtualbox.org/))
    
* [Windows 11 ISO](https://www.microsoft.com/software-download/windows11)
    
* [OSRLoader](https://www.osronline.com/article.cfm%5Earticle=157.htm)
    

### Lab Setup

Our lab will consist of two virtual machines. **A Development Machine** and **A Testing Machine**. Theoretically, you could get away with having 'only a testing machine' -- but by having a dedicated development machine, we'll be able to remotely debug over a virtual network. Below Are Screenshots of my Virtual Machine Configuration.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699670229484/e0f88ebd-0562-4d7c-bc5d-f51dab79eef9.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699670275599/86f3f615-9b3a-4cd0-b955-65a535cc4783.png align="center")

### Lab Networking

After you successfully get two virtual machines up and running, configure a NAT Network for the machines to talk to each other over. In Virtual box this can be done by through the following:

![Create a new NAT Network](https://cdn.hashnode.com/res/hashnode/image/upload/v1699670480946/b76da037-0568-490b-bc44-7e4ec101963e.png align="center")

1. Create a new NAT Network
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699670679870/55d860ef-34be-4f1b-ad72-21e1b50acd98.png align="center")

1. Attach both the Developer and Testing machine's network interface to the created NAT Network.
    
2. Use the `ipconfig /all` command in `cmd.exe` to record your machine IP addresses. My configuration was as follows:
    
    * NAT Network subnet: `10.0.2.0/24`
        
    * **Developer Machine IP:** `10.0.2.15`
        
    * **Testing Machine IP:** `10.0.2.4`
        
3. Verify your machines can ping each other.
    

```plaintext
C:\Users\starcoding>ping 10.0.2.4

Pinging 10.0.2.4 with 32 bytes of data:
Reply from 10.0.2.4: bytes=32 time=1ms TTL=128
Reply from 10.0.2.4: bytes=32 time=1ms TTL=128
Reply from 10.0.2.4: bytes=32 time=2ms TTL=128
Reply from 10.0.2.4: bytes=32 time=2ms TTL=128

Ping statistics for 10.0.2.4:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 1ms, Maximum = 2ms, Average = 1ms

C:\Users\starcoding>
```

### **Developer Machine Setup**

Nice! We now have some machines that can communicate with one another. Phew, yes... one of the most tedious parts about the Driver Development Process is how long it can take to get a lab successfully configured. With this, we will now install the software required to commence development! Recall from the prerequisite section the following:

* [Visual Studio Community Edition](https://visualstudio.microsoft.com/vs/community/)
    
* [Windows Software Development Kit](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/) (Windows SDK)
    
* [Windows Driver Kit](https://learn.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk) (Windows WDK)
    

Within the Visual Studio Installer, Install `Desktop Development with C++` and the Individual Components:

* `MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)`
    
* `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699674278312/2bd15151-ff88-40d7-bea0-d761615c765b.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">For those curious MSVC stands for 'Microsoft Visual C++'. It comes with a suite of tools for compiling and developing C and C++ code on Windows Machines. It includes with a compiler <code>cl.exe</code> , a linker <code>link.exe</code> , a debugger, an automation build tool <code>nmake</code> and much more!</div>
</div>

### **Testing Machine Setup (IMPORTANT!)**

**Boot Configuration Data**

For the test machine, we'll need to open up an administrative command prompt and make some changes to the '**Boot Configuration Data (BCD)**'. To do this, open an administrative command prompt `cmd.exe` and type these commands:

```plaintext
bcdedit /debug on
bcdedit /set testsigning on
```

As the command text suggests, this will **enable kernel debugging** and **enable test-signing** for our Windows machine! Microsoft is notorious for having strict driver security (*understandably so)* and requires a driver to have a signed-digital signature before allowing it to be executed.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The practice of digitally signing drivers is part of Microsoft's strategy to mitigate risks associated with third-party software and to protect the overall integrity of the Windows Operating System.</div>
</div>

[Install OSRLoader](https://www.osronline.com/article.cfm%5Earticle=157.htm)

OSR (Open System Resources) is a consulting company known for providing resources, information, and tools related to the Windows Kernel. As per the company website, "This GUI-based tool will make all the appropriate registry entries for your driver, and even allow you to start your driver without rebooting." Which is perfect for us in the development stage of our driver.

### Coding Time!

Still reading? Good! Yeah -- let's do what we came here to do, develop a driver! With all that setup stuff out of the way, lets open up Visual Studio 2022 on our development machine.

Lets create a Empty Kernel Mode Driver (**KMDF Project**), I'll be naming my project `kernalChronicles_1` but feel free to give it a name of your own.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699681832151/147b105d-b179-407b-b320-97da1e268d83.png align="center")

Then, lets create a Source File called `Driver.c` (You can select a C++ file type in the selection menu, just give it a `.c` extension.)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699682096707/20bf483e-54ae-4482-ab45-61ce099d72fb.png align="center")

All drivers require a [`DriverEntry`](https://learn.microsoft.com/en-us/windows-hardware/drivers/wdf/driverentry-for-kmdf-drivers) routine, which is responsible for the driver’s initialization. This is similar to the `main` function in standard C programs.

This is what our `DriverEntry` routine will look like:

```c
#include <ntddk.h>                  // Kernel header

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath) {
    KdPrint(("Hello World - I'm the DriverEntry Routine!\n"));  
    //  - prints data when build settings are set to 'Debug',
    //    otherwise doesn't do anything
    return STATUS_SUCCESS;
}
```

Great! Our Driver should now theoretically load into the kernel! However, it's important to note a key aspect of the lifecycle of a Windows Kernel Driver:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699682880653/196c6f89-64d6-4db0-a1c2-dbba770f10b9.png align="center")

In many cases, drivers function as 'event-listeners' and don't terminate automatically. Drivers require manual unloading. Upon unloading, an 'exit' or 'unload' routine is invoked to free resources and prevent memory leaks. So lets make a `DriverUnload` routine.

```c
#include <ntddk.h>

// Our Unload Routine
NTSTATUS DriverUnload(_In_ PDRIVER_OBJECT driverObject) {
    KdPrint(("Till Next Time! Goodbye!\n"));
    return STATUS_SUCCESS;
}

// Our Entry Routine
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath) {
    KdPrint(("Hello World - I'm the DriverEntry Routine!\n"));  
    //  - prints data when build settings are set to 'Debug',
    //    otherwise doesn't do anything

    driverObject->DriverUnload = DriverUnload; // Set the unload function to DriverUnload
    return STATUS_SUCCESS;
}
```

Let's build our solution and see what happens...

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699683935046/026faa80-2479-4166-a9a1-76d33677e7d5.png align="center")

Oop ;-; what's that? Dang, an error -- wait.. no that's just a warning? What gives?

Because the kernel is critical to operating System functionality -- **KMDF projects are configured to always treat warnings as errors**. While it is possible to disable this behavior, it is NOT recommended. Instead, we can use the macro `UNREFERENCED_PARAMETER(<object not referenced>)` to tell the compiler we are aware of this unused variable.

Our Final Solution looks as the following:

```c
#include <ntddk.h>

// Our Unload Routine
NTSTATUS DriverUnload(_In_ PDRIVER_OBJECT driverObject) {
    UNREFERENCED_PARAMETER(driverObject);
    KdPrint(("Till Next Time! Goodbye!\n"));
    return STATUS_SUCCESS;
}

// Our Entry Routine
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath) {
    UNREFERENCED_PARAMETER(registryPath);    
    KdPrint(("Hello World - I'm the DriverEntry Routine!\n"));  
    //  - prints data when build settings are set to 'Debug',
    //    otherwise doesn't do anything

    driverObject->DriverUnload = DriverUnload; // Set the unload function to DriverUnload
    return STATUS_SUCCESS;
}
```

And now when we build the solution, we get a success message!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699840678686/0b93e82b-210f-4960-b12d-505d38e99846.png align="center")

We can now navigate to the build path location (mine was the default location) and copy + paste it onto our test machine! Make sure to Build the Solution with `Debug + x64` settings.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699841514434/f96b54ce-4875-43e1-bdf7-26940a3ebcb4.png align="center")

### Loading the Driver

Now that we have the Driver on our test machine, lets open up OSRLoader and do the following on the home page:

1. Change the Driverpath to our `*.sys` driver
    
2. Click the Register Service button (this is only required for the first time you attempt to run the driver)
    
3. Start the Service!
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699685449378/123367e8-c546-444c-b910-3df33e781f4f.png align="center")

Hmm... You'll notice after pressing the 'start Service' button all we get is the following `'operation completed successfully'` popup. Cool! But I mean... what if we **actually want to view what our code is doing and see the debug messages printed** to a console of sorts? For that, we'll have to enable ***Kernel Debugging.***

### Kernel Debugging

To enable kernel debugging, type in the following command on your Windows Testing Machine:

```bash
bcdedit /dbgsettings net hostip:<Developer Machine IP Address> port:50000

(example)
bcdedit /dbgsettings net hostip:10.0.2.4 port:50000
```

This command will allow our developer machine to remotely debug the kernel. Copy the key generated -- we will use this later:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699841713004/e8e7d64c-cfef-43a4-a815-7fdf7f1e8c13.png align="center")

**Turn off the firewall**

We will also need to turn off the firewall to prevent any network issues that could arise.

```plaintext
netsh advfirewall set allprofiles state off
```

**Windbg**

On the development machine, navigate to `C:\Program Files (x86)\Windows Kits\10\Debuggers\x64` , launch `windbg.exe` , and Select the `File > Kernel Debug...` option.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699837023374/bef67a20-40fb-450e-830f-f898c6c47641.png align="center")

On the screen prompted, Configure a `NET` Kernel Debug connection with the port + key we received from the testing machine.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699840909299/cb6d01a7-f8bf-48fe-8b01-39f9f3324626.png align="center")

Upon a successful connection, you should get the following! (*Note, I originally had problems getting mine to successfully connect. Play around and possibly restart the testing machine if issues arise. Some Common scenarios are:*

* *Ensure your developer IP address was correct*
    
* *Ensure that you built the project with* `Debug + x64` settings
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699839013620/8663f238-46c4-43fb-aecc-f49f27471243.png align="center")

When we 'Start' our driver within OSR Loader, we'll now see debug messages. Take note of the two screenshots and their corresponding outputs within windbg!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699840406832/dc320533-26d8-4204-8e95-70d76c244d0d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699840465592/68882a86-5d24-43aa-b442-c3dc0b14de0c.png align="center")

## Conclusion

We made it to the end folks. For the most part, the heavy lifting of this blog post was creating a virtual lab environment with the correct software + networking requirements. With this setup now accomplished we'll be able to more interesting kernel programming projects, (*for example, log to a file every time a file is opened on the system... and by who!)* I hope this post also introduced you to **KMDF Projects** and **Windbg**! We will absolutely be exploring these technologies in further detail in the next post. Till then, cheers!
