windows Drivers and Kernel Debugging Learning 3

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.

official documentation

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

Addressing a Data Buffer

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:

  1. using-direct-i-o-with-pio

  2. using-direct-i-o-with-dma

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;
}

Posted by yozyk on Sun, 06 Nov 2022 17:08:45 +0530