Loading

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函数的局部变量。
posted @ 2016-01-19 23:23  Lnju  阅读(272)  评论(0)    收藏  举报