0环与3环通信(常规方式)
前言:作为0环与3环通信(常规方式)的学习笔记
设备对象
我们在开发窗口程序的时候,消息被封装成一个结构体:MSG,在内核开发时,消息被封装成另外一个结构体:IRP(I/O Request Package)。
在窗口程序中,能够接收消息的只能是窗口对象。在内核中,能够接收IRP消息的只能是设备对象。
创建设备对象
3环是不能直接0环进行通信,还需要一个介质,这里的话就是通过设备对象。
这里需要注意的是创建设备对象的名字为\\Device\\自定义的名称
,其中Device
标识当前创建的对象为设备对象,不是一定需要为Device
,为了规范最好是Device
//创建设备名称 UNICODE_STRING Devicename; RtlInitUnicodeString(&Devicename,L"\\Device\\MyDevice"); //创建设备 IoCreateDevice( pDriver, //当前设备所属的驱动对象 0, &Devicename, //设备对象的名称 FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObj //设备对象指针 );
设置交互数据的方式
0环的设备对象与外部读写有多种交互数据的方式,如下举例三种
缓冲区方式读写(DO_BUFFERED_IO)
pDeviceObject->Flags |= DO_BUFFERED_IO;
操作系统将应用程序提供缓冲区的数据复制到内核模式下的地址中。
直接方式读写(DO_DIRECT_IO)
pDeviceObject->Flags |= DO_DIRECT_IO;
操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。缺点就是要单独占用物理页面。
其他方式读写(在调用IoCreateDevice创建设备后对pDevObj->Flags即不设置DO_BUFFERED_IO也不设置DO_DIRECT_IO此时就是其他方式)
在使用其他方式读写设备时,派遣函数直接读写应用程序提供的缓冲区地址。在驱动程序中,直接操作应用程序的缓冲区地址是很危险的。只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。
为什么直接读写应用程序提供的缓冲区地址会很危险呢?A进程读写0x12345678,当进程切换B的时候,CR3寄存器中的值同样也会切换,那么相同线性地址0x12345678,此时读写的时候物理地址就不同,那么就可能会导致蓝屏。
创建符号链接
//创建符号链接名称 RtlInitUnicodeString(&SymbolicLinkName,L"\\??\\MyTestDriver"); //创建符号链接 IoCreateSymbolicLink(&SymbolicLinkName,&Devicename);
特别说明:
1、设备名称的作用是给内核对象用的,如果要在Ring3访问,必须要有符号链接
其实就是一个别名,没有这个别名,在Ring3不可见。
2、内核模式下,符号链接是以\??\
开头的,如C盘就是\??\C:
3、而在用户模式下,则是以\\.\
开头的,如C盘就是\\.\C:
IRP与派遣函数
IRP消息类型
1、当应用层通过CreateFile,ReadFile,WriteFile,CloseHandle等函数打开、从设备读取数据、向设备写入数据、关闭设备的时候,会使操作系统产生对应的IRP_MJ_CREATE,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_CLOSE等不同的IRP。
2、其他类型的IRP
如何注册派遣函数
_DRIVER_OBJECT的MajorFunction数组对象中,这个MajorFunction是一个存储对应不同的IRP消息下标所处理的派遣函数数组
kd> dt _DRIVER_OBJECT nt!_DRIVER_OBJECT +0x000 Type : Int2B +0x002 Size : Int2B +0x004 DeviceObject : Ptr32 _DEVICE_OBJECT +0x008 Flags : Uint4B +0x00c DriverStart : Ptr32 Void +0x010 DriverSize : Uint4B .... +0x030 DriverStartIo : Ptr32 void +0x034 DriverUnload : Ptr32 void //卸载函数 +0x038 MajorFunction : [28] Ptr32 long //派遣函数
所以如果想要设置派遣函数的话,那么对MajorFunction对应的坐标类型进行设置相关的函数即可
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) { //设置卸载函数 pDriverObject->DriverUnload = 卸载函数; //设置派遣函数 pDriverObject->MajorFunction[IRP_MJ_CREATE] = 派遣函数1; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = 派遣函数2; pDriverObject->MajorFunction[IRP_MJ_WRITE] = 派遣函数3; pDriverObject->MajorFunction[IRP_MJ_READ] = 派遣函数4; pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = 派遣函数5; pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = 派遣函数6; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 派遣函数7; pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = 派遣函数8; pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = 派遣函数9; }
派遣函数的写法
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp) { //处理自己的业务... //设置返回状态 pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值 pIrp->IoStatus.Information = 0; // 返回给3环多少数据 没有填0 IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
总结
如果一个3环的程序想要跟0环的进行相互通信的话,那么需要如下步骤
1、0环通过DRIVER_OBJECT驱动对象创建IoCreateDevice对应的DEVICE_OBJECT设备对象,然后指定数据交互方式Flags,并且给这个DEVICE_OBJECT设备对象通过IoCreateSymbolicLink
2、0环还需要给驱动对象设置相关的派遣函数MajorFunction,这个函数当驱动对象接收到了相关的消息CTL_CODE,然后会将给消息发给对应的IoCreateSymbolicLink名称的设备对象,然后设备对象中如果有对该消息的处理,那么会对该消息进行相应的处理。
3环通过IRP_MJ_DEVICE_CONTROL交互0环数据
前面有提到过当应用层通过CreateFile,ReadFile,WriteFile,CloseHandle等函数打开、从设备读取数据、向设备写入数据、关闭设备的时候,会使操作系统产生对应的IRP_MJ_CREATE,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_CLOSE等不同的IRP。
那么这里着重来学习IRP_MJ_DEVICE_CONTROL消息,应用层可以通过DeviceControl来使得操作系统产生IRP_MJ_DEVICE_CONTROL消息
驱动层代码
#include <ntddk.h> #define DEVICE_NAME L"\\Device\\MyDevice" #define LINK_NAME L"\\??\\test" // \\\\.\\test #define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OPER2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS) VOID UnDriver(PDRIVER_OBJECT driver) { DbgPrint(("Uninstall Driver Is OK \n")); } NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp) { DbgPrint("IrpCreateProc...\n"); pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp) { DbgPrint("IrpCloseProc...\n"); pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp) { //处理自己的业务... NTSTATUS nStatus = STATUS_INVALID_DEVICE_REQUEST; PIO_STACK_LOCATION pIrpStack; ULONG uIoControlCode; PVOID pIoBuffer; ULONG uInLength; ULONG uOutLength; ULONG uRead; ULONG uWrite; DbgPrint("IrpDeviceConrolProc...\n"); // 设置临时变量的值 uRead = 0; uWrite = 0x12345678; // 获取IRP数据 pIrpStack = IoGetCurrentIrpStackLocation(pIrp); // 获取控制码 uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; // 获取缓冲区地址(输入输出是同一个) pIoBuffer = pIrp->AssociatedIrp.SystemBuffer; // Ring3 发送数据的长度 uInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength; // Ring0 发送数据的长度 uOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength; switch (uIoControlCode) { case OPER1: { DbgPrint("IrpDeviceControlProc -> OPER1...\n"); pIrp->IoStatus.Information = 0; nStatus = STATUS_SUCCESS; break; } case OPER2: { DbgPrint("IrpDeviceControlProc -> OPER2 输入字节数: %d\n", uInLength); DbgPrint("IrpDeviceControlProc -> OPER2 输出字节数: %d\n", uOutLength); // 读取缓冲区 memcpy(&uRead, pIoBuffer, 4); DbgPrint("IrpDeviceControlProc -> OPER2 uRead: %x\n", uRead); // 写入缓冲区 memcpy(pIoBuffer, &uWrite, 4); DbgPrint("IrpDeviceControlProc -> OPER2 uWrite: %x\n", uWrite); // 设置状态 pIrp->IoStatus.Information = 2; // 返回两字节 nStatus = STATUS_SUCCESS; break; } default: pIrp->IoStatus.Information = 0; break; } //设置返回状态 pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值 IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } PDRIVER_OBJECT pDriverObject; NTSTATUS test01() { UNICODE_STRING DeviceName; UNICODE_STRING SymbolicLinkName; PDEVICE_OBJECT pDeviceObject = NULL; NTSTATUS nStatus; // 创建设备 RtlInitUnicodeString(&DeviceName, DEVICE_NAME); nStatus = IoCreateDevice( pDriverObject, //当前设备所属的驱动对象 0, &DeviceName, //设备对象的名称 FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject //设备对象指针 ); if (nStatus != STATUS_SUCCESS) { DbgPrint("IoCreateDevice Failed\n"); return -1; } // 交互数据方式 pDeviceObject->Flags |= DO_BUFFERED_IO; // 创建符号链接名称 RtlInitUnicodeString(&SymbolicLinkName, LINK_NAME); nStatus = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName); if (nStatus != STATUS_SUCCESS) { DbgPrint("IoCreateSymbolicLink Failed\n"); return -1; } pDriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceControlProc; return STATUS_SUCCESS; } NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) { pDriverObject = Driver; DbgPrint(("hello zpchcbd \n")); Driver->DriverUnload = UnDriver; test01(); return STATUS_SUCCESS; }
应用层代码
// IRPTest_R3.cpp : 定义控制台应用程序的入口点。 // #include <Windows.h> //#define DEVICE_NAME L"\\Device\\HbgDev" #define SYMBOLICLINK_NAME L"\\\\.\\test" #define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OPER2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IN_BUFFER_MAXLENGTH 4 #define OUT_BUFFER_MAXLENGTH 4 int main(int argc, char* argv[]) { // 获取设备句柄 HANDLE hDevice = CreateFile(SYMBOLICLINK_NAME, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); DWORD dwError = GetLastError(); if (hDevice == NULL) { printf("CreateFile Failed\n"); return -1; } // 测试通信 DWORD dwInBuffer = 0x11111111; DWORD dwOutBuffer = 0xFFFFFFFF; DWORD dwOutNumber; DeviceIoControl(hDevice, OPER2, &dwInBuffer, IN_BUFFER_MAXLENGTH, &dwOutBuffer, OUT_BUFFER_MAXLENGTH, &dwOutNumber, NULL); printf("dwOutBuffer: %08X dwOutNumber: %08X\n", dwOutBuffer, dwOutNumber); // 关闭设备 CloseHandle(hDevice); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY