Hello all. I’ve been very busy this past week, so apologies for the slight delay of this post. Since we’ve already set up our environment for kernel development, it is finally time to create your first driver!
Obviously, you should have read part 1 of the Kernel Mode series. If not, do that. Otherwise it won’t work. So do it.
First thing you should know is that some things that are a given within a regular program are not as readily available within a driver’s context. Things like consoles, stdin/stdout/stderr, .exe’s, and exception handling aren’t easily achieved in driver writing. To be honest, writing drivers kinda sucks. If you mess up, you have to either wait for the BSOD within your VM to conclude or quickly restore from a snapshot.
Open up Visual Studio and click “Create a new project”. Next, scroll down (or search) until you find Empty WDM Driver. Create it and call it whatever you want. I called mine MyDriver.
Firstly, under the Solution Explorer tab, click “Driver Files”. And delete MyDriver.inf. There is no need for a setup information file in a simple “Hello, world!” sample.
I hope you installed the WDK, because you need to firstly #include <ntddk.h>
.
You know that main()
function? Drivers are funny and have a different entrypoint, which is DriverEntry()
. Here is what its signature looks like.
#include <ntddk.h>
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
}
But there is one more thing to do here. DriverEntry requires extern "C"
so that the linker can find the function. Now, the function should look like so.
#include <ntddk.h>
extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
}
Another pickiness about drivers. During compilation, warnings are treated as errors by default, and unused parameters will spew a warning. Fortunately, there is a macro to aid with this.
#include <ntddk.h>
extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
}
Now, this program will be able to be compiled. It doesn’t do anything, though, so let’s make it say “Hello, World!” like all of your first programs.
But wait, there isn’t a console to print to. So, how do you print out “Hello World!”? There lie functions that send debug functions to the kernel debugger. This is how you debug print within a driver. So, let’s use one.
DbgPrint(Ex) is akin to printf. However, it cannot format floating point specifiers. We don’t have to worry about that issue for now, though.
I’m not sure why, but only DbgPrintEx works for me, whereas DbgPrint doesn’t work at all.
#include <ntddk.h>
extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrintEx(0, 0, "Hello, World!\n");
return 0;
}
Now, you can compile the driver.
Once the driver is compiled, open up your Windows VM and attach your debugger to its kernel. You can also use a program like DbgView since this is such a basic program. Future drivers will require a kernel debugger, though (especially if you want to debug them, duh). Don’t forget to continue if you break in entry.
I just dragged the MyDriver folder to my VM’s desktop.
Next, you will want to “install” the driver. This is done by using Windows’ Service Control Manager. I will use the words driver and service interchangeably at this point. Open up a command prompt as Administrator, then enter this command:
sc create <yourdrivernamewithout.sys> type= kernel binPath= C:\Path\to\your\driver.sys
Yes, you need the space after the = sign. I would avoid having a driver name with spaces in it, quotation marks might allow it though. Dunno.
Mine is this:
sc create MyDriver type= kernel binPath= C:\Users\scag\Desktop\MyDriver\x64\Debug\MyDriver.sys
You should get a response that says “[SC] CreateService SUCCESS”. If not, you are either not in test signing mode or your file path is wrong.
You can verify your driver/service’s existence by checking the registry at HKLM\SYSTEM\ControlSet001\Services\<yourdrivernamewithout.sys>\
.
Next, you can start the service with:
sc start <yourdrivernamewithoutthe.sys>
Make sure you are debugging or have DbgView open capturing kernel messages.
Your driver was initialized and DriverMain was called, presenting you with your debug print.
Let’s add one more thing. Pull your DriverMain function back up. You can emplace an unload function in your DriverObject. It is a struct and holds the member DriverUnload, which can be assigned to a function. This is what will be called when your service is stopped.
#include <ntddk.h>
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrintEx(0, 0, "Goodbye, world!\n");
}
extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrintEx(0, 0, "Hello, world!\n");
DriverObject->DriverUnload = DriverUnload;
return 0;
}
Recompile and replace the .sys file or folder on your VM. Service control is smart enough to use the new file when the service is asked to start, and your changes will remain. Enter sc start <yourdrivername>
and then sc stop <yourdrivername>
. Next, check your debug view.
That’s it for this post. If you’re feeling adventurous, try and explore what information you can get from the kernel and debug print it out as an exercise.
Have a great weekend! Next week will be another paid post as there was a double free post the last 2 weeks and as such I will balance them out.
Go!
-BowTiedCrawfish