使用Visual Studio 2022创建一个Empty WDM Driver工程
工程创建后,添加一个MyFirstDriver.cpp文件,输入以下内容
1 #include<ntddk.h> 2 3 VOID DriverUnload(PDRIVER_OBJECT DriverObject) 4 { 5 if (DriverObject != NULL) 6 { 7 DbgPrint("Driver Unload...Driver Object Address: %p\n", DriverObject); 8 } 9 10 return; 11 } 12 13 extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) 14 { 15 DbgPrint("Hello World\n"); 16 17 if (RegistryPath != NULL) 18 { 19 DbgPrint("Driver RegistryPath: %wZ\n", RegistryPath); 20 } 21 22 if (DriverObject != NULL) 23 { 24 DbgPrint("Driver Object Address: %p\n", DriverObject); 25 DriverObject->DriverUnload = DriverUnload; 26 } 27 28 return STATUS_SUCCESS; 29 }
这里需要注意的是,可以直接创建后缀为.c的文件,如果后缀是.cpp,需要在DriverEntry前增加extern "C",否则会报下面的错
DriverEntry函数必须具有C语言的链接方式,但是C++编译默认的方式不是C的。
下面详细介绍代码中的内容
ntddk.h
内核开发所需要的头文件
DriverEntry
和控制台的Main函数一样,驱动的入口函数就是DriverEntry。这个函数会被系统线程在IRQLPASSIVE_LEVEL (0)上调用(IRQL会在后面详细讨论)。
DriverEntry函数的原型如下:
1 NTSTATUS DriverEntry(PDRIVER_OBECT DriverObject, PUNICOOE_STRING RegistryPath);
第一个参数为DriverObject,表示一个驱动对象的指针,可以简单认为,一个驱动文件(sys)运行之后,操作系统在内存中为该驱动分配了一个类型为DRIVER_OBJECT的数据结构,用于记录该驱动的详细信息
第二个参数RegistryPath,是一个类型为UNICODE_STRING的指针,表示当前驱动所对应的注册表位置。UNICODE_STRING是内核中表示字符串的结构体,对应定义如下:
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWCH Buffer; //PWCH -> wchar_t* 不要求以'\0'结束 } UNICODE_STRING
因为内核驱动是作为Windows系统服务( Service)存在的,Windows系统有众多服务,如果从服务运行的环境来分区,服务分为用户态服务,以及内核态服务,但无论何种服务,都统称为“服务( Service)”,不同服务通过服务的名字来识别。一个驱动SYS文件需要运行(加载到内核中),首先需要把这个驱动文件注册(创建)成一个服务(第三方服务),注册成功后,系统会把该服务信息写入到注册表HKEY_LOCAL_MACHINE \ SYSTEM \CurrentControlSet lServices下,以服务的名字作为一个注册表的键名。
如本示例中的驱动注册成功后可以在注册表中看到如下键值
返回值NTSTATUS,NTSTATUS实际是一个LONG类型,定义如下:
1 typedef LONG NTSTATUS;
DriverEntry返回STATUS_SUCCESS表示成功,返回其他值表示失败。
STATUS_SUCCESS定义如下:
1 #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth
简单来说,内核驱动作为Windows服务运行,在执行具体代码前,驱动SYS文件首先会被映射到内核地址空间,作为内核的一个驱动模块(MODULE),接着系统对这个驱动模块执行导入表初始化、修正重定位表中对应的数据偏移等操作,最后系统会调用该驱动模块的DriverEntry 入口函数,如果这个入口函数返回STATUS_SUCCESS,系统认为这个驱动初始化成功;如果这个入口函数返回除STATUS_SUCCESS以外的其他值,系统认为驱动初始化失败,系统执行一系列的清理工作,并把驱动模块从内核空间中移除,从用户态角度看,就是服务启动失败。
DriverUnload
有时候驱动程序需要卸载。在卸载驱动时(关闭服务),DriverObject->DriverUnload函数会被调用,以便执行一些清理操作。需要注意的是,如果未在DriverUnload中执行清理工作,会产生泄漏,在下一次重启之前,内核无法清除这些泄漏。(这一点不像用户层编程,在进程退出后,资源会释放,但是内核层不会自己释放。)
DriverUnload函数非常重要,但DriverUnload函数是可选的,开发者可以不提供DriverUnload函数,这样做的结果是该驱动不支持停止,也就是说,只要开发者不提供DriverUnload函数,这个驱动对应的服务一旦启动后,再也无法停止。该特性被很多安全软件利用,刻意不提供DriverUnload函数,避免驱动被恶意停止。
需要注意的是:
驱动初始化失败不会触发DriverUnload函数的调用,DriverUnload只有在驱动服务成功启动(初始化)后,被要求停止时才会触发。
DbgPrint函数
DbgPrint函数是WDK提供的API,类似用户层的OutputDebugString函数。DbgPrint与C语言的printf使用基本一样。
与DbgPrint函数功能类似的是KdPrint函数,但KdPrint函数只是针对DEBUG版本的驱动有效。
%wZ用来输出以非'\0'结束的字符串
%ws用来输出以'\0'结束的字符串
在上述的示例代码中,对DriverObject的地址和RegistyPath进行了输出。
编译
直接在Visual Studio中编译即可,编译成功后可以在输出目录得到一个MyFirstDriver.sys文件。
加载并运行驱动
按照Windows系统要求,驱动文件必须经过微软的数字签名后,才可以运行在64位系统上。像平常我们在测试阶段是不会为驱动签名的,可以通过下面两种方式绕过系统的验证。
1、启用调试模式
2、禁用驱动程序强制签名
这两种方式都是通过启动设置里的设置来完成的。下次开机时会失效,需要重新设置。
这里以禁用驱动程序强制签名为例
打开开始菜单,在选择重启时,按住shift键
然后就会出现Windows高级启动菜单,依次选择 疑难解答->启动设置->按数字键7,选择禁用驱动程序强制签名
以管理员运行cmd,执行如下命令,创建一个服务(driverpath需要替换成上面工程生成的.sys文件路径)
1 sc create MyFirstDriver binPath= "driverpath" type= kernel
创建成功后,用管理员权限运行DebugView.exe(SysInternals工具包里的一个工具),DebugView工具可以查看DbgPrint的输出内容。
在菜单中钩选相应选项,如下图所示
此时,我们可以运行驱动(启动服务)
在控制台输入
1 sc start MyFirstDriver
可以在DebugView看到DriverEntry函数里的输出内容
我们再卸载驱动(停止服务)
在控制台输入
1 sc stop MyFirstDriver
可以在DebugView看到DriverUnload函数的输出内容
至此,一个简单的驱动程序已经编写完成了,目前我们暂时先不考虑内核开发的任何理论支撑,仅仅写一个HelloWorld一样的程序。
题外话:
因为以前也没接触过内核开发,在书上看到使用KeBugCheckEx()函数可以主动引发蓝屏,试了一下,确实可以,还挺好玩的。
1 KeBugCheckEx(INVALID_DATA_ACCESS_TRAP, NULL, NULL, NULL, NULL);
参考资料:
Windows内核开发示例代码
https://github.com/Microsoft/Windows-driver-samples
驱动程序概述
https://learn.microsoft.com/zh-cn/windows-hardware/drivers/device-and-driver-technologies
标签:DriverObject,函数,DriverUnload,Windows,DriverEntry,内核,驱动,驱动程序 From: https://www.cnblogs.com/zhaotianff/p/17942863