Development & Demo of Windows Kernel Driver
What is a driver?
As per Wikipedia, the definition of device driver is, “In computing, a device driver is a computer program that operates or controls a particular type of device that is attached to a computer or automaton”.
It means the driver is a kind of low-level software that provides an interface for information exchange between software, computer hardware, peripheral devices, humans, and combinations of these.
Types of drivers
In general, for majority of modern operating systems (OS), the driver is either of type Kernel mode or User mode.
The Kernel mode driver can run in highest privileged ring 0. It means the kernel driver mostly have highest level of permissions (like kernel) while executing. These drivers mostly preferred for low-latency networking, or communication with some hardware like GPU devices.
The User mode driver usually runs in least privileged ring 3. As a plus point, they can have more stability than kernel mode driver. So, poorly written user mode driver doesn’t crash the entire OS. But the frequent transitions between user-to-kernel modes may be expensive where it’s required to access low-level OS services.
There are some malicious drivers exist that threat actors develop & distribute to gain more control over the systems, so we should verify & install the drivers from trusted authors only.
What will we do?
In this post:
- We’ll write & build a kernel driver for Windows.
- We’ll install that driver & examine its autoloading behavior at start-up.
- We’ll explore the kernel driver’s install locations.
1. Write & Build a kernel driver for Windows
- To develop drivers in Windows, the requirements of the Windows Driver Kit (WDK) installation must be satisfied.
- In Visual Studio, select project ‘Kernel Mode Driver Framework, Empty (KMDF)’ option as:
- The driver's name must be of 32 characters or less as per Microsoft documentation. This length limit is defined ‘wdfglobals.h’ header. Provide ‘Project name’ (here used as Amey_KMDF) & click ‘Create’ button:
- Under project’s ‘Source Files’, we create a ‘Source.c’ file to write driver’s source code (as Microsoft recommends having .C file extension).
- Let’s start by including three important libraries first called ‘Ntddk.h’ (contains core Windows kernel definitions for all drivers), ‘Wdf.h’ (contains driver definitions based on the Windows Driver Framework (WDF)) & ‘ntstrsafe.h’ (to copy, concatenate, and format strings in a manner that prevents buffer overrun errors). Then we need to declare two callback functions & our own function:
- Just like Main() function, the first DriverEntry (line 6) is the common entry point for all drivers & Microsoft recommends keeping only this function name as exact same to make code analysis & debugging more helpful.
- The second callback function is named Amey_KMDFEvtDeviceAdd (line 9). It is recommended to have prefix of driver’s name Amey_KMDF to function EvtDeviceAdd so we declared name as Amey_KMDFEvtDeviceAdd. This is mostly to differentiate this function from other drivers’ functions.
- The function create_systemroot_file() (line 12) is our own function which will be called from Amey_KMDFEvtDeviceAdd callback.
- Let’s write definition for DriverEntry function:
- The DriverEntry() function takes DriverObject & RegistryPath as input parameters when invoked by the system. Also, there’s _In_ annotated before the parameters which indicates parameters only used as input to this function. The Microsoft source-code annotation language (SAL) provides a set of annotations that can be used to describe how a function uses its parameters, the assumptions that it makes about them, and the guarantees that it makes when it finishes.
- First, we print the debug message using RegistryPath so we can check the driver's registry location later (if printed within WinDbg). Then we created & initialized the configuration object for driver with Amey_KMDFEvtDeviceAdd callback event. In the end, we attempt creating a framework driver object with things we have so far, using WdfDriverCreate() function, it will return its status (NTSTATUS) as a result which we will return from DriverEntry() function as well.
- The function definition for Amey_KMDFEvtDeviceAdd would be:
- Inside Amey_KMDFEvtDeviceAdd, because we’ve not used the Driver parameter, it should be marked as unreferenced, mostly to suppress the compiler warning about parameter being unused in the function. Then we print another debug message to verify this callback being executed. The create_systemroot_file() function is our own function which we’ll define next. Then we created device object which the WdfDeviceCreate() function uses to create framework device object & return status as NTSTATUS.
- Now, the definition of create_systemroot_file() function is:
- First of all, we print the kernel debug message (lines 3 to 5) to verify that we’re in function definition. Then declared & assigned the file name to create (it will create the text file at “C:\Windows\Amey_KMDF_File.txt”), followed by initializing its object attributes using InitializeObjectAttributes() macro (from line 8 to 24).
- Afterwards, we create a file handle object (lines 26 to 46), followed by assigning fixed data string into character array buffer (lines 49 to 57), followed by calculating that data buffer size in bytes (line 62), then actually writing that data into file (lines 66 to 76) & finally closing file at the end of function (line 80).
- The fully functional & complete kernel driver code so far looks like this:
- Now we Build this driver in Visual Studio as shown below or using the shortcut <Ctrl> + B:
We see the success message once the driver built successfully:
- After successfully built, it generates three files (.cat is the driver’s security catalog, .inf is the driver’s setup information & .sys is the system file which contains the compiled driver code we wrote so far) as follows:
- We can install our kernel driver manually using “devcon.exe” application, which is available when installed Windows Driver Kit (WDK) & Windows Software Development Kit (Windows SDK), at path,
“C:\Program Files (x86)\Windows Kits\10\Tools\VERSION_DIR\x64\devcon.exe”. Where VERSION_DIR is the actual version number of tools we installed.
Now kernel driver is ready to be installed in system. Next, we’ll install & examine it’s autoloading behavior.
2. Installing driver & examine autoloading behavior
- To examine driver’s autoloading at system start-up or to see the flow of kernel debug print messages that we coded before, it’s required to have kernel debugging configured between the two systems. Those systems can be physical or virtual machines. Follow the official Microsoft guide to configure Windows kernel debugging.
- Once configured & connected WinDbg to kernel, we should enter the two commands in WinDbg to enable printing of debug messages & kernel load module events (third command ‘g’ is to resume & continue the execution of attached kernel):
- Once have these 3 driver files (.cat, .inf & .sys) along with devcon.exe, we enter below command in Command Prompt opened as Administrator:
- When installing this driver for first time, the Windows Security warning may pop up due to unverified publisher details. This is only to warn user against installing possibly malicious drivers from random sources. Since we developed this, just click on ‘Install this driver anyway’:
- In the WinDbg we see the debug messages & loaded module of driver:
- Obviously, the driver installation command was successful:
- While writing driver code we wrote a function called create_systemroot_file() to create & write text file at SystemRoot location “C:\Windows\Amey_KMDF_File.txt”. Let’s open & check that file:
- As a kernel driver, having highest permissions, it can easily manage system level things. That’s why installing any random drivers can lead to very serious issues. So, we should avoid that! 🙃
- Another interesting thing is Amey_KMDF_File.txt file will be created every time the system gets started. That’s because each time the kernel loads our driver, during callback the create_systemroot_file() function will also be called. To observe this, we delete this file then reboot/restart the system. Again, as before, we enabled WinDbg to see debug messages & loaded modules.
- Interesting to see how things get handled at kernel level, as it is the heart of operating system! 🙂
3. Exploring the kernel driver’s install locations
- After development & examining behavior, let’s quickly see where kernel drivers actually get installed in the system.
- The .sys files installed at “\SystemRoot\System32\drivers\” location (i.e., “C:\Windows\System32\drivers\”):
- Registry entries for this driver is located at:
“Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\”
- The ImagePath key points to the same location where .sys file of driver was installed. The regedit (Registry Editor) tool can be used to check this:
- To uninstall this driver, first run the (highlighted) command shown below using devcon.exe:
- After this, reboot/restart the system. Then make sure to remove .sys file from SystemRoot location & driver’s registry entries that we checked just before.
That it! We’ve gone through many things & also seen how kernel drivers can be so powerful or dangerous depending on what we install & how they’re developed…
Visit my GitHub repo for the source code of this kernel driver.
Feel free to leave comments, I’m curious to listen your experiences/opinions! Thank you for reading. Please share & follow for more upcoming writeups… 😇