Sei sulla pagina 1di 4

22/10/2016

Userland/Kernel communication DeviceIoControl method Eric Asselin

Eric Asselin
Home
About me
FAQ

Driver
Rootkit
Tutorials
Windows
Wordpress

Getting started with Windows driver/rootkit development

User

Password

Remember me Send
Recover password
Home Driver Userland/Kernel communication DeviceIoControl method

It is sometime very useful to use userlands APIs to handle different tasks such as networking or to interact with the driver from a graphical interface. In a
short serie of posts, well explain the basic technics to achieve communication between the kernel driver and a userland application.
The Windows Driver Development Kit provides a data structure called I/O Request Packet also known as IRP. This post will cover only the needed part of an
IRP structure and will not study all the details of it, for more detailled informations visit the MSDN page for IRP. To communicate, a userland application
must first open a handle to a device using the CreateFile function like if it was a file. Depending on the drivers implementation, you can use
DeviceIOControl, ReadFile or WriteFile to send and receive messages to and from the kernel driver. As with file, you must close the handle with the
CloseHandle function. This article will cover the use of the DeviceIOControl function and show both, kernel driver and userland application implementation.
The use of ReadFile and WriteFile function will be covered in the next article.
A very important concept to understand is the MajorFunction array found in the kernel driver object. It can be seen as event callback used to handle the device
status and all IRPs created. As an example, the IRP_MJ_CREATE MajorFunction will be called when a userland application used the CreateFile function so
we can do whatever it takes to properly set a device handle to the CreateFile function. Maybe its not clear, but it will become as we will talk enough about it
later. In this post, well take a look at IRP_MJ_CREATE, IRP_MJ_CLOSE and IRP_MJ_DEVICE_CONTROL.

In order to enable communication between the driver and the application, a device must be created to let the application having a handle to it with the
CreateFile function. First well declare three global variables which represent the device name, his symbolic link and a pointer to the device object. Even if
theres cleaner ways to do it by using C++ encapsulation, well keep using C to keep it simple. As we create a device, we have to delete it properly in
our
function.
http://ericasselin.com/userlandkernel-communication-deviceiocontrol-method

1/4

22/10/2016

Userland/Kernel communication DeviceIoControl method Eric Asselin

### Global declaration ###


1
2
3

const WCHAR deviceNameBuffer[] = L"\\Device\\MYDEVICE";


const WCHAR deviceSymLinkBuffer[] = L"\\DosDevices\\MyDevice";
PDEVICE_OBJECT g_MyDevice; // Global pointer to our device object

Then lets create the device in the DriverEntry function as follow:


### DriverEntry function ###
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject,


IN PUNICODE_STRING pRegistryPath )
{
NTSTATUS ntStatus = 0;
UNICODE_STRING deviceNameUnicodeString, deviceSymLinkUnicodeString;
// Normalize name and symbolic link.
RtlInitUnicodeString (&deviceNameUnicodeString,
deviceNameBuffer);
RtlInitUnicodeString (&deviceSymLinkUnicodeString,
deviceSymLinkBuffer);
// Create the device.
ntStatus = IoCreateDevice ( pDriverObject,
0, // For driver extension
&deviceNameUnicodeString,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_UNKNOWN,
FALSE,
&g_MyDevice);
// Create the symbolic link
ntStatus = IoCreateSymbolicLink(&deviceSymLinkUnicodeString,
&deviceNameUnicodeString);
pDriverObject->DriverUnload = OnUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = Function_IRP_MJ_CREATE;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Function_IRP_MJ_CLOSE;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Function_IRP_DEVICE_CONTROL;

DbgPrint("Loading driver");
return STATUS_SUCCESS;

Notice the three functions mapped to their respective IRPs MajorFunction. Here we simply tell our driver which function to call if an IRP event occurs. Well
discuss the role and the content of these functions later.
### OnUnload function ###
1
2
3
4
5
6
7
8
9
10
11

VOID OnUnload( IN PDRIVER_OBJECT pDriverObject )


{
UNICODE_STRING symLink;
RtlInitUnicodeString(&symLink, deviceSymLinkBuffer);
IoDeleteSymbolicLink(&symLink);
IoDeleteDevice(pDriverObject->DeviceObject);
}

DbgPrint("OnUnload called!");

With the DeviceIoControl method, we can build a communication protocol using the Device Input and Output Control, IOCTL. Each IOCTL will define the
device type, the function code, the method telling how to use buffers and the access rights. Some device types are already define but we have defined our own
code which is 40000. In case of the function code, it is up to your implementation and imagination as long as its no larger then 4095, this is where youll
create your protocol logic. In our example, we use the method
, it is very important to understand these methods (refer to IRPs
documentation for more details) otherwise youre just about to create your first BSOD. For the access rights, we use
. Microsoft provides a macro to create IOCTL code and well use it to define all IOCTLs. Note that both, driver
and application, must share the IOCTL definition and a good practice is to use a header file included by both.
### IOCTL declaration ###
1
2
3

#define SIOCTL_TYPE 40000


#define IOCTL_HELLO\
CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)

http://ericasselin.com/userlandkernel-communication-deviceiocontrol-method

2/4

22/10/2016

Userland/Kernel communication DeviceIoControl method Eric Asselin

Here well detail the role of the three MajorFunction and their content. Lets begin with
. This function will be called every
time an application will try to open a handle to the device with the
function. Actually, our function is empty because we dont have anything
special like network or database connection.
### Function_IRP_MJ_CREATE function ###
1
2
3
4
5

NTSTATUS Function_IRP_MJ_CREATE(PDEVICE_OBJECT pDeviceObject, PIRP Irp)


{
DbgPrint("IRP MJ CREATE received.");
return STATUS_SUCCESS;
}

Same for the


called by the

, it is empty but if we do have some connections or memory clean up, there is the place to do it. This function is
function is used by an application.

### Function_IRP_MJ_CLOSE function ###


1
2
3
4
5

NTSTATUS Function_IRP_MJ_CLOSE(PDEVICE_OBJECT pDeviceObject, PIRP Irp)


{
DbgPrint("IRP MJ CLOSE received.");
return STATUS_SUCCESS;
}

Now lets take a look at the core function where our communication logic or protocol takes place:
. This function is
use when the
function is called. Every MajorFunction calls come with the Device and the Irp pointers. Well need the Irp pointer to retrieve
the pointer to the buffer and the pointer of the current IRP stack. At this point, for those who wants to go farther, I strongly suggest to visit the MSDN Irp
documentation for a better understanding. To keep it simple, the Irp contains the IOCTL, the message from the application as well as a place to put the driver
answer. We can switch the IOCTL and then use it as control command or whatever you like. So lets take a look at this function:
### Function_IRP_DEVICE_CONTROL function ###
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

NTSTATUS Function_IRP_DEVICE_CONTROL(PDEVICE_OBJECT pDeviceObject, PIRP Irp)


{
PIO_STACK_LOCATION pIoStackLocation;
PCHAR welcome = "Hello from kerneland.";
PVOID pBuf = Irp->AssociatedIrp.SystemBuffer;
pIoStackLocation = IoGetCurrentIrpStackLocation(Irp);
switch(pIoStackLocation->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_HELLO :
DbgPrint("IOCTL HELLO.");
DbgPrint("Message received : %s", pBuf);
RtlZeroMemory(pBuf,pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength);
RtlCopyMemory( pBuf, welcome, strlen(welcome) );
}

break;

// Finish the I/O operation by simply completing the packet and returning
// the same status as in the packet itself.
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = strlen(welcome);
IoCompleteRequest(Irp,IO_NO_INCREMENT);
}

return STATUS_SUCCESS;

Here is a simple command-line program to test our communication protocol:


userland.cpp
1
2
3
4
5
6
7
8
9
10
11

#include "stdafx.h"
#include <windows.h>
// Device type
#define SIOCTL_TYPE 40000
// The IOCTL function codes from 0x800 to 0xFFF are for customer use.
#define IOCTL_HELLO\
CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA)
int __cdecl main(int argc, char* argv[])

12
{
http://ericasselin.com/userlandkernel-communication-deviceiocontrol-method

3/4

22/10/2016

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Userland/Kernel communication DeviceIoControl method Eric Asselin

HANDLE hDevice;
char *welcome = "Hello from userland.";
DWORD dwBytesRead = 0;
char ReadBuffer[50] = {0};

hDevice = CreateFile(L"\\\\.\\MyDevice", GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,


printf("Handle : %p\n",hDevice);

DeviceIoControl(hDevice, IOCTL_HELLO, welcome, strlen(welcome), ReadBuffer, sizeof(ReadBuffer), &dwBytesRead, NULL)


printf("Message received from kerneland : %s\n",ReadBuffer);
printf("Bytes read : %d\n", dwBytesRead);
CloseHandle(hDevice);
}

return 0;

Comments are closed.


(19 Ratings)

Send Your Comments

2010 Eric Asselin | Powered by Wordpress


Icons courtesy of FamFamFam and WeFunction

http://ericasselin.com/userlandkernel-communication-deviceiocontrol-method

4/4

Potrebbero piacerti anche