Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

0
0
【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

2017-09-22 11:28:23

阅读:239次
点赞(0)
收藏
来源: symeonp.github.io





【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

作者:天鸽





【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

译者:天鸽

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


MSXML库模糊测试

在这篇博客中,我将介绍怎样使用WinAFLfuzzer 来对 MSXML 库做模糊测试。

也许你还没有使用过 WinAFL,它是由 Ivan(Google's Project Zero)创造的一个大型 fuzzer,它基于 Icumtuf 创造的使用 DynamoRIO 来测量代码覆盖率的AFL,和用于内存和进程创建的 windows API。Axel Souchet一直在积极地提供新功能,如最新稳定版中的corpus minimization,将在下一篇博客中介绍的persistent execution mode和afl-tmin工具。

我们将从创建一个测试框架(test harness)开始,它可以让我们在库中 fuzz 一些解析函数,计算覆盖范围和最小化测试用例,最后以启动 fuzzer 和对结果进行分类来结束。最后,感谢来自 0patch 的 Mitja Kolsek 提供的补丁,它展示了怎样用 0patch 来修补该漏洞!

使用上述步骤,我已经在函数msxml6!DTD::findEntityGeneral 中找到了一个 NULL pointer dereference 的问题,我向微软报告后被拒绝,他们认为这不是一个安全问题。公平地说,只有 crash 确实没用,希望有人能发现一些有趣的东西。


测试框架

在做了一些研究的时候,我在这里发现了微软提供的一个 C++ 示例代码,它允许我们提供一些 XML 文件并验证其结构。我将使用 Visual Studio 2015 来构建下面的程序,但在之前,我稍微做了点修改,使用了 Ivan 的 charToWChar 方法,它接受一个参数作为一个文件:

//xmlvalidate_fuzz.cpp:Definestheentrypointfortheconsoleapplication. // #include"stdafx.h" #include<stdio.h> #include<tchar.h> #include<windows.h> #import<msxml6.dll> extern"C"__declspec(dllexport)intmain(intargc,char**argv); //MacrothatcallsaCOMmethodreturningHRESULTvalue. #defineCHK_HR(stmt)do{hr=(stmt);if(FAILED(hr))gotoCleanUp;}while(0) voiddump_com_error(_com_error&e) { _bstr_tbstrSource(e.Source()); _bstr_tbstrDescription(e.Description()); printf("Error\n"); printf("\a\tCode=%08lx\n",e.Error()); printf("\a\tCodemeaning=%s",e.ErrorMessage()); printf("\a\tSource=%s\n",(LPCSTR)bstrSource); printf("\a\tDescription=%s\n",(LPCSTR)bstrDescription); } _bstr_tvalidateFile(_bstr_tbstrFile) { //Initializeobjectsandvariables. MSXML2::IXMLDOMDocument2PtrpXMLDoc; MSXML2::IXMLDOMParseErrorPtrpError; _bstr_tbstrResult=L""; HRESULThr=S_OK; //CreateaDOMDocumentandsetitsproperties. CHK_HR(pXMLDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60),NULL,CLSCTX_INPROC_SERVER)); pXMLDoc->async=VARIANT_FALSE; pXMLDoc->validateOnParse=VARIANT_TRUE; pXMLDoc->resolveExternals=VARIANT_TRUE; //LoadandvalidatethespecifiedfileintotheDOM. //Andreturnvalidationresultsinmessagetotheuser. if(pXMLDoc->load(bstrFile)!=VARIANT_TRUE) { pError=pXMLDoc->parseError; bstrResult=_bstr_t(L"Validationfailedon")+bstrFile+ _bstr_t(L"\n=====================")+ _bstr_t(L"\nReason:")+_bstr_t(pError->Getreason())+ _bstr_t(L"\nSource:")+_bstr_t(pError->GetsrcText())+ _bstr_t(L"\nLine:")+_bstr_t(pError->Getline())+ _bstr_t(L"\n"); } else { bstrResult=_bstr_t(L"Validationsucceededfor")+bstrFile+ _bstr_t(L"\n======================\n")+ _bstr_t(pXMLDoc->xml)+_bstr_t(L"\n"); } CleanUp: returnbstrResult; } wchar_t*charToWChar(constchar*text) { size_tsize=strlen(text)+1; wchar_t*wa=newwchar_t[size]; mbstowcs(wa,text,size); returnwa; } intmain(intargc,char**argv) { if(argc<2){ printf("Usage:%s<xmlfile>\n",argv[0]); return0; } HRESULThr=CoInitialize(NULL); if(SUCCEEDED(hr)) { try { _bstr_tbstrOutput=validateFile(charToWChar(argv[1])); MessageBoxW(NULL,bstrOutput,L"noNamespace",MB_OK); } catch(_com_error&e) { dump_com_error(e); } CoUninitialize(); } return0; }

请注意下面的代码片段:

extern"C"__declspec(dllexport)intmain(intargc,char**argv); 本质上,这允许我们使用 target_method 参数,DynamoRIO 将尝试为给定的符号名(symbol name)检索地址,如这里所示。
我们可以按照 README 中使用的偏移方法,但是由于 ASLR 和所有这些东西,我们希望对模糊测试进行扩展,将二进制文件复制到许多台虚拟机里,并能使用相同的命令来进行 fuzz。指令 extern "C" 将 unmange 函数名,并使其看起来更漂亮。

要确定 DynamoRIO 确实可以使用此方法,输入下面的命令:

dumpbin/EXPORTSxmlvalidate_fuzz.exe
【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

现在让我们赶快运行二进制文件并观察输出。你应该会得到下面的输出:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

代码覆盖

WinAFL

由于要测试的库是闭源的,所以我们将通过 WinAFL 使用 DynamoRIO 的代码覆盖库功能:

C:\DRIO\bin32\drrun.exe-cwinafl.dll-debug-coverage_modulemsxml6.dll-target_modulexmlvalidate.exe-target_methodmain-fuzz_iterations10-nargs2--C:\xml_fuzz_initial\xmlvalidate.exeC:\xml_fuzz_initial\nn-valid.xml

WinAFL 将执行二进制文件十次。一旦完成,请返回到 winafl 的文件夹并检查日志文件:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

从输出中我们看到运行似乎一切正常!在文件的右侧,那些点描述了 DLL 的覆盖范围,如果你向下滚动,将会看到我们确实覆盖了许多的函数,因为我们在整个文件中获得了更多的点。我们在搜索大量的代码时,这是一个非常好的迹象,我们已经快要正确地定位到 MSXML6 的库。

Lighthouse- IDA Pro 的代码覆盖资源管理器

这个插件将帮助我们更好地了解我们命中的函数,并使用 IDA 对覆盖范围进行了很好的概述。这是一个很好的插件,且具有良好的文档,由 Markus Gaasedelen (@gaasedelen) 所开发。请确保下载了最新版的DynamoRIO 7,并按照这里的说明进行安装。幸运的是,我们从文档中获得了两个样本测试用例,一个有效一个无效。让我们输入有效的一个并观察覆盖情况。为此,运行下面的命令:

C:\DRIO7\bin64\drrun.exe-tdrcov--xmlvalidate.exenn-valid.xml

下一步启动 IDA,加载 msxml6.dll 并确保获得了符号!现在,检查是否有一个 .log 文件被创建,并在 IDA 中依次点击

File -> Load File -> Code Coverage File(s) 打开它。一旦覆盖文件被加载,它将高亮出测试用例命中的所有函数。

测试用例最小化

现在是时候测试 XML 文件了(尽可能小)。我使用了一个稍微偏黑客的 joxean find_samples.py 版本的脚本。一旦你得到了几个测试用例,就可以最小化初始 seed 文件。可以使用下面的命令完成:

pythonwinafl-cmin.py--working-dirC:\winafl\bin32-DC:\DRIO\bin32-t100000-iC:\xml_fuzz\samples-oC:\minset_xml-coverage_modulemsxml6.dll-target_modulexmlvalidate.exe-target_methodfuzzme-nargs1--C:\xml_fuzz\xmlvalidate.exe@@

你会看到下面的输出:

corpusminimizationtoolforWinAFLby<0vercl0k@tuxfamily.org> BasedonWinAFLby<ifratric@google.com> BasedonAFLby<lcamtuf@google.com> [+]CWDchangedtoC:\winafl\bin32. [*]Testingthetargetbinary... [!]Dry-runfailed,2executionsresulteddifferently: Tuplesmatching?False Returncodesmatching?True

我不太确定,但我认为 winafl-cmin.py 脚本期望初始 seed 文件指向相同的代码路径,也就是我们必须一次有效的测试用例,和一次无效的测试用例。可能是我错了,也可能是有一个 bug。

我们使用下面的 bash 脚本来确定一下“好的”和“坏的”XML 测试用例。

$forfilein*;doprintf"====FILE:$file=====\n";/cygdrive/c/xml_fuzz/xmlvalidate.exe$file;sleep1;done

下面的截图显示了我的运行结果:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

随意尝试一下,看看是哪些文件导致了这个问题(你的可能会有所不同)。一旦确定,再次运行上面的命令,希望你能得到下面的结果:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

你看,初始用例包含 76 个文件,最小化后缩减至 26 个。感谢 Axel!

使用最小化后的测试用例,我们来编写一个可以自动化执行所有代码覆盖的 python 脚本:import sys

importos testcases=[] forroot,dirs,filesinos.walk(".",topdown=False): fornameinfiles: ifname.endswith(".xml"): testcase=os.path.abspath(os.path.join(root,name)) testcases.append(testcase) fortestcaseintestcases: print"[*]RunningDynamoRIOfortestcase:",testcase os.system("C:\\DRIO7\\bin32\\drrun.exe-tdrcov--C:\\xml_fuzz\\xmlvalidate.exe%s"%testcase)

上面的脚本在我使用的用例里生成了下面的输出:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

和前面一样,使用 IDA 打开 File -> Load File -> Code Coverage File(s) 菜单下的所有 .log 文件。


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试
有趣的是,请注意存在多少个 parse 函数,如果你在覆盖范围内徘徊,将会看到我们已经设法得到了大量有趣的代码。

由于我们确实得到了不错的代码覆盖率,让我们继续前进,最终 fuzz 它!


我所做的就是 fuzz,fuzz,fuzz

让我们启动 fuzzer:

afl-fuzz.exe-iC:\minset_xml-oC:\xml_results-DC:\DRIO\bin32\-t20000---coverage_moduleMSXML6.dll-target_modulexmlvalidate.exe-target_methodmain-nargs2--C:\xml_fuzz\xmlvalidate.exe@@

运行上面的命令后得到下面的输出:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试
正如你看到的,初始代码就是做这个工作,但速度非常慢。每三秒执行一次将消耗大量的时间才能得到正确的结果。有趣的是,我曾经就是在这种速度下(在 afl/winafl 时代之前,使用 python 和 radamsa),在三天的测试中发现了 bug!

让我们尽可能地从拖慢 fuzz 速度的部分解脱出来。如果你曾经做过 Windows 编程,就会知道下面一行初始化了一个 COM 对象,这可能就是速度的瓶颈:

HRESULThr=CoInitialize(NULL);

这一行确实是一个主要的问题,因此我们来重构代码,我们将创建一个 fuzzme 方法,该方法将在 COM 初始化调用之后接受文件名作为一个参数。重构的代码如下:

---cut--- extern"C"__declspec(dllexport)_bstr_tfuzzme(wchar_t*filename); _bstr_tfuzzme(wchar_t*filename) { _bstr_tbstrOutput=validateFile(filename); //bstrOutput+=validateFile(L"nn-notValid.xml"); //MessageBoxW(NULL,bstrOutput,L"noNamespace",MB_OK); returnbstrOutput; } intmain(intargc,char**argv) { if(argc<2){ printf("Usage:%s<xmlfile>\n",argv[0]); return0; } HRESULThr=CoInitialize(NULL); if(SUCCEEDED(hr)) { try { _bstr_tbstrOutput=fuzzme(charToWChar(argv[1])); } catch(_com_error&e) { dump_com_error(e); } CoUninitialize(); } return0; } ---cut---

你可以从这里得到重构后的版本。使用重构的二进制文件我们来再一次运行 fuzzer,看看是否正确。这一次,我们将传递 fuzzme 作为 target_method,而不是 main,并且只使用一个参数,即文件名。这里,我们使用 lcamtuf 的 xml.dic,来自这里。

afl-fuzz.exe-iC:\minset_xml-oC:\xml_results-DC:\DRIO\bin32\-t20000-xxml.dict---coverage_moduleMSXML6.dll-target_modulexmlvalidate.exe-target_methodfuzzme-nargs1--C:\xml_fuzz\xmlvalidate.exe@@ 一旦你运行脚本,在 VMWare 中几秒钟就出现了下面的输出:

【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

好多了,现在我们让它运行起来然后等待崩溃吧!


结果 - 崩溃分类和分析

通常,我会尝试用不同的测试用例来 fuzz 这个二进制文件,但幸运的是我不断得到 NULL pointer dereference 的 bug。下面的截图显示了大约 12 天后的结果:

【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

请注意,总共执行了 33 万次,并发现了 26 次不同的崩溃!

为了给结果分类,我使用了 SkyLined 的Bugid工具,这是一个很棒的工具,能为你提供关于崩溃和崩溃利用的详细报告。

下面是我的 python 代码:

importsys importos sys.path.append("C:\\BugId") testcases=[] forroot,dirs,filesinos.walk(".\\fuzzer01\\crashes",topdown=False): fornameinfiles: ifname.endswith("00"): testcase=os.path.abspath(os.path.join(root,name)) testcases.append(testcase) fortestcaseintestcases: print"[*]Gonnarun:",testcase os.system("C:\\python27\\python.exeC:\\BugId\\BugId.pyC:\\Users\\IEUser\\Desktop\\xml_validate_results\\xmlvalidate.exe--%s"%testcase) 运行上面的脚本得到了下面的输出:

【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

一旦我为所有得到的崩溃运行了它,可以很清楚的看到,我们命中的是相同的 bug。为了确认这一点,让我们打开 windbg:

0:000>g (a6c.5c0):Accessviolation-codec0000005(!!!secondchance!!!) eax=03727aa0ebx=0012fc3cecx=00000000edx=00000000esi=030f4f1cedi=00000002 eip=6f95025aesp=0012fbccebp=0012fbcciopl=0nvupeiplzrnapenc cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00010246 msxml6!DTD::findEntityGeneral+0x5: 6f95025a8b4918movecx,dwordptr[ecx+18h]ds:0023:00000018=???????? 0:000>kv ChildEBPRetAddrArgstoChild 0012fbcc6f9de30003727aa000000002030f4f1cmsxml6!DTD::findEntityGeneral+0x5(FPO:[Non-Fpo])(CONV:thiscall)[d:\w7rtm\sql\xml\msxml6\xml\dtd\dtd.hxx@236] 0012fbe86f999db303727aa000000003030c5fb0msxml6!DTD::checkAttrEntityRef+0x14(FPO:[Non-Fpo])(CONV:thiscall)[d:\w7rtm\sql\xml\msxml6\xml\dtd\dtd.cxx@1470] 0012fc106f90508f030f4f180012fc3c00000000msxml6!GetAttributeValueCollapsing+0x43(FPO:[Non-Fpo])(CONV:stdcall)[d:\w7rtm\sql\xml\msxml6\xml\parse\nodefactory.cxx@771] 0012fc286f902d8700000003030f4f146f9051f4msxml6!NodeFactory::FindAttributeValue+0x3c(FPO:[Non-Fpo])(CONV:thiscall)[d:\w7rtm\sql\xml\msxml6\xml\parse\nodefactory.cxx@743] 0012fc8c6f8f7f0d030c5fb0030c3f2001570040msxml6!NodeFactory::CreateNode+0x124(FPO:[Non-Fpo])(CONV:stdcall)[d:\w7rtm\sql\xml\msxml6\xml\parse\nodefactory.cxx@444] 0012fd1c6f8f5042010c3f20ffffffffc4fd70d3msxml6!XMLParser::Run+0x740(FPO:[Non-Fpo])(CONV:stdcall)[d:\w7rtm\sql\xml\msxml6\xml\tokenizer\parser\xmlparser.cxx@1165] 0012fd586f8f4f93030c3f20c4fd701700000000msxml6!Document::run+0x89(FPO:[Non-Fpo])(CONV:thiscall)[d:\w7rtm\sql\xml\msxml6\xml\om\document.cxx@1494] 0012fd9c6f90a95b030ddf580000000000000000msxml6!Document::_load+0x1f1(FPO:[Non-Fpo])(CONV:thiscall)[d:\w7rtm\sql\xml\msxml6\xml\om\document.cxx@1012] 0012fdc86f8f6c75037278f000000000c4fd73b3msxml6!Document::load+0xa5(FPO:[Non-Fpo])(CONV:thiscall)[d:\w7rtm\sql\xml\msxml6\xml\om\document.cxx@754] 0012fe3800401d36000000000000000800000000msxml6!DOMDocumentWrapper::load+0x1ff(FPO:[Non-Fpo])(CONV:stdcall)[d:\w7rtm\sql\xml\msxml6\xml\om\xmldom.cxx@1111] --cut--
【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

让我们来看一个造成崩溃的 xml:

C:\Users\IEUser\Desktop\xml_validate_results\fuzzer01\crashes>typeid_000000_00 <?xmlversion="&a;1.0"?> <bookxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="nn.xsd" id="bk101"> <author>Gambardella,Matthew</author> <title>XMLDeveloper'sGuide</title> <genre>Computer</genre> <price>44.95</price> <publish_date>2000-10-01</publish_date> <description>Anin-depthlookatcreatingapplicationswith XML.</description>

正如你看到的,如果我们在 xml 或者其编码后提供了一些 garbage,就会得到上面的崩溃。Mitja 还将测试用例减到最小,如下所示:

<?xmlversion='1.0'encoding='&aaa;'?>

对该库进行模糊测试的整个思想,是基于在 IE 上下文中找到一个漏洞并以某种方法触发它。经过一番搜索,让我们使用下面的 Poc(crashme.html),看看它是否会使 IE11 崩溃:

<!DOCTYPEhtml> <html> <head> </head> <body> <script> varxmlDoc=newActiveXObject("Msxml2.DOMDocument.6.0"); xmlDoc.async=false; xmlDoc.load("crashme.xml"); if(xmlDoc.parseError.errorCode!=0){ varmyErr=xmlDoc.parseError; console.log("Youhaveerror"+myErr.reason); }else{ console.log(xmlDoc.xml); } </script> </body> </html>

在 Python 的 SimpleHTTPServer 中运行它,提供下面的东西:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

Bingo!正如预期的那样,至少在启用了 PageHeap 的情况下,我们能够触发与我们在测试框架中相同的崩溃。小心不要在 Microsoft Outlook 中包含该 xml,因为它也会崩溃!此外,由于它基于库本身,如果产生一个更 sexy 的崩溃,则会增加攻击面!


打补丁

在与 Mitja 通过电子邮件交流后,他向我提供了可以在完全更新的 x64 系统上使用的补丁:

;targetplatform:Windows7x64 ; RUN_CMDC:\Users\symeon\Desktop\xmlvalidate_64bit\xmlvalidate.exeC:\Users\symeon\Desktop\xmlvalidate_64bit\poc2.xml MODULE_PATH"C:\Windows\System32\msxml6.dll" PATCH_ID200000 PATCH_FORMAT_VER2 VULN_ID9999999 PLATFORMwin64 patchlet_start PATCHLET_ID1 PATCHLET_TYPE2 PATCHLET_OFFSET0xD093D PITmsxml6.dll!0xD097D code_start testrbp,rbp;isrbp(this)NULL? jnzcontinue jmpPIT_0xD097D continue: code_end patchlet_end

我们来调试和测试下这个补丁程序,我已经创建了一个账户,并未开发者安装了 0patch 代理,右击上述的 .0pp 文件:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

一旦在测试框架中使用了可以导致崩溃的 xml,我立即设置断点:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

从上面的代码中看到,rbp 寄存器确实是 null,这将导致 null pointer dereference 的问题。由于我们已经部署了 0patch 代理,实际上它会跳转到 msxml6.dll!0xD097D,从而避免崩溃:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

太棒了!接下来我在修复后的版本上再次启动 winafl,但是不幸失败了。由于 0patch(钩子函数?)的性质,它与 WinAFL 不兼容,于是崩溃了。

然而,这是一种“DoS 0day”,正如我之前提到的,我在2017年6月向微软提出报告,二十天后收到一下邮件:


【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试

我完全同意这一决定,但我最感兴趣的还是修补掉这个烦人的 bug,以便我可以继续前进。在调试器上花了几个小时之后,我发现唯一可控制的用户输入是编码字符串的长度:

eax=03052660ebx=0012fc3cecx=00000011edx=00000020esi=03054f24edi=00000002 eip=6f80e616esp=0012fbd4ebp=0012fbe4iopl=0nvupeiplzrnapenc cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246 msxml6!Name::create+0xf: 6f80e616e8e7e6f9ffcallmsxml6!Name::create(6f7acd02) 0:000>ddsespL3 0012fbd400000000 0012fbd803064ff8 0012fbdc00000003 0:000>dc03064ff8L4 03064ff80061006100000061????????????????a.a.a...????????

上面的 unicode 字符串其实是来自我们测试用例的开头,其中数字 3 很明显是长度(函数的签名:Name *__stdcall Name::create(String *pS, const wchar_t *pch, int iLen, Atom *pAtomURN))


结论

如你所见,花一些时间在微软的 API 和文档上是非常值得的!另外,重构一些基本函数并精确定位影响性能的问题也可能对我们的工作有很大的改进!

我必须感谢 lvan 将 afl 移植到 Windows 并创建了这个令人吃惊的项目。也感谢 Axel 和其他一直积极做贡献的人。

我的同事 Javier 激励我写了这篇博客,Richard 一直在回答我愚蠢的问题,并给我所有的帮助,来自 0patch 的 Mitja 建立了这个补丁,最后 Patroklo 几年前教了我一些模糊测试的技巧!


参考

Evolutionary Kernel Fuzzing-BH2017-rjohnson-FINAL.pdf

Super Awesome Fuzzing, Part One



【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试
【技术分享】使用 WinAFL 对 MSXML6 库进行模糊测试
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://symeonp.github.io/2017/09/17/fuzzing-winafl.html

Viewing all articles
Browse latest Browse all 12749

Latest Images

Trending Articles





Latest Images