准备
在做之后的动作前,因为win7及其以上的版本默认是不支持DbgPrint(大家暂时理解为内核版的printf)的打印,所以,为了方便我们的调试,我们先要修改一下注册表
创建一个reg文件然后运行
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter]
"DEFAULT"=dword:0000000f
之后我们要去网上找一下能够直接加载驱动的工具,比如我这个在52破解上找的
在做好这些之后,我们打开我们的驱动开发环境,之前专栏里面的文章应该是说过了
装好了的环境应该是能够看见这些东西
我们的驱动依次有NT(不支持即插即用功能),WDM(支持即插即用功能),WDF(以WDM为基础的框架,为了简化我们开发的流程),以及UMDF,KMDF(用户,内核)。为了理解底层原理,我们后续的demo会围绕前两个来进行
创建一个新的Empty WDM Driver文件
首先看右边,我红圈标出来的地方默认会有inf文件,里面会标记我们驱动的信息,如果不填编译器会报错,这里我们暂时不需要,因为我们现阶段还是只是在不验证驱动签名的Win7上进行开发,等到后续介绍到Win10之后,我们会重新介绍inf,这里删掉就行了
右键打开ntifs.h头文件,可以看见里面包含了很多头文件,所以我们引用这个就足够了
随便写一个小demo作为我们驱动开发的开端
#include<ntifs.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {
DbgPrint("Hello My Driver!");
return STATUS_SUCCESS;
}
还要注意,由于vs2015的原因,我们默认的目标平台是win10,这里得改下,改成win7
然后我们的驱动为了方便编译,把视警告为错误关掉
这样我们就可以编译出一个sys文件了,把它移到我们目标win7上去,打开DbgView(微软官方工具),打开驱动加载工具,注意这两个都要管理员权限,加载驱动,最后的结果应该是我们可以在DbgView里面看见我们目标的字符串
这时候如果我们按下卸载驱动,会发现我们是卸载不掉的,这是因为我们在之前的代码中没有留下卸载驱动的函数,把它补上
#include<ntifs.h>
VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
DbgPrint("Driver has benn Unloaded");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {
DbgPrint("Hello My Driver!");
DbgBreakPoint();
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
这里除了为我们卸载驱动做出了示范之外,还有一个函数DbgBreakPoint,它等价于int3,方便我们调试
编译后运行,结果应该是这样,可以看见有字符串打出来了,同时系统也进入了int3 断点
驱动里面的字符串
我们使用驱动可以像在C中一样,使用char,驱动开发本身给了我们更加安全的方法,因为它不像传统字符串以’\0’结尾,就很容易在读取的时候越界,而驱动里面提供了PUNICODE_STRING这种结构
它就规定了字符串的长度和buffer指针
但是它是Unicode宽字符,我们可以在DbgPrint中打印DriverEntry中的Reg这个参数来作为示例,反正待会我们也会介绍这个参数
我们看看打印了什么,除了我们自己的字符串外,pReg里面装的就是一个注册表的值,这个值的最后一段正是我们驱动的名字
注册表
我们跟着之前被打印出来的字符找到了这个注册表项,里面有几个值,我们来一一介绍
首先是Type,毫无疑问,我们属于1
Type值为1:表示该驱动程序是一个标准的驱动程序,通常用于设备驱动程序。
Type值为2:表示该驱动程序是一个文件系统驱动程序(File System Driver, FSD)。
Type值为3:表示该驱动程序是一个网络驱动程序(Network Driver)。
Type值为4:表示该驱动程序是一个显示驱动程序(Display Driver)或视频驱动程序。
Type值为5:表示该驱动程序是一个多媒体驱动程序(Multimedia Device Driver)。
Type值为6:表示该驱动程序是一个非设备驱动程序,可能是一个服务或内核模式的过滤器驱动程序。
然后是这个Start值,这里我们的值为3,也就是我们需要手动加载
Start的值设置为0,则驱动由启动引导器加载,应该跟“随着开机,最先启动”是同一回事;
Start的值设置为1,则驱动由操作系统的I/O子系统加载,即在系统内核初始化时加载;
Start的值设置为2,则驱动/服务在启动后自动加载;
Start的值设置为3,则驱动/服务就是按需手动加载;
Start的值设置为4,驱动/服务就是被禁用的状态
Error Control指的是当驱动加载失败时返回的值,这里暂时不细说
Image Path实际上就是我们驱动的路径
动态调试一下
借着之前在代码中下的DbgBreakPoint,我们来具体看看代码运行,打开我们的windbg,在加载PDB符号文件之后,打开pDriver
0: kd> dt pDriver
Local var @ 0x905209e0 Type _DRIVER_OBJECT*
0x8834a148
+0x000 Type : 0n4
+0x002 Size : 0n168
+0x004 DeviceObject : (null)
+0x008 Flags : 2
+0x00c DriverStart : 0x9d08c000 Void
+0x010 DriverSize : 0x6000
+0x014 DriverSection : 0x88d09008 Void
+0x018 DriverExtension : 0x8834a1f0 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING "\Driver\2"
+0x024 HardwareDatabase : 0x841af250 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
+0x028 FastIoDispatch : (null)
+0x02c DriverInit : 0x9d090000 long 2!GsDriverEntry+0
+0x030 DriverStartIo : (null)
+0x034 DriverUnload : (null)
+0x038 MajorFunction : [28] 0x83ef8da3 long nt!CcSetFileSizesEx+0
里面这个DriverStart就是我们二进制文件的首地址
0: kd> db 0x9d08c000
ReadVirtual: 9d08c000 not properly sign extended
9d08c000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
9d08c010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
9d08c020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
9d08c030 00 00 00 00 00 00 00 00-00 00 00 00 d8 00 00 00 ................
9d08c040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
9d08c050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
9d08c060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
9d08c070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
现在我们暂时需要注意的还有一个DriverInit,它指示我们GsDriverEntry+0这个位置才驱动的真正入口点,而不是我们驱动里面写的DriverEntry
再打开一个结构DriverExtension,这里我们注意到一个有意思的事情,那就是我们驱动的服务名叫做2
0: kd> dt _DRIVER_EXTENSION 0x8834a1f0
2!_DRIVER_EXTENSION
+0x000 DriverObject : 0x8834a148 _DRIVER_OBJECT
+0x004 AddDevice : (null)
+0x008 Count : 0
+0x00c ServiceKeyName : _UNICODE_STRING "2"
让我们再回顾一下驱动的加载卸载流程,注册(在注册表里面写值)->启动(按照服务来启动)->停止(按照服务来停止)->卸载(删除注册表里的值)
所以,当我们的驱动注册了之后,我们完全可以将驱动当作服务进行启动和停止
细节
我们用CFF 来打开我们的sys文件,有一项的名字叫做Debug Directory RVA,它在.rdata里面
我们再打开.rdata的描述,可以看见0x2000对应0x600,所以我们的Debug 信息从0x610开始
通过上图我们也知道了为什么要专门注意这段信息的原因,这里存储的是我们pdb的路径,由于我们开发环境路径可能会带着自己的名字之类的信息,所以这里我们是必须要抹除的