作者: riusksk@tsrc

時間: 2016-12-18

前言

2016年12月的Apple安全公告中(macOS公告與iOS公告),修復4個由騰訊安全平臺部終端安全團隊報告的漏洞,其中有2個是字體解析造成的越界訪問漏洞,影響 macOS/iOS/watchOS/tvOS等多個平臺系統,本文主要分析其中的 CVE-2016-7595 字體漏洞【圖1】。

圖1

這個漏洞在報給Apple 17天后發布 macOS 10.2.2 測試版補丁,一個半月后發布安全公告和補丁(包括iOS、watchOS和tvOS),難得看見蘋果這么積極一次。

漏洞分析

此次漏洞是通過Fuzzing發現的,直接對比poc與原始文件的數據,可以發現其實就1個字節的差異(0x00 => 0x6C)【圖2】:

圖2
用ttx命令分析字體格式:

 ╭─riusksk@MacBook ~/Downloads ??
 ╰─?$ ttx poc.ttf
 Dumping "poc.ttf" to "poc#1.ttx"...
 Dumping 'GlyphOrder' table...
 Dumping 'head' table...
 Dumping 'hhea' table...
 Dumping 'maxp' table...
 Dumping 'OS/2' table...
 Dumping 'hmtx' table...
 Dumping 'cmap' table...
 Dumping 'fpgm' table...
 Dumping 'prep' table...
 Dumping 'cvt ' table...
 Dumping 'loca' table...
 Dumping 'glyf' table...
 Dumping 'name' table...
 Dumping 'post' table...
 Dumping 'gasp' table...
 /usr/local/lib/python2.7/site-
 packages/FontTools/fontTools/ttLib/tables/otTables.py:60: UserWarning:
 Coverage table has start glyph ID out of range: glyph27713.
 warnings.warn("Coverage table has start glyph ID out of range: %s." %
 start)
 An exception occurred during the decompilation of the 'GPOS' table
 Dumping 'GPOS' table...
 Dumping 'GSUB' table...
 Dumping 'DSIG' table...

從上面的提示可以看出,是在解析 GPOS 表時,通過 glyphID 去 Coverage 表索引時導致越界了,其中 glyph27713 的數值正是 0x6C41 27713 ,也就是上面圖1中文件對比 的差異值。雖然這是FontTools工具的錯誤,不代表Apple系統本身,但它跟Apple系統 導致崩潰的是同一字節,從這可以直接得到導致崩潰的關鍵字節是glyphID值。

GPOS表

TrueType/OpenType字體格式中的GPOS表是用于為字體中文本布局及渲染提供glyph位 置信息的表,表中各個字體結構如圖3所示

圖3

GPOS表主要包含3個子表:ScriptList、FeatureList和LookupList,本次漏洞主要問題在 LookupList子表中的PairAdjustmentPositioning中,PairAdjustmentPositioning子表 (PairPos)被用于調整兩個glyphs彼此之間的位置。

PairPos表下又包含多個PairSet數組,PairSet數組包含Coverage表中每個glyph對應的偏 移量,并按Coverage Index來排序。

PairSet下包含PairValueRecord指定每一glyph配對(pair)中的第二個 glyph(SecondGlyph)的glyph名和索引值GlyphID(對應【圖3】中glyphRefID),同 時包含兩個ValueRecord值去指定第一個glyph和第二個glyph的位置信息。

導致越界的漏洞正是用于索引的GlyphID(glyphRefID),用ttx解析原有正常字體文件 生成的xml文件,如圖4所示,index="65"就是正常GlyphID值 00 41,如果隨便給第1字 節設置個值都會導致崩潰。

圖3

調試

用lldb調試下,崩潰后的地址及棧回溯如下:

 (lisa)run poc.ttf
Process 96714 launched
Process 96714 stopped
* thread #1: tid = 0x3f119d, 0x00007fffa7c01491 CoreText`OTL::GPOS::App
lyPairPos(OTL::LookupSubtable const*, TGlyphIterator&, OTL::Coverage co
nst&) const + 411, queue = 'com.apple.main-thread', stop reason = EXC_B
AD_ACCESS (code=1, address=0x3007ddfae)
  frame #0: 0x00007fffa7c01491 CoreText`OTL::GPOS::ApplyPairPos(OTL::Lo
okupSubtable const*, TGlyphIterator&, OTL::Coverage const&) const + 411
CoreText`OTL::GPOS::ApplyPairPos:
->  0x7fffa7c01491 <+411>: mov    ax, word ptr [r14 + 2*rax + 0xa]
  0x7fffa7c01497 <+417>: rol    ax, 0x8
  0x7fffa7c0149b <+421>: movzx  eax, ax
  0x7fffa7c0149e <+424>: lea    rsi, [r14 + rax]
(lisa)register read rax
   rax = 0x00000000ffffffff
(lisa)x $r14+2*$rax+0xa
error: memory read failed for 0x3007c5600
(lisa)bt
* thread #1: tid = 0x3f119d, 0x00007fffa7c01491 CoreText`OTL::GPOS::App
lyPairPos(OTL::LookupSubtable const*, TGlyphIterator&, OTL::Coverage co
nst&) const + 411, queue = 'com.apple.main-thread', stop reason = EXC_B
AD_ACCESS (code=1, address=0x3007ddfae)
* frame #0: 0x00007fffa7c01491 CoreText`OTL::GPOS::ApplyPairPos(OTL::Lo
okupSubtable const*, TGlyphIterator&, OTL::Coverage const&) const + 411
  frame #1: 0x00007fffa7c05907 CoreText`OTL::GPOS::ApplyLookupSubtable(
OTL::Lookup const&, unsigned int, OTL::LookupSubtable const*, TGlyphIte
rator&, OTL::Coverage const&) const + 85
  frame #2: 0x00007fffa7c02c7b CoreText`OTL::GPOS::ApplyLookupAt(OTL::L
ookup const&, TGlyphIterator&) const + 339
  frame #3: 0x00007fffa7b68ff4 CoreText`OTL::GPOS::ApplyLookups(TRunGlu
e&, int, OTL::GlyphLookups&) const + 448
  frame #4: 0x00007fffa7b68897 CoreText`TOpenTypePositioningEngine::Pos
itionRuns(SyncState&, KerningStatus&) + 839
   frame #5: 0x00007fffa7b66d05 CoreText`TKerningEngine::PositionGlyphs(
TLine&, TCharStream const*) + 347
  frame #6: 0x00007fffa7bbe59d CoreText`TTypesetter::FinishLayout(std::
__1::tuple<TLine const*, TCharStream const*, void const* (*)(__CTRun co
nst*, __CFString const*, void*), void*, std::__1::shared_ptr<TBidiLevel
sProvider>*, unsigned int, unsigned char> const&, TLine&, SyncState) +
35
  frame #7: 0x00007fffa7b5586d CoreText`TTypesetterAttrString::Initiali
ze(__CFAttributedString const*) + 865
  frame #8: 0x00007fffa7b552ea CoreText`CTLineCreateWithAttributedStrin
g + 59
  frame #9: 0x00007fffb8be086e UIFoundation`__NSStringDrawingEngine + 1
0669
  frame #10: 0x00007fffb8be69ca UIFoundation`-[NSAttributedString(NSExt
endedStringDrawing) boundingRectWithSize:options:context:] + 605
  frame #11: 0x00007fffb8bdcc43 UIFoundation`-[NSAttributedString(NSStr
ingDrawing) size] + 59
  frame #12: 0x0000000100047d70 Font Book`___lldb_unnamed_symbol1053$$F
ont Book + 368
  frame #13: 0x00000001000476e9 Font Book`___lldb_unnamed_symbol1052$$F
ont Book + 89
  frame #14: 0x00000001000475b0 Font Book`___lldb_unnamed_symbol1049$$F
ont Book + 774
  frame #15: 0x000000010006a2d2 Font Book`___lldb_unnamed_symbol1860$$F
ont Book + 110
  frame #16: 0x000000010005d4ee Font Book`___lldb_unnamed_symbol1545$$F
ont Book + 2651
  frame #17: 0x00007fffa46b2451 AppKit`-[NSApplication _doOpenFile:ok:t
ryTemp:] + 253
  frame #18: 0x00007fffa427f789 AppKit`-[NSApplication finishLaunching]
 + 1624
  frame #19: 0x00007fffa427ed2a AppKit`-[NSApplication run] + 267
  frame #20: 0x00007fffa4249a8a AppKit`NSApplicationMain + 1237
  frame #21: 0x0000000100001527 Font Book`___lldb_unnamed_symbol1$$Font
 Book + 11
  frame #22: 0x00007fffbb632255 libdyld.dylib`start + 1
  frame #23: 0x00007fffbb632255 libdyld.dylib`start + 1

[r14 + 2*rax + 0xa] 索引錯誤,很典型的數組越界指令。

通過設置條件斷點去記錄獲取的GlyphID以及后面的將其傳參給 OTL::Coverage::SearchFmt2Binary函數后返回值,可以發現最后當GlyphID=0x55(85)時 返回值0,最后觸發崩潰,所以樣本中的GlyphID只要>=0x55都會導致崩潰。

(lisa) p "GlyphID" $eax
(unsigned int) $95 = 85
(lisa) c
Process 25648 resuming
Command #2 'c' continued the target.
(lisa) p "SearchFmt2Binary  2" $esi (unsigned int) $96 = 85
(lisa) c
Process 25648 resuming
Command #2 'c' continued the target. (lisa) p "   " $eax
(unsigned int) $97 = 0
(lisa) c
Process 25648 resuming
Command #2 'c' continued the target.
Process 25648 stopped
* thread #1: tid = 0x43fc6, 0x00007fffcb443491 CoreText`OTL::GPOS::Appl
yPairPos(OTL::LookupSubtable const*, TGlyphIterator&, OTL::Coverage con
st&) const + 411, queue = 'com.apple.main-thread', stop reason = EXC_BA
D_ACCESS (code=1, address=0x3007bc6fe)
    frame #0: 0x00007fffcb443491 CoreText`OTL::GPOS::ApplyPairPos(OTL::
LookupSubtable const*, TGlyphIterator&, OTL::Coverage const&) const + 4
11
CoreText`OTL::GPOS::ApplyPairPos:
->  0x7fffcb443491 <+411>: mov    ax, word ptr [r14 + 2*rax + 0xa]
    0x7fffcb443497 <+417>: rol    ax, 0x8
    0x7fffcb44349b <+421>: movzx  eax, ax
    0x7fffcb44349e <+424>: lea    rsi, [r14 + rax]

返回值為0時,經dec減1后為0xFFFFFFFF,以此為索引值,最后導致越界訪問。

dec eax ; eax=0xFFFFFFFF
mov ax, [r14+rax*2+0Ah] ; 

漏洞修復

蘋果已經發布安全補丁,macOS用戶可升級到10.12.2,iOS用戶可升級到10.2。 對補丁進行比對,可以發現在漏洞函數OTL::GPOS::ApplyPairPos 中添加了判斷,獲取 到的GlyphID值傳遞給OTL::Coverage::SearchFmt2Binary函數,當查找失敗時會返回0, 因此只要添加判斷返回值是否為0,為0則直接跳走返回。

處理流程

  1. 2016-10-23 通過郵件提交給Apple
  2. 2016-11-09 Apple確認漏洞,并在 macOS Sierra 10.12.2 beta 測試版中修復
  3. 2016-12-09 分配CVE號:CVE-2016-7595
  4. 2016-12-14 Apple發布安全公告,并推送補丁

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/151/