A 'Hello World' Windows driver from scratch


One of the advantages of having no social life is that you get to spend long weekends catching up on technical topics that you otherwise wouldn't be able to :-). For this weekend, I decided to take a break from the usual feverish Popfly hacking and catch up on some stuff I've always wanted to dig into but never really found the time for.

On the top of my list was to go and learn how Windows really works in kernel mode (a pretty long way away from my daily managed code/JS hackery). I ordered a copy of Microsoft Windows Internals by the incomparable Mark Russinovich and David Solomon and after some re-reading (the last time I read it was over a year ago), set out to write my first device driver.

This is not the first time I've tried to do so and I've often forgotten how to get started . This post is mostly a reference for me to come and look up later.

Note: This really isn't a tutorial. I would strongly suggest that you read all of James Brown's tutorials which do a much better job of explaining this stuff.

Pre-requisites (all free downloads)

  • The latest Windows Driver Kit installed. I pulled a copy from one of our internal shares but I just found out that it is now a free download as well (you could only get it shipped to you in the past).

  • Microsoft Virtual PC. This is to let me test my driver without having to get another machine and finding the right cable to go between them (I'm just lazy). Modifying this tutorial to work with a real machine is pretty trivial.

  • Debugging Tools for Windows. Visual Studio cannot do kernel-mode debugging and hence you need to use the WinDBG/NTSD/CDB family of debuggers.

  • Windows XP/2003/Vista installed on VPC. In my case, I have a Win2k3 VPC but depending on which operating system you have, pick the right build environment in the 'Build your driver' section below.

Code for your driver

Create a directory where your driver sources will lie. For the following example, all my source files will lie in C:\testdrv

Create a file called test.c with the following contents. This is as simple a driver as you can get (in fact, it doesn't even know how to unload itself). When started, it prints a 'Hello World' message which any attached kernel debugger or DbgView will see.

#include <ntddk.h>     
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)    
{
    DbgPrint("Hello World\n");  
    return STATUS_SUCCESS;
}  

Create a file called makefile.def with the following line it it. This pulls in the appropriate makefile for your build environment (more on that later).

!INCLUDE $(NTMAKEENV)\makefile.def

Create a file called sources (note that it doesn't have any extension) with the following contents. This specifies which source files you are compiling, that it will build a driver and the headers and lib files to use.

TARGETNAME = test
TARGETPATH = obj
TARGETTYPE = DRIVER
INCLUDES = %BUILD%\inc
LIBS = %BUILD%\lib
SOURCES = test.c

Building your driverimage

  1. Click on Start->All Programs->Windows Driver Kits->WDK 6000->Build Environments->Windows Server 2003->Windows Server 2003 x86 Checked Build. I'm picking this since I have a 32-bit Win2k3 VPC - pick the one which matches the closest with your target machine. For example, if you have a physical Windows Vista x64 machine you want to try out your driver on, you would pick 'Windows Vista and Windows Server Longhorn x64 Checked Build Environment'.

This drops you into a command prompt with some nice environment variables (your build environment) already set for you.

  1. Change to the directory which has your driver source and run 'build'. This invokes build.exe , the de-facto Microsoft build tool for almost two decades until MSBuild came on the scene (and even now, a lot of product groups still use build.exe). Written by Steve Wood over 15 years ago, this is one tool that has withstood the test of time :-) 1

image

If you've followed along well, you should see a test.sys and a test.pdb (along with other files) in your object build directory.

Setting up your target machine for kernel debugging

  1. To attach a kernel debugger to a running instance of Windows, you need to do some minor configuration. In the target VPC Win2k3 instance, go to My Computer->Properties->Advance->Settings under Startup and Recovery->Edit. You should see a line like

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" /fastdetect /NoExecute=OptOut

Add "/debug /debugport=com1" to the line above (without the quotes).

If you're using Windows Vista as your target operating system, you can't use this technique - you'll have to use the inbuilt bcdedit.exe utility.

  1. In the virtual machine's settings, redirect the COM port to a pipe called \.\pipe\vpc . What we're doing here is telling Windows to redirect all debug spew to the COM1 port and then telling VPC to redirect COM1 to a named pipe.

image

  1. Restart the virtual machine (the boot settings need a restart to kick in).

  2. One final step - running the kernel debugger! Run windbg with the following command-line (if you already have symbols setup correctly, ignore the -y parameter).

    windbg-y SRVc:\symbolshttp://msdl.microsoft.com/download/symbols -k com:pipe,port=\.\pipe\vpc,resets=0,reconnect

This should open up windbg and connect it to your running instance of Win2k3 on VPC.

Registering and running your driver image

Whew. Just a couple of steps more and we'll be home. The last 2 things for us to do are to register the driver and then to run it. Our driver currently cannot be unloaded (the ability to do so would just require 4 more lines of code though) so you'll have to restart the machine manually to see new changes reflected.

  1. First, let's register the driver. James Brown's tutorial covers some of the ways you can register the driver but (like him), I'll recommend downloading the 'Driver Loader' utility from http://www.osronline.com which unfortunately requires a free registration. Run it on your target Win2k3 VPC

  2. Copy your drivers' binaries over to the VPC (I typically do this using the shared folder feature). Point Driver Loader to your binary and register your driver. This needs to be done only once irrespective of how many times you rebuild your binaries.

If you've followed along so far, congratulations for you're finally home. Hit 'Start Service' in 'Driver Loader'. This should result in a call to your DriverEntry method and in your WinDBG running on the host computer, you should see this

image

From here, it is pretty trivial to start exploring. Here's a screenshot of a breakpoint in my DriverEntry routine (the WRK stuff is because I'm running a custom Windows kernel built from the publicly available Windows Research Kernel code).

image

Happy hacking!

Like this? Get new essays in your inbox.