foreword
windows Driver and Kernel Debugging Learning 2 This article introduces the basic driver reading and writing, let's look at the following example
char szBuffer[1]; char* p= szBuffer-500; //read drive at an indeterminate location if (ReadFile(hFile, p, 200, &drBytes, NULL)) { std::cout << "read ok " << drBytes << " "<< szBuffer << std::endl; } else { std::cout << "read failure " << std::endl; }
Because the driver runs in ring0, there are certain security risks that can be written at will. In addition, there are thread safety issues such as multiple threads reading and writing to the same IO address. In order to solve related problems, Windows provides a flags field for the DEVICE_OBJECT object, which can increase the io method to deal with related problems.
There are two methods, DO_DIRECT_IO and DO_BUFFERED_IO, respectively, and the two use scenarios are different. DO_BUFFERED_IO is suitable for convenient use in small data transmission, while DO_DIRECT_IO is tedious but efficient in large data transmission.
Please refer to the following documents
DO_DIRECT_IO and DO_BUFFERED_IO flags in windows driver programming
Methods for Accessing Data Buffers
You can clearly see the difference between the two
DO_BUFFERED_IO
This mode will apply for a memory block of the same size as the user space in the kernel space. If it is a write request, the operation will also apply for a memory block and copy the original memory information to the newly applied memory block.
The operating system does something similar to the code shown below (example taken from the reference link with slight modifications):
PVOID uva; // User mode incoming buffer address. You can understand the incoming buffer address when calling the driver ULONG length; // User mode incoming buffer address length //Create a new memory area PVOID sva; = ExAllocatePoolWithQuota(NonPagedPoolCacheAligned, length); //In write mode, a memory block copy will be performed if (writing) RtlCopyMemory(sva, uva, length); //Copy address to specific field Irp->AssociatedIrp.SystemBuffer = sva; //Copy the length to the IRP stack PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp); if (reading) stack->Parameters.Read.Length = length; else stack->Parameters.Write.Length = length; <code to send and await IRP> //If a read request is made, copy the memory to the address passed in in user mode if (reading) RtlCopyMemory(uva, sva, length); //kernel address release ExFreePool(sva);
Case:
//This function is called when the driver is loaded NTSTATUS DriverEntry( _In_ struct _DRIVER_OBJECT* DriverObject, _In_ PUNICODE_STRING RegistryPath ) { //If you don't use parameters you need to tell the system. UNREFERENCED_PARAMETER(RegistryPath); //print information DbgPrint("hello drive loaded"); //trigger a breakpoint //DbgBreakPoint(); //Driver uninstall callback registration DriverObject->DriverUnload = myUnload; DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl; UNICODE_STRING ustrDevName; RtlInitUnicodeString(&ustrDevName, L"\\Device\\MytestDriver"); PDEVICE_OBJECT pDevObj = NULL; auto ret = IoCreateDevice(DriverObject, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevObj); if (NT_SUCCESS(ret)) { //Specify IO mode pDevObj->Flags |= DO_BUFFERED_IO; DbgPrint("IoCreateDevice success \r\n"); } else { DbgPrint("IoCreateDevice fail %d\r\n", ret); return STATUS_FAIL_CHECK; } UNICODE_STRING symbolDevName; RtlInitUnicodeString(&symbolDevName, L"\\DosDevices\\MytestDriver"); ret = IoCreateSymbolicLink(&symbolDevName, &ustrDevName); if (NT_SUCCESS(ret)) { DbgPrint("IoCreateSymbolicLink success \r\n"); } else { DbgPrint("IoCreateSymbolicLink fail%d\r\n", ret); IoDeleteDevice(pDevObj); return STATUS_FAIL_CHECK; } return STATUS_SUCCESS; }
We look at the corresponding write function
NTSTATUS DispatchWrite( _In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp ) { DbgPrint("DispatchWrite"); UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp); ULONG nLength = pIrp->Parameters.Write.Length; DbgPrint("DispatchWrite UserBuffer:%p bytes:%d content %s", Irp->UserBuffer, nLength, Irp->UserBuffer); int cLen = pIrp->Parameters.Write.Length; DbgPrint("DispatchWrite SystemBuffer:%p bytes:%d content %s", Irp->AssociatedIrp.SystemBuffer, cLen, Irp->AssociatedIrp.SystemBuffer); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 6; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
read function
NTSTATUS DispatchRead( _In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp ) { DbgPrint("DispatchRead"); UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp); ULONG nLength = pIrp->Parameters.Read.Length; //Print the buffer area passed in by the original user DbgPrint("DispatchRead UserBuffer:%p bytes:%d", Irp->UserBuffer, nLength); int cLen = pIrp->Parameters.Read.Length; DbgPrint("DispatchRead SystemBuffer:%p bytes:%d", Irp->AssociatedIrp.SystemBuffer, cLen); memcpy(Irp->AssociatedIrp.SystemBuffer,"helloread",10); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 10; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
We print what the driver looks like after triggering read and write
DO_DIRECT_IO
This IO mode is used when transferring large data. Microsoft provides two modes for us to use:
Here we use the DMA method to explain.
We first need to understand the concept of DMA. We assume that copying a movie from a USB flash drive to memory requires CPU participation in ancient times. In modern times, there is DMA hardware blessing, and CPU only needs to pass requirements to DMA. However, using DMA needs to solidify the mapping relationship between virtual addresses and physical addresses (for example, virtual address 0x111 can map hardware memory addresses that can change at any time).
When we use an IO pattern, the operation will build a macro called MDL to express the relevant structure
typedef struct _MDL { struct _MDL *Next; CSHORT Size; CSHORT MdlFlags; struct _EPROCESS *Process; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset; } MDL, *PMDL;
This structure stores virtual address information
And there is an array of physical address mappings immediately behind the data structure memory (but there is no API for driver developers to access).
We will not directly touch this data structure in the driver, but use the relevant macros provided by Microsoft. As shown below
The operating system actually does something similar to the following code:
KPROCESSOR_MODE mode; // either KernelMode or UserMode //Create MDL objects PMDL mdl = IoAllocateMdl(uva, length, FALSE, TRUE, Irp); //Locks virtual addresses and physical mappings for easy DMA MmProbeAndLockPages(mdl, mode, reading ? IoWriteAccess : IoReadAccess); <code to send and await IRP> //resolve mapping MmUnlockPages(mdl); //release MDL ExFreePool(mdl);
Let's take a look at the official user guide
Case:
NTSTATUS DispatchRead( _In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp ) { DbgPrint("DispatchRead"); UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp); ULONG nLength = pIrp->Parameters.Read.Length; //Print the buffer area passed in by the original user DbgPrint("DispatchRead UserBuffer:%p bytes:%d", Irp->UserBuffer, nLength); PVOID kenelP = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); DbgPrint("DispatchRead MmGetSystemAddressForMdlSafe:%p ", kenelP); memcpy(kenelP, "helloread", 10); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 10; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchWrite( _In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp ) { DbgPrint("DispatchWrite"); UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp); ULONG nLength = pIrp->Parameters.Write.Length; DbgPrint("DispatchWrite UserBuffer:%p bytes:%d content %s", Irp->UserBuffer, nLength, Irp->UserBuffer); PVOID kenelP = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); DbgPrint("DispatchWrite SystemBuffer:%p content %s", kenelP, kenelP); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 6; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } //This function is called when the driver is loaded NTSTATUS DriverEntry( _In_ struct _DRIVER_OBJECT* DriverObject, _In_ PUNICODE_STRING RegistryPath ) { //If you don't use parameters you need to tell the system. UNREFERENCED_PARAMETER(RegistryPath); //print information DbgPrint("hello drive loaded"); //trigger a breakpoint //DbgBreakPoint(); //Driver uninstall callback registration DriverObject->DriverUnload = myUnload; DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl; UNICODE_STRING ustrDevName; RtlInitUnicodeString(&ustrDevName, L"\\Device\\MytestDriver"); PDEVICE_OBJECT pDevObj = NULL; auto ret = IoCreateDevice(DriverObject, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevObj); if (NT_SUCCESS(ret)) { //Specify IO mode pDevObj->Flags |= DO_DIRECT_IO; DbgPrint("IoCreateDevice success \r\n"); } else { DbgPrint("IoCreateDevice fail %d\r\n", ret); return STATUS_FAIL_CHECK; } UNICODE_STRING symbolDevName; RtlInitUnicodeString(&symbolDevName, L"\\DosDevices\\MytestDriver"); ret = IoCreateSymbolicLink(&symbolDevName, &ustrDevName); if (NT_SUCCESS(ret)) { DbgPrint("IoCreateSymbolicLink success \r\n"); } else { DbgPrint("IoCreateSymbolicLink fail%d\r\n", ret); IoDeleteDevice(pDevObj); return STATUS_FAIL_CHECK; } return STATUS_SUCCESS; }