Drivers ( Kernel Mode )

To begin, this article / posts is merely to help beginners or people who has no idea what drivers are. If you know how to make a simple driver, you most likely don’t need to spend the time reading this post. For the rest, happy reading. I also do not encourage or advice people to create rootkits, bootkits or just using this knowledge and information for ill intents. This is just to educate people who have little to no idea about drivers. I am not responsible for any damages caused by misused or abuse. Always use a virtual machine to test and load your driver, this will get rid of your main PC from being damaged, i am also no responsible for any damages caused from drivers.

What Are Drivers

To start off, one major difference between Drivers and normal programs is that Drivers cannot display any types of overlay or user interfaces. To have a overlay, you will need to make your own overlay in usermode and communicate with the driver . Drivers also cannot be loaded if the user isnt admin and if the driver doesnt have a cert ( test mode is the only way or disable patch guard ). The other thing about drivers is that, if it crashes, thats it, your PC will crash with it. Drivers also ran in Kernel mode rather then usermode like regular programs, giving access to kernel APIs. Drivers also have full access to the system, meaning you can do pretty much anything with no-one really stopping you. Anti Viruses does have measures against Rottkits ( viruses in the form of drivers ), Windows also have implemented a feature called DSE which i will talk about later on. Cool features that Drivers can do is hiding processes from pretty much everything, Drivers also can monitor callbacks such as creating Handles and so on. Driver also have full access to pretty much everything

What Is PatchGuard & DSE

Patch Guard was developed by windows themselves. They made this to avoid advanced rootkits. Patch Guard Avoids Kernel Drivers Modifying system service tables, Modifying the interrupt descriptor table, Modifying the global descriptor table, Using kernel stacks not allocated by the kernel and Modifying or patching code contained within the kernel itself, or the HAL or NDIS kernel libraries. Doing any of this will BSOD your PC and return a CRITICAL_STRUCTURE_CORRUPTION. This feature was implemented on vista x64 and above ( 64 bit PCs only ). DSE basically does not allow you to load any drivers which doesnt have a Driver Cert. There are programs to get rid of this feature ( https://github.com/hfiref0x/DSEFix ). Note to self that x32 bit OS does not have this “feature”. There are ways to even bypass this by using existing signed drivers such as Capcom or even just signing your own drivers.

Coding A Driver

Well, everyone who is willing to put in effort and such are able to make a Driver. Its not as hard to make a simple Driver. Advanced Drivers do require alot of experience, but the only way to learn is to test and try. Failure will always catch up and in those times, dont give up. I almost gave up afew times when making my own Drivers, its not easy but if you are willing to put in the effort and such, anything is possible. The only reason why i am here is because of people such as daax, wlan and such helping me out. Other then that, lets start!

What You Need

  • MicroSoft Visual Studio
  • Windows Driver Kit
  • C++ Knowledge

Driver’s Main

Drivers also has a Main function like every other program, but its slightly different i would say. Driver’s Main Function is called “DriverEntry”, this function will be basically your entry point of your driver.

#include <>
#include <>
DRIVER_INITIALIZE DriverEntry;
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
      return STATUS_SUCCESS;
}

Identifying The Driver:

Drivers also need a SymbolName and a Name. This can easily done by afew lines of code. By using IoCreateDevice and IoCreateSymbolicLink.

UNICODE_STRING DriverName, SymbolName;
PDEVICE_OBJECT pDeviceObj;
RtlInitUnicodeString(&DriverName, L"\Device\Driver1"); // Giving the driver a name
RtlInitUnicodeString(&SymbolName, L"\DosDevices\Driver1"); // Giving the driver a symbol
UNICODE_STRING deviceNameUnicodeString, deviceSymLinkUnicodeString;
NTSTATUS NtRet2 = IoCreateDevice(pDriverObject, 0, &DriverName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObj);
pDeviceObject->Flags |= DO_DIRECT_IO;
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

Identifying The Driver:

You will need a function for Unloading. Without This function, the Driver will have no way to unload, which would be quite annoying. We will be using two simple fucntions which just deletes the Driver’s Symbol Name and The Driver Itself.

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
  IoDeleteSymbolicLink(&SymbolName);
  IoDeleteDevice(pDriverObject->DeviceObject);
}

Now once this is done, you will have to tell the Driver what the Unload function is.

pDriverObject->DriverUnload = DriverUnload;

his would be basically called when unloading the driver. Communication With Usermode Program: To start off, we will need a Create Call and a Close Call Functions, these functions will not do much for now, but its a good practice to always do it.

NTSTATUS CreateCall(PDEVICE_OBJECT DeviceObject, PIRP irp)
{
  irp->IoStatus.Status = STATUS_SUCCESS;
  irp->IoStatus.Information = 0;

  IoCompleteRequest(irp, IO_NO_INCREMENT);
  return STATUS_SUCCESS;
}

NTSTATUS CloseCall(PDEVICE_OBJECT DeviceObject, PIRP irp)
{
  irp->IoStatus.Status = STATUS_SUCCESS;
  irp->IoStatus.Information = 0;

  IoCompleteRequest(irp, IO_NO_INCREMENT);
  return STATUS_SUCCESS;
}

Now after that is done, we need to somewhat tell the driver these are our close and create functions.

pDriverObject->MajorFunction[IRP_MJ_CREATE] = CreateCall;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseCall;

Alrite, now moving on to the main function to communicate with the driver and back. Lets make a function for it and lets name it “IoControl”. This will control all inputs and outputs. Lets make it empty for now and tell the Driver that this is our Input and Output Control functions.

NTSTATUS IoControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
  NTSTATUS Status = STATUS_SUCCESS;
  return Status;
}
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControl;

Alrite once this is done, lets move on to filling in the IOControl. We will be basically using a Code for each communication. Lets say, you want the Driver to write some part of memory, then you can use a code which will be sent to the Driver and the Driver would know what you want. So if i set the Write Request code to 0x0814, then in the usermode i will have to use that code if i wanted the Driver to write. Same for Read and so on. Firstly lets define the codes

// Write 0x812
// Read 0x813

// Request Code To Read
#define IO_READ_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)

// Request Code To Write
#define IO_WRITE_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)

One more note, you can change the code if you like. I will just use 0x812 and 0x813 for this example. Alrite once that is done, lets store the received code in a variable for the Driver to check the request and take actions.

PIO_STACK_LOCATION StackLocation= IoGetCurrentIrpStackLocation(Irp);
// Code received from user space
ULONG CodeRequest = StackLocation->Parameters.DeviceIoControl.IoControlCode;

This will store the Code received from usermode. Lets setup the Requestions And Actions, this is quite easy, just setting up if and else statements.

if (ControlCode == IO_READ_REQUEST)
{
  Status = STATUS_SUCCESS;
}
else if (ControlCode == IO_WRITE_REQUEST)
{
  Status = STATUS_SUCCESS;
}

Once this is setup, Lets move on to the Usermode program to setup their communication. To start off, lets get a handle to the Driver.

HANDLE hDriver = CreateFileA("\.\Driver1", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);

Other thing you can do is checking if the Handle is valid or not and if its NULL then doing something about it. You also need to Define the request codes in the usermode program, this is also quite easy.

// Write 0x812
// Read 0x813

// Request Code To Read
#define IO_READ_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)

// Request Code To Write
#define IO_WRITE_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)

Once done, you can pretty much send requests and so on. The way you can send information from usermode is by using a function called DeviceIoControl. Its simple as this

DeviceIoControl(hDriver, IO_READ_REQUEST, &ReadRequest,
sizeof(ReadRequest), &ReturnBytes, sizeof(ReadRequest), 0, 0)

You can make afew other request such as Process ID, where you can send the driver the game’s process ID as there is not documented way of getting a game’s processID using the name. Unless you loop through the entire Process list and find the name you are looking for.

Debugging

This is one of the worst moments i went through with this, just because i dont really have a spare PC to use for testing. Nevertheless there are ways to debug with only one PC.

Alrite, lets start off by saying that when you are trying to see whats wrong with your Driver or why is it causing BSOD, the minidump file is important. A minidump is created everytime you get a BSOD. It is usually located under C:WindowsMinidump. You might ask, well how do i open these files. You will need a program called WinDbg. This program allows you to debug Drivers and view the minidumps. This can be helpful as you will know where the problem is. You are able to view the problem in detail online. The common problem people have are PAGE FAULT IN NON PAGE AREA and CRITICAL_STRUCTURE_CORRUPTION. CRITICAL_STRUCTURE_CORRUPTION is caused by Patch Guard most of the time and PAGE FAULT IN NON PAGE AREA is when you screw up your Unload.

Other ways to debug your Driver is by using DbgPrintEx or DbgPrint. This together with DebugView can help you out. This will not work if you Driver just gives you BSOD, you will need to use the method above, this method is for people who dont really understand what is happening inside their Driver. You can use this method to view what your variables are storing or what some of the function returns.

The last method you can use is downloading a VMWare or a virtual machine. You can setup your virtual machine, by downloading softwares like VMWare VMBox and other few softwares. From there, you can use WinDbg and setup a debugger there.

Signing Your Driver & How It Works

To start off, why sign your driver? Well, this is quite simple, the reason why people sign their Drivers is mainly to just allow them to normally load their Driver instead of going to Test Mode, disabling DSE or abusing other Drivers. Signing a Driver will also add your Driver to the “Trusted” list. This would basically would make Windows trust your Driver by letting it load normally, plus this will somewhat ensure your customers or such that they are dealing with trusted people.

Signing your Driver isn’t cheap or even free to start off. It would cost afew hundreds of dollars per year. This plus with other downsides. Paying the price is usually the most reason why people would just not bother signing their Driver and once an Anti Cheat finds that you are making a Cheat using that certificate, then oh boi, you would usually be screwed.

How signing works is that, Microsoft or whoever you bought it from, will send you a “Certificate”. This “Certificate” is fully digital, i have seen companies shipping hardware tokens, which are just basically things that hold your digital certificate and a code ( Private key ).

Next thing you will need to download some tools online to help you with signing your Driver is the Windows Driver’s Kit. Make sure to make signtool and makecat as well, its a command line. ( https://docs.microsoft.com/en-us/dotnet/framework/tools/signtool-exe )

You will need the Microsoft-issued cross-certificate.

Signing The Driver

The way you sign the Driver is extremely easy, all you need to use is the developer console for visual studio and basically using one simple command line, called signtool. Open up your developer console and basically type this line of code. As simple as that.

signtool.exe sign /ac digitalcertificate.cer /sha1
ca40385b9971ab32d1ad4c93225688dbc168c867 sacdriver.sys

Well, let me explain what this exactly is. Firstly, the digitalcertificate.cer is the cross certificate that you got, it would be different. Secondly the “sha1” basically tells signtool to find the correct certificate and not use the wrong one, this would be different for everyone. Thirdly, sacdriver.sys, is just the Driver you would like to sign. You are also allowed to give a short description of your Driver, for me i just added “Sagaan’s Anti-Cheat”.

signtool.exe sign /ac digitalcertificate.cer /sha1
ca40385b9971ab32d1ad4c93225688dbc168c867 /t
"http://timestamp.verisign.com/scripts/timestamp.dll" /d
"Anti Cheat" driver.sys

One thought on “Drivers ( Kernel Mode )

Leave a Reply