I/O Request Packets (IRP)
In this documentation, we will discuss IRP.
What is I/O Request Packet
An I/O Request Packet (IRP) in the Windows operating system is a structure used to facilitate communication between drivers and the operating system. You can think of it as a “message carrier”.
In most cases, requests sent to drivers are packaged into an IRP. An operating system component or another driver sends an IRP to a driver using the IoCallDriver function. This function takes two parameters: a device object (DEVICEOBJECT) and a pointer to an IRP. The device object points to the driver object associated with that device. Therefore, when IoCallDriver is called, the IRP is sent to the corresponding device object and, by extension, to its associated driver. This process is often referred to as “passing” or “forwarding” the IRP.
An IRP is often processed by multiple drivers, which are arranged in a specific order. While being processed, drivers are treated like layers in a stack. The IRP first reaches the driver at the top of the stack. Each driver processes the IRP in turn, performs the necessary operations, and then passes it on to the next driver in the stack. This step-by-step processing continues until the IRP has passed through all the relevant drivers. Once the processing is complete, the desired operation is performed, and the result is returned.
An IRP also serves as the mechanism for handling user-initiated operations within the kernel. It ensures that drivers receive and respond to requests in a consistent and structured way. For example, when you print a document, the request is sent as an IRP to the printer driver. The printer driver receives the IRP and initiates the printing process. This system provides a safe and efficient bridge between user operations and hardware.
Coding
We will create two separate projects for the sample code. In the first project we will create a driver and I will talk about how to handle IRP. In the other project we will create a user mode program and in this program I will show how to send IRP to the driver using the WriteFile function.
Kernel Mode Driver
#include "main.h"
NTSTATUS IrpCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
DbgPrintEx(0, 0, "\n\nMJ_Create Received!\n");
Irp->IoStatus.Status = STATUS_SUCCESS;;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Irp->IoStatus.Status;
}
NTSTATUS IrpWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
PCHAR Buffer = Irp->UserBuffer;
ULONG Length = Stack->Parameters.Write.Length;
DbgPrintEx(0, 0, "\n\nMJ_WRITE Received!\n");
if (NULL == Buffer || 0 == Length) {
DbgPrintEx(0, 0, "Failed to Received Data!\n");
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information = 0;
return Irp->IoStatus.Status;
}
DbgPrintEx(0, 0, "Data to be written received: %.*s\n", Length, Buffer);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = Length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Irp->IoStatus.Status;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT PDrvObj, PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\MyDevice");
UNICODE_STRING SymName = RTL_CONSTANT_STRING(L"\\??\\MyDevice");
PDEVICE_OBJECT DeviceObject;
NTSTATUS Status;
Status = IoCreateDevice(
PDrvObj,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject
);
if (!NT_SUCCESS(Status)) {
DbgPrintEx(0, 0, "Failed to Create IO Device!\n");
return Status;
}
Status = IoCreateSymbolicLink(
&SymName,
&DeviceName
);
if (!NT_SUCCESS(Status)) {
DbgPrintEx(0, 0, "Failed to Create Smybolic Link!\n");
return Status;
}
PDrvObj->MajorFunction[IRP_MJ_CREATE] = IrpCreate;
PDrvObj->MajorFunction[IRP_MJ_WRITE] = IrpWrite;
return STATUS_SUCCESS;
}
NTSTATUS UnloadDriver(PDRIVER_OBJECT PDrvObj) {
UNICODE_STRING SymName = RTL_CONSTANT_STRING(L"\\??\\MyDevice");
DbgPrintEx(0, 0, "Unloading Driver...\n");
IoDeleteSymbolicLink(&SymName);
IoDeleteDevice(PDrvObj->DeviceObject);
return STATUS_SUCCESS;
}
It looks very long, but I assure you it is a very simple driver code. Let’s look at the code step by step and start with the DriverEntry function:
UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(L"\\Device\\MyDevice");
UNICODE_STRING SymName = RTL_CONSTANT_STRING(L"\\??\\MyDevice");
PDEVICE_OBJECT DeviceObject;
First, we define two UNICODE_STRING variables called DeviceName and SymName. These two variables hold the name and symbolic name of our driver.
Status = IoCreateDevice(
PDrvObj,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject
);
if (!NT_SUCCESS(Status)) {
DbgPrintEx(0, 0, "Failed to Create IO Device!\n");
return Status;
}
Then we create a device object with the IoCreateDevice function. This function creates a device object for our driver and assigns it to the DeviceObject variable.
Status = IoCreateSymbolicLink(
&SymName,
&DeviceName
);
if (!NT_SUCCESS(Status)) {
DbgPrintEx(0, 0, "Failed to Create Smybolic Link!\n");
return Status;
}
We create a symbolic link with the IoCreateSymbolicLink function. In this way, we will be accessing our drive using the name and symbolic name of the drive in our user mode program.
PDrvObj->MajorFunction[IRP_MJ_CREATE] = IrpCreate;
PDrvObj->MajorFunction[IRP_MJ_WRITE] = IrpWrite;
Finally, we specify the IRP functions of our driver. These functions determine how our driver handles IRPs. In the code, we use IrpCreate and IrpWrite. MJ_CREATE determines what our drive does when a file is created. IrpWrite determines what our driver does when a file is written to. IRPs are passed to our driver through these functions and our driver processes the IRPs through these functions.
Now let’s take a look at our functions to handle IRP requests:
NTSTATUS IrpCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
DbgPrintEx(0, 0, "\n\nMJ_Create Received!\n");
...
Firstly, when we look at IrpCreate, it takes the DeviceObject and Irp parameters. These parameters represent our driver’s device object and IRP, as mentioned above. Then, we print an information message using the DbgPrintEx function.
Irp->IoStatus.Status = STATUS_SUCCESS;;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Irp->IoStatus.Status;
In the last part of the MJ_CREATE function, we set the status and information of the IRP. This status and information determine the result of the IRP operation. Finally, we complete the IRP with the IoCompleteRequest function and terminate the operation.
Now let’s look at the IrpWrite function:
NTSTATUS IrpWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
PCHAR Buffer = Irp->UserBuffer;
ULONG Length = Stack->Parameters.Write.Length;
...
- Stack variable represents the stack location of the IRP.
- Buffer variable represents the data area of the IRP. This data area contains the data carried by the IRP.
- Length variable represents the data length of the IRP. This length specifies the length of the data carried by the IRP.
if (NULL == Buffer || 0 == Length) {
DbgPrintEx(0, 0, "Failed to Received Data!\n");
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information = 0;
return Irp->IoStatus.Status;
}
We check whether the Buffer pointer is NULL and whether the Length value is 0.
If the condition is met, i.e., no data has been received, we set the value of Irp->IoStatus.Status to STATUS_INVALID_PARAMETER. This indicates a parameter error. We set the value of Irp->IoStatus.Information to 0. Finally, the IRP’s operation status is returned, and the process is terminated.
DbgPrintEx(0, 0, "Data to be written received: %.*s\n", Length, Buffer);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = Length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Irp->IoStatus.Status;
In this section, if the data has been retrieved correctly, we print this data. Then, in the same way, we set the status and information of the IRP and terminate the process.
User Mode Program
Now let’s create our user mode program. In this program, we will send an IRP to our driver using the WriteFile function.
#include "main.h"
int main(int argc, char* argv[]) {
HANDLE HandleDevice = NULL;
CHAR Buffer[] = "Hello, kernel!";
DWORD BytesWritten = 0;
DWORD BytesRead = 0;
BOOL Status = 0;
HandleDevice = CreateFile(
DEVICE_NAME,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (INVALID_HANDLE_VALUE == HandleDevice) {
printf("Failed to Open Device! Error Code: 0x%lx\n", GetLastError());
return -1;
}
Status = WriteFile(
HandleDevice,
Buffer,
(DWORD)sizeof(Buffer),
&BytesWritten,
NULL
);
if (!Status) {
printf("Failed to Write Data!\n");
CloseHandle(HandleDevice);
return -1;
}
CloseHandle(HandleDevice);
return 0;
}
Now let’s take a closer look:
HandleDevice = CreateFile(
DEVICE_NAME,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (INVALID_HANDLE_VALUE == HandleDevice) {
printf("Failed to Open Device! Error Code: 0x%lx\n", GetLastError());
return -1;
}
First, we open our drive using CreateFile in our program. This function opens our drive and returns a handle.
Some may wonder, “Is the drive opened using CreateFile?” . The CreateFile function can be used to create or open a file or device object in the Windows operating system. You can refer to the documentation for further research.
Status = WriteFile(
HandleDevice,
Buffer,
(DWORD)sizeof(Buffer),
&BytesWritten,
NULL
);
if (!Status) {
printf("Failed to Write Data!\n");
CloseHandle(HandleDevice);
return -1;
}
In this section, we send an IRP request to our drive using the WriteFile function.
Running the Project
Here’s the result:

As you can see, when we run our user mode program, we see that it sends data to the driver and prints the sent data to the screen:

Conclusion
An I/O Request Packet (IRP) is Windows’ standard mechanism for delivering requests from user-mode applications to kernel-mode drivers. In our example, the driver registered specific routines (IrpCreate, IrpWrite) to handle incoming IRPs, while the user-mode program opened the device with CreateFile and sent data using WriteFile, which internally generated an IRP.