Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC10.0

Device hooking

4.80/5 (13 votes)
25 May 2012CPOL4 min read 41.8K   1.4K  
Device hooking

Introduction

Network snoop is a continuation of the article http://www.codeproject.com/Articles/163408/APIHooking with primary focus on device hooking. Here I plan to introduce device stack, device extensions, and build a driver using WDM using C++ (on Windows 7 : 64-bit).

Background

The reader is advised to read up on device drivers by Toby Opferman, the reader is required to know how to build a driver. Also you will require WDK (Windows Driver Kit) and Dbgview (Google for it).

We will be writing code that is to be executed at ring '0' (kernel mode), processor privilege protection is provided to execute tasks greater than ring '0'. Feel free to modify the code to print using DbgPrint, the CS register. Notice the last two bits will be set to 0 indicating ring '0' code execution. Ring 0 code is required to access some portion of memory which is flagged as privileged via page table. Privileged pages can only be accessed by privileged segments (i.e., Current Privilege Level < 3), and user code runs at CPL=3 (hence cannot access them). Also to access IO, CPL <= IOPL.

Using the code

The attached code is written in C++ and must be referred to at all times.

We will start by writing a device entry function (analogous to 'main/winmain' in C/C++ user mode program):

C++
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPath);
// remember we are using C++, remember to provide external linkages for C//

The next thing we need to do is to create a device:

C++
NtStatus = IoCreateDevice(pDriverObject, sizeof(PDEVICE_OBJECT), NULL, 
     FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject_main);
//allocate in-device memory: sizeof(PDEVICE_OBJECT)) 

IoCreateDevice lets you create a device and also allocate four bytes: sizeof(PDEVICE_OBJECT). This memory will belong to the context of the device we have created and is called device extension. This device extension related memory is always available with the device pointer. It is usually used to store a pointer to the memory created by calling ExAllocatePool and is ideal for passing memory across different drivers. Other drivers can access this pointer by attaching themselves by calling IoAttachDevice (more on this later) or by calling IoGetDeviceObjectPointer.

Now let's attach the driver.

C++
RtlInitUnicodeString(&usDeviceToFilter, L"\\Device\\tcp"); 
NtStatus = IoAttachDevice(pDeviceObject_main, &usDeviceToFilter, 
              (PDEVICE_OBJECT*)&pDeviceObject_main->DeviceExtension);

As mentioned earlier, we make use of IoAttachDevice, we attach this to device\TCP since we are hooking TCP calls.

When a driver is hooked, the driver that does the hooking (the one we created and not device\TCP) will receive all calls to this device. Our driver is now on top of the device stack (until somebody hooks device\TCP). It is the responsibility of the driver to pass these requests down to the lower driver in the stack. The lowest driver is usually your port driver (a.k.a., miniports).

Requests along with parameters are passed to the device via IRP (IO request package), in a layered driver (like ours, using device stacks). An IRP exists for every device in the stack. Hence we have an IRP stack.

We must register all possible IRP functions so that there is a function for every IRP:

C++
for(uiIndex = 0; uiIndex < IRP_MJ_MAXIMUM_FUNCTION; uiIndex++)
   pDriverObject->MajorFunction[uiIndex] = UnSupportedFunction;

UnSupportedFunction does nothing but passes the IRP to a lower:

C++
NTSTATUS UnSupportedFunction(PDEVICE_OBJECT DeviceObject, PIRP Irp) { NTSTATUS NtStatus = STATUS_NOT_SUPPORTED; 
  IoSkipCurrentIrpStackLocation(Irp);
NtStatus = IoCallDriver((PDEVICE_OBJECT)DeviceObject->DeviceExtension, Irp); }
return NtStatus; 

We must skip the current IRP processing and blindly pass it to the next driver in the stack.

To snoop network packages, we are interested only in IRP_MJ_INTERNAL_DEVICE_CONTROL. We add the following line to ensure that the appropriate function related to IO processing is called:

C++
pDriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = IOFunction; //(refer to code)

IOFunction is called very time an IRP's current stack location major function is equal to IRP_MJ_INTERNAL_DEVICE_CONTROL.

To process any function (in our case it is IOFunction) we must acquire the current stack location

C++
PIO_STACK_LOCATION StackIrpPointer= IoGetCurrentIrpStackLocation(Irp);

Through this we can get parameters being passed down from user mode code (refer code):

C++
PTDI_REQUEST_KERNEL_CONNECT TDI_connectRequest =
   ( PTDI_REQUEST_KERNEL_CONNECT ) (& StackIrpPointer -> Parameters );
PTA_ADDRESS TA_Address_data =( ( PTRANSPORT_ADDRESS )( TDI_connectRequest -> 
   RequestConnectionInformation -> RemoteAddress ))-> 
   Address; PTDI_ADDRESS_IP TDI_data = ( PTDI_ADDRESS_IP ) ( TA_Address_data->Address );
   { UINT Address = TDI_data -> in_addr ; UINT Port = TDI_data -> sin_port ; 
     DbgPrint (address:%x Port: %x",Address,Port) } 

I have also added code for send and recv.

For sending code we make use of MmGetSystemAddressForMdlSafe since we are using METHOD_IN_DIRECT.

For recv:

C++
PCHAR pWriteDataBuffer = 0;
DbgPrint ( IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,Complete,0,TRUE,TRUE,TRUE);
NtStatus = IoCallDriver((PDEVICE_OBJECT)
  DeviceObject->DeviceExtension, Irp); "inside recv function to pull out data");

We now copy the current stack location to the next and not skip it since we are calling IOSetCompletionRoutine. This will set a completion routine that gets called when an IO completes.

Let's fire it up

Drivers are installed in a similar way as services, we will use the service control manager for this.

C++
system(sc create Driver1 binPath= "..//Driver.sys" type= kernel");
system("sc start Driver1");
MessageBoxA(0,"UnloadFilter","UnloadFilter",0);"sc stop Driver1");
system("sc stop Driver1");
system("sc delete Driver1");

Additional code: SimpleSocket is for you to test the code, simple socket is a simple server, use Telnet to connect to it while the filter driver is active, view the output in Dbgview.

Points of Interest

I do hope that the reader has learned device stack being implemented as a network snoop (filter driver).

As mentioned before, this article only aims to teach the reader device stack and not network filter, the TDI implementation is adapted from articles found on the Internet.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)