漏洞分析一百篇-02-Adobe Reader TTF字体SING表栈溢出漏洞
分析此漏洞旨在学习基于字符串定位的漏洞方法和ROP编程技巧,结合漏洞战争一书,事半功倍。
分析环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows7 x64 SP1 | Build 7601 简体中文版 |
漏洞软件 | Adobe Reader | 版本号:9.3.0英文版 |
首先在msf中使用cve-2010-2883对目标环境进行测试,验证成功之后开始我们的分析之旅。
基于字符串定位的漏洞分析方法
根据漏洞描述,该漏洞是Adobe Reader中的CoolType.dll库在解析字体文件SING表中的uniqueName时出现了栈溢出,Alt+B
搜索字符串SING,得到几个可疑的地址,对样本动态调试时对可疑地址下断点,由于栈溢出中一般不会离可疑地址太远。我们在函数中直接执行到返回,程序崩溃或者进入了payload,那么离崩溃点最近的断点极有可能在就是溢出点所在的函数中,断点如下:
实际过程中定位到0x7012dc6d
(由于Win7默认开启ASLR,CoolType.dll基址为0x700f0000
),换算成相对地址0x0003dc5c。
.text:0003DBF2 push ebp
.text:0003DBF3 sub esp, 104h ; 分配栈的大小为0x104
.text:0003DBF9 lea ebp, [esp-4]
.text:0003DBFD mov eax, ___security_cookie
.text:0003DC02 xor eax, ebp
.text:0003DC04 mov [ebp+108h+var_4], eax
.text:0003DC0A push 4Ch
.text:0003DC0C mov eax, offset sub_1847C4
.text:0003DC11 call __EH_prolog3_catch
.text:0003DC16 mov eax, [ebp+108h+arg_C]
.text:0003DC1C mov edi, [ebp+108h+arg_0]
.text:0003DC22 mov ebx, [ebp+108h+arg_4]
.text:0003DC28 mov [ebp+108h+var_130], edi
.text:0003DC2B mov [ebp+108h+var_138], eax
.text:0003DC2E call sub_41626
.text:0003DC33 xor esi, esi
.text:0003DC35 cmp dword ptr [edi+8], 3
.text:0003DC39 mov [ebp+108h+var_10C], esi
.text:0003DC3C jz loc_3DDF9
.text:0003DC42 mov [ebp+108h+var_124], esi
.text:0003DC45 mov [ebp+108h+var_120], esi
.text:0003DC48 cmp dword ptr [edi+0Ch], 1
.text:0003DC4C mov byte ptr [ebp+108h+var_10C], 1
.text:0003DC50 jnz loc_3DDA2
.text:0003DC56 push offset aName ; "name"
.text:0003DC5B push edi ; int
.text:0003DC5C lea ecx, [ebp+108h+var_124]
.text:0003DC5F mov [ebp+108h+var_119], 0
.text:0003DC63 call sub_2178F
.text:0003DC68 cmp [ebp+108h+var_124], esi
.text:0003DC6B jnz short loc_3DCD6
.text:0003DC6D push offset aSing ; "SING"
.text:0003DC72 push edi ; int
.text:0003DC73 lea ecx, [ebp+108h+var_12C]
.text:0003DC76 call sub_21ABE ; 处理完该函数后,ebp+108h+var_12C指向SING表首地址
.text:0003DC7B mov eax, [ebp+108h+var_12C] ; SING表数据结构在堆上
.text:0003DC7E cmp eax, esi ; 判断SING表首地址是否为空
.text:0003DC80 mov byte ptr [ebp+108h+var_10C], 2
.text:0003DC84 jz short loc_3DCBD ; 非空,不跳转
.text:0003DC86 mov ecx, [eax] ; 字体版本号(0x00010000)
.text:0003DC88 and ecx, 0FFFFh ; 取低位位
.text:0003DC8E jz short loc_3DC98 ; 跳转
.text:0003DC90 cmp ecx, 100h
.text:0003DC96 jnz short loc_3DCB9
.text:0003DC98
.text:0003DC98 loc_3DC98: ; CODE XREF: sub_3DBF2+9Cj
.text:0003DC98 add eax, 10h ; 指向uniqueName
.text:0003DC9B push eax ; char * ; strcat第二个参数,源地址
.text:0003DC9C lea eax, [ebp+108h+var_108]
.text:0003DC9F push eax ; char * ; strcat第一个参数,目的地址,使用ebp做目的地址
.text:0003DCA0 mov [ebp+108h+var_108], 0
.text:0003DCA4 call strcat ; 样本中的0x368-0x12c=0x23c,超出了0x104,造成溢出
下面两幅图是strcat之后memory dump和样本中uniqueName字段的比较,可以观察到栈溢出。
样本调试memory dump
样本中的uniqueName字段
Exploit分析
如何跳转到ROPChain
做EXP的分析就是为了我们以后能写出自己的EXP,在这里我们再深入一些思考。定位到溢出点之后为了写一个有趣的EXP,这里我们选择覆盖SEH指针绕过GS,再通过ROP绕过DEP,大致思路是这样,接下来我们详细分析。
执行strcat之后,我们进行逐函数跟踪,由于栈已经被破坏回溯的方法在这里并不好用,当然这里也不推崇泉哥的内存访问断点,因为这种方法忽略了很多细节,并不利于我们写EXP,而且导致部分人以为是通过SEH进入ROP的。下面列出单步关键位置:
.text:0003DDA8 call sub_16B96 ; 此处StepOver会崩溃,定位到sub_16B96
.text:00016C0E call sub_1BAD9 ; 此处StepOver会崩溃,定位到sub_1BAD9
.text:0001BAF9 call dword ptr [eax] ; 在sub_1BAD9中此处StepOver会崩溃,实际进入sub_8AFCE
.text:0008B1C0 call dword ptr [eax] ; 此处[EAX]=0x4a80cb38,是我们样本中的数据,从这里开始进入ROPChain
这个时候朋友们就要问一句自己写EXP的时候怎么知道在0x8B1C0处call dword ptr [eax]
能达到利用的目的呢?我们再想一想,由于有safeSEH的存在,这里通过覆盖SEH进入payload并不是一个特别好的选择,由于有GS保护覆盖返回地址也行不通。通过上面call dword ptr [eax]
指令,在strcat覆盖之前[eax]=0x832b5(相对地址),猜测极有可能是通过覆盖虚函数地址,进入payload。
开启了safeSEH保护:
逆向验证是,发现call dword ptr [eax]
确实应该是结构体中的函数指针,更深入的分析可能就需要一个官方资料或者逆向大牛提供数据结构参考了。
ROPChain分析
下图中0x4a82a714和0x4a80cb38是精心选择的两处地址,我们选择未开启ASLR保护的icucnv36.dll绕过ASLR,从而保证了EXP的通用性。现在有ROP gadgets查找的工具,更方便EXP的开发,ROP指令1需要满足ebp在我们溢出的范围内可控;ROP指令2配合js的Heap Spray将需要的数据提前分配到栈上0x0c0c0c08,js样本参考书中,metasploit简单混淆了下。
ROP指令1
ROP指令2
在之后的ROP中通过CreateFileA创建一个名为iso88591的文件,使用CreateFileMappingA和MapViewOfFile分配一片可执行文件内存映射,然后使用memcpy将shellcode拷贝到分配的内存中以此来绕过DEP,最后进入shellcode。
ROP指令3
ROP指令4
ROP指令5
createFile的参数
createFilemMapping的参数
mapViewofFile的参数
memcpy的参数
其中我们仔细观察CreateFileA的返回值是0xffffffff,根据CreateFileMappingA的参数创建的是一个物理文件无关的内存映射,所以这个CreateFileA是冗余的。这里我们分析两段重要的ROP代码:
1.函数的调用,在icucnv36.dll分别找到0x4a84903c、0x4a849038、0x4a849030和0x4a849170分别指向各API地址。
这里调试发现kernel32.dll固定加载在0x75f30000处。
0x4a801f90, # pop eax / ret
0x4a84903c, # becomes eax (import for CreateFileA)
0x4a80b692, # jmp [eax] 等效于 jmp CreateFileA
0x4a801064, # ret CreateFileA的返回地址
2.函数之间的参数传递,CreateFileMappingA的返回值是做MapViewofFile的参数,MapViewofFile的返回值做memcpy的目的地址。
0x4a842db2, # xchg eax,edi / ret 返回值在eax,先将eax和edi交换
0x4a802ab1, # pop ebx / ret
0x00000030, # becomes ebx - offset to modify 利用ebx作修正值,来修改即将调用函数的参数
0x4a80a8a6, # execute fun block 等效于 AND DWORD PTR SS:[ESP+EBX*2],EDI 结合ebx将edi参数传递到栈中
通过上面一段代码就可以达到参数传递的目的,其中memcpy需要三次修改栈上数据(目的地址、源地址和memcpy返回值),其中两次要用到目的地址(一次是拷贝、一次是返回),会先将目的地址存在0x4a8a0004处,书中要求0x4a8a08e2可读可写也是这个道理。
这里说明一点,该ROPChain中修改栈上参数都是用的上述法2,Metasploit给的CVE-2010-2883的EXP,这里注释不准确,0x4a80aedc
处的代码只是将栈的偏移存在edx备用,并未直接修改memcpy的返回值。
总结
写完博客才发现截图特别不清晰,下次改进,再就是老生常谈的一句话“尽信书不如无书”,自己多动手才能获得属于自己的知识。有经验的Pwner在CTF中很容易发现漏洞点,大家更多侧重是漏洞利用,而真实的漏洞可能需要更多时间去定位漏洞点,会处理更为复杂的数据结构和代码逻辑,比方说逆向定位结构体和虚函数表,在下一步的学习中,会继续介绍相关方面的资料。