首页
社区
课程
招聘
[翻译]Windows Uniscribe Fuzzing 笔记
发表于: 2017-5-1 16:30 6277

[翻译]Windows Uniscribe Fuzzing 笔记

2017-5-1 16:30
6277

原文链接:https://googleprojectzero.blogspot.jp/2017/04/notes-on-windows-uniscribe-fuzzing.html


在几周前微软三月星期二补丁日1 修复的119CVE2 漏洞中,有29个漏洞来自我们报告的Uniscribe库的字体处理代码。诚然,字体相关的安全话题已经在这个博客上进行过广泛的讨论,包括手工分析[1][2]Fuzzing[3][4]两个方面的内容。然而,与包含在win32k.sysATMFD.DLL3 这两个驱动里的内核模式的字体实现相反,这次的尝试和之前的的一点不同之处在于Uniscribe是一个鲜为人知的用户模式下的组件,此前它并未被广泛认为是一个可行的攻击媒介。在这篇文章中,我们先列出Uniscribe的简要历史和描述,解释一下我们是如何实现规模化地fuzzing这个库,然后强调一些我们目前已有的更有趣的发现。所有我们涉及到的这些bug(正如它们被提交给微软那样),都有相应的POC(概念证明)样本,它们可以在Project Zero bug追踪的官方网页[5]上找到。请享受这一切!


752378f3 668b4c5002      mov     cx,word ptr [eax+edx*2+2] ds:002b:09340fff=????



我们马上认定usp10.dll是与“Uniscribe Unicode脚本处理器”(Uniscribe Unicode script processor)(微软自己是这样说的) [7]相对应的库。这是一个相对较大的模块(600-800KB,具体取决于操作系统版本与位数),用来渲染Unicode编码的文本,正如它的名字所指的那样。从安全的视角看去,它是重要的,因为代码的日期可以追溯到Windows 2000,并且包含一个用C++实现的对多种复杂TrueType/OpenType结构的解析,作为对已经实现在内核功能的补充。Uniscribe涉及到的特定的表主要是高级排版表(“GDEF”, “GSUB”, “GPOS”, “BASE”, “JSTF”),但某种程度上也有“OS/2”, “cmap”  “maxp”。同样重要的是这个库的代码可以通过调用DrawText[8]或另外等价的API配合Unicode编码的文本和攻击者控制的字体访问到。既然执行这个库的大部分暴露对外的代码时,除了这几个典型API外没有别的API是必须的,这就为使用GDI去渲染文本,同时使用不可信任源的字体的应用程序中制造了一个极好的攻击媒介。这一点也被原始crash的栈回溯所证明,我们可以发现这样的事实:它发生在一个(源代码)不包含任何usp10相关代码的程序中:

0026f824 ffffffff USER32!DrawTextExW+0x1e



除一个外,所有这些bug都通过一个标准的DrawText调用进行触发,并且导致了堆破坏。那额外的一个是#1030号问题,它位于一个文档化的Uniscribe特定的API函数:ScriptGetFontAlternateGlyphs中。这个例程用来获取一个特定字符的一组交叉字形,关于这个bug的有趣的一点是它并不是一个操作任何内部数据结构的问题。取而代之的,这个函数没有正确处理cMaxAlternates参数的值,因此导致可向pAlternateGlyphs缓冲区中写入比被调用者允许的更多的返回数据。这意味着这个缓冲区溢出并不与任何特殊的内存类型相关联——而是取决于客户传入了哪个指针,这个溢出可以发生在栈上,堆上或是静态内存上。这样一个bug的利用性极大依赖于用来编译程序的设计和编译选项。然而,我们必须承认,真实世界中的函数情况是怎样,它们是否满足变成一个可被用于攻击对象的要求,我们并不清楚。


更进一步,我们提取了27个不同的空指针导致的内存访问违例的崩溃,这些可能导致存储在进程地址空间内的信息泄露。鉴于这些crash的巨大数量,我们不可能一一去更详细地分析它们或进行任何高级的数据去重。取而代之的,我们通过顶层异常地址对它们进行了区分,并将这些bug统一填在#1031的入口中。


最终,这27个崩溃里面存在21个实际的bug,这21个漏洞微软在MS17-011安全公告里面以编号为CVE-2017-0083, CVE-2017-0091, CVE-2017-0092CVE-2017-0111  CVE-2017-0128修复。


最后,我们也报告了7个没有修复时间限制的unicode空指针引用问题,希望微软修复这些漏洞能够使我们的fuzzer发现更多另外的bug。在317号,MSRC回复说他们调查了这些例子,得出的结论是它们只是低安全等级的DoS问题,并且在最近的将来不会作为安全公告的一部分修复。


突变的配置文件也与我们之前为内核准备的相当相似:我们使用相同的五个标准的位翻转,比特翻转,chunkspew,特殊整形和二进制算数算法配合预先计算好的每个表里面突变的比例范围。为Uniscribe做的唯一的特别改变是为“BASE”  “JSTF”表增加了突变,这是我们之前没有用到的。


最后但并不是最不重要的,我们扩展了客户机Fuzzing用具的功能性,负责调用被测试的字体相关API(大多数以不同的点大小展现字形,但也要求一些属性等)。当我们清楚其中的一些相关代码只需通过调用无需修改的user32!DrawText时就会被自动执行后,我们想要去尽可能最大化地覆盖Uniscribe库的代码。关于它的导出函数可以MSDN[9]上找到。在浏览了这些文档后,我们增加了对ScriptCacheGetHeight,ScriptGetFontPropertiesScriptGetCMapScriptGetFontAlternateGlyphsScriptSubstituteSingleGlyphScriptFreeCache的调用。这马上被证明是一个成功的主意,因为它允许我们在ScriptGetFontAlternateGlyphs内去发现上述通用的bug。更进一步,我们决定移除对GetKerningPairsGetGlyphOutline系列API函数调用,因为它们对应的逻辑在内核里面,而我们当前的关注重点被严格限制在用户态模式。被移除的那些API不会导致任何Uniscribe的新bug的发现,取而代之的是降低整个fuzzing过程的速度。除了这些微小的改动外,测试用具的核心都保持不变。


通过采取上面提到的方法,我们希望它们在触发大多数低层次的bug上是高效的。有了这个假设,现在剩下的唯一问题就是确保crash会被可靠地捕获并且报告给fuzzer。这个问题在下面一节里面讨论。


感谢这一事实:usp10.dll里被用来fuzz测试的代码执行在与剩下的用具逻辑相同的上下文。我们不需要写一个成熟的Windows调试器去监视另外一个进程。取而代之的,我们只需要使用SetUnhandledExceptionFilter 函数设定一个顶层异常处理例程,这个函数在每次进程发生关键异常的时候都会被调用。这个历程的作用是将当前发生崩溃的CPU的上下文信息(通过ExceptionInfo->ContextRecord传递)通过“debug print”这一上层调用发送给虚拟机接管程序(例如Bochs指令),从而报告崩溃发生的特定地址。


在内核模式的字体fuzzing场景中,crash通过 BX_INSTR_RESET指令的回调例程被被Boshs指令检测到。这个方法是管用的,因为客户系统被配置为在蓝屏发生后自动重启,因而触发了bx_instr_reset例程。在用户模式Fuzzing下整合这个方法只需要在异常处理的结束例程中增加一个ExitWindowsEx调用。这使得任何事物都可以直接使用而无需涉及已有的Bochs指令。然而,这个方法会导致有关崩溃位置的信息丢失,这使得自动去重变为不可能。为了解决这个问题,我们引入看一种新的“崩溃发生”的Hyper调用,它从客户机的参数里面接收出错指令的地址,并将它进一步传到我们可扩展的fuzzing体系中。在开启后立即通过异常地址对崩溃点进行分组,从而帮助我们节省了大量的处理时间,并且将我们需要去看的测试案例限制在了最低。


这是为到目前为止我们已经使用快两年的Windows内核字体Fuzzing组织的不同系列的最后一篇,也是对我们几个月前刚开始,但已经被证明很高效的用户模式的fuzzing组织的一个总结。除此之外的每件事都与去年“font fuzzing techniques”系列文章中描述的一模一样。[4]


这次的努力和它的结果显示fuzzing是一门非常通用的技术,它的大部分组件可以被简单地从一个目标移到另一个目标重复使用,特别是在单个文件格式的范围内。最终,它被证明不仅可以fuzz Windows内核,也可以fuzz常规的用户态模式,如果不考虑客户机的系统环境(在我们的例子里面,Windows或是Linux)。尽管Bochs x86模拟器与原始操作系统执行速度相比增大了很多开销,它通常可以缩小以达到每秒迭代次数的净增益。作为一个有趣的事实,问题#993(Windows内核注册表加载),在上个补丁日修复的问题#1042(GDI+中的EMF+处理),问题#1052#1054(颜色配置文件处理),我们也是通过在Boshsfuzzing Windows发现的,只是在输入样本,测试用具和突变策略上有些轻微的不同。:)


翻译 by:银雁冰
作者:Google Project Zero  Mateusz Jurczyk

原文链接:https://googleprojectzero.blogspot.jp/2017/04/notes-on-windows-uniscribe-fuzzing.html


在几周前微软三月星期二补丁日1 修复的119CVE2 漏洞中,有29个漏洞来自我们报告的Uniscribe库的字体处理代码。诚然,字体相关的安全话题已经在这个博客上进行过广泛的讨论,包括手工分析[1][2]Fuzzing[3][4]两个方面的内容。然而,与包含在win32k.sysATMFD.DLL3 这两个驱动里的内核模式的字体实现相反,这次的尝试和之前的的一点不同之处在于Uniscribe是一个鲜为人知的用户模式下的组件,此前它并未被广泛认为是一个可行的攻击媒介。在这篇文章中,我们先列出Uniscribe的简要历史和描述,解释一下我们是如何实现规模化地fuzzing这个库,然后强调一些我们目前已有的更有趣的发现。所有我们涉及到的这些bug(正如它们被提交给微软那样),都有相应的POC(概念证明)样本,它们可以在Project Zero bug追踪的官方网页[5]上找到。请享受这一切!


简介

201611月,当我们开启我们的另一轮Windows字体Fuzzing工作(这些工作的架构已经在[4]充分描述过)。在那个时刻,采用我们使用的技术,内核攻击面已经差不多被Fuzz干净了,但是我们仍然想用配置文件和输入实时语料库来看一下是否能够在现有的基础设施之上再挤出一些更多的bug。几天之后,我们以得到一些如预期的,将运行在Bochs里的客户机Windows系统弄崩溃的样本而结束。当我们将它们投喂到我们的复制流水线后,出于某种不清楚的原因,没有一个蓝屏bug再次发生。失望如那般,这里面依然有一个有趣且不符合预期的结果:这些测试用例的其中一个在用户态就自己崩溃了,与此同时并没有让整个操作系统宕机。这可能意味两种原因:在我们的代码里面有一个bug,或者是在环三层有一些字体解析的意外发生。当我们开始更深入地挖掘后。我们发现这个未处理的异常发生在下列上下文中:
(4464.11b4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0933d8bf ebx=00000000 ecx=09340ffc edx=00001b9f esi=0026ecac edi=00000009
eip=752378f3 esp=0026ec24 ebp=0026ec2c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
USP10!ScriptPositionSingleGlyph+0x28533:

752378f3 668b4c5002      mov     cx,word ptr [eax+edx*2+2] ds:002b:09340fff=????


直到那一刻,我们还未完全意识到我们的工具触发了除广为人知的内核实现外的任何字体处理代码(除了一些已经在过去被公开的修复的相关bug,例如CVE-2016-7274[6])。这导致我们的fuzzing系统并没有准备好去捕获任何用户模式的错误,因此由于系统蓝屏优先发生,任何这样的崩溃都仍处于未被检测到的情况下。


我们马上认定usp10.dll是与“Uniscribe Unicode脚本处理器”(Uniscribe Unicode script processor)(微软自己是这样说的) [7]相对应的库。这是一个相对较大的模块(600-800KB,具体取决于操作系统版本与位数),用来渲染Unicode编码的文本,正如它的名字所指的那样。从安全的视角看去,它是重要的,因为代码的日期可以追溯到Windows 2000,并且包含一个用C++实现的对多种复杂TrueType/OpenType结构的解析,作为对已经实现在内核功能的补充。Uniscribe涉及到的特定的表主要是高级排版表(“GDEF”, “GSUB”, “GPOS”, “BASE”, “JSTF”),但某种程度上也有“OS/2”, “cmap”  “maxp”。同样重要的是这个库的代码可以通过调用DrawText[8]或另外等价的API配合Unicode编码的文本和攻击者控制的字体访问到。既然执行这个库的大部分暴露对外的代码时,除了这几个典型API外没有别的API是必须的,这就为使用GDI去渲染文本,同时使用不可信任源的字体的应用程序中制造了一个极好的攻击媒介。这一点也被原始crash的栈回溯所证明,我们可以发现这样的事实:它发生在一个(源代码)不包含任何usp10相关代码的程序中:

0:000> kb
ChildEBP RetAddr
0026ec2c 09340ffc USP10!otlChainRuleSetTable::rule+0x13
0026eccc 0133d7d2 USP10!otlChainingLookup::apply+0x7d3
0026ed48 0026f09c USP10!ApplyLookup+0x261
0026ef4c 0026f078 USP10!ApplyFeatures+0x481
0026ef98 09342f40 USP10!SubstituteOtlGlyphs+0x1bf
0026efd4 0026f0b4 USP10!SubstituteOtlChars+0x220
0026f250 0026f370 USP10!HebrewEngineGetGlyphs+0x690
0026f310 0026f370 USP10!ShapingGetGlyphs+0x36a
0026f3fc 09316318 USP10!ShlShape+0x2ef
0026f440 09316318 USP10!ScriptShape+0x15f
0026f4a0 0026f520 USP10!RenderItemNoFallback+0xfa
0026f4cc 0026f520 USP10!RenderItemWithFallback+0x104
0026f4f0 09316124 USP10!RenderItem+0x22
0026f534 2d011da2 USP10!ScriptStringAnalyzeGlyphs+0x1e9
0026f54c 0000000a USP10!ScriptStringAnalyse+0x284
0026f598 0000000a LPK!LpkStringAnalyse+0xe5
0026f694 00000000 LPK!LpkCharsetDraw+0x332
0026f6c8 00000000 LPK!LpkDrawTextEx+0x40
0026f708 00000000 USER32!DT_DrawStr+0x13c
0026f754 0026fa30 USER32!DT_GetLineBreak+0x78
0026f800 0000000a USER32!DrawTextExWorker+0x255

0026f824 ffffffff USER32!DrawTextExW+0x1e


正如可以看到的这样,Uniscribe的功能函数被user32.dll通过lpk.dll(Language Pack 语言包)间接调用。从学会这一新的攻击媒介开始,我们就马上开始对其进行第一轮fuzz。大多数基础设施已经就位,既然用户和内核模式之间共享共享大量代码片段。我们所需要做的额外工作大部分和筛选输入语料库,使用突变的配置文件,调整系统配置和实现逻辑以检测用户态的崩溃(测试用具和Bochs指令都进行改动)相关。所有这些步骤都在下面进行了详细的说明。几天后,我们已经让一切准备就位,又过了几天,已经有在不同的地址处的80个崩溃等待进一步分析。下面是第一次Fuzzing运行过程中发现并在201612月报告给微软的问题的一份总结。


结果一览

既然约80个仍然是一个能够被手工分析的相当可控的数字,我们尝试去逐个手工重现它们,从崩溃信息里面提取它们,与此同时写下它们的详细信息。当我们结束的时候,我们发现了8个可能导致远程代码执行的不同的高危害等级的问题:
Tracker ID
崩溃点的内存访问类型
崩溃函数
CVE
n字节的非法写内存(memcpy)
usp10!otlList::insertAt
CVE-2017-0108
2字节的非法读/写内存
usp10!AssignGlyphTypes
CVE-2017-0084
n字节的非法写内存(memset)
usp10!otlCacheManager::GlyphsSubstituted
CVE-2017-0086
n字节的非法写内存(memcpy)
usp10!MergeLigRecords
CVE-2017-0087
2字节的非法写内存
usp10!ttoGetTableData
CVE-2017-0088
2字节的非法写内存
usp10!UpdateGlyphFlags
CVE-2017-0089
n字节的非法写内存
usp10!BuildFSM and nearby functions
CVE-2017-0090
n字节的非法写内存
usp10!FillAlternatesList
CVE-2017-0072


除一个外,所有这些bug都通过一个标准的DrawText调用进行触发,并且导致了堆破坏。那额外的一个是#1030号问题,它位于一个文档化的Uniscribe特定的API函数:ScriptGetFontAlternateGlyphs中。这个例程用来获取一个特定字符的一组交叉字形,关于这个bug的有趣的一点是它并不是一个操作任何内部数据结构的问题。取而代之的,这个函数没有正确处理cMaxAlternates参数的值,因此导致可向pAlternateGlyphs缓冲区中写入比被调用者允许的更多的返回数据。这意味着这个缓冲区溢出并不与任何特殊的内存类型相关联——而是取决于客户传入了哪个指针,这个溢出可以发生在栈上,堆上或是静态内存上。这样一个bug的利用性极大依赖于用来编译程序的设计和编译选项。然而,我们必须承认,真实世界中的函数情况是怎样,它们是否满足变成一个可被用于攻击对象的要求,我们并不清楚。


更进一步,我们提取了27个不同的空指针导致的内存访问违例的崩溃,这些可能导致存储在进程地址空间内的信息泄露。鉴于这些crash的巨大数量,我们不可能一一去更详细地分析它们或进行任何高级的数据去重。取而代之的,我们通过顶层异常地址对它们进行了区分,并将这些bug统一填在#1031的入口中。

usp10!otlMultiSubstLookup::apply+0xa8
usp10!otlSingleSubstLookup::applyToSingleGlyph+0x98
usp10!otlSingleSubstLookup::apply+0xa9
usp10!otlMultiSubstLookup::getCoverageTable+0x2c
usp10!otlMark2Array::mark2Anchor+0x18
usp10!GetSubstGlyph+0x2e
usp10!BuildTableCache+0x1ca
usp10!otlMkMkPosLookup::apply+0x1b4
usp10!otlLookupTable::markFilteringSet+0x1a
usp10!otlSinglePosLookup::getCoverageTable+0x12
usp10!BuildTableCache+0x1e7
usp10!otlChainingLookup::getCoverageTable+0x15
usp10!otlReverseChainingLookup::getCoverageTable+0x15
usp10!otlLigCaretListTable::coverage+0x7
usp10!otlMultiSubstLookup::apply+0x99
usp10!otlTableCacheData::FindLookupList+0x9
usp10!ttoGetTableData+0x4b4
usp10!GetSubtableCoverage+0x1ab
usp10!otlChainingLookup::apply+0x2d
usp10!MergeLigRecords+0x132
usp10!otlLookupTable::subTable+0x23
usp10!GetMaxParameter+0x53
usp10!ApplyLookup+0xc3
usp10!ApplyLookupToSingleGlyph+0x6f
usp10!ttoGetTableData+0x19f6
usp10!otlExtensionLookup::extensionSubTable+0x1d
usp10!ttoGetTableData+0x1a77


最终,这27个崩溃里面存在21个实际的bug,这21个漏洞微软在MS17-011安全公告里面以编号为CVE-2017-0083, CVE-2017-0091, CVE-2017-0092CVE-2017-0111  CVE-2017-0128修复。


最后,我们也报告了7个没有修复时间限制的unicode空指针引用问题,希望微软修复这些漏洞能够使我们的fuzzer发现更多另外的bug。在317号,MSRC回复说他们调查了这些例子,得出的结论是它们只是低安全等级的DoS问题,并且在最近的将来不会作为安全公告的一部分修复。


输入语料,突变配置文件和调整测试用具

生成一个坚实的输入样本语料可以说是fuzzing准备中一个最重要的部分,特别是在不涉及代码的覆盖面反馈时,语料逐渐演变成更可选的形式是不可能的。我们足够幸运,已经有来自之前fuzzing运行出来的几个字体语料库让我们使用。我们决定使用与在过去已经帮助我们发现18windows内核bug的相同文件集合(参见[4]的“准备语料库”这一节)。它最初通过在一个从网上爬取的大量的字体文件中运行一个语料库蒸馏算法,并使用一个FreeType2开源库的批量构建而成,包括14848TrueType文件和4659OpenType文件,总共占用2.4G的磁盘空间。为了裁剪得到更适合Uniscribe的语料,我们减少了前面的语料库,只要那些包含至少一个“GDEF”, “GSUB”, “GPOS”, “BASE”  “JSTF”表的字体文件,这些文件会被Uniscribe库所解析。这让我们最终剩下3768TrueType文件和2520OpenType文件,总共消耗1.68G磁盘空间,这些文件比移除的文件更有可能去暴露Uniscribe库中的bug。这就是我们最终用到的语料库。


突变的配置文件也与我们之前为内核准备的相当相似:我们使用相同的五个标准的位翻转,比特翻转,chunkspew,特殊整形和二进制算数算法配合预先计算好的每个表里面突变的比例范围。为Uniscribe做的唯一的特别改变是为“BASE”  “JSTF”表增加了突变,这是我们之前没有用到的。


最后但并不是最不重要的,我们扩展了客户机Fuzzing用具的功能性,负责调用被测试的字体相关API(大多数以不同的点大小展现字形,但也要求一些属性等)。当我们清楚其中的一些相关代码只需通过调用无需修改的user32!DrawText时就会被自动执行后,我们想要去尽可能最大化地覆盖Uniscribe库的代码。关于它的导出函数可以MSDN[9]上找到。在浏览了这些文档后,我们增加了对ScriptCacheGetHeight,ScriptGetFontPropertiesScriptGetCMapScriptGetFontAlternateGlyphsScriptSubstituteSingleGlyphScriptFreeCache的调用。这马上被证明是一个成功的主意,因为它允许我们在ScriptGetFontAlternateGlyphs内去发现上述通用的bug。更进一步,我们决定移除对GetKerningPairsGetGlyphOutline系列API函数调用,因为它们对应的逻辑在内核里面,而我们当前的关注重点被严格限制在用户态模式。被移除的那些API不会导致任何Uniscribe的新bug的发现,取而代之的是降低整个fuzzing过程的速度。除了这些微小的改动外,测试用具的核心都保持不变。


通过采取上面提到的方法,我们希望它们在触发大多数低层次的bug上是高效的。有了这个假设,现在剩下的唯一问题就是确保crash会被可靠地捕获并且报告给fuzzer。这个问题在下面一节里面讨论。


崩溃检测

我们采用去高效检测Uniscribe的第一个步骤是禁止win32k.sysATMFD.DLL特定的内存池(这会导致在用户模式下增加的不必要的开销),同时为测试进程在应用程序验证器里开启页堆选项。这些是为了改善我们检测内存访问违例的机会,并使得问题重现和去重更加可靠。


感谢这一事实:usp10.dll里被用来fuzz测试的代码执行在与剩下的用具逻辑相同的上下文。我们不需要写一个成熟的Windows调试器去监视另外一个进程。取而代之的,我们只需要使用SetUnhandledExceptionFilter 函数设定一个顶层异常处理例程,这个函数在每次进程发生关键异常的时候都会被调用。这个历程的作用是将当前发生崩溃的CPU的上下文信息(通过ExceptionInfo->ContextRecord传递)通过“debug print”这一上层调用发送给虚拟机接管程序(例如Bochs指令),从而报告崩溃发生的特定地址。


在内核模式的字体fuzzing场景中,crash通过 BX_INSTR_RESET指令的回调例程被被Boshs指令检测到。这个方法是管用的,因为客户系统被配置为在蓝屏发生后自动重启,因而触发了bx_instr_reset例程。在用户模式Fuzzing下整合这个方法只需要在异常处理的结束例程中增加一个ExitWindowsEx调用。这使得任何事物都可以直接使用而无需涉及已有的Bochs指令。然而,这个方法会导致有关崩溃位置的信息丢失,这使得自动去重变为不可能。为了解决这个问题,我们引入看一种新的“崩溃发生”的Hyper调用,它从客户机的参数里面接收出错指令的地址,并将它进一步传到我们可扩展的fuzzing体系中。在开启后立即通过异常地址对崩溃点进行分组,从而帮助我们节省了大量的处理时间,并且将我们需要去看的测试案例限制在了最低。


这是为到目前为止我们已经使用快两年的Windows内核字体Fuzzing组织的不同系列的最后一篇,也是对我们几个月前刚开始,但已经被证明很高效的用户模式的fuzzing组织的一个总结。除此之外的每件事都与去年“font fuzzing techniques”系列文章中描述的一模一样。[4]


结论

我们意识到令人兴奋但又可怕的一点,即使对字体解析实现这样广为人知的漏洞挖掘目标类别,仍然可能发现可追溯到上个世纪的新的攻击媒介,并且(相关代码)直到现在都在很大程度上未经审计,正如我们已经知道的暴露为接口的那样。我们相信这是一个关于如何逐渐提升大量软件的标准以产生更多影响,而不是去杀死在一个狭窄的代码范围每一个遗留的bug的伟大案例。这也是对花时间在完全分析攻击面和寻找鲜为人知的攻击目标上能够最终获累累得硕果这一事实的一个说明,只要安全社区仍然没有对每一个重要的数据处理阶段的攻击媒介(例如在这个案例里面的Windows字体处理)有完全的认识。


这次的努力和它的结果显示fuzzing是一门非常通用的技术,它的大部分组件可以被简单地从一个目标移到另一个目标重复使用,特别是在单个文件格式的范围内。最终,它被证明不仅可以fuzz Windows内核,也可以fuzz常规的用户态模式,如果不考虑客户机的系统环境(在我们的例子里面,Windows或是Linux)。尽管Bochs x86模拟器与原始操作系统执行速度相比增大了很多开销,它通常可以缩小以达到每秒迭代次数的净增益。作为一个有趣的事实,问题#993(Windows内核注册表加载),在上个补丁日修复的问题#1042(GDI+中的EMF+处理),问题#1052#1054(颜色配置文件处理),我们也是通过在Boshsfuzzing Windows发现的,只是在输入样本,测试用具和突变策略上有些轻微的不同。:)


参考文献

“一个字体漏洞适用于所有”系列文章:
翻译 by:银雁冰
作者:Google Project Zero  Mateusz Jurczyk
201611月,当我们开启我们的另一轮Windows字体Fuzzing工作(这些工作的架构已经在[4]充分描述过)。在那个时刻,采用我们使用的技术,内核攻击面已经差不多被Fuzz干净了,但是我们仍然想用配置文件和输入实时语料库来看一下是否能够在现有的基础设施之上再挤出一些更多的bug。几天之后,我们以得到一些如预期的,将运行在Bochs里的客户机Windows系统弄崩溃的样本而结束。当我们将它们投喂到我们的复制流水线后,出于某种不清楚的原因,没有一个蓝屏bug再次发生。失望如那般,这里面依然有一个有趣且不符合预期的结果:这些测试用例的其中一个在用户态就自己崩溃了,与此同时并没有让整个操作系统宕机。这可能意味两种原因:在我们的代码里面有一个bug,或者是在环三层有一些字体解析的意外发生。当我们开始更深入地挖掘后。我们发现这个未处理的异常发生在下列上下文中:
(4464.11b4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0933d8bf ebx=00000000 ecx=09340ffc edx=00001b9f esi=0026ecac edi=00000009
eip=752378f3 esp=0026ec24 ebp=0026ec2c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
USP10!ScriptPositionSingleGlyph+0x28533:
直到那一刻,我们还未完全意识到我们的工具触发了除广为人知的内核实现外的任何字体处理代码(除了一些已经在过去被公开的修复的相关bug,例如CVE-2016-7274[6])。这导致我们的fuzzing系统并没有准备好去捕获任何用户模式的错误,因此由于系统蓝屏优先发生,任何这样的崩溃都仍处于未被检测到的情况下。
0:000> kb
ChildEBP RetAddr
0026ec2c 09340ffc USP10!otlChainRuleSetTable::rule+0x13
0026eccc 0133d7d2 USP10!otlChainingLookup::apply+0x7d3
0026ed48 0026f09c USP10!ApplyLookup+0x261
0026ef4c 0026f078 USP10!ApplyFeatures+0x481
0026ef98 09342f40 USP10!SubstituteOtlGlyphs+0x1bf
0026efd4 0026f0b4 USP10!SubstituteOtlChars+0x220
0026f250 0026f370 USP10!HebrewEngineGetGlyphs+0x690
0026f310 0026f370 USP10!ShapingGetGlyphs+0x36a
0026f3fc 09316318 USP10!ShlShape+0x2ef
0026f440 09316318 USP10!ScriptShape+0x15f
0026f4a0 0026f520 USP10!RenderItemNoFallback+0xfa
0026f4cc 0026f520 USP10!RenderItemWithFallback+0x104
0026f4f0 09316124 USP10!RenderItem+0x22
0026f534 2d011da2 USP10!ScriptStringAnalyzeGlyphs+0x1e9
0026f54c 0000000a USP10!ScriptStringAnalyse+0x284
0026f598 0000000a LPK!LpkStringAnalyse+0xe5
0026f694 00000000 LPK!LpkCharsetDraw+0x332
0026f6c8 00000000 LPK!LpkDrawTextEx+0x40
0026f708 00000000 USER32!DT_DrawStr+0x13c
0026f754 0026fa30 USER32!DT_GetLineBreak+0x78
0026f800 0000000a USER32!DrawTextExWorker+0x255
正如可以看到的这样,Uniscribe的功能函数被user32.dll通过lpk.dll(Language Pack 语言包)间接调用。从学会这一新的攻击媒介开始,我们就马上开始对其进行第一轮fuzz。大多数基础设施已经就位,既然用户和内核模式之间共享共享大量代码片段。我们所需要做的额外工作大部分和筛选输入语料库,使用突变的配置文件,调整系统配置和实现逻辑以检测用户态的崩溃(测试用具和Bochs指令都进行改动)相关。所有这些步骤都在下面进行了详细的说明。几天后,我们已经让一切准备就位,又过了几天,已经有在不同的地址处的80个崩溃等待进一步分析。下面是第一次Fuzzing运行过程中发现并在201612月报告给微软的问题的一份总结。
既然约80个仍然是一个能够被手工分析的相当可控的数字,我们尝试去逐个手工重现它们,从崩溃信息里面提取它们,与此同时写下它们的详细信息。当我们结束的时候,我们发现了8个可能导致远程代码执行的不同的高危害等级的问题:
Tracker ID
崩溃点的内存访问类型
崩溃函数
CVE
n字节的非法写内存(memcpy)
usp10!otlList::insertAt
CVE-2017-0108
2字节的非法读/写内存
usp10!AssignGlyphTypes
CVE-2017-0084
n字节的非法写内存(memset)
usp10!otlCacheManager::GlyphsSubstituted
CVE-2017-0086
n字节的非法写内存(memcpy)
usp10!MergeLigRecords
CVE-2017-0087
2字节的非法写内存
usp10!ttoGetTableData
CVE-2017-0088
2字节的非法写内存
usp10!UpdateGlyphFlags
CVE-2017-0089
n字节的非法写内存
usp10!BuildFSM and nearby functions
CVE-2017-0090
n字节的非法写内存
usp10!FillAlternatesList
CVE-2017-0072
Tracker ID
崩溃点的内存访问类型
崩溃函数
CVE

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 3574
活跃值: (4719)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark
2017-5-1 17:28
0
游客
登录 | 注册 方可回帖
返回
//