本篇介绍一下3环与0环通信的原理(常规方式),介绍与之相关的结构体,对象等,最后代码实现并模拟操作系统进行3环和0环的通信。
设备对象
内核通信的对象
内核中的通信,与应用层窗口间的通信类似,只是封装消息的结构体不同,接收消息结构体的对象不同
所以想要在内核通信,需要有(至少)一个用来接收和发送消息的设备对象。
创建设备对象
窗口对象的创建也与设备对象也有不少共同点,这里继续拿来类比
窗口对象:
1)可以创建多个
2)需要指定父窗口
设备对象:
1)可以创建多个
2)需要指定所属驱动对象
Windows提供了内核函数IoCreateDevice用来创建设备对象,参考如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #define DEVICE_NAME L"\\Device\\MyDevice"
PDEVICE_OBJECT pDeviceObj = NULL; UNICODE_STRING DeviceName; RtlInitUnicodeString(&DeviceName, DEVICE_NAME); NTSTATUS status = IoCreateDevice(pDriverObj, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObj);
if (status != STATUS_SUCCESS) { DbgPrint("Device Create Failed!\n"); return status; }
|
- pDriverObj:指定该设备创建后属于哪个驱动对象(PDRIVER_OBJECT)
- &DeviceName:定义了一个DEVICE_NAME的宏,被用来初始化设备对象的名字。这个名字不能随便改(即”\\Device\\xxxxx”形式),在设备创建时,会根据设备名,将该设备挂到一个名为Device的树形结构中,几乎所有设备都挂在这。若改变此值,则会挂到其它树中。
- FILE_DEVICE_UNKNOWN:该处填写设备的类型,由于我们并没有实际的设备,所以选择UNKNOWN
- &pDeviceObj:这个参数可以看作是一个二级指针,它指向一个地址。这个地址存着一个指针pDeviceObj,这个指针指向一个设备结构体。另一个要说明的是,这个参数是一个OUT类型的参数,原本pDeviceObj指向的内容是空的,在执行完设备创建的函数后,其指向创建出的设备对象。
以上为几个比较关键的参数介绍,其余参数按照上述代码填写即可,具体含义可以参考官方文档
数据传输方式
首先查看一下设备对象这个结构体,发现它有很多字段,这里我们只需要关注其中一个,就是Flags,这是一个四字节的值,设置了3环和0环数据交互的方式。语法如下:
1 2
| pDeviceObj->Flags |= DO_BUFFERED_IO;
|
来看看有哪几种方式:
- 缓冲区方式读写(DO_BUFFERED_IO):I/O管理器会在内核空间中分配一块内存,把用户空间的数据复制到这块内存中,这样内核程序就可以访问这些数据,实现数据通信。适合数据量较小时使用。(之前介绍的跨进程读取内存用的就是这种方法)
- 直接方式读写(DO_DIRECT_IO):I/O管理器会将用户空间内的某片内存对应的物理页锁住,同时在内核空间再映射一份,这样内核空间线性地址与用户空间线性地址对应的是同一个物理页,这是双方均可以对这个物理页的内容进行读写,实现数据通信,此方法适合数据量较大时使用。(类似_KUSER_SHARED_DATA结构)
- 默认方式读写(NEITHER_IO):当创建完设备对象后,不设置Flags的值,使用的就是此类读写方式。默认读写方式,仅仅提供给内核程序用户空间的线性地址,直接进行数据的读取。这样做的坏处是,如果发生线程切换,读取的就不再是同一份数据,容易造成程序读取错误。
通常情况下,我们实验的数据不会太大,主要采取DO_BUFFERED_IO这种方式。这里有一点要注意的是,在设置DeviceObject.Flags的值时,千万不要直接用”=”,必须使用”|=”,因为在创建设备对象结构体时,Flags是有初始值的,若这里直接给Flags赋值,会刷新掉之前的初始值,导致程序执行时发生错误(驱动技仅能成功执行一次,第二次会失败)。
设置符号链接
Windows规定,应用层的程序是不能直接访问设备对象的,所以符号链接诞生了。符号链接可以与设备对象绑定,这样应用层的程序就可以通过符号链接进行对设备对象的访问。符号链接在内核与3环的形式有所不同:
- 内核:符号链接以”\??\“开头,例如C盘就是”\??\C:”
- 用户模式:符号链接以”\\.\“开头,例如C就算”\\.\C:”
具体在代码中还需要加入更多的“\“用来转译符号,代码如下:
Ring0
1 2 3 4 5
| #define SYM_LINK_NAME L"\\??\\MyRing3Device"
UNICODE_STRING SymbolicLinkName; RtlInitUnicodeString(&SymbolicLinkName, SYM_LINK_NAME); IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
|
Ring3
1 2 3 4 5 6 7 8 9 10 11
| #define SYM_LINK_NAME L"\\\\.\\MyRing3Device"
HANDLE hDevice = CreateFileW( SYM_LINK_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
IRP与派遣函数
对比消息处理
先来看一张图
继续拿3环的窗口应用来做对比
用户空间:当用户单击鼠标时,会触发一个事件,操作系统会将这个事件的内容描述信息封装到一个MSG结构中,作为消息,发送给窗口对象,窗口对象接收到消息,会根据这个消息的类型,来执行相应的处理函数,我们称这种处理函数叫做回调函数。
1)触发事件:鼠标点击等
2)消息结构体:MSG
3)消息接收对象:窗口对象
4)处理函数:窗口回调函数
内核空间:当3环程序调用CreateFile函数时,这是操作系统会产生相应的IRP,这个IRP封装了3环程序调用的相关描述信息,接着会把IRP发送给内核空间的设备对象,设备对象会解析IRP,然后会根据IRP提供的信息,执行相应的派遣函数
1)触发事件:3环程序调用CreateFile函数等
2)消息结构体:IRP
3)消息接收对象:设备对象
4)处理函数:派遣函数
IRP的类型
正如3环的窗口对象,在接收到不同类型消息时会执行不同的回调函数。当应用层通过CreateFile,ReadFile,WriteFile,CloseHandle等函数对设备进行操作时,也会使操作系统产生不同种类的IRP,这里简要总结一下部分3环函数与IRP的对应关系:
应用层函数 |
IRP种类 |
CreateFile |
IRP_MJ_CREATE |
ReadFile |
IRP_MJ_READ |
WriteFile |
IRP_MJ_WRITE |
CloseHandle |
IRP_MJ_CLOSE |
DeviceIoControl |
IRP_MJ_DEVICE_CONTROL |
派遣函数
1)注册派遣函数
当IRP传递给设备对象后,会根据IRP的种类调用特定的派遣函数。不同的IRP对应不同的派遣函数,NT框架预定了28种派遣函数,可以在驱动对象MajorFunction数组中注册这些派遣函数
代码示例:
1 2 3
| pDriverObj->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc; pDriverObj->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;
|
其中IrpCreateProc和IrpCloseProc都是我们需要自己定义的派遣函数,遵守一定的格式
2)派遣函数格式
这里以IrpCreateProc来举例:
1 2 3 4 5 6 7 8 9 10 11
| NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDeviceObj, PIRP pIrp) { DbgPrint("Irp Create Dispatch Function...\n");
pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
|
参数:
1)设备对象指针
2)IRP指针
IRP是一个结构体,通过指针可以指向IRP内部的一个字段IoStatus(_IO_STATUS_BLOCK结构),该结构中有两个字段:
1)Status:三环程序调用GetLastError得到的就是这个值
2)Information:返回给3环多少数据,没有则填0
IoCompleteRequest:表示调用方已完成所有I/O请求处理操作,并将给定的IRP返回给I/O管理器
以上是必须设置的值/执行的语句,完成后,即可
代码实现
有了以上基础后就可以实现简单的3环和0环的通信了,这里附上代码:
Ring0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| #include "ntifs.h"
#define DEVICE_NAME L"\\Device\\MyDevice" #define SYM_LINK_NAME L"\\??\\MyRing3Device" #define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
VOID Drvier_Unload(PDRIVER_OBJECT pDriverObj); NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDeviceObj, PIRP pIrp); NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDeviceObj, PIRP pIrp);
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING RegistryPath) { DbgPrint("Driver is running!\n");
PDEVICE_OBJECT pDeviceObj = NULL; NTSTATUS status = 0;
UNICODE_STRING DeviceName; RtlInitUnicodeString(&DeviceName, DEVICE_NAME); status = IoCreateDevice(pDriverObj, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObj);
if (status != STATUS_SUCCESS) { DbgPrint("Device Create Failed!\n"); return status; } else { DbgPrint("Device Create Success!\n"); }
pDeviceObj->Flags |= DO_BUFFERED_IO;
UNICODE_STRING SymbolicLinkName; RtlInitUnicodeString(&SymbolicLinkName, SYM_LINK_NAME); IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
pDriverObj->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc; pDriverObj->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;
pDriverObj->DriverUnload = Drvier_Unload;
return STATUS_SUCCESS; }
NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDeviceObj, PIRP pIrp) { DbgPrint("Irp Create Dispatch Function...\n");
pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDeviceObj, PIRP pIrp) { DbgPrint("Irp Close Dispatch Function...\n");
pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
VOID Drvier_Unload(PDRIVER_OBJECT pDriverObj) { UNICODE_STRING SymbolicLinkName; RtlInitUnicodeString(&SymbolicLinkName, SYM_LINK_NAME); IoDeleteSymbolicLink(&SymbolicLinkName);
IoDeleteDevice(pDriverObj->DeviceObject);
DbgPrint("Unload Success!\n"); }
|
Ring3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include "stdafx.h" #include "Windows.h" #include "winioctl.h"
#define OPCODE1 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define OPCODE2 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS) #define SYM_LINK_NAME L"\\\\.\\MyRing3Device"
int main(int argc, char* argv[]) { getchar(); HANDLE hDevice = CreateFileW( SYM_LINK_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hDevice == INVALID_HANDLE_VALUE){ printf("Create File Failed!"); getchar(); return -1; } else { printf("Create File Success!"); }
getchar(); BOOL bCH = CloseHandle(hDevice); if(bCH != 0){ printf("Close File Success!"); } getchar(); return 0; }
|
程序测试
- 载入并运行驱动
- 运行3环程序
- 执行CreateFile
- 执行CloseHandle
查看运行结果,可以看到,在调用3环函数时,会执行相应的驱动函数。若想增加相应功能,只需在派遣函数中写上,即可在调用3环函数时执行,实现3环和0环的通信。当然,这样的结果,看着并不明显,下一篇,会介绍IRP这个结构,并进一步完善这份代码
参考链接
参考文章:CTL_CODE宏 详解
参考笔记:Joney,张嘉杰
参考代码:Joney,张嘉杰