【编程技术-驱动通信基础】此文章归类为:编程技术。
驱动本身无法进行通信,通信一般是通过设备进行通信,设备对象是挂在驱动对象下的,一个驱动对象可以创建多个设备对象

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 | typedef struct _DEVICE_OBJECT { CSHORT Type; // 设备类型 USHORT Size; // 设备大小 LONG ReferenceCount; struct _DRIVER_OBJECT *DriverObject; // 由哪个驱动对象创建 struct _DEVICE_OBJECT *NextDevice; //下一个设备对象 struct _DEVICE_OBJECT *AttachedDevice; // 附加设备对象 struct _IRP *CurrentIrp; // 当前设备对象指向的IRP请求包 PIO_TIMER Timer; // 定时器 ULONG Flags; ULONG Characteristics; // 设备属性 __volatile PVPB Vpb; PVOID DeviceExtension; DEVICE_TYPE DeviceType; // 设备具体类型 CCHAR StackSize; union { LIST_ENTRY ListEntry; // 插入LIST_ENTRY,使DeivceObject称为链表结构 WAIT_CONTEXT_BLOCK Wcb; } Queue; ULONG AlignmentRequirement; KDEVICE_QUEUE DeviceQueue; KDPC Dpc; ULONG ActiveThreadCount; PSECURITY_DESCRIPTOR SecurityDescriptor; // 安全描述符 KEVENT DeviceLock; USHORT SectorSize; USHORT Spare1; struct _DEVOBJ_EXTENSION *DeviceObjectExtension; PVOID Reserved;} DEVICE_OBJECT, *PDEVICE_OBJECT; |
存在两个设备类型,一个是Type,一个是DeviceType
- Type:描述对象的基本类型
- DeviceType:描述设备的具体类型NextDevice,通过该设备对象指向下一个设备对象,所以在驱动下的设备对象也是一个链表结构,并且该链表为单向链表,最后一个设备对象的NextDevice为空,而对于AttachedDevice,首先需要明确设备对象也有区分,一个是FDO过滤设备对象,一个是PDO物理设备对象,该对象是对硬件的一个抽象,通过该抽象可以控制设备
而内核驱动中对于PDO的通信都会先通过FDO,由FDO决定消息是否往PDO发送,而FDO可能有多个:

CurrentIrp指向当前设备的IRP,IRP为Io Request Package ,理解为Web的POST和GET包即可,具体参数通过实验再一步步了解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | nt!_IRP +0x000 Type : Int2B +0x002 Size : Uint2B +0x004 MdlAddress : Ptr32 _MDL +0x008 Flags : Uint4B +0x00c AssociatedIrp : <unnamed-tag> +0x010 ThreadListEntry : _LIST_ENTRY +0x018 IoStatus : _IO_STATUS_BLOCK +0x020 RequestorMode : Char +0x021 PendingReturned : UChar +0x022 StackCount : Char // 设备栈的数量 +0x023 CurrentLocation : Char // 当前设备栈的索引 +0x024 Cancel : UChar +0x025 CancelIrql : UChar +0x026 ApcEnvironment : Char +0x027 AllocationFlags : UChar +0x028 UserIosb : Ptr32 _IO_STATUS_BLOCK +0x02c UserEvent : Ptr32 _KEVENT +0x030 Overlay : <unnamed-tag> +0x038 CancelRoutine : Ptr32 void +0x03c UserBuffer : Ptr32 Void +0x040 Tail : <unnamed-tag> |
通过IoCreateDevice创建设备对象:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | NTSTATUSIoCreateDevice( _In_ PDRIVER_OBJECT DriverObject, // 指向的驱动对象 _In_ ULONG DeviceExtensionSize, // 设备扩展内存,如有会分配对应字节的空间 _In_opt_ PUNICODE_STRING DeviceName, // 设备名称 _In_ DEVICE_TYPE DeviceType, // 设备类型 _In_ ULONG DeviceCharacteristics, // 设备属性 _In_ BOOLEAN Exclusive, _Outptr_result_nullonfailure_ _At_(*DeviceObject, __drv_allocatesMem(Mem) _When_((((_In_function_class_(DRIVER_INITIALIZE)) ||(_In_function_class_(DRIVER_DISPATCH)))), __drv_aliasesMem)) PDEVICE_OBJECT *DeviceObject ); |
设备类型,总共是0x5D种类型,大部分类型针对到具体的设备,在wdm.h中具有所有DEVICE_TYPE的枚举定义,这里我们直接用FILE_DEVICE_UNKNOWN
1 2 3 4 5 6 7 8 9 10 11 | #define FILE_DEVICE_BEEP 0x00000001#define FILE_DEVICE_CD_ROM 0x00000002#define FILE_DEVICE_CD_ROM_FILE_SYSTEM 0x00000003......#define FILE_DEVICE_UNKNOWN 0x00000022......#define FILE_DEVICE_PERSISTENT_MEMORY 0x00000059#define FILE_DEVICE_NVDIMM 0x0000005a#define FILE_DEVICE_HOLOGRAPHIC 0x0000005b#define FILE_DEVICE_SDFXHCI 0x0000005c#define FILE_DEVICE_UCMUCSI 0x0000005d |
设备属性在DEVICE_OBJECT中有定义,一般创建的虚拟对象选择FILE_DEVICE_SECURE_OPEN即可:

Exclusive:根据文档提示直接设置成FALSE即可

此时就能成功创建一个设备了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | UNICODE_STRING deviceName = { 0 };RtlInitUnicodeString(&deviceName, DEVICE_NAME);PDEVICE_OBJECT pDevice = NULL;NTSTATUS status = IoCreateDevice( pDriver, 0, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, pDevice);if (NT_SUCCESS(status)) { KdPrintEx((77, 0, "res: %x\r\n", status)); return status;} |
但此时创建的设备还无法被三环下的程序进行访问,还需要通过一个符号链接将设备的接口暴露给三环,通过IoCreateSymbolicLink绑定设备
1 2 3 4 5 6 7 8 9 10 11 12 | #define SYM_NAME L"\\??\\TestDevice"UNICODE_STRING symName = { 0 };RtlInitUnicodeString(&symName, SYM_NAME);// 绑定符号链接status = IoCreateSymbolicLink(&symName, &deviceName);if (!NT_SUCCESS(status)) { IoDeleteDevice(pDevice); KdPrintEx((77, 0, "res: %x\r\n", status)); return status;} |
在某些系统下,创建完设备后需要将标志位上的初始化去掉,并且设置通信方式:
1 2 | pDevice->Flags &= ~DO_DEVICE_INITIALIZING;pDevice->Flags |= DO_BUFFERED_IO; |
当完成以上操作后,此时还需要一个派遣函数,来处理接收到的消息:
1 2 3 4 | NTSTATUS dispatch( IN PDEVICE_OBJECT dev, IN PIRP irp); |
在驱动对象的最后一组参数中:
1 | PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; |
跟进看到所有的IRP功能号的枚举值:
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 | #define IRP_MJ_CREATE 0x00#define IRP_MJ_CREATE_NAMED_PIPE 0x01#define IRP_MJ_CLOSE 0x02#define IRP_MJ_READ 0x03#define IRP_MJ_WRITE 0x04#define IRP_MJ_QUERY_INFORMATION 0x05#define IRP_MJ_SET_INFORMATION 0x06#define IRP_MJ_QUERY_EA 0x07#define IRP_MJ_SET_EA 0x08#define IRP_MJ_FLUSH_BUFFERS 0x09#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a#define IRP_MJ_SET_VOLUME_INFORMATION 0x0b#define IRP_MJ_DIRECTORY_CONTROL 0x0c#define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d#define IRP_MJ_DEVICE_CONTROL 0x0e#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f#define IRP_MJ_SHUTDOWN 0x10#define IRP_MJ_LOCK_CONTROL 0x11#define IRP_MJ_CLEANUP 0x12#define IRP_MJ_CREATE_MAILSLOT 0x13#define IRP_MJ_QUERY_SECURITY 0x14#define IRP_MJ_SET_SECURITY 0x15#define IRP_MJ_POWER 0x16#define IRP_MJ_SYSTEM_CONTROL 0x17#define IRP_MJ_DEVICE_CHANGE 0x18#define IRP_MJ_QUERY_QUOTA 0x19#define IRP_MJ_SET_QUOTA 0x1a#define IRP_MJ_PNP 0x1b#define IRP_MJ_PNP_POWER IRP_MJ_PNP // Obsolete....#define IRP_MJ_MAXIMUM_FUNCTION 0x1b |
该函数中指向的是驱动的派遣函数,可以将每个功能号都对应一个派遣函数,在正式开发中,对每个功能编写一个派遣函数是最佳选择,在该测试中,可以将所有的功能号都指向同一个派遣函数:
1 2 3 | for (int i = 0; i < IRP_MJ_MAXIMUN_FUNCTION; i++) { driver->MajorFunction[i] = testDispatch; } |
主要通信方式:
1 2 3 4 5 6 | // 读写的方式#define IRP_MJ_READ 0x03#define IRP_MJ_WRITE 0x04// 设备IO的方式#define IRP_MJ_DEVICE_CONTROL 0x0e |
在驱动对象的MajorFunction中的下标跟功能号的枚举值是一一对应的,所以也可以不用对所有的功能号都填充派遣函数,仅填充基础通信的派遣函数即可:
1 2 3 4 5 6 7 8 | #define IRP_MJ_CREATE 0x00#define IRP_MJ_CLOSE 0x02#define IRP_MJ_DEVICE_CONTROL 0x0epDriver->MajorFunction[IRP_MJ_CREATE] = InitDispatch;pDriver->MajorFunction[IRP_MJ_CLOSE] = InitDispatch;pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TestDispatch; |
对IRP_MJ_CREATE和IRP_MJ_CLOSE定义一个派遣函数,只需要将IoStatus的状态设置成STATUS_SUCCESS即可,调用IoCompeleteRequest表示IRP请求已完成:
1 2 3 4 5 6 7 8 | NTSTATUS InitDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS status = STATUS_SUCCESS; ULONG retLen = 0; Irp->IoStatus.Status = status; Irp->IoStatus.Information = retLen; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status;} |
处理请求的第一步是获得请求的当前栈空间(Current Stack Location),可以通过IoGetCurrentIrpStackLocation获得,判断功能号后(一般如果在前面设置了对应功能号的派遣函数,此时没必要再判断,这里为了严谨性可以重复判断一下功能号是不是对应的),获取Parameters,该参数是一个联合体,对于不同的功能号都有不同的结构体,当前的功能号为IRP_MJ_DEVICE_CONTROL,对应的联合体结构为DeviceIoControl(该结构体不需要手动定义,在wdm.h中已定义)
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 | struct { ULONG OutputBufferLengSth; ULONG POINTER_ALIGNMENT InputBufferLength; ULONG POINTER_ALIGNMENT IoControlCode; PVOID Type3InputBuffer;} DeviceIoControl;NTSTATUS TestDispatch( PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp); // 获取IRP的栈空间 NTSTATUS status = STATUS_SUCCESS; ULONG retLen = 0; DbgBreakPoint(); if (ioStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) { PVOID buffer = Irp->AssociatedIrp.SystemBuffer; // 获取从3环输入Buffer ULONG inlen = ioStack->Parameters.DeviceIoControl.InputBufferLength; // 获取3环传入的字节数 ULONG outlen = ioStack->Parameters.DeviceIoControl.OutputBufferLength; // 获取可传回3环的字节数 switch (IoControlCode) { case TEST_CODE: { int* buffer = (int*)Irp->AssociatedIrp.SystemBuffer; // 读取3环传入的字符串 KdPrintEx((77, 0, "[db]: %x\r\n", *buffer)); break; } }} |
IoControlCode控制码的定义:
1 2 3 | #define CTL_CODE( DeviceType, Function, Method, Access ) ( \ ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \) |
需要传入四个参数:
DeviceType:由于控制设备和任何硬件没有关系,次数的设备控制是抽象的,而不是硬件设备控制,所以一般都设置为FILE_DEVICE_UNKNOWNFunction:功能号的核心数字,其中0x000-0x7ff被微软预留,所以个人设置的话必须在(0x7ff, 0xfff]之间Method:第三个设置为METHOD_BUFFERED,表示为用缓冲方式,相对来说比较安全Access:FILE_READ_DATA, FILE_WRITE_DATA,一般设置成FILE_ANY_ACCESS即可1 2 | #define CODE_CTR_INDEX 0x800#define TEST_CODE CTL_CODE(FILE_DEVICE_UNKNOWN, CODE_CTR_INDEX, METHOD_BUFFERED, FILE_ANY_ACCESS) |
启动驱动后,再运行应用层程序,此时程序就获取到IRP包后断下,可以看到创建的设备对象,此时的驱动对象指向的第一个设备对象就是创建的设备对象,StackSize=1,说明没有其他设备附加到当前设备对象上,只有设备对象本身,驱动对象跟设备对象此时就是互相引用的状态:

dt Irp -r1展开一层查看IRP请求包:

此时成功将R3的数据传入到0环中:


以上为Ring3到Ring0的数据传输,还有Ring0-Ring3的数据传输,也是类似的,通过DeviceIoControl设置lpOutBuffer和nOutBufferSize即可:
应用层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #define TEST_R0TOR3_CODE (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, CRL_CODE_INDEX+1, METHOD_BUFFERED, FILE_ANY_ACCESS)void receiveMessage() { HANDLE pDevice = OpenDevice(); int ret = 0; ULONG retLen = 0; BOOLEAN res = DeviceIoControl(pDevice, TEST_R0TOR3_CODE, NULL, NULL, &ret, sizeof(ULONG), &retLen, NULL); if (!res) { printf("[db] Receive Message Failed..\r\n"); CloseHandle(pDevice); } printf("[db] Receive Message From Ring0: %x\r\n", ret);} |
驱动层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | switch (ioStack->Parameters.DeviceIoControl.IoControlCode) { case TEST_R3TOR0_CODE: { // receive Message KdPrintEx((77, 0, "[db]:%s\r\n", buffer)); break; } case TEST_R0TOR3_CODE: { // send Message ULONG x = 2000; memcpy(buffer, &x, outlen); Irp->IoStatus.Information = outlen; break; } default: status = STATUS_INVALID_PARAMETER; break;} |


注意到此时的SystemBuffer的地址3环是不可读不可写的,对比前面ring3到ring0的读取操作,当从ring0往ring3写数据时,多了一块UserBuffer的空间,该空间就是3环下读取ring0写入的地址,也就是说会将SystemBuffer中的值复制一份到Userbuffer:

此时UserBuffer中的值还是空,说明此时仅开辟了空间未做内存复制,通过ba对该地址下硬件断点:

1 | ba w4 0x0015fb2c |
当下完断点运行时,可以在IoCompleteRequest的时候断下,此时会根据IoStatus.Information的值,将SystemBuffer的值赋值到UserBuffer中,通过bc *清除硬件断点,输出结果:


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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #include <ntifs.h>#define CRL_CODE_INDEX 0x800#define TEST_R3TOR0_CODE (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, CRL_CODE_INDEX, METHOD_BUFFERED, FILE_ANY_ACCESS)#define TEST_R0TOR3_CODE (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, CRL_CODE_INDEX+1, METHOD_BUFFERED, FILE_ANY_ACCESS)#define CDO_NAME L"\\Device\\TestDevice2"#define SYM_NAME L"\\??\\TestDevice2"PDEVICE_OBJECT pDevice = NULL;NTSTATUS TestCreateCDO(PDRIVER_OBJECT pDriver) { UNICODE_STRING cdoName = RTL_CONSTANT_STRING(CDO_NAME); NTSTATUS status = IoCreateDevice( pDriver, 0, &cdoName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice ); return status;}NTSTATUS TestCreateSymbolicLink(PDRIVER_OBJECT drvier) { UNICODE_STRING cdoName = RTL_CONSTANT_STRING(CDO_NAME); UNICODE_STRING symName = RTL_CONSTANT_STRING(SYM_NAME); return IoCreateSymbolicLink(&symName, &cdoName);}NTSTATUS InitDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS status = STATUS_SUCCESS; ULONG retLen = 0; Irp->IoStatus.Status = status; Irp->IoStatus.Information = retLen; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status;}NTSTATUS TestDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp); NTSTATUS status = STATUS_SUCCESS; ULONG retLen = 0; if (ioStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) { switch (ioStack->Parameters.DeviceIoControl.IoControlCode) { case TEST_R3TOR0_CODE: { //DbgBreakPoint(); PVOID buffer = Irp->AssociatedIrp.SystemBuffer; ULONG inlen = ioStack->Parameters.DeviceIoControl.InputBufferLength; // receive Message KdPrintEx((77, 0, "[db]:%s\r\n", buffer)); break; } case TEST_R0TOR3_CODE: { ULONG outlen = ioStack->Parameters.DeviceIoControl.OutputBufferLength; // send Message ULONG x = 2000; memcpy(Irp->AssociatedIrp.SystemBuffer, &x, outlen); Irp->IoStatus.Information = outlen; // 设置 DbgBreakPoint(); break; } default: status = STATUS_INVALID_PARAMETER; break; } } Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status;}VOID Unload(PDRIVER_OBJECT driver) { UNICODE_STRING symName = RTL_CONSTANT_STRING(SYM_NAME); if (NT_SUCCESS(IoDeleteSymbolicLink(&symName))) { KdPrintEx((77, 0, "Delete Symbolic Link..\r\n")); } IoDeleteDevice(pDevice);}NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) { pDriver->DriverUnload = Unload; pDriver->MajorFunction[IRP_MJ_CREATE] = InitDispatch; pDriver->MajorFunction[IRP_MJ_CLOSE] = InitDispatch; pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TestDispatch; NTSTATUS status; status = TestCreateCDO(pDriver); if (!NT_SUCCESS(status)) { KdPrint(("create CDO Failed...")); return status; } pDevice->Flags &= ~DO_DEVICE_INITIALIZING; pDevice->Flags |= DO_BUFFERED_IO; status = TestCreateSymbolicLink(pDriver); if (!NT_SUCCESS(status)) { KdPrint(("create symboliclink failed...")); return status; } return STATUS_SUCCESS;} |
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 | #include <Windows.h>#include <tchar.h>#include <stdio.h>#define CWK_DEV_SYM L"\\\\.\\TestDevice2"#define CRL_CODE_INDEX 0x800#define TEST_R3TOR0_CODE (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, CRL_CODE_INDEX, METHOD_BUFFERED, FILE_ANY_ACCESS)#define TEST_R0TOR3_CODE (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, CRL_CODE_INDEX+1, METHOD_BUFFERED, FILE_ANY_ACCESS)HANDLE OpenDevice() { HANDLE pDevice = NULL; pDevice = CreateFile( CWK_DEV_SYM, GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0 ); // check DEVICE_OBJECT if (pDevice == INVALID_HANDLE_VALUE) { printf("Open Device Failed. \r\n"); return NULL; } return pDevice;}void sendMessage() { HANDLE pDevice = OpenDevice(); char msgArray[] = "Test Message From app.\r\n"; char* msg = msgArray; ULONG retLen = 0; BOOLEAN res = DeviceIoControl(pDevice, TEST_R3TOR0_CODE, msg, strlen(msg) + 1, NULL, NULL, &retLen, NULL); if (!res) { printf("Send Message Failed..\r\n"); } CloseHandle(pDevice);}void receiveMessage() { HANDLE pDevice = OpenDevice(); int ret = 0; ULONG retLen = 0; BOOLEAN res = DeviceIoControl(pDevice, TEST_R0TOR3_CODE, NULL, NULL, &ret, 4, &retLen, NULL); if (!res) { printf("Receive Message Failed..\r\n"); } printf("retLen: %d\r\n", retLen); printf("[db] Receive Message From Ring0: %d\r\n", ret); CloseHandle(pDevice);}int _tmain(int argc, _TCHAR* argv[]) { sendMessage(); receiveMessage(); return 0;} |
前面提到过,在0环下的栈空间只有12KB,而3环下有1MB,此时如果将1MB的数据从3环传到0环,就会直接蓝屏
可以通过一个链表存储从应用层接收的字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #define CWK_STR_LEN_MAX 512typedef struct { LIST_ENTRY list_entry; char buf[CWK_STR_LEN_MAX];} CWK_STR_NODE;// 自旋锁保证链表操作的安全性KSPIN_LOCK g_cwk_lock;// 事件标识是否有字符串可以取KEVENT g_cwk_event;// 链表头LIST_ENTRY g_cwk_str_list; |
分配内存并初始化一个链表节点:
1 2 3 4 5 6 7 8 9 10 11 12 | #define MEM_TAG "MyMm"CWK_STR_NODE *cwkMallocStrNode() { CWK_STR_NODE *ret = ExAllocatePoolWithTag( NonPagedPool, sizeof(CWK_STR_NODE), MEM_TAG ); if (ret == NULL) { return NULL; } return ret;} |
在前面的代码中可以发现存在ASSERT断言,需要注意断言是不能作为正式版使用,仅在开发调试中使用,所以需要手动做缓冲区长度检查:
1 2 3 4 5 | case CWK_DVC_SEND_STR: if (inlen > CWK_STR_LEN_MAX) { status = STATUS_INVALID_PARAMETER; break; } |
安全检查的原则之一就是越简单越好
当前面做了输入长度的限制,攻击者可使用的缓冲区攻击的长度就从无限大急剧缩小到512字节
输入缓冲区的长度即使是正确,也需要对内容做检查
使用strlen内容检查往往是不正确,因为strlen是以\\0 为结束符,攻击者只需要构造没有\\0的字符串,strlen则会一直找下去,这很有可能导致异常。
所以可以使用strnlen检查字符串长度,因为其存在一个最大限度,超过限度便不会继续搜索结束符
1 2 3 4 5 6 | // strnlen检查字符串长度DbgPrint("strnlen = %d\\r\\n", strnlen((char*)buffer, inlen));if (strnlen((char*)buffer, inlen) == inlen) { status = STATUS_INVALID_PARAMETER; break;} |
1 2 3 4 5 6 | // 初始化内存,并创建一个缓冲区节点,若初始化失败,则返回资源不足str_node = cwkMallocStrNode();if (str_node == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; return status;} |
字符串拷贝出于安全考虑,也需要用strncpy替代strcpy:
1 2 3 4 5 6 7 | strncpy(str_node->buf, (char*)buffer, CWK_STR_LEN_MAX);// 加锁保证线程安全ExInterlockedInsertTailList(&g_cwk_str_list, (PLIST_ENTRY)str_node, &g_cwk_lock);KeSetEvent(&g_cwk_event, 0, TRUE);break; |
以上是对于输入处理,输出也需要进行处理,同样需要通过缓冲区:
1 2 3 4 5 6 7 8 | ASSERT(buffer != NULL);ASSERT(inlen == 0);// 直接判断是否大于最大长度,大于则直接返回失败if (outlen < CWK_STR_LEN_MAX) { status = STATUS_INVALID_PARAMETER; break;} |
通过while死循环做等待会浪费CPU资源,所以此时需要用到事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 | while(1) { str_node = (CWK_STR_NODE*)ExInterlockedRemoveHeadList(&g_cwk_str_list, &g_cwk_lock); if (str_node != NULL) { strncpy((char*)buffer, str_node->buf, CWK_STR_LEN_MAX); ret_len = strnlen(str_node->buf, CWK_STR_LEN_MAX) + 1; ExFreePool(str_node); break; } else { // 等待事件释放 KeWaitForSingleObject(&g_cwk_event, Executive, KernelMode, 0, 0); }} |
到这里基本输入输出就完成了,然后需要注意链表节点结构需要卸载:
1 2 3 4 5 6 7 8 9 10 11 12 | while(TRUE) { str_node = (CWK_STR_NODE *) ExInterlockedRemoveHeadList( &g_cwk_str_list, &g_cwk_lock ); if (str_node != NULL) { ExFreePool(str_node); } else { break; }}break; |
实际的运用中,DeviceIoControl容易被检测,只需要获取到驱动对象,通过驱动对象找到IRP_MJ_DEVICE_CONTROL下的派遣函数就能进行检测,所以利用IRP_MJ_READ和IRP_MJ_WRITE更容易绕过检测
1 2 | #define IRP_MJ_READ 0x03#define IRP_MJ_WRITE 0x04 |
同样,需要针对IRP_MJ_CREATE和IRP_MJ_CLOSE编写通用派遣函数:
1 2 3 4 5 6 7 8 | NTSTATUS InitDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS status = STATUS_SUCCESS; ULONG retLen = 0; Irp->IoStatus.Status = status; Irp->IoStatus.Information = retLen; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status;} |
在DeviceIoControl中有IoControlCode,此时不需要再定义IoControlCode,通过IO_STACK_LOCATION查看Read和Write的结构:
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 | //// System service parameters for: NtReadFile//struct { ULONG Length; ULONG POINTER_ALIGNMENT Key;#if defined(_WIN64) ULONG Flags;#endif LARGE_INTEGER ByteOffset;} Read;//// System service parameters for: NtWriteFile//struct { ULONG Length; ULONG POINTER_ALIGNMENT Key;#if defined(_WIN64) ULONG Flags;#endif LARGE_INTEGER ByteOffset;} Write; |
在3环中通过ReadFile和WriteFile进行读写,需要注意
ReadFile对应IRP_MJ_READ,此时是Ring3从Ring0读数据
WriteFile对应IRP_MJ_WRITE,此时是Ring3写数据到Ring0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | HANDLE pDevice = OpenDevice();DWORD p = 0;char sendStr[] = "Send Message To Ring0 By IRP_MJ_WRITE";BOOLEAN res = WriteFile(pDevice, sendStr, strlen(sendStr), &p, NULL);if (res) { printf("Write Success\r\n");}char* recStr = (char*)malloc(0x1000);res = ReadFile(pDevice, recStr, 0x1000, &p, NULL);if (res) { printf("Read Success\r\n"); printf("[db]: %s\r\n", recStr);}CloseHandle(pDevice); |
对应的驱动层代码:
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 | NTSTATUS ReadWriteDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp); NTSTATUS status = STATUS_SUCCESS; ULONG retLen = 0; PVOID buffer = Irp->AssociatedIrp.SystemBuffer; if (ioStack->MajorFunction == IRP_MJ_WRITE) { // Write Data To Ring3 ULONG readLen = ioStack->Parameters.Read.Length; LARGE_INTEGER ByteOffset = ioStack->Parameters.Read.ByteOffset; KdPrintEx((77, 0, "[db]: %s\r\n", buffer)); } if (ioStack->MajorFunction == IRP_MJ_READ) { // Read Data From Ring3 LARGE_INTEGER ByteOffset = ioStack->Parameters.Write.ByteOffset; char* writeStr = "Send Message To Ring3 by IRP_MJ_READ"; ULONG writeLen = ioStack->Parameters.Write.Length = strlen(writeStr) + 1; memcpy(buffer, writeStr, writeLen); Irp->IoStatus.Information = writeLen; } Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status;}VOID BindMajorFunctionByReadWrite(PDRIVER_OBJECT pDriver) { pDriver->DriverUnload = Unload; pDriver->MajorFunction[IRP_MJ_CREATE] = InitDispatch; pDriver->MajorFunction[IRP_MJ_CLOSE] = InitDispatch; pDriver->MajorFunction[IRP_MJ_READ] = ReadWriteDispatch; pDriver->MajorFunction[IRP_MJ_WRITE] = ReadWriteDispatch;} |

更多【编程技术-驱动通信基础】相关视频教程:www.yxfzedu.com