Windows驱动开发入门-应用与内核通信

内核部分

生成CDO

IoCreateDevice

内核想要与应用通信时,必须生成控制设备对象CDO。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
//从DriverEntry参数获得
IN ULONG DeviceExtensionSize,
//设备扩展大小 后面讲
IN OPTIONAL PUNICODE_STRING DeviceName,
//设备名 可空但别
IN DEVICE_TYPE DeviceType,
//设备类型
IN ULONG DeviceCharacteristics,
//设备属性
IN BOOLEAN Exclusive,
//是否为独占设备
OUT PDEVICE_OBJECT* DeviceObject
//返回生成设备对象指针
)

IoCreateDeviceSecure

上面那个有默认安全属性,必须得管理员权限应用才能打开,所以改用这个不安全的来玩。设备GUID理论上要用CoCreateGuid来生成,但有别的方法比如在线工具:https://www.gjk.cn/guid。

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS IoCreateDeviceSecure(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN OPTIONAL PUNICODE_STRING DeviceName,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
IN PCUNICODE_STRING DefaultSDDLString,
//表示安全设置的字符串 待会儿例子有个万能(但最不安全)的一个
IN LPCGUID DeviceClassGuid,
//设备GUID 该驱动一直用这个GUID不变
OUT PDEVICE_OBJECT* DeviceObject
)

IoDeleteDevice

删除控制设备。

符号链接

设备对象DO可以没名字,但控制设备对象CDO需要个名字,这样才可以被暴露出来。暴露给应用层需要建立符号链接。最稳妥的方法还是用GUID比较好,符号链接很容易冲突。当然有一种粗暴的方法就是删除已存在的符号链接再生成一个自己的。

1
2
3
4
NTSTATUS IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
)

删除符号链接。

分发函数

处理发送给设备对象请求的函数,入口在DriverEntry中对PDRIVER_OBJECT的MajorFunction中设置。这个MajorFunction记录多个不同请求的处理函数,但我们把它全都设置成该分发函数,然后在分发函数中处理各种请求。标准的分发函数原型:

1
2
3
4
NTSTATUS DriverDispatcher(
IN PDEVICE_OBJECT dev,
IN PIRP irp
)

请求的处理

在分发函数中,需要用IoGetCurrentIrpStackLocation先获取请求的当前栈空间,然后判断请求是不是发给自己这个驱动的,是的话对请求种类判断,不是的话返回错误即可。最后设置返回输出需要的空间和请求完成状态,最后用IoCompleteRequest结束请求。

例子

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
#include <ntddk.h>
VOID UnDriver(PDRIVER_OBJECT pDriver) {
PDEVICE_OBJECT pDev;     // 用来取得要删除设备对象
UNICODE_STRING SymLinkName;  // 局部变量symLinkName
pDev = pDriver->DeviceObject;
IoDeleteDevice(pDev);                    // 调用
IoDeleteDevice用于删除设备
RtlInitUnicodeString(&SymLinkName, L"\\??\\My_Driver");   // 初始化字符串将symLinkName定义成需要删除的符号链接名称
IoDeleteSymbolicLink(&SymLinkName);             // 调用IoDeleteSymbolicLink删除符号链接
}
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp){
pIrp->IoStatus.Status = STATUS_SUCCESS;      // 返回成功
IoCompleteRequest(pIrp, IO_NO_INCREMENT);     // 指示完成此IRP
return STATUS_SUCCESS;              // 返回成功
}
NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
pIrp->IoStatus.Status = STATUS_SUCCESS;      // 返回成功
IoCompleteRequest(pIrp, IO_NO_INCREMENT);     // 指示完成此IRP
return STATUS_SUCCESS;              // 返回成功
}
NTSTATUS DispatchRead(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulReadLength = Stack->Parameters.Read.Length;
pIrp->IoStatus.Status = Status;
pIrp->IoStatus.Information = ulReadLength;
DbgPrint("应用要读取的长度:%d\n", ulReadLength);
// 将内核中的缓冲区全部填充为0x68 方便演示读取的效果
memset(pIrp->AssociatedIrp.SystemBuffer, 0x68, ulReadLength);
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return Status;
}
NTSTATUS CreateDriverObject(IN PDRIVER_OBJECT pDriver) {
NTSTATUS Status;
PDEVICE_OBJECT pDevObj;
UNICODE_STRING DriverName;
UNICODE_STRING SymLinkName;
RtlInitUnicodeString(&DriverName, L"\\Device\\My_Device");
Status = IoCreateDevice(pDriver, 0, &DriverName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
DbgPrint("命令 IoCreateDevice 状态: %d", Status);
pDevObj->Flags |= DO_BUFFERED_IO;
RtlInitUnicodeString(&SymLinkName, L"\\??\\My_Device");
Status = IoCreateSymbolicLink(&SymLinkName, &DriverName);
DbgPrint("当前命令IoCreateSymbolicLink状态: %d", Status);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath) {
CreateDriverObject(pDriver);                // 调用创建设备
pDriver->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;   // 创建成功派遣函数
pDriver->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;    // 关闭派遣函数
pDriver->MajorFunction[IRP_MJ_READ] = DispatchRead;
pDriver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

应用部分

应用层一般直接用CreateFile打开设备,然后用DeviceIoControl发送请求,发送的请求宏为CTL_CODE,像上面内核部分例子一样使用即可,该宏的使用方法为:

1
2
3
4
5
6
CTL_CODE(
FILE_DEVICE_UNKNOWN, //设备类型 因为这驱动与任何硬件没关系 所以未知就行
0x911, //功能号 用户只能使用0x7FF~0xFFF
METHOD_BUFFERED, //使用缓冲方式
FILE_WRITE_DATA //操作所需权限
)

不讲了直接上例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <windows.h>
#include <stdio.h>
#include <winioctl.h>
int main() {
HANDLE hDevice = CreateFile(L"\\\\.\\My_Device", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("获取驱动句柄失败: %d\n", GetLastError());
getchar();
}
UCHAR buffer[10];
ULONG ulRead;
ReadFile(hDevice, buffer, 10, &ulRead, 0);
for (int i = 0; i < (int)ulRead; i++)
printf("%02X", buffer[i]);
getchar();
CloseHandle(hDevice);
return 0;
}

DeviceIoControl法

驱动层:

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
#include <ntifs.h>
#include <windef.h>
// 控制器
#define IOCTL_IO_LyShark
CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
// 卸载驱动执行
VOID UnDriver(PDRIVER_OBJECT pDriver) {
PDEVICE_OBJECT pDev;                     // 用来取得要删除设备对象
UNICODE_STRING SymLinkName;                 // 局部变量symLinkName
pDev = pDriver->DeviceObject;
IoDeleteDevice(pDev);                    // 调用IoDeleteDevice用于删除设备
RtlInitUnicodeString(&SymLinkName, L"\\??\\MyDriver");   // 初始化字符串将symLinkName定义成需要删除的符号链接名称
IoDeleteSymbolicLink(&SymLinkName);             // 调用IoDeleteSymbolicLink删除符号链接
DbgPrint("驱动卸载完毕...");
}
// 创建设备连接
NTSTATUS CreateDriverObject(IN PDRIVER_OBJECT pDriver) {
NTSTATUS Status;
PDEVICE_OBJECT pDevObj;
UNICODE_STRING DriverName;
UNICODE_STRING SymLinkName;
// 创建设备名称字符串
RtlInitUnicodeString(&DriverName, L"\\Device\\MyDriver");
Status = IoCreateDevice(pDriver, 0, &DriverName, FILE_DEVICE_UNKNOWN, 0,TRUE, &pDevObj);
// 指定通信方式为缓冲区
pDevObj->Flags |= DO_BUFFERED_IO;
// 创建符号链接
RtlInitUnicodeString(&SymLinkName, L"\\??\\MyDriver");
Status = IoCreateSymbolicLink(&SymLinkName, &DriverName);
return STATUS_SUCCESS;
}
// 创建回调函数
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp){
pIrp->IoStatus.Status = STATUS_SUCCESS;      // 返回成功
IoCompleteRequest(pIrp, IO_NO_INCREMENT);     // 指示完成此IRP
return STATUS_SUCCESS;              // 返回成功
}
// 关闭回调函数
NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
pIrp->IoStatus.Status = STATUS_SUCCESS;      // 返回成功
IoCompleteRequest(pIrp, IO_NO_INCREMENT);     // 指示完成此IRP
return STATUS_SUCCESS;              // 返回成功
}
// 主控制器,用于判断R3发送的控制信号
NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
PIO_STACK_LOCATION pIrpStack;
ULONG uIoControlCode;
PVOID pIoBuffer;
ULONG uInSize;
ULONG uOutSize;
// 获得IRP里的关键数据
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// 获取控制码
uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 输入和输出的缓冲区(DeviceIoControl的InBuffer和OutBuffer都是它)
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
// EXE发送传入数据的BUFFER长度(DeviceIoControl的nInBufferSize)
uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
// EXE接收传出数据的BUFFER长度(DeviceIoControl的nOutBufferSize)
uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
// 对不同控制信号的处理流程
switch (uIoControlCode) {
// 接收或发送
case IOCTL_IO_LyShark:{
DWORD dw = 0;
// 得到输入参数
memcpy(&dw, pIoBuffer, sizeof(DWORD));
DbgPrint("[+] hello \n");
// 对输入参数进行处理
dw++;
// 设置输出参数
memcpy(pIoBuffer, &dw, sizeof(DWORD));
// 返回通信状态
status = STATUS_SUCCESS;
break;
}
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = uOutSize;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
// 设定DeviceIoControl的*lpBytesReturned的值(如果通信失败则返回0长度)
if (status == STATUS_SUCCESS)
pIrp->IoStatus.Information = uOutSize;
else
pIrp->IoStatus.Information = 0;
// 设定DeviceIoControl的返回值是成功还是失败
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
// 入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath) {
// 调用创建设备
CreateDriverObject(pDriver);
pDriver->DriverUnload = UnDriver;                // 卸载函数
pDriver->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;     // 创建派遣函数
pDriver->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;      // 关闭派遣函数
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;  // 分发函数
return STATUS_SUCCESS;
}

用户层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <Windows.h>
#include <winioctl.h>
#define IOCTL_IO_LyShark
CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
int main(int argc, char *argv[]) {
HANDLE hDevice = CreateFileA("\\\\.\\MyDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
CloseHandle(hDevice);
return 0;
}
// 发送控制信号
// input = 发送数据 output = 接受数据 ref_len = 数据长度
DWORD input = 100, output = 0, ref_len = 0;
DeviceIoControl(hDevice, IOCTL_IO_LyShark, &input, sizeof(input), &output,
sizeof(output), &ref_len, 0);
printf("输出: %d \n", output);
system("pause");
CloseHandle(hDevice);
return 0;
};

PIPE与ReadFile相结合

内核层:

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
#include <ntifs.h>
#include <ndis.h>
#include <stdio.h>
HANDLE g_hClient;
IO_STATUS_BLOCK g_ioStatusBlock;
KEVENT g_event;
VOID NdisMSleep(IN ULONG  MicrosecondsToSleep);
// 初始化管道
void init() {
UNICODE_STRING uniName;
OBJECT_ATTRIBUTES objAttr;
RtlInitUnicodeString(&uniName, L"\\DosDevices\\Pipe\\MyPipeConn");
InitializeObjectAttributes(&objAttr, &uniName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
ZwCreateFile(&g_hClient, GENERIC_READ | GENERIC_WRITE, &objAttr,&g_ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN,FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!g_hClient)
return;
KeInitializeEvent(&g_event, SynchronizationEvent, TRUE);
}
// 将数据传到R3应用层
VOID ReportToR3(char* m_parameter, int lent) {
if (!NT_SUCCESS(ZwWriteFile(g_hClient, NULL, NULL, NULL, &g_ioStatusBlock, (void*)m_parameter, lent, NULL, NULL)))
DbgPrint("写出错误");
}
VOID UnDriver(PDRIVER_OBJECT driver){
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath){
init();
// 延时3秒
NdisMSleep(3000000);
for (int x = 0; x < 10; x++){
// 分配空间
char *report = (char*)ExAllocatePoolWithTag(NonPagedPool, 4096, 'abcd');
if (report){
RtlZeroMemory(report, 4096);
RtlCopyMemory(report, "hello", 13);
// 发送到应用层
ReportToR3(report, 4096);
ExFreePool(report);
}
}
Driver->DriverUnload = UnDriver;
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
#include <iostream>
#include <windows.h>
int main(int argc, char *argv[]) {
HANDLE hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\MyPipeConn"), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, NULL);
if (INVALID_HANDLE_VALUE == hPipe)
return false;
const int size = 1024 * 10;
char buf[size];
DWORD rlen = 0;
while (true) {
if (1){
if (ReadFile(hPipe, buf, size, &rlen, NULL) == FALSE)
continue;
else{
//接收信息
char* buffer_tmp = (char*)&buf;
// 拷贝前半部分,不包括 buffer_data
char* buffer = (char*)malloc(size);
memcpy(buffer, buffer_tmp, size);
printf("内核层数据: %s \n", buffer);
free(buffer_tmp);
free(buffer);
}
}
}
system("pause");
return 0;
}

还可以传输结构体,如内核层:

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
#include <ntifs.h>
#include <ndis.h>
#include <stdio.h>
HANDLE g_hClient;
IO_STATUS_BLOCK g_ioStatusBlock;
KEVENT g_event;
typedef struct {
int type;
unsigned long address;
unsigned long buffer_data_len;
char buffer_data[0];
}Networkreport;
VOID NdisMSleep(IN ULONG MicrosecondsToSleep);
// 初始化管道
void init(){
UNICODE_STRING uniName;
OBJECT_ATTRIBUTES objAttr;
RtlInitUnicodeString(&uniName, L"\\DosDevices\\Pipe\\MyPipeConn");
InitializeObjectAttributes(&objAttr, &uniName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
ZwCreateFile(&g_hClient, GENERIC_READ | GENERIC_WRITE, &objAttr, &g_ioStatusBlock, NULL, ILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!g_hClient)
return;
KeInitializeEvent(&g_event, SynchronizationEvent, TRUE);
}
// 将数据传到R3应用层
VOID ReportToR3(Networkreport* m_parameter, int lent) {
if (!NT_SUCCESS(ZwWriteFile(g_hClient, NULL, NULL, NULL, &g_ioStatusBlock,(void*)m_parameter, lent, NULL, NULL)))
DbgPrint("写出错误");
}
VOID UnDriver(PDRIVER_OBJECT driver){
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {
init();
// 延时3秒
NdisMSleep(3000000);
for (int x = 0; x < 10; x++) {
// 分配空间
Networkreport *report = (Networkreport*)ExAllocatePoolWithTag(NonPagedPool, 4096, 'abcd');
if (report) {
RtlZeroMemory(report, 4096);
report->type = x;
report->address = 401000 + x;
report->buffer_data_len = 5;
// 定位到结构体最后一个元素上
unsigned char * tmp = (unsigned char *)report + sizeof(Networkreport);
memcpy(tmp, "hello", 5);
// 发送到应用层
ReportToR3(report, 4096);
ExFreePool(report);
}
}
Driver->DriverUnload = UnDriver;
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
#include <iostream>
#include <windows.h>
typedef struct {
int type;
unsigned long address;
unsigned long buffer_data_len;
char *buffer_data;
}Networkreport;
int main(int argc, char *argv[]){
HANDLE hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\MyPipeConn"), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, NULL);
if (INVALID_HANDLE_VALUE == hPipe)
return false;
const int size = 1024 * 10;
char buf[size];
DWORD rlen = 0;
while (true){
if (1 == 1) {
if (ReadFile(hPipe, buf, size, &rlen, NULL) == FALSE)
continue;
else {
//接收信息
Networkreport* buffer_tmp = (Networkreport*)&buf;
SIZE_T buffer_len = sizeof(Networkreport) + buffer_tmp->buffer_data_len;
// 拷贝前半部分,不包括 buffer_data
Networkreport* buffer = (Networkreport*)malloc(buffer_len);
memcpy(buffer, buffer_tmp, buffer_len);
// 对后半部 分配空间
char* data = (char*)malloc(buffer->buffer_data_len);
unsigned char* tmp = (unsigned char *)buffer + sizeof(Networkreport) - 4;
memcpy(data, tmp, buffer->buffer_data_len);
printf("输出数据: %s \n", data);
printf("地址: %d \n", buffer_tmp->address);
printf("长度: %d \n", buffer_tmp->type);
printf("输出长度: %d \n", buffer_tmp->buffer_data_len);
free(data);
free(buffer);
}
}
}
system("pause");
return 0;
}

Async反向通信与DPC

驱动层:

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <ntddk.h>
// 自定义接口扩展
typedef struct _DeviceExtension {
LIST_ENTRY IrpList;
KTIMER timer;
LARGE_INTEGER liDueTime;
KDPC dpc;
}DEV_EXT, *PDEV_EXT;
// 默认派遣函数
NTSTATUS _DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp) {
_pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
_pIrp->IoStatus.Information = 0;
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return _pIrp->IoStatus.Status;
}
// 创建派遣函数
NTSTATUS _AsyncCreateCloseDispatch(PDEVICE_OBJECT _pDevcieObject, PIRP _pIrp) {
_pIrp->IoStatus.Status = STATUS_SUCCESS;
_pIrp->IoStatus.Information = 0;
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return _pIrp->IoStatus.Status;
}
// 读取派遣函数
NTSTATUS _AsyncReadDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp) {
NTSTATUS status;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(_pIrp);
PDEV_EXT pDevExt = (PDEV_EXT)_pDeviceObject->DeviceExtension;
IoMarkIrpPending(_pIrp);
// 将IRP插入自定义链表中插入的是ListEntry
InsertTailList(&pDevExt->IrpList, &_pIrp->Tail.Overlay.ListEntry);
// 返回pending 主要返回给I/O管理器的值必须和IRP的Pending标志位一致
// 即调用iomarkirppending和返回值要一致
return STATUS_PENDING;
}
// DPC线程
VOID _CustomDpc(PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2) {
PIRP pIrp;
PDEV_EXT pDevExt = (PDEV_EXT)DeferredContext;
PVOID pBuffer = NULL;
ULONG uBufferLen = 0;
PIO_STACK_LOCATION pIrpStack = NULL;
do {
if (!pDevExt)
break;
// 检查尾端IRP链表是否为空 为空则跳出
if (IsListEmpty(&pDevExt->IrpList))
break;
// 从IRP链表中取出一个IRP并完成该IRP 取出的是ListEntry的地址
PLIST_ENTRY pListEntry = (PLIST_ENTRY)RemoveHeadList(&pDevExt->IrpList);
if (!pListEntry)
break;
pIrp = (PIRP)CONTAINING_RECORD(pListEntry, IRP, Tail.Overlay.ListEntry);
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
DbgPrint("当前DPC Irp: 0x%x\n", pIrp);
// 驱动程序的读写方式位直接I/O
pBuffer = MmGetSystemAddressForMdl(pIrp->MdlAddress);
if (pBuffer == NULL) {
pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
break;
}
uBufferLen = pIrpStack->Parameters.Read.Length;
DbgPrint("读取DPC长度: %d\n", uBufferLen);
// 支持5字节以下的读请求
uBufferLen = uBufferLen > 5 ? 5 : uBufferLen;
// 复制请求内容
RtlCopyMemory(pBuffer, "hello", uBufferLen);
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = uBufferLen;
// 完成该IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
} while (FALSE);
// 重新设置定时器
KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);
}
// 卸载驱动
VOID _UnloadDispatch(PDRIVER_OBJECT _pDriverObject) {
UNICODE_STRING Win32DeviceName;
PDEV_EXT pDevExt = (PDEV_EXT)_pDriverObject->DeviceObject->DeviceExtension;
RtlInitUnicodeString(&Win32DeviceName, L"\\DosDevices\\MyAsync");
// 删除定时器
KeCancelTimer(&pDevExt->timer);
// 删除创建的设备
IoDeleteDevice(_pDriverObject->DeviceObject);
}
// 驱动入口
NTSTATUS DriverEntry(PDRIVER_OBJECT _pDriverObject, PUNICODE_STRING _pRegistryPath) {
UNICODE_STRING DeviceName, Win32DeivceName;
PDEVICE_OBJECT pDeviceObject = NULL;
NTSTATUS status;
PDEV_EXT pDevExt = NULL;
HANDLE hThread;
OBJECT_ATTRIBUTES ObjectAttributes;
CLIENT_ID CID;
RtlInitUnicodeString(&DeviceName, L"\\Device\\MyAsync");
RtlInitUnicodeString(&Win32DeivceName, L"\\DosDevices\\MyAsync");
for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
_pDriverObject->MajorFunction[i] = _DefaultDispatch;
_pDriverObject->MajorFunction[IRP_MJ_CREATE] = _AsyncCreateCloseDispatch;
_pDriverObject->MajorFunction[IRP_MJ_CLOSE] = _AsyncCreateCloseDispatch;
_pDriverObject->MajorFunction[IRP_MJ_READ] = _AsyncReadDispatch;
_pDriverObject->DriverUnload = _UnloadDispatch;
// 分配自定义扩展
status = IoCreateDevice(_pDriverObject, sizeof(DEV_EXT), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
if (!NT_SUCCESS(status))
return status;
if (!pDeviceObject)
return STATUS_UNEXPECTED_IO_ERROR;
pDeviceObject->Flags |= DO_DIRECT_IO;
pDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
status = IoCreateSymbolicLink(&Win32DeivceName, &DeviceName);
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
pDevExt = (PDEV_EXT)pDeviceObject->DeviceExtension;
// 初始化IRP链表
InitializeListHead(&pDevExt->IrpList);
// 初始化定时器
KeInitializeTimer(&(pDevExt->timer));
// 初始化DPC pDevExt是传给_CustomDpc函数的参数
KeInitializeDpc(&pDevExt->dpc, (PKDEFERRED_ROUTINE)_CustomDpc, pDevExt);
// 设置定时时间位1s
pDevExt->liDueTime = RtlConvertLongToLargeInteger(-10000000);
// 启动定时器
KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);
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
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[]) {
HANDLE hFile;
char Buffer[3][32] = { 0 };
DWORD dwRet[3] = { 0 };
OVERLAPPED ol[3] = { 0 };
HANDLE hEvent[3] = { 0 };
hFile = CreateFileA("\\\\.\\MyAsync", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (INVALID_HANDLE_VALUE == hFile)
return 0;
// event用来通知请求完成
hEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
ol[0].hEvent = hEvent[0];
hEvent[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
ol[1].hEvent = hEvent[1];
hEvent[2] = CreateEvent(NULL, TRUE, FALSE, NULL);
ol[2].hEvent = hEvent[2];
// 读取事件内容到缓存
ReadFile(hFile, Buffer[0], 13, &dwRet[0], &ol[0]);
ReadFile(hFile, Buffer[1], 13, &dwRet[1], &ol[1]);
ReadFile(hFile, Buffer[2], 13, &dwRet[2], &ol[2]);
// 等待三个事件执行完毕
WaitForMultipleObjects(3, hEvent, TRUE, INFINITE);
// 输出结果
printf("缓存LyShark A: %s \n", Buffer[0]);
printf("缓存LyShark B: %s \n", Buffer[1]);
printf("缓存LyShark C: %s \n", Buffer[2]);
CloseHandle(hFile);
return 0;
}

阻塞、等待、安全设计

在这个例子中,限定字符串最大长度为$511$字节,再加一个结束符。输入缓冲区超长的一律返回失败,输出缓冲区最小长度为$512$字节,小于这个长度的一律返回失败即使实际字符串小于这个长度。缓冲区用LIST_ENTRY实现,每次应用层来轮循时卸下一个链表,每个节点为$512$字节,还要用自旋锁来通知应用程序缓冲区有内容可取。

函数KdBreakPoint和宏ASSERT千万不要在发行版中使用,否则直接唤起蓝屏。函数strlen也不要使用,为了寻找结束符而没有限度,搜索到无效空间后唤起蓝屏,应使用strnlen,同理使用strncpy

输出的话如果还使用等待事件则特别繁琐,这里直接用死循环轮循,但为了防止处理器占用率过高,搞个进程休眠使。

内核部分例子:

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <ntddk.h>
PDEVICE_OBJECT g_cdo = NULL;
CONST GUID CWK_GUID_CLASS_MYCDO={0x17a0d1e0L,0x3249,0x12e1,{0x92,0x16,0x45,0x1a,0x21,0x30,0x29,0x06}};
#define CWK_CDO_SYB_NAME L"\\??\\slbkcdo_3948d33e"
// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x911,METHOD_BUFFERED,FILE_WRITE_DATA)
// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x912,METHOD_BUFFERED,FILE_READ_DATA)
// 定义一个链表用来保存字符串
#define CWK_STR_LEN_MAX 512
typedef struct {
LIST_ENTRY list_entry;
char buf[CWK_STR_LEN_MAX];
} CWK_STR_NODE,*PCWK_STR_NODE;
// 还必须有一把自旋锁来保证链表操作的安全性
KSPIN_LOCK g_cwk_lock = {0};
// 一个事件来标识是否有字符串可以取
KEVENT g_cwk_event = {0};
// 必须有个链表头
LIST_ENTRY g_cwk_str_list = {0};
#define MEM_TAG 'cwkr'
// 分配内存并初始化一个链表节点
PCWK_STR_NODE cwkMallocStrNode(VOID) {
PCWK_STR_NODE ret = ExAllocatePoolWithTag(NonPagedPool, sizeof(CWK_STR_NODE), MEM_TAG);
if (ret == NULL)
return NULL;
return ret;
};
VOID cwkUnload(PDRIVER_OBJECT pDriver) {
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
PCWK_STR_NODE str_node = {0};
ASSERT(g_cdo != NULL);
IoDeleteSymbolicLink(&cdo_syb);
IoDeleteDevice(g_cdo);
// 负责的编程态度:释放分配过的所有内核内存。
while (TRUE) {
str_node = (PCWK_STR_NODE)ExInterlockedRemoveHeadList(&g_cwk_str_list, &g_cwk_lock);
// str_node = RemoveHeadList(&g_cwk_str_list);
if (str_node != NULL)
ExFreePool(str_node);
else
break;
};
return;
};
NTSTATUS cwkDispatch(IN PDEVICE_OBJECT dev, IN PIRP irp) {
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG ret_len = 0;
while (dev == g_cdo) {
// 如果这个请求不是发给g_cdo的,那就非常奇怪了。因为这个驱动只生成过这一个设备。所以可以直接返回失败。
if (irpsp->MajorFunction == IRP_MJ_CREATE || irpsp->MajorFunction == IRP_MJ_CLOSE)
// 生成和关闭请求,这个一律简单地返回成功就可以了。就是无论何时打开和关闭都可以成功。
break;
if (irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
// 处理DeviceIoControl。
PVOID buffer = irp->AssociatedIrp.SystemBuffer;
ULONG inlen = irpsp->Parameters.DeviceIoControl.InputBufferLength;
ULONG outlen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
ULONG len=0;
PCWK_STR_NODE str_node = NULL;
switch (irpsp->Parameters.DeviceIoControl.IoControlCode) {
case CWK_DVC_SEND_STR: {
ASSERT(buffer != NULL);
ASSERT(outlen == 0);
// 安全的编程态度之一:检查输入缓冲的长度对于长度超出预期的,果断返回错误。
if (inlen > CWK_STR_LEN_MAX) {
status = STATUS_INVALID_PARAMETER;
break;
};
// 安全的编程态度之二:检查字符串的长度,不要使用strlen!如果使用strlen,一旦攻击者故意输入没有结束符的字符串,会导致内核驱动访问非法内存空间而崩溃。
DbgPrint("strnlen = %d\r\n", strnlen((char*)buffer, inlen));
if (strnlen((char*)buffer, inlen) == inlen) {
// 字符串占满了缓冲区,且中间没有结束符。立刻返回错误。
status = STATUS_INVALID_PARAMETER;
break;
};
// 现在可以认为输入缓冲是安全而且不含恶意的。分配节点。
str_node = cwkMallocStrNode();
if (str_node == NULL) {
// 如果分配不到空间了,返回资源不足的错误
status = STATUS_INSUFFICIENT_RESOURCES;
break;
};
// 前面已经检查了缓冲区中的字符串的确长度合适而且含有结束符,所以这里用什么函数来拷贝字符串对安全性而言并不非常重要。
strncpy(str_node->buf, (char*)buffer, CWK_STR_LEN_MAX);
// 插入到链表末尾。用锁来保证安全性。
ExInterlockedInsertTailList(&g_cwk_str_list, (PLIST_ENTRY)str_node, &g_cwk_lock);
// InsertTailList(&g_cwk_str_list, (PLIST_ENTRY)str_node);
// 打印
// DbgPrint((char *)buffer);
// 那么现在就可以认为这个请求已经成功。因为刚刚已经插入了一个,那么可以设置事件来表明队列中已经有元素了。
KeSetEvent(&g_cwk_event, 0, TRUE);
break;
};
case CWK_DVC_RECV_STR: {
ASSERT(buffer != NULL);
ASSERT(inlen == 0);
// 应用要求接收字符串。对此,安全上要求是输出缓冲要足够长。
if (outlen < CWK_STR_LEN_MAX) {
status = STATUS_INVALID_PARAMETER;
break;
};
while (TRUE) {
// 插入到链表末尾。用锁来保证安全性。
str_node = (PCWK_STR_NODE)ExInterlockedRemoveHeadList(&g_cwk_str_list, &g_cwk_lock);
// str_node = RemoveHeadList(&g_cwk_str_list);
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
// 对于合法的要求,在缓冲链表为空的情况下,等待事件进行阻塞。也就是说,如果缓冲区中没有字符串,就停下来等待 。这样应用程序也会被阻塞住,DeviceIoControl是不会返回的。但是一旦有就会返回。等于驱动“主动”通知了应用。
KeWaitForSingleObject(&g_cwk_event, Executive, KernelMode, 0, 0);
};
break;
};
default: {
// 到这里的请求都是不接受的请求。未知的请求一律返回非法参数错误。
status = STATUS_INVALID_PARAMETER;
break;
};
};
};
break;
};
// 返回结果
irp->IoStatus.Information = ret_len;
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
};
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path) {
NTSTATUS status=STATUS_SUCCESS;
ULONG i=0;
UCHAR mem[256] = { 0 };
// 生成一个控制设备。然后生成符号链接。
UNICODE_STRING sddl = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
UNICODE_STRING cdo_name = RTL_CONSTANT_STRING(L"\\Device\\cwk_3948d33e");
UNICODE_STRING cdo_syb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
KdBreakPoint();
// 生成一个控制设备对象。
status = IoCreateDeviceSecure(pDriver, 0, &cdo_name, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddl, (LPCGUID)&CWK_GUID_CLASS_MYCDO, &g_cdo);
if (!NT_SUCCESS(status))
return status;
// 生成符号链接.
IoDeleteSymbolicLink(&cdo_syb);
status = IoCreateSymbolicLink(&cdo_syb, &cdo_name);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(g_cdo);
return status;
};
// 初始化事件、锁、链表头。
KeInitializeEvent(&g_cwk_event, SynchronizationEvent, TRUE);
KeInitializeSpinLock(&g_cwk_lock);
InitializeListHead(&g_cwk_str_list);
// 所有的分发函数都设置成一样的。
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
pDriver->MajorFunction[i] = cwkDispatch;
// 支持动态卸载。
pDriver->DriverUnload = cwkUnload;
// 清除控制设备的初始化标记。
g_cdo->Flags &= ~DO_DEVICE_INITIALIZING;
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
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
#include <Windows.h>
#include <tchar.h>
#include <cstdio>
#define CWK_DEV_SYM L"\\\\.\\slbkcdo_3948d33e"
// 从应用层给驱动发送一个字符串。
#define CWK_DVC_SEND_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x911,METHOD_BUFFERED,FILE_WRITE_DATA)
// 从驱动读取一个字符串
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x912,METHOD_BUFFERED,FILE_READ_DATA)
int _tmain(int argc, _TCHAR* argv[]) {
HANDLE device = NULL;
ULONG ret_len;
int ret = 0;
char msg[] = "Hello driver, this is a message from app.\r\n";
char tst_msg[1024] = { 0 };
// 打开设备.每次要操作驱动的时候,先以此为例子打开设备
device = CreateFile(CWK_DEV_SYM, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
if (device == INVALID_HANDLE_VALUE){
printf("coworker demo: Open device failed.\r\n");
return -1;
}
else
printf("coworker demo: Open device successfully.\r\n");
if (!DeviceIoControl(device, CWK_DVC_SEND_STR, msg, strlen(msg) + 1, NULL, 0, &ret_len, 0)){
printf("coworker demo: Send message failed.\r\n");
ret = -2;
}
else
printf("coworker demo: Send message successfully.\r\n");
// 这里开始,其实是对驱动的一系列测试。分配3个字符串:
// 1.长度为0.应该可以正常输入。
// 2.长度为511字节,应该可以正常输入。
// 3.长度为512字节,应该返回失败。
// 4.长度为1024字节的字符串,但声明缓冲区长度为128,应该返回失败。
// 5.第一次读取,应该读出msg的内容。
// 5.第一次读取,应该读出长度为511字节的字符串。
// 6.第二次读取,应该读出长度为0的字符串。
do {
memset(tst_msg, '\0', 1);
if (!DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 1, NULL, 0, &ret_len, 0)){
ret = -3;
break;
}
else
printf("TEST1 PASS.\r\n");
memset(tst_msg, '\0', 512);
memset(tst_msg, 'a', 511);
if (!DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 512, NULL, 0, &ret_len, 0)){
ret = -5;
break;
}
else
printf("TEST2 PASS.\r\n");
memset(tst_msg, '\0', 513);
memset(tst_msg, 'a', 512);
if (DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 513, NULL, 0, &ret_len, 0)){
// 这个缓冲区已经过长,理应返回失败。如果成功了则认为是错误。
ret = -5;
break;
}
else
printf("TEST3 PASS.\r\n");
memset(tst_msg, '\0', 1024);
memset(tst_msg, 'a', 1023);
if (DeviceIoControl(device, CWK_DVC_SEND_STR, tst_msg, 128, NULL, 0, &ret_len, 0)){
// 这个缓冲区虽然不过长,但是字符串过长,理应返回失败。如果成功了则认为是错误。
ret = -5;
break;
}
else
printf("TEST4 PASS.\r\n");
free(tst_msg);
// 现在开始测试输出。第一个读出的应该是msg.
if (DeviceIoControl(device, CWK_DVC_RECV_STR, NULL, 0, tst_msg, 1024, &ret_len, 0) == 0 || ret_len != strlen(msg) + 1){
ret = -6;
break;
}
else
printf("TEST5 PASS.\r\n");
// 第二个读出的应该是长度为0的空字符串。
if (DeviceIoControl(device, CWK_DVC_RECV_STR, NULL, 0, tst_msg, 1024, &ret_len, 0) == 0 || ret_len != 1){
ret = -6;
break;
}
else
printf("TEST6 PASS.\r\n");
// 第三个读出的应该是长度为511的全a字符串
if (DeviceIoControl(device, CWK_DVC_RECV_STR, NULL, 0, tst_msg, 1024, &ret_len, 0) != 0 || ret_len != 511 + 1){
ret = -6;
break;
}
else
printf("TEST7 PASS.\r\n");
} while (0);
CloseHandle(device);
return ret;
};