官方的漏洞通报中,关于这个漏洞的信息其实很少:
Integer overflow in Adobe Reader and Acrobat 9.x before 9.5.1 and 10.x before 10.1.3 allows attackers to execute arbitrary code via a crafted TrueType font.
只有几个关键点:构造的TTF文件,Adobe Reader版本,整数溢出漏洞。
因为这是一个很老的漏洞,网上能搜到的很多分析文章都是基于《漏洞战争》这本书完成的,并且其中的大多数只是在进行书中内容的复述。在阅读书中内容的过程中,作者提到使用TrueType Font Analyzer对ttf文件进行解析时出错,由此判断问题出现在glyf表中。虽然我找到了这个工具,但实在是太小众了,是一个日本博客中提供的,而用010editor对TTF文件进行解析的过程中没有得到什么有用的输出信息。所以漏洞分析的一开始,最困扰我的就是,如果没有《漏洞战争》这本书,我要如何确定异常数据的位置。
以下的分析内容有些做的其实是无用功,但是体现了针对该漏洞我的整个思考思路以及分析流程,因此全部保留下来。
如果不看书,我能想到的就是用010editor打开TTF文件。
使用PdfStreamDumper将poc.pdf中的TTF文件提取出来(之前分析过一次Adobe Reader中的字体漏洞 ,所以知道该怎么做),命名为poc.ttf,用010editor打开。软件自动用Template进行解析,Output中显示:
双击这个警告信息,会直接打开用于解析TTF文件的bt文件,定位到出现问题的结构体中:
那么为什么会出现这个警告信息呢?
搜索tcmap_format4
字段会定位到tcmap
结构体,也就是TTF文件中的cmap
表。在010editor中找到cmap
表,其中包含了两个子表,第二个子表中就包含了出现问题的tcmap_format4
,点开之后可以发现它的length
字段是64,如果你选中整个tcmap_format4
结构,会发现它的长度也是64,所以计算(length-(FTell()-cmap_subtable))/2
得到的值是0。因此出现了警告信息。
不过我也不知道警告信息有什么用,但是既然这里出现了警告,那么至少说明这个文件中的结构是有一些问题的,再加上template的解析结果其实比较乱,我将结果导出到文档中,并进行了整理:
根据之前的漏洞分析经验,已经知道TTF文件中都是由一个个表组成的,这里汇总的就是不同表的位置以及大小数据,其中用(head)
标注的数据指的是文件开头的Table Directory中记录的各个表的偏移及大小,而没有使用(head)
标注的数据则是template整理出来的实际的位置和大小。
注意到Table Directory中记录的表的大小信息有3处与实际不符,但是由于对表的具体功能不了解,所以还要继续查资料。
通过TTF中template的输出结果,已经对poc.ttf文件有了一个初步的了解,但是由于对于每个表的具体功能并不了解,因此仍旧是一头雾水,所以接下来开始直接阅读文档。
注:以下内容之所以会注意到那么多细节的内容是因为后面调试阶段遇到了相关问题,所以又回过头来补充的。所以可以先看下面的调试,再回过头来看这里的文件格式分析。
name表中包含的是一些关于字体的可读信息,可以被其他表引用,从而向用户提供有用的信息。它的结构是这样的:
注意到其中的char name[35]
了吗,它的Start
数据是0xFBE
,这里其实就是上面统计的数据中,name表Size
中未包含的部分。准确的说010editor的template并没有把这部分数据包含在name表的Size
中,因此出现了和Table Directory中不符的情况,但是实际上没有任何问题。
所谓cmap,其实就是character mapping的缩写,它用来将字符编码映射成实际的字形。由于存在多种平台环境,多种编码形式,因此就对应了多种编码表,因此cmap表中也就可能包含多个子表,每个子表对应一个编码形式。在实际使用的时候会根据情况选择使用哪个子表。
根据文档中的描述,对poc.ttf文件中的cmap进行解释:
其中没有展开的两个tcmap_format
结构就是具体的映射表了,注意它们的Start
信息,会发现这两个映射表其实就占据了上面tamplate总结的Size信息未包含的那部分。因此虽然和Table Directory中的记录不符,但是也没有问题。
不过在2.1小节中,我们提到了Variable 'glyphIdArray' not generated
的警告信息,这个警告信息就是tcmap_format4
中产生的,因此再具体的看一下tcmap_format4
结构:
format 4格式针对的是2字节编码格式,当字体编码位于多个连续区间之内的时候使用这种格式。上图中的segCount
表示的就是连续区间的个数,startCount
和endCount
可以用于确定编码落在哪个区间范围内,针对上图,六个区间分别是[32, 34]
、[77, 77]
、[100, 101]
、[114, 116]
、[160, 160]
、[-1, -1]
,其中最后一个区间不对应任何有效编码。
idDelta
和idRangeOffset
用于确认编码对应的glyph索引值,针对上图,由于idRangeOffset
为0,因此索引值的计算方法为:glyphIndex = idDelta[i] + c
。
索引值最后用于在glyphIndexArray
中索引,但是在此例中缺少了glyphIndexArray
。
看到现在,还是不确定glyphIndexArray
这个结构怎么对应到实际的字形上,先看下一个表。
maxp表中的数据说明了字体的内存需求,这里只关注一个数值:numGlyphs
,保存了glyph的个数。在此例中,这个数值是271。
loca表中保存了字形数据相对于glyf表起始部位的偏移位置,这个表主要是为了对字形数据能够快速索引。里面就是一个USHORT的数组,一共由numGlyphs+1
项(还包括一个表示字符不存在的字形)。
在此例中,数组中有多项是重复的,因为下面分析glyf表时要用到,所以这里做一个整理:
glyf表中保存了定义字体字形的数据信息,其中既包括定义字形轮廓的点信息,也包括填充字形的指令信息:
在检查这个表的时候,没有发现和name以及cmap表类似的数据索引的情况,因此需要搞清楚为什么glyf表后面会空余出一大块数据。
这里就要回头看一下loca表中的数据了,如果你将loca表中保存的偏移量*2,再加上glyf表的起始位置0x600
,就会得到各个SimpleGlyph
的Start值了。
注:关于为什么要 2,head表中定义了一个indexToLocFormat
数值,如果该值为0
,代表short,单位就是2个字节,所以要2。
注意到loca表中重复的数据所对应的SimpleGlyph的Start值也是相同的,虽然它们在template的结果中表示成了不同的项。
但是template的结果中只显示到偏移为0x156
的字形数据,之后偏移的字形数据没有解析出来。
现在我们把后面的数据复制出来,然后手工按照SimpleGlyph的结果进行简单的解析:
后面的compressedFlags和contours有点复杂所以我没有进一步处理。
操作系统: Win7 sp1 32位
Adobe Reader 9.4 英文版
打开poc.pdf文件之后,由于发生异常,windbg自动打开,程序中断:
看一下这个地址:
发现这是一个具有只读权限的地址,而现在程序在尝试写入,因此出现异常。
由于CoolType.dll每次加载的基地址都不一样,所以为了方便在IDA中定位,直接将CoolType.dll在IDA中的基地址修改成0,然后根据偏移定位到发生漏洞的位置,并将其函数命名为vulFunc
。
先看一下IDA中的伪代码分析:
我对变量名进行了一些修改以使过程更加清晰,整个函数是在对一个范围的数据进行循环前移操作。a-4是范围的终点,终点位置保存了整个范围的长度。函数一开始对整个范围的地址进行了一个上下界的判断,符合要求后才会进行下一步循环前移操作。
确定了函数功能之后,重新加载调试,在vulFunc
的起始位置设置断点,然后单步调试,跟踪到计算start的语句start = (a - 4 - 4 * len)
的时候发现了问题:
注意一开始读取到的长度是0x40000001
,执行完*4操作之后的ebx的值是00000004
,这里发生了溢出。画成图来看比较清晰:
由于范围判断的不严谨,程序没有发现发生了整数溢出,导致了异常的发生。
既然是长度信息有误,一开始自然会想到要确定这个长度信息来自哪里,扩展一点说,这里的循环前移操作想要操作的是什么数据。
根据windbg的输出确定长度信息来自于地址6426622c
,看一下这个地址前面的数据是什么(因为长度信息位于整个数据的末尾):
其中的4141
吸引了我的注意力,这样的数据不太自然,有很大的可能性是人为设置的。目前已知是TTF文件有问题,所以到TTF文件中搜索一下0x4141
出现的位置:
只有这一个位置出现了连续的6个0x41
。
如果和2.2.5小节最后对于数据的解析结果来看,这部分数据位于glyf表中最后一个SimpleGlyph的指令部分:
如果查找TrueType文档中关于指令的介绍 ,可以看到指令0x41
是NPUSHW
操作,0x06
表示入栈个数,说明要入栈6个WORD,同时将其扩展为DWORD,这也就是内存中三个0x00004141
出现的原因,但是现在最关键的是要知道0x40000001
出现的原因。
我最初根据TrueType文档中的指令介绍,对glyf表中最后一个SimpleGlyph中的指令进行了解释:
然后画出如下图的栈中数据变化情况,结果发现不太对劲:
执行到MINDEX
这个指令的时候就是在做vulFunc
中的循环移位操作,但是得到的长度并不是0x40000001
,如上图中所示,应该是执行到JROT
指令的时候做了跳转,EIP向前跳转24个字节,现在不知道24个字节对应于多少指令,再手工分析就有点丧心病狂了。
鉴于现在对于TrueType文件结构以及其中的指令系统有了更加深入的了解,我决定回到IDA和Windbg,通过动态调试的方法最终确定0x40000001
的数据来源。
还是回到IDA中vulFunc
的位置,在IDA中发现了两个交叉引用,分别位于偏移690E
和偏移6C605
,重新打开Adobe Reader,在这两个偏移位置下断点,然后加载POC文件,程序中断在了690E
的位置,说明异常发生在调用690E
之后,在IDA中看一下调用到了vulFunc
的那个语句:
发现这里在通过ecx寄存器索引一个函数数组。
根据上面对指令系统的分析,已经知道vulFunc
是在执行MINDEX
指令,那么很自然的会想到这些函数对应于TrueType中的不同指令。vulFunc
在整个数组的偏移38
的位置,对应于十六进制就是0x26
,就是MINDEX
的指令码,b( ̄▽ ̄)d。
所以程序应该就是在偏移690E
的函数中调用不同的函数来处理不同的指令,我完全可以在.text:00006957 call funcs_6409C64D[ecx*4]
这里设置一个断点,然后通过查看ecx寄存器的值来确定每次执行的指令都是什么。
最后得到了657条输出结果……但是不要着急,如果仔细检查,会发现其中0x78
指令起了很大的作用,一共出现了64次,也就是进行了63次指令跳转,直到最后一次没有跳转,继续往下执行,才到达了0x26
指令处。
重复的指令序列如下:
如果和上面3.2.1中的图相对应,就会发现程序已知在循环执行这部分指令:
相当于已知在0x00000003
的上面递增0x00FFFC00
,计算一下0x00000003 + 0x00FFFC00 * 0x40 = 0x3FFF0003
。
最后一次的读取结果是0,所以不再进行跳转,而是继续往下执行:
这次得到的长度结果正好就是之前调试看到的数值0x40000001
。
在此次的漏洞分析过程中,由于无法说服自己接受“通过TrueType Font Analyzer对于TTF文件的解析结果确定漏洞位于glyf表”中这一因果关系(因为这个工具过于小众,且信息太少),因此我完全放弃根据书中的步骤对漏洞进行分析,转而去查看TrueType的文档。在本文中花费了大量篇幅对TTF文件格式进行了介绍,正是通过对文件格式的理解,我确定了问题处在glyf表中,并进一步确定了问题数据0x40000001
的来源。
在我完成漏洞分析转而看书中的介绍时,发现两者殊途同归,最后竟然都对poc文件中的指令进行了分析,只不过我是从文件格式手工解析出发,转而通过调试验证,而书中是先通过调试确定了指令执行顺序,进而解析文件中的部分指令。
从我个人的角度来说,通过对文件格式的理解进而进行漏洞分析,整个逻辑过程会比较通顺,也易于理解。经过了此次漏洞分析,对于TTF文件格式也有了更深一步的认识。
Executing template
'C:\Users\test\Documents\SweetScape\010 Templates\Repository\TTF.bt'
on
'D:\Myfiles\vul_study\ldzz\2012-0774\poc.ttf'
...
*
WARNING Line
158
: Variable
'glyphIdArray'
not
generated since array size
is
zero.
Executing template
'C:\Users\test\Documents\SweetScape\010 Templates\Repository\TTF.bt'
on
'D:\Myfiles\vul_study\ldzz\2012-0774\poc.ttf'
...
*
WARNING Line
158
: Variable
'glyphIdArray'
not
generated since array size
is
zero.
typedef struct tcmap_format4 {
cmap_subtable
=
FTell();
USHORT
format
;
/
/
Format
number
is
set
to
4.
USHORT length;
/
/
This
is
the length
in
bytes of the subtable.
USHORT language;
/
/
Please see
"Note on the language field in 'cmap' subtables"
in
this document.
USHORT segCountX2;
/
/
2
x segCount.
USHORT searchRange;
/
/
2
x (
2
*
*
floor(log2(segCount)))
USHORT entrySelector;
/
/
log2(searchRange
/
2
)
USHORT rangeShift;
/
/
2
x segCount
-
searchRange
USHORT endCount[segCountX2
/
2
];
/
/
End characterCode
for
each segment, last
=
0xFFFF
.
USHORT reservedPad;
/
/
Set
to
0.
USHORT startCount[segCountX2
/
2
];
/
/
Start character code
for
each segment.
SHORT idDelta[segCountX2
/
2
];
/
/
Delta
for
all
character codes
in
segment.
USHORT idRangeOffset[segCountX2
/
2
];
/
/
Offsets into glyphIdArray
or
0
USHORT glyphIdArray[(length
-
(FTell()
-
cmap_subtable))
/
2
];
/
/
Glyph index array (arbitrary length) !!!就是这里出现了问题!!!
};
typedef struct tcmap_format4 {
cmap_subtable
=
FTell();
USHORT
format
;
/
/
Format
number
is
set
to
4.
USHORT length;
/
/
This
is
the length
in
bytes of the subtable.
USHORT language;
/
/
Please see
"Note on the language field in 'cmap' subtables"
in
this document.
USHORT segCountX2;
/
/
2
x segCount.
USHORT searchRange;
/
/
2
x (
2
*
*
floor(log2(segCount)))
USHORT entrySelector;
/
/
log2(searchRange
/
2
)
USHORT rangeShift;
/
/
2
x segCount
-
searchRange
USHORT endCount[segCountX2
/
2
];
/
/
End characterCode
for
each segment, last
=
0xFFFF
.
USHORT reservedPad;
/
/
Set
to
0.
USHORT startCount[segCountX2
/
2
];
/
/
Start character code
for
each segment.
SHORT idDelta[segCountX2
/
2
];
/
/
Delta
for
all
character codes
in
segment.
USHORT idRangeOffset[segCountX2
/
2
];
/
/
Offsets into glyphIdArray
or
0
USHORT glyphIdArray[(length
-
(FTell()
-
cmap_subtable))
/
2
];
/
/
Glyph index array (arbitrary length) !!!就是这里出现了问题!!!
};
(
158.f9c
): Access violation
-
code c0000005 (!!! second chance !!!)
eax
=
632c622c
ebx
=
00000214
ecx
=
632d0000
edx
=
3fffd88a
esi
=
632d0004
edi
=
00004141
eip
=
630979ce
esp
=
0030cdf0
ebp
=
0030ce84
iopl
=
0
nv up ei pl nz na po nc
cs
=
001b
ss
=
0023
ds
=
0023
es
=
0023
fs
=
003b
gs
=
0000
efl
=
00010202
*
*
*
ERROR: Symbol
file
could
not
be found. Defaulted to export symbols
for
C:\Program Files\Adobe\Reader
9.0
\Reader\CoolType.dll
-
CoolType
+
0x79ce
:
630979ce
8919
mov dword ptr [ecx],ebx ds:
0023
:
632d0000
=
00001000
(
158.f9c
): Access violation
-
code c0000005 (!!! second chance !!!)
eax
=
632c622c
ebx
=
00000214
ecx
=
632d0000
edx
=
3fffd88a
esi
=
632d0004
edi
=
00004141
eip
=
630979ce
esp
=
0030cdf0
ebp
=
0030ce84
iopl
=
0
nv up ei pl nz na po nc
cs
=
001b
ss
=
0023
ds
=
0023
es
=
0023
fs
=
003b
gs
=
0000
efl
=
00010202
*
*
*
ERROR: Symbol
file
could
not
be found. Defaulted to export symbols
for
C:\Program Files\Adobe\Reader
9.0
\Reader\CoolType.dll
-
CoolType
+
0x79ce
:
630979ce
8919
mov dword ptr [ecx],ebx ds:
0023
:
632d0000
=
00001000
0
:
000
> !address
632d0000
Failed to
map
Heaps (error
80004005
)
Usage: Image
Allocation Base:
63090000
Base Address:
632d0000
End Address:
632ef000
Region Size:
0001f000
Type
:
01000000
MEM_IMAGE
State:
00001000
MEM_COMMIT
Protect:
00000002
PAGE_READONLY
More info: lmv m CoolType
More info: !lmi CoolType
More info: ln
0x632d0000
0
:
000
> !address
632d0000
Failed to
map
Heaps (error
80004005
)
Usage: Image
Allocation Base:
63090000
Base Address:
632d0000
End Address:
632ef000
Region Size:
0001f000
Type
:
01000000
MEM_IMAGE
State:
00001000
MEM_COMMIT
Protect:
00000002
PAGE_READONLY
More info: lmv m CoolType
More info: !lmi CoolType
More info: ln
0x632d0000
int
__cdecl vulFunc(
int
a1) {
if
( (a
-
4
) <
*
b
|| (high
=
*
(b
+
0x154
), a
-
4
>
=
high)
|| (end
=
(a
-
4
),
len
=
*
(a
-
4
), start
=
(a
-
4
-
4
*
len
), start <
*
b)
|| start >
=
high ) {
result
=
dword_232438;
dword_232434
=
0x1110
;
}
else
{
v5
=
*
start;
if
(
len
>
0
) {
do {
-
-
len
;
*
start
=
start[
1
];
/
/
异常发生位置
+
+
start;
}
while
(
len
);
-
-
end;
}
*
end
=
v5;
a
=
(end
+
1
);
result
=
a1;
}
return
result;
}
int
__cdecl vulFunc(
int
a1) {
if
( (a
-
4
) <
*
b
|| (high
=
*
(b
+
0x154
), a
-
4
>
=
high)
|| (end
=
(a
-
4
),
len
=
*
(a
-
4
), start
=
(a
-
4
-
4
*
len
), start <
*
b)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-9-20 16:02
被LarryS编辑
,原因: 修改标题