最近学习驱动开发,参照深入浅出Windows驱动开发第一章节HelloWorld驱动敲了代码,在停止驱动时虚拟机蓝屏,本次解决这个问题,并进行记录。
开发环境为:vs2013 WDK8.1
调试环境:虚拟机VMware 12、 系统Windows7 sp1 x86、VirtualKD-Redux-2020.4、Windbg 10
安装驱动软件:InstDrv.exe 这款软件可以安装驱动服务、启动、停止、卸载驱动服务,非常方便。
问题表现为:在驱动启动后,使用InstDrv软件停止驱动,会导致win7虚拟机蓝屏。
截取一部分代码如下:
#define DRIVER_SYMBOLLINK_NAME L"\\??\\MySymbolLinkName"
#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, DefaultDispatch)
#pragma alloc_text(PAGE, DriverUnload)
DriverEntry函数部分代码:
deviceExtension = (PDEVICE_EXTENSION)deviceObject->DeviceExtension;//额外数据指针
deviceExtension->DeviceObject = deviceObject;
deviceExtension->DeviceName = deviceName;
RtlInitUnicodeString(&symbolicLink, DRIVER_SYMBOLLINK_NAME);
deviceExtension->SymbolicLink = symbolicLink;//
DriverUnload函数部分代码如下:
while (NULL != deviceObject)
{
PDEVICE_EXTENSION deviceExtesion = \
(PDEVICE_EXTENSION)deviceObject->DeviceExtension;
// 删除符号链接与设备
linkName = deviceExtesion->SymbolicLink;
IoDeleteSymbolicLink(&linkName);//此处导致崩溃
deviceObject = deviceObject->NextDevice;
IoDeleteDevice(deviceExtesion->DeviceObject);
}
使用VirtualKD-Redux-2020.4 搭建虚拟机双机调试内核环境,成功启动后,设置好windbg符号文件路径,自己写的驱动文件名为 HelloWorldWDM.sys,debug模式生成的文件,生成sys时并且本机生成pdb文件
动态调试:
1. 使用CTRL+BREAK中断虚拟机系统,输入命令 sxe ld:HelloWorldWDM,输入命令 g 回车 运行。
2. 已管理员权限运行InstDrv.exe 安装驱动服务,启动服务,会中断到调试器中。
3. 使用命令 !reload /f HelloWorldWDM.sys可以加载本地磁盘中驱动模块的PDB文件。
4. 使用lm命令查看 pdb文件是否加载成功
5. 输入命令 bp HelloWorldWDM!DriverEntry打下int 3断点,输入g运行,自动打开源码调试
6.动态调试分别在DriverEntry和DriverUnload函数下断点,IoDeleteSymbolicLink(&linkName)函数导致崩溃,这个linkName是符号链接名,初始化及使用流程如下:
(1) 在DriverEntry函数中调用RtlInitUnicodeString(&symbolicLink, DRIVER_SYMBOLLINK_NAME)对symbolicLink初始化,symbolicLink.Buffer指向字符串L"\\??\\MySymbolLinkName"
(2)在DriverEntry函数中使用语句deviceExtension->SymbolicLink = symbolicLink; 进行复制,这个可以理解为浅拷贝,所以当前deviceExtension->SymbolicLink中的Buffer变量与symbolicLink.Buffer指向同一块内存
(3)在 DriverUnload函数中查看linkName = deviceExtesion->SymbolicLink, 动态调试可知 linkName指向的内存区域此时不可访问。
7. 所以现在的问题在于为什么在DriverEntry函数字符串区域可以访问,在DriverUnload中字符串区域不可访问,是内存页面被换出了吗?
静态分析:
使用IDA分析驱动文件,查看DriverEntry函数:
从上面两幅图中可以看到调用RtlInitUnicodeString函数并使用符号链接名来初始化变量,而且看最左侧还有INIT标记。
这个之前有#pragma alloc_text(INIT, DriverEntry)一句话,这个表明DriverEntry函数执行完毕后是可以从内存页面中换出的,变量字符串\??\MySymbolLinkName也是INIT标记,也会被换出,之前是\\,此处是\,是因为C语言字符串中\\代表一个\。
通过动态调试查看 RtlInitUnicodeString函数是如何实现的
kd> u nt!RtlInitUnicodeString l30
nt!RtlInitUnicodeString:
83e73ed8 57 push edi
83e73ed9 8b7c240c mov edi,dword ptr [esp+0Ch] //字符串指针
83e73edd 8b542408 mov edx,dword ptr [esp+8]
83e73ee1 c70200000000 mov dword ptr [edx],0
83e73ee7 897a04 mov dword ptr [edx+4],edi //复制Buffer为字符串指针
83e73eea 0bff or edi,edi
83e73eec 7422 je nt!RtlInitUnicodeString+0x38 (83e73f10)
83e73eee 83c9ff or ecx,0FFFFFFFFh
83e73ef1 33c0 xor eax,eax
83e73ef3 f266af repne scas word ptr es:[edi]
83e73ef6 f7d1 not ecx
83e73ef8 d1e1 shl ecx,1 //计算 字符串长度
83e73efa 81f9feff0000 cmp ecx,0FFFEh
83e73f00 7605 jbe nt!RtlInitUnicodeString+0x2f (83e73f07)
83e73f02 b9feff0000 mov ecx,0FFFEh
83e73f07 66894a02 mov word ptr [edx+2],cx
83e73f0b 49 dec ecx
83e73f0c 49 dec ecx
83e73f0d 66890a mov word ptr [edx],cx
83e73f10 5f pop edi
83e73f11 c20800 ret 8
由上面可知RtlInitUnicodeString函数是通过复制Buffer变量指向字符串区域,并没有再次拷贝一份字符串,所以当DriverEntry函数被换出内存后,调用DriverUnload函数时访问Buffer指向的内存区域无法访问导致蓝屏。
修改:
笔者修改DriverUnload部分源码如下:
UNICODE_STRING SymbolLinkName;
RtlInitUnicodeString(&SymbolLinkName, DRIVER_SYMBOLLINK_NAME);
IoDeleteSymbolicLink(&SymbolLinkName);
运行时不会崩溃,查看这部分代码的静态反汇编:
由上面两幅图可以看到 调用RtlInitUnicodeString函数时使用的字符串是位于PAGE页面,这个页面代表不可换出到内存。
总结:
1. 本次学习了内核双机调试、源码调试、符号配置。
2. 用户态编程时上面定义#define 字符串的形式,字符串位于PE端的.const段,可读不可写,程序内部多次用到此字符串时,始终用的是.const段的那一份,这与内核态编程不一样,目前来看,在INIT页面与PAGE页面使用的不同内存地址的字符串。
3. 学习了RtlInitUnicodeString函数初始化UNICODE_STRING的方式。