引用注明>> 【做者:张佩】【原文:www.yiiyee.cn/blog】算法
最近我发现为Win8开发的驱动程序,有些能安装在Win7上(包括更早系统),有些则不能。那些不能安装的状况很可怕:一旦安装并加载驱动,系统就会马上蓝屏。针对这个问题,作了一番调查研究。发现了一个简单的规律:windows
若是开发时使用的是WDM驱动框架,则存在此问题;若是使用KMDF驱动框架,则正常。cookie
这是为何呢?一块儿来看看吧。框架
Visual Studio 2012(简称VS2012)中包含了Windows驱动程序编译器,使得Windows驱动也能够在Visual Studio的集成开发环境中进行开发了。安装VS2012后,再安装Win8 WDK,打开VS2012会发现多了两种新的“平台工具集”,支持Windows内核和用户驱动程序的编译。以下图所示:yii
新建的内核驱动项目,会自动选择合适的工具集:WindowsKernelModeDriver8.0。更改工做集,会影响相关的工程项目设置,好比包含目录的查找路径等。因此,若是把驱动项目的工做集改为用户程序相关的话,编译器会报不少找不到头文件的错误,好比找不到<ntddk.h>。函数
和之前的控制台编译环境相似,在IDE环境中,咱们也能够选择不一样的目标系统:操做系统,硬件平台。经过工具栏上的列表框进行选择。以下图所示:工具
咱们能够经过工程向导来产生一个WDM内核驱动项目,这里将项目名称设为Test。测试
编译器默认开启/GS编译选项,用来保护内核栈的完整性。编译器会在程序开始的地方,保存一个cookie值到栈上;在程序退出时再检查这个cookie值是否被破坏,若是被破坏,说明栈溢出,代表系统遭到了破坏从而须要蓝屏保护。ui
GS是一种被广泛运用的保护机制。开启了GS选项后,编译器会连接一个GS相关的库文件来实现GS功能,当目标系统为Win7时库文件是BufferOverflowK.lib,当目标系统为Win8+时库文件是BufferOverflowFastFailK.lib。连接器是如何实现栈保护的呢?它先在驱动加载的时候,也就是GsDriverEntry函数内初始化cookie。而后在每一个驱动函数的开始和结束的地方,添加cookie检查的代码。spa
连接器为了对cookie进行初始化,会为驱动程序从新生成一个名为GsDriverEntry的入口函数,初始化Cookie后再调用驱动程序本身的DriverEntry入口函数。下面是一个典型的GsDriverEntry的实现:
Test!GsDriverEntry: 82ea403e 8bff mov edi,edi 82ea4040 55 push ebp 82ea4041 8bec mov ebp,esp 82ea4043 e8bdffffff call Test!__security_init_cookie// 初始化cookie 82ea4048 5d pop ebp 82ea4049 e9b8fff7ff jmp Test!DriverEntry (82e24006)//调用DriverEntry 82ea404e cc int 3
下面是Win7版本security_init_cookie函数的逻辑:
test!__security_init_cookie: 82eb3005 a10050e382 mov eax,dword ptr [test!__security_cookie (82e35000)] ds:0023:82e35000=00300083 82eb300a b94ee640bb mov ecx,0BB40E64Eh 82eb300f 85c0 test eax,eax 82eb3011 7404 je test!__security_init_cookie+0x12 (82eb3017) // 判断是否等于0 82eb3013 3bc1 cmp eax,ecx // 判断是否等于0BB40E64Eh 82eb3015 751a jne test!__security_init_cookie+0x2c (82eb3031) 82eb3017 a13040e382 mov eax,dword ptr [test!KeTickCount (82e34030)] // 获取当前系统时间 82eb301c 8b00 mov eax,dword ptr [eax] 82eb301e 350050e382 xor eax,offset test!__security_cookie (82e35000) 82eb3023 a30050e382 mov dword ptr [test!__security_cookie (82e35000)],eax 82eb3028 7507 jne test!__security_init_cookie+0x2c (82eb3031) 82eb302a 8bc1 mov eax,ecx 82eb302c a30050e382 mov dword ptr [test!__security_cookie (82e35000)],eax // 生成新cookie值 // 返回 82eb3031 f7d0 not eax 82eb3033 a30450e382 mov dword ptr [test!__security_cookie_complement (82e35004)],eax 82eb3038 c3 ret
这段逻辑先检查security_cookie的当前值,若是不等于0且不等于0xBB40E64E,就当即退出;不然根据当前的系统时间,产生一个随机的cookie值。这里的值0xBB40E64E是个特征值,多是系统默认生成的。大部分的镜像在加载的时候,其cookie都会被初始化成这个特征值。
当目标OS为Win8时,编译器默认连接BufferOverflowFastFailK.lib文件,它使用新的Cookie算法。正是这个新算法致使了不兼容性。看看Win8中security_init_cookie函数的逻辑,它硬是和0xBB40E64E较上了劲:
mov eax, __security_cookie test eax, eax jz short loc_404029 cmp eax, 0BB40E64Eh // 若是等于0BB40E64Eh,跳到下面产生0x29中断,产生蓝屏 jz short loc_404029 not eax mov __security_cookie_complement, eax retn loc_404029: ; CODE XREF: push 6 pop ecx int 29h // 蓝屏
它判断当前的cookie值是否等于特征值0xBB40E64E,若是相等,马上蓝屏。神奇的是,在 Win7及之前的OS上,大部分的驱动程序的cookie值都会被加载器初始化为0xBB40E64E。而在Win8+系统上则永远不会这样。这就是为何能在Win8+正常运行的驱动,一放到Win7上就蓝屏的缘由。
那为何使用KMDF编译的驱动,又没有问题呢?缘由很简单,KMDF框架连接的静态库文件仍是BufferOverflowK.lib。不保证KMDF之后的版本不会连接新库文件的可能。
虽然KMDF兼容性良好,但不少状况下,仍是会用WDM框架编写驱动,并且还有不少小端口驱动也可能存在相似问题。如何避免呢。有三种简单的方法:
其一是为Win7及之前系统和Win8+系统产生不一样的镜像文件。这也是比较推荐的方法。
其二是在Win8+系统上安装为Win7系统编译的驱动程序。通过测试,大多数为Win7编译的驱动程序,都能正常运行在Win8和Win blue系统上。但不保证更新的Windows系统出来后,这种向前兼容性仍然有效。
其三是干脆关闭GS编译选项。但这样就缺乏了栈保护,不推荐。
最后介绍一种MSDN中介绍的比较高级的修改编译选项的办法,可让编译器在为Win8+目标系统编译驱动程序时,仍选择旧版本的栈保护库文件BufferOverflowK.lib:
msbuild /p:KernelBufferOverflowLib="C:\Program Files (x86)\Windows Kits\8.1\Lib\win8\km\x64\BufferOverflowK.lib" /p:platform=x64 /p:Configuration="Win8 Release" myDriver.sln
<KernelBufferOverflowLib>$(DDK_LIB_PATH)\BufferOverflowK.lib<KernelBufferOverflowLib>