Windows R3与R0通信详解

kaling / 2024-03-01 / 原文

R3与R0通信
R0 #define 设备名 L"\\Device\\My_Device"
R0 #define 符号链接名 L"\\??\\My_Device"
R3 文件名 = "\\\\.\\My_Device"

创建设备的时候,可以设置设备通信方式,通常为DO_BUFFERED_IO:
IO管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。而你的驱动程序将使用这个系统缓冲区工作。IO管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。

读取数据前,R3手动创建用户模式缓冲区lpBuffer,大小通常与试图读取字节数相等,R0创建同样大小系统缓冲区pIrp->AssociatedIrp.SystemBuffer,
IoStatus.Information大小决定用户缓冲区复制系统缓冲区数据的大小.
R3:
BOOL WINAPI ReadFile(
HANDLE hFile, //R3:hFile=CreateFile("\\\\.\\My_Device", ...)
LPVOID lpBuffer, //R3:lpBuffer=>pIrp->AssociatedIrp.SystemBuffer
DWORD nNumberOfBytesToRead, //R3:目标读取字节数
LPDWORD lpNumberOfBytesRead, //R0:实际读取字节数,内核返回
LPOVERLAPPED lpOverlapped);
R0:
NTSTATUS DispatchRead(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
//Parameters.Read.Length=R3:nNumberOfBytesToRead
ULONG ulReadLength = pStack->Parameters.Read.Length;
pIrp->IoStatus.Status = status;
DbgPrint("yjx:应用要读取的长度:%d\n", ulReadLength);
// 将内核中的缓冲区全部填充为0x68 方便演示读取的效果
memset(pIrp->AssociatedIrp.SystemBuffer, 0x68, ulReadLength);
//IoStatus.Information = R3:lpNumberOfBytesRead
pIrp->IoStatus.Information = ulReadLength;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}

---------------------------------------------
R3:WriteFile
BOOL WINAPI WriteFile(
HANDLE hFile,
LPCVOID lpBuffer, //R3:lpBuffer=>pIrp->AssociatedIrp.SystemBuffer
DWORD nNumberOfBytesToWrite, //R3:目标写入字节数
LPDWORD lpNumberOfBytesWritten, //R0:实际写入字节数,内核返回
LPOVERLAPPED lpOverlapped);

R0:
NTSTATUS DispatchWrite(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulWriteLength = pStack->Parameters.Write.Length;//R3目标写入字节数
PVOID ulWriteData = pIrp->AssociatedIrp.SystemBuffer;
pIrp->IoStatus.Information = ulWriteLength;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return Status;
}

---------------------------------------------------
R3:
BOOL WINAPI DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode, //case My_Code
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped);

R0:
NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
ULONG_PTR Informaiton = 0;
PVOID InputData = NULL;
ULONG InputDataLength = 0;
PVOID OutputData = NULL;
ULONG OutputDataLength = 0;
PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(pIrp); // Irp堆栈
InputData = pIrp->AssociatedIrp.SystemBuffer; // 对应R3 lpInBuffer
OutputData = pIrp->AssociatedIrp.SystemBuffer; // 对应R3 lpOutBuffer
InputDataLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
ULONG Code = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
switch (Code)
{
case My_Code:
{
PtrHread PtrBuff = (PtrHread)InputData;
ULONG RetFlage = PtrBuff->Flage;
ULONG RetAddr = PtrBuff->Addr;
ULONG RetBufferAddr = PtrBuff->WriteBufferAddr;
ULONG Size = PtrBuff->Size;
ULONG Pid = PtrBuff->Pid;

DbgPrint("读取文件标志:%d", RetFlage);
DbgPrint("读取写入地址:%x", RetAddr);
DbgPrint("读取缓冲区大小:%d", RetBufferAddr);
DbgPrint("读取当前大小:%d", Size);
DbgPrint("要操作进程PID: %d", Pid);

// 通过内存返回数据.
char *retBuffer = "hello lyshark";
memcpy(OutputData, retBuffer, strlen(retBuffer));
Informaiton = strlen(retBuffer) + 1; //对应R3 lpBytesReturned
Status = STATUS_SUCCESS;

// 通过内存返回数据,另一种通信方式.
/*
PVOID addr = (PVOID)"ok";
RtlCopyMemory(OutputData, addr, 4);
Informaiton = 4;
Status = STATUS_SUCCESS;
*/
break;
}
}
pIrp->IoStatus.Status = Status; // 设置IRP完成状态,会设置用户模式下的GetLastError
pIrp->IoStatus.Information = Informaiton; // 对应R3 lpBytesReturned
IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成IRP,不增加优先级
return Status; // 内核模式下的GetLastError
}
SystemBuffer大小取决于R3:目标写入字节数InBufferSize和目标读取字节数OutBufferSize,较大者为内核系统缓冲区大小.与InBuffer和OutBuffer缓冲区本身大小无关,无视缓冲区溢出.

DeviceIoControl函数运行后,首先设置SystemBuffer,用零填充.之后复制从InBuffer缓冲区地址开始的InBufferSize大小数据到系统缓冲区.之后内核写操作系统缓冲区,并将实际操作字节数Information返回R3.Information将写入DeviceIoControl倒数第二个参数BytesReturned地址处.

标准操作为:禁止缓冲区溢出,目标写入字节数等于写入缓冲区大小,目标读取字节数等于读取缓冲区大小.
实际读取数BytesReturned小于目标读取字节数OutBufferSize.
DeviceIoControl函数运行后,首先根据InBufferSize和OutBufferSize中较大者设置SystemBuffer,用零填充.之后复制写入缓冲区数据到系统缓冲区.然后写操作系统缓冲区,并把实际操作字节数Information返回R3,即BytesReturned.