首页
社区
课程
招聘
[原创]驱动通信基础
发表于: 2024-11-29 23:29 2020

[原创]驱动通信基础

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控制码的定义:

  1. DeviceType: 设备类型,定义了设备的类别(例如,文件系统、串口、图形设备等)。
  2. Function: 功能代码,指定了设备支持的操作或功能。
  3. Method: 方法,表示如何传递数据(例如,直接存取、缓冲区存取等)。
  4. Access: 访问权限,指示对设备进行操作所需的权限(例如,读、写或读写)。
1
2
3
#define CTL_CODE( DeviceType, Function, Method, Access ) (                 \
    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)

需要传入四个参数:

  • DeviceType:由于控制设备和任何硬件没有关系,次数的设备控制是抽象的,而不是硬件设备控制,所以一般都设置为FILE_DEVICE_UNKNOWN
  • Function:功能号的核心数字,其中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_READIRP_MJ_WRITE更容易绕过检测

1
2
#define IRP_MJ_READ                     0x03
#define IRP_MJ_WRITE                    0x04

同样,需要针对IRP_MJ_CREATEIRP_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直播授课

最后于 2024-11-29 23:33 被Gushang编辑 ,原因: 修改话题为原创
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//