端午节蓝屏之谜:金山系列软件同微软KB2839229冲突技术分析

蓝屏冲突事件来历

在2013年6月12日,也就是端午节当天,微软发布了6月例行补丁日的Windows/Office相关补丁,国内各大安全厂商也向用户及时推送了这些补丁,而在补丁发布后,各大安全厂商、电脑厂商却接到了大量的用户反馈,说安装了微软KB2839229补丁后,导致系统蓝屏,无法开机。

经过对蓝屏DUMP深入分析,可以明确这是金山系列软件中的驱动程序,在挂钩Windows内核时处理不当引发的蓝屏问题,受影响的软件包括金山毒霸、金山卫士、猎豹浏览器、驱动精灵(被金山收购)和WPS Office,受影响的驱动程序组件是:kisknl.sys(金山毒霸)、 KsDef.sys(金山卫士)、knbdrv.sys(猎豹浏览器)、dgsafe.sys(驱动精灵)和WpsSafe.sys(WPS Office)。金山毒霸于12日下午1点紧急更新了新版驱动解决此问题,其他的产品升级状况暂时还不清楚。

蓝屏冲突事件Q&A

这里先给出一些关于此次蓝屏事件的一些Q&A,再进入技术分析的部分:

Q:这次蓝屏的技术原因简单来说是什么?

A:是由于金山系列软件在挂钩 Windows内核时,强行跳过系统代码进行执行,而跳过代码的计算方式是使用硬编码的方式导致的。因此只要操作系统代码发生变更,金山系列软件就会引发系统蓝屏

Q:金山的中国区论坛的官方置顶贴说此次蓝屏是因为“微软打补丁没更新版本号导致的”,这是真的吗?这次补丁蓝屏是因为微软的补丁存在问题吗?

A:这不是真的,微软的补丁都会更新版本号,且此次蓝屏同微软更新补丁是否修改版本号无关,也不是微软补丁的责任。

Q:网上有人说360、电脑管家和瑞星等“国内主流安全软件”都会和这个补丁冲突发生蓝屏,在这些安全软件厂商论坛上也会看到有用户反馈蓝屏,这是真的吗?

A:这不是真的,除了金山外,360等安全软件都未采用这种强行跳过系统代码执行的技术方案,不会同本次补丁冲突蓝屏。其它厂商论坛上的蓝屏反馈贴子,经过沟通了解后,主要也都是同时安装了金山系列软件。

Q:为什么有些金山用户打补丁后并未蓝屏?为什么我现在安装了金山系列软件,然后去打这个补丁没出问题?

A:金山毒霸于12日下午更新了新版驱动,因此更新驱动后再打补丁不会出现问题。另外,此补丁只影响32位系统,如果是64位系统的用户无需安装此补丁,因此也不会遇到蓝屏问题。

Q:QQ电脑管家发布新闻说KB2839229、KB2839727这两款补丁都有问题,会导致蓝屏,这是真的吗?

A:由于金山软件的问题,导致蓝屏的只有KB2839229这一款补丁,KB2839727这款补丁是不存在的,这应该新闻作者对补丁“KB2838727”之误。

KB2838727这个补丁是IE浏览器积累性更新补丁,没有升级内核相关文件,不会导致蓝屏

 

下面我们来具体分析下KB2839229这个补丁究竟更新了什么?为什么金山系列软件在安装了这个补丁后,会导致系统蓝屏?

KB2839229补丁的来历和分析

KB2839229这个补丁修复了由 Google的著名研究人员j00ru发现的一个微软内核信息泄漏安全漏洞CVE-2013-3136,这个漏洞是Windows系统内核在处理页面异常的处理函数KiTrap0E中,由于针对系统内核服务函数KiFastCallEntry的参数处理部分的特殊处理没有考虑Ring3的请求的情况,而引发的一个信息泄漏或拒绝服务安全漏洞。

J00ru在今年5月法国举办的安全大会NoSuchCon上公布了这一安全漏洞,并发表了一篇很有意思的论文:<<Abusing the Windows Kernel: How to Crash an Operating System With Two Instructions>>(如何只用两条指令让操作系统崩溃),这篇论文也被公开在他的技术博客上,对内核安全感兴趣的同学可以学习一下:http://j00ru.vexillium.org/?p=1767

微软6月的补丁KB2839229就是用来修复这个安全漏洞的,首先,微软在修复后的Windows系统内核:ntoskrnl.exe中,对KiTrap0E函数做了一些修改,使用了一个新的名为KiPreprocessAccessViolation的函数,来统一处理针对特殊的内核页面异常的处理,其中就包括出现漏洞的,针对KiFastCallEntry复制参数部分的特殊处理。同时,这个补丁也修改了部分KiFastCallEntry的代码,原先在 KiFastCallEntry复制参数之前,会有一个判断是否是内核模式的逻辑被优化到KiFastCallEntry函数之外(这是微软内核的一个常见编译优化手段),在新的版本中,由于新增了一个判断,这部分逻辑被移动回了KiFastCallEntry函数内,而正是这么一点点小小的正常改动,就引发了金山系列软件的蓝屏。

我们来看一下补丁前和补丁后的ntoskrnl!KiFastCallEntry函数的对比

补丁前nt!KiFastCallEntry部分代码(win7 SP1 x86)

mov     cl, [eax+edx]
mov     edx, [edi+eax*4]
sub     esp, ecx
shr     ecx, 2
mov     edi, esp
cmp     esi, ds:_MmUserProbeAddress
jnb     loc_43DAF5
rep movsd
test    byte ptr [ebp+6Ch], 1
jz      short loc_43D8E5

 

补丁后nt!KiFastCallEntry部分代码(win7 SP1 x86)

mov     cl, [eax+edx]
mov     edx, [edi+eax*4] 
sub     esp, ecx
shr     ecx, 2
mov     edi, esp
test    byte ptr [ebp+72h], 2
jnz     short loc_435807    
test    byte ptr [ebp+6Ch], 1
jz      short _KiSystemServiceCopyArguments@0
cmp     esi, ds:_MmUserProbeAddress
jnb     loc_435A41
rep movsd
test    byte ptr [ebp+6Ch], 1
jz      short loc_435831

 

对比之下就可以看到,从test byte ptr[ebp+72h],2开始,到jz short _KiSystemServiceCopyArguments的部分就是此次修改的代码,这部分代码(ebp+6c的检查)本应是在补丁前的那个jnb loc_43DAF5会去做检查的(由于优化,跳转到函数之外,由于参数堆栈在内核态区域,要判断是否是内核模式调用,如果不是会被拒绝),但这里由于增加了ebp+72h的处理,因此一起被放回了代码流中。

那么为什么这样一个小小的修改,就引发金山系列软件的驱动程序导致系统蓝屏,无法进入系统呢?下面我们来分析金山驱动的挂钩机制,解释这个问题的原因。

金山驱动的分析

首先简单介绍下KiFastCallEntry这个函数的背景,这是系统中所有的内核服务分发的起点,当用户态的程序要调用一个内核态的服务函数,比如打开文件(通过 ZwCreateFile)或创建窗口(NtUserCreateWindowEx),都会经过这个函数点的分发,因此在这里进行挂钩,可以比较方便地针对系统中的可疑程序的行为进行监控、分析和拦截。自360保险箱最早从2007年开始使用了针对KiFastCallEntry的挂钩技术开始,国内外大量的安全软件都开始通过挂钩这个位置来实现主动防御、自我保护或沙箱等相关的安全功能,包括国内的360,金山,QQ电脑管家,瑞星,国外的赛门铁克, BitDefender等,都采用过类似的技术,当然,各家的实现方式各有不同。

金山毒霸的KisKnl.sys驱动实现了一个特别的版本(金山系列的此次其他受影响驱动也类似,这里暂以KisKnl.sys为例进行分析),该驱动在挂钩 KiFastCallEntry时,会抢在系统从用户态堆栈复制参数之前,获得执行机会,然后强行跳过包括系统复制参数、相关检查的逻辑,自己接管并实现这部分系统代码,然后再跳转到后面的执行代码。

这个强行跳过系统代码的特别的逻辑不仅奇怪,也存在着很多安全问题和蓝屏隐患。笔者在2011年1月就曾向报告过一个金山毒霸2011中因为这个逻辑而引发的安全漏洞:CVE-2011-0515,被国外漏洞库Secunia(http://secunia.com/advisories/42937)等和中国国家信息安全漏洞库(http://www.cnnvd.org.cn/vulnerability/show/cv_id/2011010320 CNNVD-201101-320)收录,此漏洞的原因就是因为金山的驱动跳走了这部分代码,使得这部分代码在复制堆栈参数时,不受KiTrap0E的异常保护,从而引发了安全漏洞,导致简单的三条汇编指令,就可以在安装了金山毒霸的系统上,触发系统崩溃。

虽然金山后来修复了这个漏洞,但由于没有修改这个错误的逻辑,仍留下了不小的性能隐患,这里不再赘述,下面就来看看引发这次补丁蓝屏的金山驱动代码的具体分析。

这里分析的 KisKnl.sys是金山毒霸修复补丁蓝屏问题的上一个版本,数字签名是2013年的5月30日。KisKnl.sys挂钩KiFastCallEntry的大概原理是,先通过SSDT HOOK挂钩ZwDispalyString这个系统内核服务,然后通过特殊的参数触发这个函数的调用,由于在调用系统服务函数时,函数返回地址就是KiFastCallEntry的函数中部,因此KisKnl.sys就此获得了KiFastCallEntry这个未导出函数的中部位置的具体地址。

接着KisKnl.sys会通过一个简易的二进制搜索引擎,从KiFastCallEntry函数中部向上搜索一段被称为KiSystemServiceAccessTeb的位置附近的特征码,在找到特征码后,金山会根据系统的大版本号(这里的大版本号是指微软大的产品版本号,例如Windows XP, Windows7)来决定不同的硬编码距离差值,通过这个距离差值,直接定位到跳过系统代码后的位置,然后将其设为要跳转的地址。

如我们上面分析的,微软此次补丁,就恰好向KiFastCallEntry这部分代码添加了4条指令,这就导致了金山原来以为雷打不动的大版本距离差值发生了变化,从而导致定位到了错误的位置,最后挂钩函数跳转到了错误的代码位置,引发系统蓝屏。由于KiFastCallEntry是非常重要的系统分发函数,因此这部分的代码异常就导致了系统反复蓝屏,无法进入。

下面是对KisKnl.sys的这部分逻辑的反汇编,可以清楚的看到这部分逻辑:

.text:000310D7                 mov     eax, os_version
.text:000310DC                 push    esi
.text:000310DD                 mov     esi, callebx_return
.text:000310E3                 add     esi, 0FFFFFF38h
.text:000310E9                 test    eax, eax
.text:000310EB                 jle     loc_313C7
.text:000310F1                 cmp     eax, 3
.text:000310F4                 jle     loc_312F2
.text:000310FA                 cmp     eax, 4
.text:000310FD                 jz      loc_31251
.text:00031103                 cmp     eax, 5
.text:00031106                 jz      loc_311B3
.text:0003110C                 cmp     eax, 6
.text:0003110F                 jnz     loc_313C7
.text:00031115                 lea     eax, [ebp+code_offset]
.text:00031118                 push    eax
.text:00031119                 push    27h
.text:0003111B                 push    offset KiAccessSystemTebCodeSpec
.text:00031120                 push    0C8h
.text:00031125                 push    esi
.text:00031126                 call    code_search
.text:0003112B                 test    eax, eax
.text:0003112D                 jz      loc_3126B
.text:00031133                 mov     eax, [ebp+code_offset]
.text:00031136                 lea     ecx, [eax+esi+25h]
.text:0003113A                 lea     eax, [ecx+48h]
.text:0003113D                 mov     jump_cross_return_address, eax

 

从代码中我们可以看到, KisKnl先是判断系统版本号,然后在获得的中部地址向上搜索获得KiSystemServiceAccessTeb附近地址后,通过版本号硬编码的偏移来决定跳转返回的地址。

在新版本的KisKnl.sys在确定硬编码偏移后,还会再判断下代码是否符合,暂时避免了这个问题,但仍留有继续蓝屏,或者保护失效的隐患。

国内外使用类似技术的主流安全产品,不会采用硬编码偏移的这种方式来确定跳转地址,也不会采用强行跳走系统代码这种不稳定的做法,因此不存在同此补丁的冲突问题。

金山系列软件发生这次的问题,主要还是开发人员技术经验不足,在技术方案选择时没有慎重考虑,在发生问题后没有仔细考虑引发问题的方案的不足导致的。

内核驱动程序的方案稳定性决定着用户系统与资料的稳定与健全,开发人员都应引以为戒,慎重对待和考虑技术方案。

关于 “端午节蓝屏之谜:金山系列软件同微软KB2839229冲突技术分析” 的 10 个意见

评论关闭。