All Your (d)Base Are Belong To Us, Part 2: Microsoft Office中的代码执行(CVE-2021-38646)
引言 🔗
在发现小型DBF解析器和Apache OpenOffice中相对简单的内存损坏漏洞后,我想扩大搜索范围。通过在微软桌面数据库引擎中寻找与DBF相关的漏洞,我向模糊测试的深水区迈出了一步。我不再依赖源代码审查和简单模糊测试;这次,我应用了黑盒覆盖引导模糊测试,并辅以逆向工程。我的同事Hui Yi已经写了几篇关于使用WinAFL和DynamoRIO进行模糊测试的优秀文章;我希望本文能为这些技术在真实漏洞中的实际应用提供一个案例。
首先,让我通过深入Windows桌面数据库驱动程序的历史来给你一些背景信息。
Windows桌面数据库驱动程序的简史 🔗
随着1990年Windows 3.0的成功发布,Windows应用程序的数量迅速增长。许多这些应用程序需要持久存储。在那个时代,计算机内存有限,使得像MySQL这样的现代基于服务器的数据库难以运行。因此,索引顺序访问方法(ISAM)被开发出来。简而言之,ISAM是一种基于文件的数据库存储方法,包括dBase数据库文件(DBF)格式。
随着SQL和ISAM数据库格式数量的增加,微软试图创建一个单一的通用接口,让应用程序与这些数据库通信。1992年,它发布了开放数据库连接(ODBC)1.0,通过额外的桌面数据库驱动程序支持各种数据库格式。其中一个驱动程序是微软的联合引擎技术(Jet)引擎,由一组DLL组成,增加了对不同ISAM数据库格式的兼容性。对于DBF格式,Jet引擎使用微软Jet xBASE ISAM驱动程序(msxbde40.dll)。
尽管名称复杂,ODBC和Jet引擎都得到了广泛采用。许多公司还为他们自己的专有数据库格式编写了第三方ODBC桌面数据库驱动程序。Jet引擎在Microsoft Access中的包含确保了其超过30年的寿命,尽管它已被SQL Server Express等新技术 largely deprecated。Microsoft Office现在使用Microsoft Office Access连接引擎,这是Jet引擎的一个分支。
更令人困惑的是,微软在1996年发布了对象链接和嵌入数据库(OLEDB)API,它作为ODBC之上的更高级接口,可以访问更广泛的数据库格式,如对象数据库和电子表格。此外,微软还发布了ActiveX数据对象,这是一个额外的API来访问OLEDB。Jason Roff试图用下图来澄清这一点:
[图表描述:OLEDB和ODBC的关系]
然而,你可能会注意到图表遗漏了ODBC也可以调用Jet引擎驱动程序来访问非SQL数据源,如DBF!这 just goes to show 微软桌面数据库驱动程序环境变得多么复杂——即使是相当权威的来源也无法捕捉全貌。
安全研究人员利用OLEDB/ODBC/Jet引擎架构的年龄和复杂性发现了无数内存损坏漏洞。更吸引人的是,许多重要的微软应用程序,如Microsoft Office和IIS,都依赖这个堆栈。关于这个主题的最新出版物,“给我一个SQL注入,我将攻破IIS和SQL Server”,由Palo Alto研究人员在Black Hat Asia 2021上 presentation,详细描述了许多这些依赖关系。事实上,这个拼凑的架构如此复杂,以至于当微软在2011年试图弃用OLEDB时,它导致的破坏数量迫使微软在六年后撤销了这个决定。
鉴于这个背景,Jet引擎是我通过DBF格式寻找漏洞的第一个目标。
用DBF模糊测试Jet引擎 🔗
如果你读过本系列的第一部分,你应该对基于格式的简单模糊测试有很好的理解。虽然这可能是模糊测试简单目标的一种经济有效的方式,但现代方法应用覆盖引导模糊测试。简而言之,这些模糊测试器依赖编译时或运行时插桩来确定在每个模糊测试迭代中 reached 了哪些代码路径。基于这些信息,模糊测试器试图到达尽可能多的代码路径,以确保对目标的适当覆盖。例如,我们来看一个简单的伪代码函数:
|
|
如果模糊测试器突变输入文件以匹配第一个条件,它会知道它已经到达了一个新的代码路径以进一步模糊测试。它会保存那个突变(第一个字节匹配opCode1)并继续在那个保存的突变基础上进行突变。这将确保模糊测试器不是浪费时间在 fall-through 条件(else { die(); })上,而是深入到runOpCode1中可能易受攻击的代码。这种方法非常强大,大多数现代模糊测试器都是覆盖引导的,包括我选择的模糊测试器,Google Project Zero的WinAFL。
由于插桩是一个计算昂贵的操作,覆盖引导模糊测试器应该在harness上运行。想象一个大型办公应用程序,它加载一个xyzFormat模块,并在打开XYZ文件时运行xyzFormat.openXyz函数。我们可以通过使用大型办公应用程序重复打开突变的XYZ文件来模糊测试这个,但这在覆盖引导插桩下会非常耗时和资源密集。相反,为什么不编写我们自己的小程序或harness来导入xyzFormat模块并直接运行xyzFormat.openXyz函数呢?这将涉及逆向工程函数调用并提供正确的输入,但会大大加快模糊测试速度。这里还有更多要讨论的,但如果你想要一个关于使用WinAFL进行覆盖引导模糊测试的快速指南,请查看Hui Yi的博客文章。
正如我提到的,模糊测试Jet引擎是一条 well-travelled 路径。在咨询了Palo Alto研究人员之后,我决定基于Microsoft OLE DB Provider for Microsoft Jet构建一个harness。研究人员指出,打开突变文件并执行一些简单查询就足以实现一个成功的harness。因此,我使用Microsoft的OLEDB编程文档中描述的CDataSource和CCommand类来打开突变文件(CDataSource.OpenFromInitializationString/CSession.Open),执行一个select all查询(CCommand.Open),检索列信息(CCommand.GetColumnInfo),最后迭代行数据(CCommand.GetString)。反过来,这些OLEDB函数依赖于Microsoft Jet OLEDB提供程序(msjetoledb40.dll),它使用Jet引擎(msjet40.dll)。
在这里,我遇到了一个障碍。尽管我可以通过使用Microsoft.Jet.OLEDB.4.0连接字符串通过OLEDB模糊测试Jet引擎,但我在设置模糊测试环境中的Jet引擎时遇到了许多困难。Jet引擎已被弃用,并且与我的更新环境交互不佳。在经过一些调整后,我决定切换目标,改为通过Microsoft Access OLEDB提供程序(aceoledb.dll)模糊测试Microsoft Access数据库引擎(acecore.dll)。为了解析DBF文件,Access数据库引擎会调用它自己的xBASE ISAM(acexbe.dll)。由于我的最终目标是Microsoft Office,模糊测试Access数据库引擎而不是Jet引擎是有意义的。此外,由于DBF支持在2016年被移除,然后又被添加回Access,有可能包含一些有趣的代码。因此,我切换到Microsoft.ACE.OLEDB.12.0连接字符串。
接下来,我用winafl-cmin.py最小化了DBF样本语料库,它选择了具有最大可能覆盖的最小集合。最后,我可以启动我的模糊测试器了!或者更确切地说,我的模糊测试器们——由于WinAFL的并行模糊测试支持,我同时运行了十二个实例。
幽灵崩溃之谜 🔗
当模糊测试器在后台工作时,我继续研究其他解析DBF文件的办公应用程序。没有立即发生崩溃,但我认为这是正常的,因为我的模糊测试机器相当慢。这种情况持续了几天,直到一天早上我检查时发现了一堆崩溃!
WinAFL将导致每个崩溃的突变文件保存在crashes文件夹中,文件名中包含错误,如EXCEPTION_ACCESS_VIOLATION。
为了重现漏洞,我将崩溃文件下载到具有相同OLEDB和Microsoft Access数据库引擎环境的虚拟机中,然后用harness打开文件。然而,崩溃不再发生!即使我用WinDBG检查harness执行,也没有发现任何问题;harness打开并解析了突变的DBF文件,没有任何问题。
发生了什么?
我回到模糊测试机器,用崩溃文件运行harness。没有错误。
经过大量的 head scratching,我将其归因于误报,并返回到研究其他办公应用程序,而模糊测试器继续运行。同时,崩溃停止了发生。
几个小时后,同样的事情发生了!困惑中,我检查了模糊测试机器上的文件;这次,它们成功使harness崩溃了。
我开始把两点联系起来。模糊测试机器和调试机器之间必须有一些差异导致了这种不一致。经过几个小时的艰苦调试,我有一个发现:作为我研究的一部分,我安装在模糊测试机器上的一个办公应用程序似乎是导致崩溃的原因。
当我卸载那个办公应用程序(它将保持匿名)时,崩溃停止了。当我重新安装它时,突变文件再次使harness崩溃。
更深入地挖掘,我对崩溃运行了一个堆栈跟踪:
|
|
崩溃发生在IDAPI32中,它被ACEXBE(记住这是Microsoft Access xBASE ISAM)调用。这是从哪里来的?快速Google“IDAPI32”显示这个库是“Borland Database Engine library”。嗯?困惑中,我检查了库的路径:c:\Program Files\Common Files\Borland Shared\BDE\IDAPI32.DLL。
然后,它 clicked。那个未命名的办公应用程序已经安装了Borland Database Engine(BDE)作为依赖项。不知何故,一旦安装了这个,Microsoft Access数据库引擎xBASE ISAM就切换到BDE来解析DBF文件。这是怎么发生的?
在IDA Pro中查看ACEXBE的反汇编代码,我发现了它加载IDAPI32的地方:
|
|
看起来Access xBase ISAM包含一个对BDE路径的硬编码检查,如果存在就会运行BDE!由于BDE是一个 long-deprecated 库,根据WaybackMachine,最后一个版本发布于2001年,这是一个典型的CWE-1104:使用未维护的第三方组件。在这个经典软件中, undoubtedly 留下了许多导致崩溃的漏洞。
我已经解释了崩溃的技术原因。然而,要理解一个几乎三十年的库如何最终出现在Microsoft Office Access数据库引擎的代码中,我们需要了解Borland Database Engine的历史。
Borland Database Engine的简史 🔗
在1980年代,dBase是早期软件开发人员用来构建应用程序的首批工具之一。它包括一个数据库引擎和自己的编程语言,由于先发优势而 massively 增长,并激发了大量的模仿者,如FoxPro。一个名为“xBase”的竞争dBase标准被创建出来,以区别于dBase的专有技术。许多当时的消费者应用程序都是使用dBase工具及其衍生产品编写的。
1991年,当时的软件巨头Borland收购了dBase的所有者Ashton-Tate。然而,竞争正在加剧,一家名为Microsoft的初创公司收购了FoxPro并推出了自己的Microsoft Access数据库引擎。为了加强其产品阵容,Borland还收购了WordPerfect,最终推出了自己的Borland Office套件,其中包括DBF兼容性。
随着时间的推移,Borland未能跟上Microsoft的步伐,因为它被迫适应其正在开发的平台——Windows的不断变化。最终,dBase、WordPerfect和其他核心Borland产品被 piecemeal 出售给 various 公司。到2009年,Borland finished——被Micro Focus以7500万美元收购, shadow of its former self。在对手的地盘上赢得一场战争是困难的。
然而,dBase在早期软件开发中的深远影响持续到今天。毕竟,Microsoft Access仍然包括一个遗留的xBase ISAM引擎。甚至选择“xBase”而不是“dBase”也反映了过去 cutthroat 的企业战争。
大数据库能量 🔗
回到Borland Database Engine本身。当我意识到崩溃发生在IDAPI32库中时,我决定直接模糊测试IDAPI32库函数,如DbiOpenTableList和ImltCreateTable2,而不是通过高级OLEDB API。幸运的是,网上仍然有一些教程和代码片段演示了如何调用BDE函数来读取DBF文件。我必须导入几个自定义结构来支持harness,它运行dbiOpenTable和dbiGetNextRecord来打开和解析数据库。这消除了OLEDB API的许多处理开销,并允许我更准确地 pinpoint 崩溃。
随着崩溃的堆积,是时候对它们进行分诊了。与Peach Fuzzer不同,WinAFL没有方便的分诊助手,但我可以轻松地使用WinDBG命令行界面和PowerShell重新创建它:
|
|
该脚本迭代所有崩溃文件,使用harness在WinDBG中运行它们,然后生成一个包含!exploitable输出的日志文件。接下来,我专注于EXPLOITABLE崩溃,并将具有相同崩溃指令的分组。
立刻,两个崩溃引起了我的注意。
第二顺序EIP覆盖 🔗
第一个崩溃看起来像这样:
|
|