竹林蹊径-深入浅出Windows驱动开发第一章HelloWorld疑问解决

  最近学习驱动开发,参照深入浅出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函数:

 竹林蹊径-深入浅出Windows驱动开发第一章HelloWorld疑问解决竹林蹊径-深入浅出Windows驱动开发第一章HelloWorld疑问解决

  从上面两幅图中可以看到调用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);

  运行时不会崩溃,查看这部分代码的静态反汇编:

  竹林蹊径-深入浅出Windows驱动开发第一章HelloWorld疑问解决竹林蹊径-深入浅出Windows驱动开发第一章HelloWorld疑问解决

  由上面两幅图可以看到 调用RtlInitUnicodeString函数时使用的字符串是位于PAGE页面,这个页面代表不可换出到内存。

 

  总结:

  1. 本次学习了内核双机调试、源码调试、符号配置。

  2. 用户态编程时上面定义#define 字符串的形式,字符串位于PE端的.const段,可读不可写,程序内部多次用到此字符串时,始终用的是.const段的那一份,这与内核态编程不一样,目前来看,在INIT页面与PAGE页面使用的不同内存地址的字符串。

  3. 学习了RtlInitUnicodeString函数初始化UNICODE_STRING的方式。

 

 

 

 

  

  

  

竹林蹊径-深入浅出Windows驱动开发第一章HelloWorld疑问解决

上一篇:C# 获取一个byte数据中某一位的值


下一篇:第一天