-
-
[原创]驱动通信基础
-
发表于: 2024-11-29 23:29 2020
-
DeviceObject
驱动本身无法进行通信,通信一般是通过设备进行通信,设备对象是挂在驱动对象下的,一个驱动对象可以创建多个设备对象
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 | NTSTATUS IoCreateDevice( _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 0x0e pDriver->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控制码的定义:
- DeviceType: 设备类型,定义了设备的类别(例如,文件系统、串口、图形设备等)。
- Function: 功能代码,指定了设备支持的操作或功能。
- Method: 方法,表示如何传递数据(例如,直接存取、缓冲区存取等)。
- Access: 访问权限,指示对设备进行操作所需的权限(例如,读、写或读写)。
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 512 typedef 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 ; |
番外-ReadWrite
实际的运用中,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; } |
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
- [原创]驱动通信基础 2021
- [原创]PE解析思路 17969
- [原创] SWPUCTF 2019 easyRE 9104
- [原创] ACTF2020 Splendid_MineCraft 7101
- [原创] SUSCTF 2022 DigitalCircuits 分析过程及解题脚本 8246