As said above, major functions are set to handle any common events for a driver, such as a child IO Device of the driver being open or written to. So lets setup some basic major functions. First open your SOURCES file, and add majorFunctions.c. Then create a new header file, call it majorFunctions.h.
majorFunctions.h will contain the following:
[highlight=cpp]
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
[/highlight]And the correspond source file:
[highlight=cpp]
#include "majorFunctions.h"
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Handle to IO Device has been opened.");
return NtStatus;
}
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Handle to IO Device has been closed.");
return NtStatus;
}
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIoStackIrp = NULL;
PCHAR pInBuffer = NULL;
DbgPrint("IO Device has been written to.");
pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
if(pIoStackIrp)
{
pInBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
if(pInBuffer)
{
DbgPrint("Processing Message: SZ:%u, STR:%s", pIoStackIrp->Parameters.Write.Length, pInBuffer);
//Later in this tutorial, we'll be writing code to enforce null-terminated strings
}
else
{
DbgPrint("Write called with null buffer pointer.");
}
}
else
{
DbgPrint("Invalid IRP stack pointer..");
}
return NtStatus;
}
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Unsupported Majour Function Requested. Returning STATUS_SUCCESS");
return NtStatus;
}
[/highlight]Major functions have two parameters passed to them when they're called, the first has already been explained, the second is an IRP structure describing the recived message.
The only method here that isn't self-explanatory is Buffered_Write, and I will now go over it.
[highlight=cpp]
NTSTATUS NtStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIoStackIrp = NULL;
PCHAR pInBuffer = NULL;
[/highlight]IRP stands for Io Request Packet, and when write is called, there is an IRP placed on the Driver's stack, this IRP structure will contain more information about the message that has been sent to us. There are some pretty advanced sides to this structure, we won't need to go to deep into it though.
[highlight=cpp]pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);[/highlight]This will set our pIoStackIrp pointer to a pointer to the most recent IRP on the stack. The first parameter is the IRP passed to our write event handler. The difference between this IRP and the one passed to our even handler, is that this will contain parameters on the request, Ex buffer length.
[highlight=cpp]
pInBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;[/highlight]Contains a pointer to the recived buffer written to our IO Device. Note this returned string may not be null-terminated, and if it isn't, and it's passed to calls that read it as a character array, it will continue to read until is has encountered a null byte. This null byte might not be in memory with read access, which would throw an exception when read, and ultimatly a cause BSoD.
[highlight=cpp]pIoStackIrp->Parameters.Write.Length [/highlight]This is an example of one of the parameters the stack IRP will contain. This will contain the length of our recived buffer. We'll be using this at later time to enforce null terminated strings.
Also notice how the event handler has various if's to catch null pointers. You should know that reading from a null pointer will throw an exception and cause a BSoD.
So now lets setup the major functions.
Your entry.h header file should now contain:
[highlight=cpp]
#ifndef ENTRY_H_
#define ENTRY_H_
#include "ntddk.h"
#include "majorFunctions.h"
void OnUnload(IN PDRIVER_OBJECT driverObject);
#endif
[/highlight]And the source file:
[highlight=cpp]
#include "entry.h"
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING regPath)
{
unsigned int i;
driverObject->DriverUnload = OnUnload;
for(i=0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
driverObject -> MajorFunction[i] = Io_Unsupported;
driverObject->MajorFunction[IRP_MJ_CREATE] = Create_DeviceIo;
driverObject->MajorFunction[IRP_MJ_CLOSE] = Close_DeviceIo;
driverObject->MajorFunction[IRP_MJ_WRITE] = Buffered_Write;
DbgPrint("My Driver loaded.");
return STATUS_SUCCESS;
}
VOID OnUnload(IN PDRIVER_OBJECT driverObject)
{
DbgPrint("My Driver unloaded.");
}
[/highlight]IRP_MJ_MAXIMUM_FUNCTION is defined when ntddk.h is included. It is the maximum major IRP methods in the MajorFunction array. IRP_MJ_CREATE, IRP_MJ_CLOSE, etc are defined indexes in the array. Whenever a major event occurs, this array is looked at to locate the corresponding handler.We first set the whole array to point to our method that handles unsupported events, then we set particular indexes to their corresponding handler. This will ensure everything is handled.
Now we can go ahead and create our IO-Device and symbolic link to it.
An Io device is a device created with the IoCreateDevice API that can be open, and read\written to. A symbolic link pretty much provides another way to access the device. Think of the symbolic link as the domain name, and the actual device as the IP Address in terms of access.
We're first going to create a method that will initilize all of our unicode strings, for methods that require unicode strings to be passed to them. In this case, IoCreateDevice requires a unicode string to be passed to it.
Open your entry header file, and add
int IniIoDevice();
[highlight=cpp]
#ifndef ENTRY_H_
#define ENTRY_H_
#include "ntddk.h"
#include "majorFunctions.h"
void OnUnload(IN PDRIVER_OBJECT driverObject);
int InitUnicodeStrings();
#endif
[/highlight]Now in entry.c, we're going to define a couple of wide-character string and UNICODE_STRING structures to be initilized, as well as create our InitUnicodeStrings function.
[highlight=cpp]
#include "entry.h"
//Define constant unicode strings, which are used to initlize their corresponding UNICODE_STRING structures.
//Name of the IO Device to be created
const WCHAR deviceName[] = L"\\Device\\MYDRIVER";
//Name of symbolic link to be created
const WCHAR deviceSymbolicLink[] = L"\\DosDevices\\MYDRIVER";
//Unicode structures, which are to be initilized using their corresponding constant character array.
UNICODE_STRING unicodeDeviceNameBuffer;
UNICODE_STRING unicodeSymLinkBuffer;
//Struct to hold information about our IO Device. More on this later in the tutorial.
PDEVICE_OBJECT g_pDeviceOvject = 0;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING regPath)
{
unsigned int i;
driverObject->DriverUnload = OnUnload;
for(i=0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
driverObject -> MajorFunction[i] = Io_Unsupported;
driverObject->MajorFunction[IRP_MJ_CREATE] = Create_DeviceIo;
driverObject->MajorFunction[IRP_MJ_CLOSE] = Close_DeviceIo;
driverObject->MajorFunction[IRP_MJ_WRITE] = Buffered_Write;
if(!InitUnicodeStrings())
return STATUS_UNSUCCESSFUL; //report the driver could not be loaded.
DbgPrint("My Driver loaded.");
return STATUS_SUCCESS;
}
int InitUnicodeStrings()
{
//Initilize unicode structures.
RtlInitUnicodeString(&unicodeDeviceNameBuffer, deviceName);
RtlInitUnicodeString(&unicodeSymLinkBuffer, deviceSymbolicLink);
return 1;
}
VOID OnUnload(IN PDRIVER_OBJECT driverObject)
{
DbgPrint("My Driver unloaded.");
}
[/highlight]Read the comment I've placed in the code above.
RtlInitUnicodeString initializes the UNICODE_STRING structure, passed to it's first parameter, to the null-terminated string passed in the second parameter. More information on this API can be found at MSDN.
Next, go back to the header and create a new method header, call it:
int SetupIoDevice(PDRIVER_OBJECT pDriverObject);
In the source file, we will use the corresponding code:
[highlight=cpp]
int SetupIoDevice(PDRIVER_OBJECT pDriverObject)
{
int ret;
DbgPrint("Creating I\\O device under name: %ws", unicodeDeviceNameBuffer.Buffer);
ret = IoCreateDevice(pDriverObject,
0,
&unicodeDeviceNameBuffer,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&g_pDeviceObject);
g_pDeviceObject->Flags |= DO_BUFFERED_IO;
g_pDeviceObject->Flags &= (~DO_DEVICE_INITIALIZING);
if(ret != STATUS_SUCCESS)
{
DbgPrint("Error Creating Device");
return -1;
}
DbgPrint("Device Created");
ret = IoCreateSymbolicLink(&unicodeSymLinkBuffer, &unicodeDeviceNameBuffer);
if(ret != STATUS_SUCCESS)
{
DbgPrint("error creating symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, deviceSymbolicLink);
return -1;
}
DbgPrint("Created symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, unicodeSymLinkBuffer.Buffer);
return 1;
}
[/highlight]This is a lot simpler then it looks. First we make a call to IoCreateDevice, and provide the according parameters. Which are defined at MSDN. But hell, I'll relay them to you.
[highlight=cpp]
NTSTATUS
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject
);[/highlight]The first parameter is a pointer to your DRIVER_OBJECT struct. You pass this through the first parameter of the SetupIoDevice function you created, the second defines the size of the device extensions. We aren't going to have any so we can simply set this to 0. The third is the device name, we pass a pointer to our corresponding initilized UNICODE_STRING structure here, the device type we're going to set to FILE_DEVICE_UNKNOWN, or if you can find a device type appropriate for your driver here:
Specifying Device Types go ahead and use that. For the fourth, as I had said, we aren't using any extentions, we can ignore this parameter and set it to 0. The exclusive boolean can be set to true to allow only one open handle to the device at a time. If it it set to false, it will allow multiple handles open to the device. The last parameter is a pointer to a DEVICE_OBJECT structure to retrive information about the created device. When you want to delete an IO Device, you'll need to pass this struct. We're going to store it in a global variable called g_pDeviceObject.
After that we check to see if the device was created successfully, if it isn't, we return -1, otherwise we move on to setting the flags in our newly create device object.
There are various types of device IO, we're going to be using buffered IO in this tutorial, and thus we're going to
OR in
DO_BUFFERED_IO Which is defined when ntddk.h is included. This tells the IO device we're going to be using a buffered IO method. Then we're going to unset the DEVICE_INTERNALIZING flag. Which is set when the device is first created. We do this by ANDing in it's opposite. If the device's initializing flag remains set, we will not be able to access the device.
Then we create a symbolic link to the device using the IoCreateSymbolicLink API.
The first parameter it takes is a pointer to an initialized UNICODE_STRING structure describing the name of the symbolic link, the second is the name of the device it links to.
[highlight=cpp]
#ifndef MAJORFUNCTIONS_H_
#define MAJORFUNCTIONS_H_
#include "ntddk.h"
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
#endif
[/highlight]
[highlight=cpp]
#include "entry.h"
const WCHAR deviceName[] = L"\\Device\\MYDRIVER";
const WCHAR deviceSymbolicLink[] = L"\\DosDevices\\MYDRIVER";
UNICODE_STRING unicodeDeviceNameBuffer;
UNICODE_STRING unicodeSymLinkBuffer;
PDEVICE_OBJECT g_pDeviceObject = 0;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING regPath)
{
unsigned int i;
driverObject->DriverUnload = OnUnload;
for(i=0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
driverObject -> MajorFunction[i] = Io_Unsupported;
driverObject->MajorFunction[IRP_MJ_CREATE] = Create_DeviceIo;
driverObject->MajorFunction[IRP_MJ_CLOSE] = Close_DeviceIo;
driverObject->MajorFunction[IRP_MJ_WRITE] = Buffered_Write;
if(!InitUnicodeStrings())
return STATUS_UNSUCCESSFUL; //report the driver could not be loaded.
SetupIoDevice(driverObject);
DbgPrint("My Driver loaded.");
return STATUS_SUCCESS;
}
int InitUnicodeStrings()
{
RtlInitUnicodeString(&unicodeDeviceNameBuffer, deviceName);
RtlInitUnicodeString(&unicodeSymLinkBuffer, deviceSymbolicLink);
return 1;
}
int SetupIoDevice(PDRIVER_OBJECT pDriverObject)
{
int ret;
DbgPrint("Creating I\\O device under name: %ws", unicodeDeviceNameBuffer.Buffer);
ret = IoCreateDevice(pDriverObject,
0,
&unicodeDeviceNameBuffer,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&g_pDeviceObject);
g_pDeviceObject->Flags |= DO_BUFFERED_IO;
g_pDeviceObject->Flags &= (~DO_DEVICE_INITIALIZING);
if(ret != STATUS_SUCCESS)
{
DbgPrint("Error Creating Device");
return -1;
}
DbgPrint("Device Created");
ret = IoCreateSymbolicLink(&unicodeSymLinkBuffer, &unicodeDeviceNameBuffer);
if(ret != STATUS_SUCCESS)
{
DbgPrint("error creating symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, deviceSymbolicLink);
return -1;
}
DbgPrint("Created symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, unicodeSymLinkBuffer.Buffer);
return 1;
}
void OnUnload(IN PDRIVER_OBJECT driverObject)
{
if(g_pDeviceObject)
{
IoDeleteDevice(g_pDeviceObject);
IoDeleteSymbolicLink(&unicodeSymLinkBuffer);
DbgPrint("Deleted IO Device and symbolic link.");
}
DbgPrint("My Driver unloaded.");
}[/highlight]
Notice the bolded text, where I added a call to SetupIoDevice, and where I added the cleanup code for our IO Device.
After that's done, we're nearly ready to test the driver. First, we need to enforce null-terminated strings in our write event. Create a new source in your driver working folder, call it stringTools.h.
[highlight=cpp]
#ifndef STRINGTOOLS_H_
#define STRINGTOOLS_H_
int forceNullTermination(char* string, unsigned int len);
#endif
[/highlight]And the corresponding source:
[highlight=cpp]
#include "stringTools.h"
int forceNullTermination(char* string, unsigned int len)
{
if(string[len] != 0)
string[len] = 0;
return 1;
}
[/highlight]That's pretty simple and doesn't need any explaining.
Now go back to majorFunctions.c and change it accordingly.
[highlight=cpp]
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIoStackIrp = NULL;
PCHAR pInBuffer = NULL;
DbgPrint("IO Device has been written to.");
pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
if(pIoStackIrp)
{
pInBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
if(pInBuffer)
{
forceNullTermination(pInBuffer, pIoStackIrp->Parameters.Write.Length);
DbgPrint("Processing Message: SZ:%u, STR:%s", pIoStackIrp->Parameters.Write.Length, pInBuffer);
}
else
{
DbgPrint("Write called with null buffer pointer.");
}
}
else
{
DbgPrint("Invalid IRP stack pointer..");
}
return NtStatus;
}
[/highlight]As well as the header:
[highlight=cpp]
#ifndef MAJORFUNCTIONS_H_
#define MAJORFUNCTIONS_H_
#include "ntddk.h"
#include "stringTools.h"
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
#endif[/highlight]And now you're read to build your driver, with basic I\O functionality. Here's the source
(refer to download for valid source files, parent tags might modify code)
MAKEFILE
[highlight=cpp]
!INCLUDE $(NTMAKEENV)\makefile.def
[/highlight]SOURCES
[highlight=cpp]
TARGETNAME=MYDRIVER
TARGETPATH=OUTPUT
TARGETTYPE=DRIVER
TARGETLIBS= $(BASEDIR)\lib\w2k\i386\ndis.lib
SOURCES= entry.c majorFunctions.c stringTools.c
[/highlight]entry.h
[highlight=cpp]
#ifndef ENTRY_H_
#define ENTRY_H_
#include "ntddk.h"
#include "majorFunctions.h"
void OnUnload(IN PDRIVER_OBJECT driverObject);
int InitUnicodeStrings();
int SetupIoDevice();
#endif
[/highlight]entry.c
[highlight=cpp]
#include "entry.h"
const WCHAR deviceName[] = L"\\Device\\MYDRIVER";
const WCHAR deviceSymbolicLink[] = L"\\DosDevices\\MYDRIVER";
UNICODE_STRING unicodeDeviceNameBuffer;
UNICODE_STRING unicodeSymLinkBuffer;
PDEVICE_OBJECT g_pDeviceObject = 0;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING regPath)
{
unsigned int i;
driverObject->DriverUnload = OnUnload;
for(i=0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
driverObject -> MajorFunction[i] = Io_Unsupported;
driverObject->MajorFunction[IRP_MJ_CREATE] = Create_DeviceIo;
driverObject->MajorFunction[IRP_MJ_CLOSE] = Close_DeviceIo;
driverObject->MajorFunction[IRP_MJ_WRITE] = Buffered_Write;
if(!InitUnicodeStrings())
return STATUS_UNSUCCESSFUL; //report the driver could not be loaded.
SetupIoDevice(driverObject);
DbgPrint("My Driver loaded.");
return STATUS_SUCCESS;
}
int InitUnicodeStrings()
{
RtlInitUnicodeString(&unicodeDeviceNameBuffer, deviceName);
RtlInitUnicodeString(&unicodeSymLinkBuffer, deviceSymbolicLink);
return 1;
}
int SetupIoDevice(PDRIVER_OBJECT pDriverObject)
{
int ret;
DbgPrint("Creating I\\O device under name: %ws", unicodeDeviceNameBuffer.Buffer);
ret = IoCreateDevice(pDriverObject,
0,
&unicodeDeviceNameBuffer,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&g_pDeviceObject);
g_pDeviceObject->Flags |= DO_BUFFERED_IO;
g_pDeviceObject->Flags &= (~DO_DEVICE_INITIALIZING);
if(ret != STATUS_SUCCESS)
{
DbgPrint("Error Creating Device");
return -1;
}
DbgPrint("Device Created");
ret = IoCreateSymbolicLink(&unicodeSymLinkBuffer, &unicodeDeviceNameBuffer);
if(ret != STATUS_SUCCESS)
{
DbgPrint("error creating symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, deviceSymbolicLink);
return -1;
}
DbgPrint("Created symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, unicodeSymLinkBuffer.Buffer);
return 1;
}
VOID OnUnload(IN PDRIVER_OBJECT driverObject)
{
if(g_pDeviceObject)
{
IoDeleteDevice(g_pDeviceObject);
IoDeleteSymbolicLink(&unicodeSymLinkBuffer);
DbgPrint("Deleted IO Device and symbolic link.");
}
DbgPrint("My Driver unloaded.");
}[/highlight]majorFunctions.h
[highlight=cpp]
#ifndef MAJORFUNCTIONS_H_
#define MAJORFUNCTIONS_H_
#include "ntddk.h"
#include "stringTools.h"
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
#endif
[/highlight]majorFunctions.c
[highlight=cpp]
#include "majorFunctions.h"
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Handle to IO Device has been opened.");
return NtStatus;
}
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Handle to IO Device has been closed.");
return NtStatus;
}
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIoStackIrp = NULL;
PCHAR pInBuffer = NULL;
DbgPrint("IO Device has been written to.");
pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
if(pIoStackIrp)
{
pInBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
if(pInBuffer)
{
forceNullTermination(pInBuffer, pIoStackIrp->Parameters.Write.Length);
DbgPrint("Processing Message: SZ:%u, STR:%s", pIoStackIrp->Parameters.Write.Length, pInBuffer);
}
else
{
DbgPrint("Write called with null buffer pointer.");
}
}
else
{
DbgPrint("Invalid IRP stack pointer..");
}
return NtStatus;
}
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Unsupported Majour Function Requested. Returning STATUS_SUCCESS");
return NtStatus;
}
[/highlight]stringTools.h
[highlight=cpp]
#ifndef STRINGTOOLS_H_
#define STRINGTOOLS_H_
int forceNullTermination(char* string, unsigned int len);
#endif
[/highlight]stringTools.c
[highlight=cpp]
#include "stringTools.h"
int forceNullTermination(char* string, unsigned int len)
{
if(string[len] != 0)
string[len] = 0;
return 1;
}
[/highlight]So lets test this!
1. Open DebugView.
2. Filter out any annoying messages that aren't from your driver.
3. Copy your compile console application into the same directory as your compiled driver.
4. Write in the console:
ss MYDRIVER.sys
start MYDRIVER
open \\.\MYDRIVER
write haiThyur
close
stop
ds MYDRIVER
You should see some relevant messages in your DebugView console.
Here's what I got:
Creating I\O device under name: \Device\MYDRIVER
Device Created
Created symbolic link to \Device\MYDRIVER, named: \DosDevices\MYDRIVER
My Driver loaded.
Handle to IO Device has been opened.
IO Device has been written to.
Processing Message: SZ:4, STR:aaa
IO Device has been written to.
Processing Message: SZ:19, STR:aaaaaaaaaaaaaaaaaa
Unsupported Majour Function Requested. Returning STATUS_SUCCESS
Handle to IO Device has been closed.
If all that worked out, you're ready start programming and designing your own messaging protocol.
Creating A Message Protocol
Our messaging protocol is going to be pretty simple. It will be as displayed below.
command\param1\param2\param3
Before we start building the message processing parts of code, we need to build some methods we can use to do basic operations to our strings. Because we aren't provided with the standard C runtime libraries, we have to create our own methods. In this case, we need to create our own zeroMemory. So we'll start by now writing zeroMemory. Create a new header, call it memory.h.
[highlight=cpp]
#ifndef MEMORY_H_
#define MEMORY_H_
void zeroMemory(void* loc, unsigned long size);
#endif
[/highlight]And the corresponding source file:
[highlight=cpp]
#include "memory.h"
void zeroMemory(char* loc, unsigned long size)
{
unsigned long i = 0;
for(i = 0; i < size; i++)
loc[i] = 0;
}
[/highlight]And now lets move on to GetParam, and other important string related methods I've written for you. Open up stringTools.h
[highlight=cpp]
#ifndef STRINGTOOLS_H_
#define STRINGTOOLS_H_
#include "memory.h"
int forceNullTermination(char* string, unsigned int len);
int getParam(char* source, char* out, unsigned int outSize, unsigned int param, char clipChar);
unsigned int getStrLen(char* str, int includeTerminator, unsigned int maxLen);
int strIsEqual(char* source, char* source2);
#endif
[/highlight]And the source file
[highlight=cpp]
#include "stringTools.h"
int forceNullTermination(char* string, unsigned int len)
{
if(string[len] != 0)
string[len] = 0;
return 1;
}
unsigned int getStrLen(char* str, int includeTerminator, unsigned int maxLen)
{
unsigned int i = 0;
for(i = 0; i < maxLen; i++)
if(str[i] == 0)
break;
return (includeTerminator ? i + 1: i);
}
int getParam(char* source, char* out, unsigned int outSize, unsigned int param, char clipChar)
{
unsigned int clipCount = 0;
unsigned int i = 0;
int len = strlen(source);
zeroMemory((char*)out, outSize);
for(i = 0; i < len; i++)
{
if(source[i] == clipChar)
clipCount++;
if(clipCount == param)
{
int bufCount = 0;
if(source[i] == clipChar)
i++;
for(; i < len; i++)
if(source[i] == clipChar)
break;
else
{
out[bufCount] = source[i];
bufCount++;
}
break;
}
}
return 1;
}
int strIsEqual(char* source, char* source2)
{
unsigned int i;
unsigned int sourceLen;
sourceLen = getStrLen(source, 0, 100);
if(sourceLen != getStrLen(source2, 0, 100))
return 0;
for(i = 0; i < sourceLen; i++)
{
if(source[i] != source2[i])
return 0;
}
return 1;
}
[/highlight]All of these functions are pretty basic. After that's been written, we can continue writing the message protocol.
We're going to start be creating message.h for our driver. We want this to contain a function to convert a received character array, into a struct.
[highlight=cpp]
#ifndef MESSAGE_H_
#define MESSAGE_H_
#include "stringTools.h"
#include "memory.h"
#define MSG_PARAM_SPLIT_CHAR '\\'
typedef struct Msg MSG;
struct Msg
{
char command[80];
char param1[80];
char param2[80];
char param3[80];
}Msg;
int GetStructFromChar(char* input, MSG* msgOut);
#endif
[/highlight]And the corresponding source file:
[highlight=cpp]
#include "message.h"
int GetStructFromChar(char* input, MSG* msgOut)
{
zeroMemory((char*)msgOut, sizeof(MSG));
getParam(input, msgOut->command, 80, 0, MSG_PARAM_SPLIT_CHAR);
getParam(input, msgOut->param1 , 80, 1, MSG_PARAM_SPLIT_CHAR);
getParam(input, msgOut->param2 , 80, 2, MSG_PARAM_SPLIT_CHAR);
getParam(input, msgOut->param3 , 80, 3, MSG_PARAM_SPLIT_CHAR);
return 1;
}[/highlight]Again this is a pretty simple function, Be sure to add all these source files accordingly to SOURCES.
getParamParam1: Pointer to char array to search for params
Param2: Pointer to Output buffer
Param3: Size of output buffer
Param4: Param number, starting from 0
Param5: char used to split parameters.
Now that this is all setup, we can actually start processing received messages from the command console. So lets create another header file, messageProcessor.h
[highlight=cpp]
#ifndef MESSAGEPROCESSOR_H_
#define MESSAGEPROCESSOR_H_
#include "ntddk.h"
#include "message.h"
#include "stringTools.h"
int processMessage(char* msg);
#endif
[/highlight]We'll be adding to this one a little later. We're going to need the message struct and some of the methods written in message.c, so we're including it's header, we're also going to be comparing strings, and thus we should include stringTools.h. The job of processMessage is so take a string containing what has been written to the device, compile it into a message structure, and then execute functions corresponding to the requested commands and recived parameters.
Now lets create the source file:
[highlight=cpp]
#include "messageProcessor.h"
int processMessage(char* msg)
{
MSG msgBuffer;
GetStructFromChar(msg, &msgBuffer);
DbgPrint("%s", msgBuffer.command);
DbgPrint("%s", msgBuffer.param1);
DbgPrint("%s", msgBuffer.param2);
DbgPrint("%s", msgBuffer.param3);
}
[/highlight]All we're doing here is testing the message protocol. In the next chapter we'll be using this to determine whether to disable or enable a hook. So lets open up majorFunctions.h, have it include messageProcessor.h, then in majorFunctions.c, on write event handler, to call processMessage();
[highlight=cpp]
#ifndef MAJORFUNCTIONS_H_
#define MAJORFUNCTIONS_H_
#include "ntddk.h"
#include "stringTools.h"
#include "messageProcessor.h"
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp);
#endif
[/highlight][highlight=cpp]
#include "majorFunctions.h"
NTSTATUS Create_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Handle to IO Device has been opened.");
return NtStatus;
}
NTSTATUS Close_DeviceIo(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Handle to IO Device has been closed.");
return NtStatus;
}
NTSTATUS Buffered_Write(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIoStackIrp = NULL;
PCHAR pInBuffer = NULL;
DbgPrint("IO Device has been written to.");
pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
if(pIoStackIrp)
{
pInBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
if(pInBuffer)
{
forceNullTermination(pInBuffer, pIoStackIrp->Parameters.Write.Length);
DbgPrint("Processing Message: SZ:%u, STR:%s", pIoStackIrp->Parameters.Write.Length, pInBuffer);
processMessage(pInBuffer);
}
else
{
DbgPrint("Write called with null buffer pointer.");
}
}
else
{
DbgPrint("Invalid IRP stack pointer..");
}
return NtStatus;
}
NTSTATUS Io_Unsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DbgPrint("Unsupported Majour Function Requested. Returning STATUS_SUCCESS");
return NtStatus;
}
[/highlight]Now that that's over with, we can test it, and then start writing out system service hook on the SSDT.
Getting access to protected memory using MDLs.
When your code is running in ring 0, there are various ways to access memory that shouldn't be accessible. The best method is using MDLs, but we'll be discussing three.
The first and most undocumented method, is changing the flags in the CONTROL 0 register in the processor, know as CR0. Refer to the Intel-Developer manual on specifics of the flags.
Another method, which isn't nearly convenient is modifying a registry key Microsoft allows you to access. Located here:
[highlight=cpp]HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory\Management\EnforceWriteProtection [/highlight] Altering the key is a messy inefficient and unreliable method, and lastly, we can use MDLs, which I will describe now.
MDL stands for Memory Descriptor list. You can use an MDL to describe a memory region, as well as alter the regions security. The System Service Dispatch Table is read only, and thus, in order to change the addresses of the services in the table, we're going to need to write to it, and thus we're going to need to change the table's permissions. More on the SSDT in the next chapter, we're only concerend about getting write rights to the SSDT in this chapter.
First of all, where is the SSDT located, and how can we find it? Well, the kernel actually exports a structure called the KeServiceDescriptorTable. Because there is really no reason for the average program to be modifying this table, and there really isn't a need to do so in Microsoft's eyes, the KeServiceDescriptorTable isn't documented by Microsoft itself. Infact, you need to define the structure yourself. If you google around, this is a structure that is well documented by the user, and is defined like so:
[highlight=cpp]
typedef struct SystemServiceDescriptorTable {
void* ServiceTableBase;
void* ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char* ParamTableBase;
} SSDT;
[/highlight]I'll try to leave as much as I can for the next chapter, but I don't really think I can :S. The first parameter is the base address of the system service table, the third defines the number of services in the table, and the last one is a pointer to a table called the param table. I'm not 100% sure how a 64bit OS works when it comes to this, but I can presume the addresses in the Service Table are double the size. Anyway, the service table contains all the addresses to all the services. And the param table defines the size, in bytes, that the function takes as parameters.
Say 0x10000000 is the base address for the service table base.
0x10000000: 0xFirstPointerToASystemService
0x10000004: 0xSecondPointerToASystemService
0x10000008: 0xThirdPointerToASystemService
and 0x20000000 was the param table base address
0x20000000: (BYTE) size of first service's params
0x20000001: (BYTE) size of the second service's params
0x20000002: etc..
So in theory, the memory we need to get write access to is
ServicesTableBasePtr to ServicesTableBasePtr + (NumberOfServices * sizeOfAddress)
On a 32bit machine, the sizeOfAddress variable would be 4 bytes, or 32bits. I can only assume on 64 bit machine, it is 64 bits.
Create a new header file, called ssdt.h
[highlight=cpp]
#ifndef SSDT_H_
#define SSDT_H_
#include "ntddk.h"
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
typedef struct SystemServiceDescriptorTable {
void* ServiceTableBase;
void* ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char* ParamTableBase;
} SSDT;
int ssdt_init();
int ssdt_deinit();
#endif
[/highlight]I'm first going to explain the SYSCALL_INDEX macro I find useful, which I got from various websites. Basically, all of the addresses of Zw* functions, begin with the opcodes mov eax, xxx. Where xxx is an unsigned long defining the functions index in the SSDT.
Ex, say it were mov eax, 10000000
In bytes this is:
B8 00 00 00 10.
Cut off the first byte(mov instruction), and convert the remaining into an unsigned long and you have it's index in the SSDT.
ssdt_init will remove protection of the SSDT, and allows us to write to it and map the memory region into our driver, ssdt_deinit, will do exactly the opposite.
[highlight=cpp]
#include "ssdt.h"
unsigned long* g_pMappedSCT;
MDL* g_pMdlSCT;
int g_iInitStatus;
__declspec(dllimport) SSDT KeServiceDescriptorTable;
int ssdt_init()
{
g_pMdlSCT = MmCreateMdl(0,KeServiceDescriptorTable.ServiceTabl eBase,
KeServiceDescriptorTable.NumberOfServices * 4);
if(!g_pMdlSCT)
{
g_iInitStatus = -1;
return -1;
}
MmBuildMdlForNonPagedPool(g_pMdlSCT);
g_pMdlSCT->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA;
g_pMappedSCT = MmMapLockedPages(g_pMdlSCT, KernelMode);
g_iInitStatus = 1;
return g_iInitStatus;
}
int ssdt_deinit()
{
if(g_iInitStatus)
{
MmUnmapLockedPages(g_pMappedSCT, g_pMdlSCT);
IoFreeMdl(g_pMdlSCT);
}
return 1;
}
[/highlight]In ssdt_init
First we make a call MmCreateMdl, describing the region of memory we want to create a MDL for. This will allocate an MDL for us, and then return a pointer to it. After that we need to update the MDL, so it describes the memory region we've requested, and to do this we can call MmBuildMdlForNonPagedPool, which takes a pointer to the MDL you wish to have updated. Then we can start modifying the page. We're going to be modifying the flags, so we can write to it. The MDL Flags is another poorly documented part of windows, setting this bit will allow us to map this memory region into our driver, and perform IO operations to the memory region. And then we call MmMapLockedPages, which returns a pointer to the first mapped page, we need to store this pointer for hooking, described later on, and unmapping the mapped pages.
The exact opposite is performed in ssdt_deinit.
First we assure the ssdt_init call was a success. If it failed and we try to perform this operation, we will encounter a blue screen of death for making use of uninitialized variables. Then we unmap the memory region, and free our previously allocated MDL.
Lets make a call to ssdt_init on our DriverEntry function, and ssdt_deinit on our driver unload function. First open up entry.h, and add ssdt.h, then open the source file, and add the call to ssdt_init. Like so:
[highlight=cpp]
#ifndef ENTRY_H_
#define ENTRY_H_
#include "ntddk.h"
#include "majorFunctions.h"
#include "ssdt.h"
void OnUnload(IN PDRIVER_OBJECT driverObject);
int InitUnicodeStrings();
int SetupIoDevice();
#endif
[/highlight][highlight=cpp]
#include "entry.h"
const WCHAR deviceName[] = L"\\Device\\MYDRIVER";
const WCHAR deviceSymbolicLink[] = L"\\DosDevices\\MYDRIVER";
UNICODE_STRING unicodeDeviceNameBuffer;
UNICODE_STRING unicodeSymLinkBuffer;
PDEVICE_OBJECT g_pDeviceObject = 0;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING regPath)
{
unsigned int i;
driverObject->DriverUnload = OnUnload;
for(i=0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
driverObject -> MajorFunction[i] = Io_Unsupported;
driverObject->MajorFunction[IRP_MJ_CREATE] = Create_DeviceIo;
driverObject->MajorFunction[IRP_MJ_CLOSE] = Close_DeviceIo;
driverObject->MajorFunction[IRP_MJ_WRITE] = Buffered_Write;
if(!InitUnicodeStrings())
return STATUS_UNSUCCESSFUL; //report the driver could not be loaded.
DbgPrint("ssdt inited with return code: %d", ssdt_init());
SetupIoDevice(driverObject);
DbgPrint("My Driver loaded.");
return STATUS_SUCCESS;
}
int InitUnicodeStrings()
{
RtlInitUnicodeString(&unicodeDeviceNameBuffer, deviceName);
RtlInitUnicodeString(&unicodeSymLinkBuffer, deviceSymbolicLink);
return 1;
}
int SetupIoDevice(PDRIVER_OBJECT pDriverObject)
{
int ret;
DbgPrint("Creating I\\O device under name: %ws", unicodeDeviceNameBuffer.Buffer);
ret = IoCreateDevice(pDriverObject,
0,
&unicodeDeviceNameBuffer,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&g_pDeviceObject);
g_pDeviceObject->Flags |= DO_BUFFERED_IO;
g_pDeviceObject->Flags &= (~DO_DEVICE_INITIALIZING);
if(ret != STATUS_SUCCESS)
{
DbgPrint("Error Creating Device");
return -1;
}
DbgPrint("Device Created");
ret = IoCreateSymbolicLink(&unicodeSymLinkBuffer, &unicodeDeviceNameBuffer);
if(ret != STATUS_SUCCESS)
{
DbgPrint("error creating symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, deviceSymbolicLink);
return -1;
}
DbgPrint("Created symbolic link to %ws, named: %ws", unicodeDeviceNameBuffer.Buffer, unicodeSymLinkBuffer.Buffer);
return 1;
}
VOID OnUnload(IN PDRIVER_OBJECT driverObject)
{
if(g_pDeviceObject)
{
IoDeleteDevice(g_pDeviceObject);
IoDeleteSymbolicLink(&unicodeSymLinkBuffer);
DbgPrint("Deleted IO Device and symbolic link.");
}
ssdt_deinit();
DbgPrint("My Driver unloaded.");
}
[/highlight]Now you're ready to start writing the hook.
Writing The Hook
And now... We can finally write our hook. Open ssdt.h and add
unsigned long ssdt_writeService(unsigned long sysServiceFunction, unsigned long hookAddr);
[highlight=cpp]
#ifndef SSDT_H_
#define SSDT_H_
#include "ntddk.h"
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
typedef struct SystemServiceDescriptorTable {
void* ServiceTableBase;
void* ServiceCounterTableBase;
unsigned int NumberOfServices;
char* ParamTableBase;
} SSDT;
int ssdt_init();
int ssdt_deinit();
unsigned long ssdt_writeService(unsigned long sysServiceFunction, unsigned long hookAddr);
#endif
[/highlight]And the corresponding source file:
[highlight=cpp]
unsigned long ssdt_writeService(unsigned long sysServiceFunction, unsigned long hookAddr)
{
unsigned long ret;
if(!g_iInitStatus)
return 0;
ret = (unsigned long)InterlockedExchange((PULONG)&g_pMappedSCT[SYSCALL_INDEX(sysServiceFunction)], (long)hookAddr);
return ret;
}
[/highlight]The only thing here that really need to be explained is the InterlockedExchange API. First of all, consider using
InterlockedExchange64 when working with a 64 bit machine. In this case, I'm developing for a 32 bit machine. So I'm fine using InterlockedExchange. This exchanges a long(32 bits) worth of data at the address provided in the first param with the 32 bits of data provided in the second parameter. It then returns the orginal value, before the exchange occured. The advantage of using InterlockedExchange, is that it will prevent one thread from reading from the address, while our driver's thread is writing to it. Avoiding half-read pointers from being read, and causing a BSOD when a call to an inexistant method has been made(ex, half written to).
Only thing left to do is incorporate this hook with one of the system services. And have our message processor handle incoming messages to enable and disable the hook. First things first, lets setup our hooking code, then our message processor.
Lets start by creating a new header file, called hooking.h. In here we're going to have all hooked related code, and code that manages hooking requests. Of course we're going to be making use of our previously created ssdt.h.
[highlight=cpp]
#ifndef HOOKING_H_
#define HOOKING_H_
#include "ntddk.h"
#include "ssdt.h"
#include "stringTools.h"
int HookZwQuerySystemInformation(char* param2, char* param3);
#endif
[/highlight]Later we'll be adding function prototypes and pointers to write the actual hook, but for now we're setting the console IO segment of the hook. HookZwQuerySystemInformation will create and disable the hook. We're going to pass it param2 and param3 to make use of(if it needs to make use of them). That's giving our message processor 2 level of input, and our hook managing code two input levels, seems fair enough.
And the corresponding source file
[highlight=cpp]
#include "hooking.h"
int HookZwQuerySystemInformation(char* param2, char* param3)
{
if(strIsEqual(param2, "set"))
DbgPrint("Performing hook on ZwQuerySystemInformation");
else
DbgPrint("Removing hook on ZwQuerySystemInformation");
return 1;
}
[/highlight]Open up messageProcessor.h, add hooking.h. Then open messageProcess.c and start to make dicissions based on recived commands. Here's what I've got:
[highlight=cpp]
#ifndef MESSAGEPROCESSOR_H_
#define MESSAGEPROCESSOR_H_
#include "ntddk.h"
#include "message.h"
#include "stringTools.h"
int processMessage(char* msg);
#endif
[/highlight][highlight=cpp]
#include "messageProcessor.h"
int processMessage(char* msg)
{
MSG msgBuffer;
GetStructFromChar(msg, &msgBuffer);
if(strIsEqual(msgBuffer.command, "hook.ssdt.ss"))
if(strIsEqual(msgBuffer.param1, "ZwQuerySystemInformation"))
HookZwQuerySystemInformation(msgBuffer.param2, msgBuffer.param3);
else
DbgPrint("Driver does not know how to hook this method.");
else
DbgPrint("Unknow command requested from console");
return 1;
}
[/highlight]HookZwQuerySystemInformation is a function we created back in hooking.c.
And you're good to go, give that a test, it should work to what you expect. Then we can start writing the actual hooking code.
In the console, type:
ss MYDRIVER.sys
start MYDRIVER
open \\.\MYDRIVER
write hook.ssdt.ss\ZwQuerySystemInformation\set
write hook.ssdt.ss\ZwQuerySystemInformation\unset
close
stop MYDRIVER
ds MYDRIVER
DebugView output
ssdt inited with return code: 1
Creating I\O device under name: \Device\MYDRIVER
Device Created
Created symbolic link to \Device\MYDRIVER, named: \DosDevices\MYDRIVER
My Driver loaded.
Handle to IO Device has been opened.
IO Device has been written to.
Processing Message: SZ:42, STR:hook.ssdt.ss\ZwQuerySystemInformation\set
Performing hook on ZwQuerySystemInformation
IO Device has been written to.
Processing Message: SZ:44, STR:hook.ssdt.ss\ZwQuerySystemInformation\unset
Removing hook on ZwQuerySystemInformation
No actual hooks were performed. This was just assuring you've got the basic IO code setup. Now lets finish up the hooking.
Open up hooking.h. First we're going to add the ZwQuerySystemInformation function prototype, the hook function header, and then define ZwQuerySystemInformation, which will then be imported, like so:
[highlight=cpp]
#ifndef HOOKING_H_
#define HOOKING_H_
#include "ntddk.h"
#include "ssdt.h"
#include "stringTools.h"
typedef NTSTATUS (*ZwQuerySystemInformationPrototype)(ULONG SystemInformationCLass,PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
NTSTATUS Hook_ZwQuerySystemInformation(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength);
int HookZwQuerySystemInformation(char* param2, char* param3);
#endif
[/highlight]and the corresponding source file
[highlight=cpp]
#include "hooking.h"
ZwQuerySystemInformationPrototype ori****Info = 0;
int HookZwQuerySystemInformation(char* param2, char* param3)
{
if(strIsEqual(param2, "set"))
{
if(!ori****Info)
{
DbgPrint("Performing hook on ZwQuerySystemInformation.");
(unsigned long)ori****Info = ssdt_writeService((unsigned long)ZwQuerySystemInformation, (long)Hook_ZwQuerySystemInformation);
}
else
{
DbgPrint("Hook has already been applied.");
}
}
else
{
if(ori****Info)
{
DbgPrint("Removing hook on ZwQuerySystemInformation.");
ssdt_writeService((unsigned long)ZwQuerySystemInformation, (long)ori****Info);
ori****Info = 0;
}
else
{
DbgPrint("No current hook has been applied.");
}
}
return 1;
}
NTSTATUS Hook_ZwQuerySystemInformation(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength)
{
DbgPrint("ZwQuerySystemInformation hook called. Redirecting to orignal method.");
return ori****Info(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
}
[/highlight]Our hook is pretty simple, it just calls DbgPrint, saying the call has been made, then calls the original method. Feel free to do some research on this method and elaborate a little more, filtering out processes and files.
Source Files: Look at attachments
And the tutorial is done! Finally! Congrats if you followed all the way through, I hope you learned something because I put a lot of effort into writing this!