/
*
*
*
@brief DriverEntry 是加载驱动程序后调用的第一个函数,它负责初始化驱动程序;
*
@param[
in
] pDriverObject 由IO管理器创建,标识驱动,创造设备对象,指向 DRIVER_OBJECT 结构体;
*
@param[
in
] pRegPath 驱动安装后在注册表中的路径,指向 UNICODE_STRING 字符串的指针,
*
该结构指定注册表中驱动程序的 Parameters 项的路径;
*
@
return
ntStatus 如果例程成功,则它必须返回 STATUS_SUCCESS.
*
由于
*
即成功返回
0
,否则为非
0
,
*
*
与应用层相反,应用层返回
0
则是失败
*
*
.
*
否则,它必须返回在 ntstatus 中定义的错误状态值之一;
*
@warning 驱动入口函数名称是写死的不可修改.
*
@warning 不要使用.cpp来写驱动,因为C
+
+
在编译器里面会改名(命名粉碎规则(name mangling))驱动框架就不认识DriverEntry这个名字了,会导致编译出错;
*
@see https:
/
/
docs.microsoft.com
/
zh
-
cn
/
windows
-
hardware
/
drivers
/
wdf
/
driverentry
-
for
-
kmdf
-
drivers
*
@author cisco(微信公众号:坚毅猿)
*
@date
2022
-
02
-
13
22
:
09
*
/
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
/
/
/
函数内部并没有用到参数pRegPath,所以需要UNREFERENCED_PARAMETER(pRegPath),让编译器忽略掉未使用的变量;
UNREFERENCED_PARAMETER(pRegPath);
UNICODE_STRING uDeviceName
=
{
0
};
/
/
/
< 设备对象的名字,UNICODE_STRING是windows内核中统一使用的字符串类型
UNICODE_STRING uLinkName
=
{
0
};
/
/
/
< 符号链接的名字
NTSTATUS ntStatus
=
0
;
/
/
/
< 返回状态
PDEVICE_OBJECT pDeviceObject
=
NULL;
/
/
/
< 设备对象指针
ULONG i
=
0
;
DbgPrint(
"Driver load begin\n"
);
/
/
/
初始化初始化设备对象名和符号链接名
/
/
/
就是将
"wchar_t *"
转换
"UNICODE_STRING *"
,同时将DEVICE_NAME赋值给uDeviceName
/
/
/
@todo 没有找到它的实现代码
RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
RtlInitUnicodeString(&uLinkName, LINK_NAME);
/
*
*
*
@brief IoCreateDevice 创建设备对象;
*
@param[
in
] pDriverObject 由IO管理器创建,标识驱动,创造设备对象,指向 DRIVER_OBJECT 结构体;
*
@param[
in
] DeviceExtensionSize 设备扩展,创建设备对象的一个缓冲区空间,用来存放一些数据,不需要空间,则设置为
0
;
*
@param[
in
] DeviceName 设备对象名
*
@param[
in
] DeviceType 设备对象的类型,比如磁盘设备类型等,未知设备类型的定义为
*
"#define FILE_DEVICE_UNKNOWN 0x00000022"
*
@param[
in
] DeviceCharacteristics 文件的属性
*
@param[
in
] Exclusive 排他性,最好设置为TURE表示只要有一个进程打开,其他进程就无法打开,从而提高驱动安全性;
*
@param[out] &pDeviceObject 被创建的设备对象的指针的指针,是二级指针;
*
@warning 如果不传二级指针,pDeviceObject是不会发生改变的,存在内存泄漏;
*
@todo 为什么会导致内存泄漏
*
@
return
ntStatus 如果例程成功,则它必须返回 STATUS_SUCCESS.
*
因为
*
即成功返回
0
,否则为非
0
,
*
*
与应用层相反,应用层返回
0
则是失败
*
*
.
*
否则,它必须返回在 ntstatus 中定义的错误状态值之一;
*
@note 为什么要在驱动中创建一个设备对象?是因为只能用设备对象是用来接收应用层的IRP数据包.
*
eg1:应用层调用CreateFile()进入内核层会封装成一个irp数据包,irp发给指定的由驱动创建的设备对象,
*
设备对象接收到irp之后才会传给驱动分发函数去处理;
*
@note Q1:驱动对象和设备的对象的关系?互指.
*
因为同一个驱动对象可以创建多个设备对象,比如可以创建控制设备对象,可以创建过滤设备对象等,
*
这些设备对象都被串联起来存放在同一个链表中,而且每个设备对象都有一个指针指向创建它的驱动对象.
*
@see C:\Program Files (x86)\Windows Kits\
10
\Include\
10.0
.
22000.0
\km\wdm.h
*
@author cisco(微信公众号:坚毅猿)
*
@date
2022
-
01
-
22
22
:
09
*
/
ntStatus
=
IoCreateDevice(pDriverObject,
0
, &uDeviceName, FILE_DEVICE_UNKNOWN,
0
, FALSE, &pDeviceObject);
if
(!NT_SUCCESS(ntStatus))
{
/
/
/
错误状态值ntStatus如果以十进制打印出来还可能是个负数,不如十六进制来得直观
/
/
/
通过查询ntstatus.h的宏可以很方便找到对应的错误类型.
/
/
/
例如:
DbgPrint(
"IoCreateDevice failed:%x"
, ntStatus);
return
ntStatus;
}
/
/
/
对刚创建的设备对象指定一个通信方式;
/
/
/
@note
"|="
是往Flges添加一些标志
/
/
/
@note Q2:何为通信?A2:应用层传数据到内核层,或者内核层发数据到应用层
/
/
/
Q3:何为通信协议?A3:把数据放在什么地方去
/
/
/
DO_BUFFERED_IO规定R3和R0之间的read和write的通信方式有三种
/
/
/
1.buffered
io 在内核层分配一块缓存,io管理器负责把应用层
/
内核层copy到
buffer
,
/
/
/
io管理器负责把
buffer
拷贝到io管理器负责把内核层
/
应用层
/
/
/
优点:安全简单,因为不会操作应用层的内存,
buffer
是来自内核态的,
/
/
/
应用层无法改内核层的数据,所以是安全的.
/
/
/
缺点:效率低,因为一次通信有两次拷贝,一般传输数据量是不大的,
buffer
io是够用的,
/
/
/
但如果是类似
3d
渲染,数据量大,direct io更适合;
/
/
/
2.direct
io io管理器通过MPL把应用层
/
内核层的虚拟地址映射成物理地址,然后lock,
/
/
/
防止被这块内存切换出去(pageout),io管理器通过MPL把同一物理地址映射成内核层
/
应用层的物理地址.
/
/
/
效率是最高的,一次通信只有一次拷贝,但稍复杂
/
/
/
3.neither
io 内核层直接访问应用层的数据,前提是应用层和内核层同处于一个进程上下文
/
/
/
(因为应用层内存地址是私有的,应用层进程切换之后内存就失效了)
/
/
/
@warnnig 要对内核层传入的内存地址要做检查(ProbeForRead
/
ProbeForWrite),否则会有提取漏洞
/
/
/
@note DO_DEVICE_INITIALIZING 是用来标识设备对象刚刚创建出来,正在初始化,还不能工作,
/
/
/
告诉应用层我现在暂时无法处理ipr请求,不要给我发,发了我也处理不了,
/
/
/
Q4:由谁来去除这个标志?A4:在DriverEntry中创建的设备对象是由io管理器把这个标志去掉,
/
/
/
但在其他地方创建的,比如过滤驱动,由驱动程序负责清除,即必须用程序员自己手动去除这个标志;
pDeviceObject
-
>Flags |
=
DO_BUFFERED_IO;
/
/
/
创建符号链接;
/
/
/
@note 设备对象在应用层的体现,r0和r3通信的过程中,r3需要符号链接才能看到设备对象将其打开,
/
/
/
进而获得句柄,然后发送读写等各种请求;
/
/
/
eg2:磁盘中看到的盘符号(C:)、(D:)都是符号链接,对应内核的卷设备对象\Device\HarddiskVolume
/
/
/
ntStatus
=
IoCreateSymbolicLink(&uLinkName, &uDeviceName);
if
(!NT_SUCCESS(ntStatus))
{
IoDeleteDevice(pDeviceObject);
DbgPrint(
"IoCreateSymbolicLink failed:%x\n"
, ntStatus);
return
ntStatus;
}
/
/
/
注册分发函数
/
/
/
"#define IRP_MJ_MAXIMUM_FUNCTION 0x1b"
分发函数存放在pDriverObject
-
>MajorFunction,
/
/
/
0x1b
+
1
=
28
,所有的分发函数有
28
个
for
(i
=
0
; i < IRP_MJ_MAXIMUM_FUNCTION
+
1
; i
+
+
)
{
pDriverObject
-
>MajorFunction[i]
=
DispatchCommon;
}
/
/
/
对感兴趣的Irp进行注册,还有两个重要的Irp(重命名和删除,Major),复制、粘贴、移动没有对应的Irp,因为这三个动作本质是读和写,
/
/
/
文件过滤驱动中为了检测这三个动作,只需要监控都和写的Irp就可以了;
/
/
/
@warnig 分发函数名字可以随便起,但接口定义,参数类型必须一样
pDriverObject
-
>MajorFunction[IRP_MJ_CREATE]
=
DispatchCreate;
/
/
创建
pDriverObject
-
>MajorFunction[IRP_MJ_READ]
=
DispatchRead;
/
/
读 是相对于应用层来说,数据流向:r0
-
>R3
pDriverObject
-
>MajorFunction[IRP_MJ_WRITE]
=
DispatchWrite;
/
/
写 是相对于应用层来说,数据流向:r3
-
>R0
pDriverObject
-
>MajorFunction[IRP_MJ_DEVICE_CONTROL]
=
DispatchIoctrl;
pDriverObject
-
>MajorFunction[IRP_MJ_CLEANUP]
=
DispatchClean;
pDriverObject
-
>MajorFunction[IRP_MJ_CLOSE]
=
DispatchClose;
/
/
/
注册卸载函数
pDriverObject
-
>DriverUnload
=
DriverUnload;
DbgPrint(
"Driver load ok!\n"
);
return
STATUS_SUCCESS;
}