MS13-101分析
由于没有找到网上对于这个漏洞的分析(估计是太简单了吧),我独立分析一下这个漏洞,是一次寻求通解思考的过程
0x1 看微软的公告
得知漏洞的一般信息,漏洞造成的危害是什么?漏洞影响的系统的版本是什么?(利于执行POC复现漏洞)漏洞利用的条件是什么?(本地 or 远程)漏洞的补丁文件?(可以通过diff补丁前后来分析漏洞)
以MS13-101为例,漏洞的危害是本地提权,影响XP sp3,windwos7……
0x2 看相关安全网站的漏洞详情
找到漏洞的POC进行漏洞复现,以便后续调试漏洞。有POC有时还会有漏洞细节,比如漏洞位于哪个模块?哪个函数?是什么类型的漏洞(整数溢出 or 栈溢出……)这些都会有一定的说明。还可以以MS-xxx或CVE-xxxx为关键字google一下这个漏洞的在其他网站上的描述。
参考链接:
http://www.coresecurity.com/advisories/divide-error-windows-kernel
https://www.exploit-db.com/exploits/30397/
以MS13-101为例,漏洞存在于win32k.sys模块的RFONTOBJ::bTextExtent函数。攻击者可以通过调用用户态下的NtGdiGetTextExtent函数,并且传递构造好的参数,去利用这个漏洞。找到的POC是使系统crash。更加细节的漏洞描述是因为执行有符号除法IDIV,结果不合适目标操作数,内核引发整数溢出异常。
分析过程
运行POC,crash,提示
FOLLOWUP_IP:
win32k!RFONTOBJ::bTextExtent+10f //->造成内核crash的最后一条指令
bf86c88f f77d20 idiv eax,dword ptr [ebp+20h]
kb
b1c610b4 804f8bdb 00000003 b1c61410 00000000 nt!RtlpBreakWithStatusInstruction
b1c61100 804f97c8 00000003 bf86c88f e164d168 nt!KiBugCheckDebugBreak+0x19
b1c614e0 804f9cce 0000007f 00000000 00000000 nt!KeBugCheck2+0x574
b1c61500 80598bab 0000007f bf86c88f e164d168 nt!KeBugCheck+0x14
b1c61558 8053f32f b1c61564 b1c61b20 bf86c88f nt!Ki386CheckDivideByZeroTrap+0x41
b1c61558 bf86c88f b1c61564 b1c61b20 bf86c88f nt!KiTrap00+0x83
b1c61b20 bf86c587 b1c61cd0 e157d000 00000011 win32k!RFONTOBJ::bTextExtent+0x10f // ->最后一个call在地址bf86c587的上一条,这个call也是crash所在函数
b1c61cc8 bf867de1 e12b7008 e157d000 00000011 win32k!GreGetTextExtentW+0x152
b1c61d48 8053e748 08010564 0012ef80 00000011 win32k!NtGdiGetTextExtent+0xdf
b1c61d48 7c92e514 08010564 0012ef80 00000011 nt!KiFastCallEntry+0xf8
0012ee88 0040106c 0040118c 08010564 0012ef80 ntdll!KiFastSystemCallRet
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ff80 00401499 00000001 00a40df0 00a40e78 POC+0x106c
0012ffc0 7c81776f 80000001 00ddda80 7ffd4000 POC+0x1499
0012fff0 00000000 004013b0 00000000 78746341 kernel32!BaseProcessStart+0x23
看POC,调用了这两个函数
_NtGdiSetTextJustification(hdc, 0x08000000, 0xffffffff);
_NtGdiGetTextExtent(hdc, (int) buffer, 0x11, 0x44444444,
0x55555555);
//一个set,一个get
直觉上来说,set就是写配置进内存,相对比较简单,先看看写在里哪里,下一个函数肯定会用到这步设置的值。而get是读配置,感觉相对复杂点,可以回溯,不要正向分析。
看堆栈调用,能看到第二个函数在内核中是win32k!NtGdiGetTextExtent,所以可以推测出第一个函数在内核中表示为win32k!NtGdiSetTextJustification,在windbg中x命令找到这个函数的地址,在IDA打开这个函数,看他对参数的操作。
kd> x win32k!NtGdiSetTextJustification
bf9575e6 win32k!NtGdiSetTextJustification = <no type information>
在图中可以看到对HDC参数先调用了构造函数,然后把其余两个参数放到了成员变量中。windbg验证
kd> p
win32k!NtGdiSetTextJustification+0x39:
bf95761f 85c0 test eax,eax
kd> dd 003d0910 + 88h
003d0998 08000000 ffffffff 018a0021 40000000
003d09a8 00000006 00000000 00000000 00000000
003d09b8 00000000 40000000 00000006 00000000
//+ 88h 和 + 8ch处放的是后两个参数
在crash的地址看下,crash是因为除法指令溢出,首先看下是怎么溢出的
win32k!RFONTOBJ::bTextExtent+0x10e: //此时eax是80000000
bf86c88e 99 cdq
kd> r eax
eax=80000000```
那么cdq之后,edx = 0xFFFFFFFF.用python简单的算了下
```python
>>> bin(0xffffffff80000000 /0x80000000)
'0b111111111111111111111111111111111'
>>> len(bin(0xffffffff80000000 /0x80000000))
35 #35位溢出了eax
深入分析
那么此时,关键的值是eax,为什么恰巧是0x80000000,看到是由_lCvt@12函数算出来的,这个算法不去关心,关心函数的3个参数都是怎么传递进来的。这也是逆向分析和漏洞利用不同的关注点。
参数3
参数3是上个函数传进来的参数5(其实是参数6,我是从0开始算的)。为了验证是否正确,动态调试到函数call之前,观察参数
kd> r ecx,edi
edi=b1cf1cc4
win32k!RFONTOBJ::bTextExtent+0x109:
bf86c889 e8b3f9fbff call win32k!lCvt (bf82c241)
kd> dd esp
b1cf15cc 40000000 00000006 08000000 00000000
b1cf15dc 00000000 b1cf1cfc 00000060 e1228d88
断下在函数头,第5个参数是08000000
win32k!RFONTOBJ::bTextExtent:
bf86c71b 8bff mov edi,edi
kd> dd esp Lc
b1803b24 bf86c587 b1803cd0 e157d000 00000011
b1803b34 00000000 00000000 08000000 ffffffff
b1803b44 55555555 b1803cfc 00000001 0012ef80
IDA看第6个参数的由来,发现是POC第一个API传进来的。
EDI
接下来的任务是回溯参数1,参数2,实际上是回溯[edi],因为上层函数传递进来的是上层函数的局部变量的地址,所以里面的内容才是我们关注的重点。
总之,最后回溯到最后发现是一连串的指针指来指去。过程凭记忆是比较麻烦的,用纸和笔简单的画图记录一下函数调用传参的过程就很容易理解了。
ChildEBP RetAddr Args to Child
b17f3b24 bf807a88 e1548618 00000000 00000002 win32k!RFONTOBJ::bInit+0x264
b17f3b3c bf86c532 b17f3cd0 00000000 00000002 win32k!RFONTOBJ::vInit+0x16 //b17f3cd0是变量的地址,保存e114c008
b17f3cc8 bf867de1 e114c008 e157d000 00000011 win32k!GreGetTextExtentW+0x5b //e114c008 是指向1501055e的指针
b17f3d48 8053e748 1501055e 0012ef80 00000011 win32k!NtGdiGetTextExtent+0xdf //1501055e是HDC
b17f3d48 7c92e514 1501055e 0012ef80 00000011 nt!KiFastCallEntry+0xf8 //1501055e是HDC
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012ff80 00401499 00000001 00a40df0 00a40e78 0x7c92e514
0012ffd0 80545d7d 0012ffc8 820ad848 ffffffff 0x401499
0012fff0 00000000 004013b0 00000000 78746341 nt!ExFreePoolWithTag+0x417
//模仿参数传递的过程
kd> dd b17f3cd0 L1
b17f3cd0 e114c008
kd> dd e114c008+1ch L1
e114c024 e166a008
kd> dd e166a008+260h L1
e166a268 e11f3d08
//取到了参数1,参数2这2个值
kd> dd e11f3d08+190h L1
e11f3e98 00000006
kd> dd e11f3d08+18ch L1
e11f3e94 40000000
总结:
学会看堆栈调用
- 每行从下到上的顺序为堆栈调用顺序;
-每行从左到右的顺序是低地址到高地址;
-分别是ebp+0,ebp+4;ebp-4,ebp-8正是ida中函数的var_4,var_8,没有见过var_0对吧,var_0就是ebp本身。最后面的函数符号funX表示堆栈所在的函数;arg1,arg2是上层函数调用funX函数的所传递的参数;ebp-4是funX函数的局部变量。


浙公网安备 33010602011771号