/**
* @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;
}