WDM与KMDF

WDM是微软一开始发布的驱动开发模型,全都是底层内容。在开发时,既要处理硬件,也要处理驱动程序与操作系统内核的交互。

WDF模型,为了降低驱动的开发难度,WDF中对WDM进行了多次封装,比如实现了对象,事件,自动排队等等,同时封装了一下驱动程序中的共同行为。不用再关心IRP分发,调度等问题

KMDF和UMDF是基于WDF的模型,一个是内核模式,一个是用户模式。

选择

虽然标题是开发,但是实际上是逆向,中断,服务例程之类的东西还是需要学的,所以这里还是用WDM开发

之前那篇博客疑似是用KMDF写的,所以从这里开始重来吧。

推荐教程

https://space.bilibili.com/348612384

基本框架

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
#include <wdm.h>


#define DEVICENAME L"\\DEVICE\\DeviceTest"
#define SYMNAME L"\\??\\SymboilcLinkTest"


void DrvUnload(PDRIVER_OBJECT pdriver) {

if (pdriver->DeviceObject)
{
IoDeleteDevice(pdriver->DeviceObject);
UNICODE_STRING symname = {};
RtlInitUnicodeString(&symname, SYMNAME);
IoDeleteSymbolicLink(&symname);
}


DbgPrint("Driver Unloaded\n");
}

// 定义对应的回调函数
NTSTATUS myCreate(PDEVICE_OBJECT pdevice, PIRP pirp)
{
NTSTATUS status = STATUS_SUCCESS;

DbgPrint("my divice has be opened\n");

pirp->IoStatus.Status = status;
pirp->IoStatus.Information = 0;
IoCompleteRequest(pirp, IO_NO_INCREMENT);

return status;
}
NTSTATUS myClose(PDEVICE_OBJECT pdevice, PIRP pirp)
{
NTSTATUS status = STATUS_SUCCESS;

DbgPrint("my divice has be closed\n");

pirp->IoStatus.Status = status;
pirp->IoStatus.Information = 0;
IoCompleteRequest(pirp, IO_NO_INCREMENT);

return status;
}
NTSTATUS myCleanup(PDEVICE_OBJECT pdevice, PIRP pirp)
{
NTSTATUS status = STATUS_SUCCESS;

DbgPrint("my divice has be cleaned\n");

pirp->IoStatus.Status = status;
pirp->IoStatus.Information = 0;
IoCompleteRequest(pirp, IO_NO_INCREMENT);

return status;
}


//
// 驱动入口
//
NTSTATUS DriverEntry(
PDRIVER_OBJECT driver,//驱动对象
PUNICODE_STRING reg_path//unicode字符串,指明注册表位置
)
{
driver->DriverUnload = DrvUnload;//设置卸载函数,卸载时调用


// 创建设备对象
// 步骤为生成unicode字符串作为名字,然后使用IoCreateDevice来获取传出的pdevice
UNICODE_STRING deviceName = { 0 };
RtlInitUnicodeString(&deviceName, DEVICENAME);
PDEVICE_OBJECT pdevice = NULL;
NTSTATUS status = IoCreateDevice(driver, 0, &deviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pdevice);
if (!NT_SUCCESS(status))
{
DbgPrint("Create Device fail:%x\n", status);
return status;
}

// 创建符号链接
// 步骤同上
UNICODE_STRING symname = { 0 };

RtlInitUnicodeString(&symname, SYMNAME);
status = IoCreateSymbolicLink(&symname, &deviceName);

if (!NT_SUCCESS(status))
{
DbgPrint("Create symbolicLink fail:%x\n", status);
IoDeleteDevice(pdevice);
return status;
}

// 为设备定义对应的回调函数,注意这里是以驱动的身份获取的
driver->MajorFunction[IRP_MJ_CREATE] = myCreate;
driver->MajorFunction[IRP_MJ_CLOSE] = myClose;
driver->MajorFunction[IRP_MJ_CLEANUP] = myCleanup;


return 0;
}

补充之前的驱动开发入门这篇博客,这里需要说明一下对于驱动,设备对象,以及符号链接这三个东西的概念。

驱动:运行在操作系统OS内存中,作为补充操作系统功能的软件。类似于R3里的程序与DLL的关系。一般分为两种,一种是硬件驱动:用来实现接收硬件的数字信号并进行处理,或者实现操作系统对硬件的控制。这里的硬件可以是虚拟硬件,因为驱动是中间层,操作系统只会发送消息,让它执行某些操作,系统并不关心驱动如何完成这些任务。因此,虚拟硬盘(类似将内存当作硬盘用)驱动可以在系统要读取其控制的数据时,将地址转化为内存中的读取等等。另一种是和硬件无关的驱动,只是借助驱动的权限高的特点,来实现R3层不好实现的内容,一般叫它内核模块。

设备对象:操作系统控制硬件时,不是直接和硬件交互,而是中间间隔了硬件抽象层(hal.dll),也就是虚拟设备。虚拟设备需要处理系统的irp请求,比如文件打开,文件关闭等等。这些内容都应该是函数/过程,r0也只能写在都写sys文件(驱动)里面。因此,驱动需要生成并绑定设备对象,之后所有的irp回调,都是由驱动来定义的,系统收到对这个对象的请求,都会先给驱动,在驱动中找到对应的irp回调并触发。

符号链接:操作系统需要一个指明设备对象的句柄才可以知道找哪个设备对象,同时,为了让R3也可以使用不同的硬件,设备对象需要绑定一个全局的符号链接,通过使用这个链接,表示了指向这个设备对象,符号链接是一个字符串。比如调用上面例子的设备,在R3需要使用

1
CreateFile("\\\\.\\SymboilcLinkTest",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);