2012年(366)
分类: 网络与安全
2012-03-04 13:00:24
早就听说duqu是第二代的stuxnet,心中就保留了一份好奇。之前对stuxnet的分析不仅可以看到作者在漏洞挖掘上的功力,同时也对作者那些精巧的利用思路由衷敬佩。而且,据说这个内核漏洞是以doc形式来触发的,心中更是想仔细跟一下。搜了一下,找到两篇比较好的文章启明星辰的分析,以及kk牛的《Analysing CVE-2011-3402 Font Parasing Vulnerability》。这两篇资料,对我的分析起到了很大的帮助。
一、漏洞原理:
在“启文”和KK的文章中,已经把漏洞原理说得比较清楚了,这里就是对他们所描述原理的理解、抄袭。
Win32k在处理字体的时候,需要计算出两个重要的变量:ulBitmapOffset、ulWorkMemSize。如果计算出来的ulBitmapOffset>ulWorkMemSize则会导致问题。因为,sfac_GetSbitBitmap函数会对ulBitmapOffset+0xA48位置的数据进行了修改,导致修改了后面结构fnt_GlobalGraphicStateType中的数据。而ulBitmapOffset+0xA48恰好指向fnt_GlobalGraphicStateType中的cvtcount成员。将修改cvtcount的值改得比较大以后,将从而使得解析引擎RCVT和WCVT函数在后面读和写ControlValueTable的时候越界读写。
Win32k!sfac_GetSbitBitmap
bf988bab 8d2424 lea esp,[esp]
bf988bae 6685c0 test ax,ax
bf988bb1 8b5528 mov edx,dword ptr [ebp+28h]
bf988bb4 8d3413 lea esi,[ebx+edx]
bf988bb7 760e jbe win32k!sfac_GetSbitBitmap+0x109 (bf988bc7)
bf988bb9 0fb7f8 movzx edi,ax
bf988bbc 8bff mov edi,edi
bf988bbe 8a11 mov dl,byte ptr [ecx]
bf988bc0 0816 or byte ptr [esi],dl
由于字体解析的时候ulBitmapOffset越界,sfac_GetSbitBitmap函数修改了Bitmap后面相邻结构中的变量(fnt_GlobalGraphicStateType中的cvtcount),导致cvtcount变大,从而使得解析引擎RCVT和WCVT函数在后面读和写ControlValueTable的时候越界读写。而在ControlValueTable后面紧跟的是fnt_GlobalGraphicStateType结构(本例原始cvtcount=1,因此fnt_GlobalGraphicStateType在controlValueTable偏移4的位置)。
利用时,通过精心构造字体文件0x3BAF2处存放的字体虚拟机的指令及操作数,可以控制字体的执行流程。通过RCVT函数依靠cvtcount过大的错误将指向shellcode的指针取出来,再通过WCVT函数将其写入fnt_GlobalGraphicStateType偏移0xAC的位置,再通过itrp_LSW函数产生对fnt_GlobalGraphicStateType偏移0xAC处的调用,从而达到执行任意代码的目的。
二、对溢出过程的调试
对itrp_InnerExecute的调试:
按照“启文”的说法itrp_InnerExecute是字体引擎的核心,会不断会读取instruments的值,然后调用具体的函数。在字体文件中包含的Instruments表(该表保存着解析该字体的时候需要调用的解析函数的index值以及参数值。)位于文件偏移的0x3BAF2处。
“启文”中还对itrp_InnerExecute和itrp_PUSHB1进行了注解说明。读者可以参阅“启文”的说明自己阅读相关代码。代码逻辑并不复杂,在相关提示下很容易就能看得懂。
另外要注意的是,字体虚拟机所使用的内存栈和我们熟悉的系统栈有所区别:
1、在字体虚拟机中,栈指针指向当前栈顶数据的下一个内存单元,也就是栈指针总是比当前栈顶元素地址大4。
2、在字体虚拟机中,栈是向高地址生长的。
函数名 | 功能描述 | 参数个数 |
Itrp_PUSHB1 0xB0 |
向栈内压入一个字节(该参数存储在该index 值后面),同时栈指针加4 | 0 |
Itrp_PUSHW1 0xB8 |
向栈内压入一个字,栈指针加4 | 0 |
Itrp_RS 0x43 |
以栈中的参数作为storeindex读取storage存储的一个临时变量,并存储在栈中storeindex的位置 将数据从storage读入栈中,用于存一些临时变量 |
1(该参数作为storeindex,该函数会以GlobalGS.storeaddress为基址,并通过index作索引读取存储的临时变量) |
Itrp_WS 0x42 |
以当前栈倒数第二个压入得数值作storeindex,将当前栈最后一个压入的参数赋值给storage+storeindex*4的地方,栈指针-8 将栈中数据取出,存到storage中。用于存一些临时变量 |
2 |
Itrp_RCVT 0x45 |
以栈中最后一个压入的参数为cvtIndex,执行检测操作,如果cvtIndex>=cvtcount且小于255或者cvtIndex小于cvtcount的时候,从controlValueTable取数值,放入栈中之前存放cvtIndex的位置。栈指针不变。 将数据从controlValueTable读入栈中 该函数还将调用itrp_GetCVTEntryFast,从pfnt_GlobalGraphicStateType+0x8读取指针pControlValueTable,该指针指向ControlValueTable。ControlValueTable结构紧邻在fnt_GlobalGraphicStateType前面 |
1(cvtIndex) |
Itrp_FLIPON 0x4D |
修改autoflip的值为1,修改FLIP标记 | 0 |
Itrp_FLIPOFF 0x4E |
修改autoflip的值为0 | 0 |
Itrp_ADD 0x60 |
当前栈倒数第二个压入的值加上当前栈最后压入的数值,将其和放入当前栈倒数第二个压入的值的位置,栈指针-4 | 2 |
Itrp_SUB 0x61 |
当前栈倒数第二个压入的值减去当前栈最后压入的数值,将其差放入当前栈倒数第二个压入的值的位置,栈指针-4 | 2 |
Itrp_SWAP 0x23 |
当前栈倒数第二个压入的值和当前栈最后压入的值作交换,栈指针不变 | 2 |
Itrp_DUP 0x20 |
当前栈-4位置的数据复制到当前栈的位置,栈指针加4 | 2 |
Itrp_JROT 0x78 |
有条件跳转,这里的跳转是指在instruments列表中跳转。该函数判断栈中最后压入的参数是否为0,如果为0,则不跳转;如果不为0,则以栈中倒数第二个参数为跳转的offset,并跳转到当前instruments指针+offset-1的instruments地址上。栈-8 在instruments列表上跳转 |
2 |
Itrp_JMPR | 相当于无条件跳转,以当前栈中最后压入的参数为offset,跳转到当前instruments指针+offset-1的instruments地址上。栈-4 | 1 |
Itrp_LT2 | 比较两个数据的大小,如果栈中最后压入的参数大于栈中倒数第二个压入的参数,则返回1,否则返回0。并将结果放到倒数第二个参数的位置。栈-4 | 2 |
Itrp_NOT | 检测当前栈中最后压入的数据,如果为0的话,则返回1,并存储在栈中最后压入的数据的地址处。 | 1 |
Itrp_WCVT 0x44 |
对应于itrp_RCVT,也就是写controlValueTable数据。该函数使用栈中倒数第二个参数作为cvtindex值,将倒数第一个参数作为数据存储在controlValueTable+cvtindex*4的地址中 将数据从栈中写入到trolValueTable |
2 |
Itrp_LSW 0x1f |
关键是要使用call dword ptr [eax+0ACh],使其执行到shellcode | 2 |
序号 | 对应函数 | 执行完后的栈分布情况 | 执行结果 | |||
偏移0 | 偏移4 | 偏移8 | 偏移C | |||
1 | Itrp_PUSHB1 | 80 | 压入参数0 | |||
2 | Itrp_PUSHB1 | 压入参数0 | ||||
3 | WS | storage=e22adf00,将栈中数据0取出,存到storage中 | ||||
4 | FLIPOFF | 修改autoflip标志位为0 | ||||
5 | Itrp_PUSHB1 | 压入参数0 | ||||
6 | RS | 0 | 以刚压入0作为storeindex取出WS存储的数据 | |||
7 | RCVT | 0 | 读取controlValueTable偏移为0处的数据 | |||
8 | FLIPON | 0 | 修改autoflip标志位为1 | |||
9 | PUSHB1 | 0 | 压入参数0 | |||
10 | RS | 0 | 以刚压入0作为storeindex取出storage中存储的数据 | |||
11 | RCVT | 0 | 读取controlValueTable偏移为0处的数据 | |||
12 | Sub | 0 | 0 | 对结果没有影响 | ||
13 | PUSHB1 | 0 | 17 | 压入参数17 | ||
14 | SWAP | 栈顶的两个元素交换 | ||||
15 | JROT | 不跳转,通过对比函数返回前后eax可以知道是否跳转 | ||||
16 | PUSHB1 | 0 | 压入参数0 | |||
17 | RS | 将storage中index=0位置的数据 读入栈中 | ||||
18 | PUSHB1 | 01 | 通过db ecx l1可以看到参数值 | |||
19 | Add | 栈顶的两个数据相加,放到栈顶之下的空间1+0->0 | ||||
20 | DUP | 复制数据 | ||||
21 | PUSHB1 | 01 | 压入参数0 | |||
22 | SWAP | 1 | 0 | 1 | ||
23 | WS | 1 | ||||
24 | PUSHB1 | 1 | 50 | 压入参数50 | ||
25 | SUB | Ffffffb1 | 1-x050 | |||
26 | PUSHW1 | 1 | ffffffdf | 压入参数ffdf,实际结果是0xffffffdf | ||
27 | SWAP | 1 | Ffffffb1 | |||
28 | JROT | 跳转成功,instrument从0x3bb18跳转到0x3baf7,从指令表中看是回到指令4处 第一次产生循环 | ||||
29 | FLIPOFF | |||||
30 | Itrp_PUSHB1 | 压入参数0 | ||||
31 | RS | 0 | 以刚压入0作为storeindex取出storage存储的数据,0x1,存入栈中 | |||
32 | RCVT | ffffffb1 | e22adafc | 读取controlValueTable中index=1处的数据,由于cvtcount被恶意篡改,导致index | ||
33 | FLIPON | ffffffb1 | e22adafc | |||
34 | Itrp_PUSHB1 | 压入参数0 | ||||
35 | RS | 1 | 1 | 以刚压入0作为storeindex取出storage中存储的数据,压入栈 | ||
36 | RCVT | 再次使用错误的cvtcount数据从controlValueTable中读取数据,得到fnt_GlobalGraphicStateType的stackbase的值。 | ||||
37 | SUB | e22adafc | 0 | |||
38 | PUSHB1 | 1 | 17 | 压入参数17 | ||
39 | SWAP | 17 | 1 | 交换 | ||
40 | JROT | 不跳转 | ||||
41 | PUSHB1 | 0 | 压入参数0 | |||
42 | RS | 0 | 以刚压入0作为storeindex取出storage存储的数据,0x0,存入栈中 | |||
43 | PUSHB1 | 1 | 1 | 压入参数1 | ||
44 | ADD | 2 | 1 | 栈顶的两个数据相加,放到栈顶之下的空间1+1->2 | ||
45 | DUP | 1 | 2 | 当前栈-4位置的数据复制到当前栈的位置,栈指针加4 | ||
46 | PUSHB1 | 0 | 0 | 压入参数1 | ||
47 | SWAP | 0 | 2 | |||
48 | WS | 2 | 相当于是将2保存下来,作为storage的第一个临时变量 | |||
49 | PUSHB1 | 50 | 2 | 压入参数50 | ||
50 | SUB | Ffffffb2 | 50 | |||
51 | PUSHW1 | ffffffdf | 00000002 | |||
52 | SWAP | ffffffdf | ffffffb2 | |||
53 | JROT | 跳转成功,instrument从0x3bb18跳转到0x3baf7,从指令表中看是回到指令4处 这里是第二次产生循环 | ||||
54 | 从这里可以看出,由于程序将一直在表项4与表项28所构成的循环序列中来回循环操作。循环结束的条件将是add指令构成的累加结果等于0x50 |