21.1 内核基础知识介绍
21.1.3 派遣例程与 IRP 结构
- Ring3 通过 DeviceIoControl 等函数向驱动发出 I/O 请求后,在内核中由操作系统将其转化为 IRP 的数据结构,并“派遣”到对应驱动的派遣函数中。也就是说Ring3发出的的I/O请求在内核中由操作系统转化为IRP数据结构,然后再发送给对应驱动的派遣函数。
21.1.4 Ring3 打开驱动设备
- 要让Ring3能够打开设备,需要驱动程序创建符号链接。
- 在helloword.c中的驱动程序中创建一个设备,并且为该设备创建符号链接,符号链接的格式为“\DosDevices\DosDeviceName”:
Ring3通过CreateFile函数打开设备,CreateFile函数的文件名参数和符号链接名称不同,应该是“\.\DosDeviceName”
HANDLE CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
21.1.5 DeviceIoControl 函数与 IoControlCode
IoControlCode是DeviceIoControl函数的参数。上面Ring3使用CreateFile打开驱动设备后,接下来又使用DeviceIoControl函数和驱动进行通信或调用驱动的派遣例程函数。
BOOL DeviceIoControl(
HANDLE hDevice, //设备句柄
DWORD dwIoControlCode, //Io 控制号
LPVOID lpInBuffer, //输入缓冲区指针
DWORD nInBufferSize, //输入缓冲区字节数
LPVOID lpOutBuffer, //输出缓冲区指针
DWORD nOutBufferSize, //输出缓冲区字节数
LPDWORD lpBytesReturned, //返回输出字节数
LPOVERLAPPED lpOverlapped //异步调用时指向的 OVERLAPPED 指针
);
参数dwIoControlCode非常重要,它的结构由宏 CTL_CODE 构造而成,如下:
- DeviceType 表示设备类型;
- Access 表示对设备的访问权限;
- Function 表示设备 IoControl 的功能号,0~0x7ff 为微软保留,0x800~0xfff 由程序员自己定义;
- Method 表示 Ring3/Ring0 的通信中的内存访问方式,有四种方式:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT 和METHOD_NEITHER。
#define CTL_CODE(DeviceType, Function, Method, Access) ( ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
21.1.6 Ring3/Ring0 的四种通信方式
Ring3和Ring0间的通信使用的是DeviceIoControl函数,而DeviceIoControl函数中有一个IoControlCode的参数,这个参数中的Method结构规定了Ring3和Ring0的通信方式。
方式1:METHOD_BUFFERED
(1)系统将Ring3指定的输入缓冲区(UserInputBuffer)数据,按照指定的长度(InputBufferLen)复制到Ring0中事先分配好的缓冲内充(SystemBuffer)中。
(2)驱动程序就可以将 SystemBuffer 视为输入数据进行读取,当然也可以将SystemBuffer 视为输出数据的缓冲区,也就是说 SystemBuffer 既可以读也可以写。
(3)驱动程序处理完后,系统会按照 pIrp->IoStatus->Information 指定的字节数,将SystemBuffer上的数据复制到 Ring3 指定的输出缓冲区(UserOutputBuffer)中。方式2:METHOD_NEITHER
METHOD_NEITHER方式不对Ring3的输入输出进行缓冲(METHOD_BUFFERED对Ring3的输入输出会进行缓冲到SystemBuffer)
- METHOD_NEITHER方式可以直接使用Ring3的输入输出内存地址。
- METHOD_NEITHER的内存访问方式:
- 驱动程序可以通过 pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer 得到Ring3 的输入缓冲区地址(其中 pIrpStack 是 IoGetCurrentIrpStackLocation(pIrp)的返回);
- 通过 pIrp->UserBuffer 得到 Ring3 的输出缓冲区地址。
方式3和方式4:METHOD_IN_DIRECT、METHOD_OUT_DIRECT
- 使用这两种方式,系统依然对Ring3 的输入缓冲区进行缓冲,但是对 Ring3 的输出缓冲区并没有缓冲,而是在内核中进行了锁定,具体的:
- 对于Ring3的输入缓冲区和 METHOD_BUFFERED 方式是一致的。
- 对于Ring3的输出缓冲区,首先由系统锁定,并使用 pIrp->MdlAddress 来描述这段内存,驱动程序需要使用 MmGetSystemAddressForMdlSafe 函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入 OutputBuffer 地址,最终在驱动派遣例程返回后,由系统解除这段内的锁定。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别仅在于打开设备的权限上:
按照漏洞严重程度可分为以下 4 类:远程拒绝服务、本地拒绝服务、远程任意代码执行和本地权限提升:
- 远程拒绝服务是指能够利用来使得远程系统崩溃或资源耗尽的内核程序 bug或缺陷。
- 本地拒绝服务是指能够利用来使得本地系统崩溃或资源耗尽的内核程序 bug或缺陷。
- 按照漏洞利用原理可分为以下 4 类:拒绝服务、缓冲区溢出、内存篡改和设计缺陷:
- 任意地址写任意数据:指能够利用来使得向任意内核空间虚拟地址写入任意数据的内核程序 bug 或缺陷。
- 固定地址写任意数据:指能够利用来使得向固定内核空间虚拟地址写入任意数据的内核程序 bug 或缺陷。
- 任意地址写固定数据:指能够利用来使得向任意内核空间虚拟地址写入固定数据的内核程序 bug 或缺陷。