地狱怪客

使用WinAFL模糊MSXML6库

文章来源:https://symeonp.github.io/2017/09/17/fuzzing-winafl.html

使用WinAFL模糊MSXML6库

介绍

在这篇博客文章中,我将介绍如何使用WinAFL fuzzer 来模拟 MSXML库。

如果你还没有玩过WinAFL,那么它是由Ivan(Google的Project Zero)创建的一个大型的Fuzzer,它基于lcumtuf的AFL ,它使用DynamoRIO来测量代码覆盖率和用于内存和进程创建的Windows API。 Axel Souchet一直在积极地提供功能,如语料库最小化,最新的afl稳定构建,持续执行模式,将覆盖下一篇博客文章以及最后的afl-tmin工具。

我们将首先创建一个测试工具,这将使我们能够在库内模糊一些解析功能,计算覆盖范围,最小化测试用例,并通过启动模糊器并对发现进行分类。最后,感谢来自0patch的Mitja Kolsek 私人补丁,看看如何使用0patch修补这个问题!

使用上述步骤,我已经设法找到一个NULL指针取消引用的msxml6!DTD::findEntityGeneral功能,我向Microsoft报告但被拒绝,因为这不是一个安全问题。公平的,确实崩溃是废话,但希望有人会发现有趣的技术我遵循!

线束

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

// xmlvalidate_fuzz.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#import <msxml6.dll>
extern "C" __declspec(dllexport)  int main(int argc, char** argv);

// Macro that calls a COM method returning HRESULT value.
#define CHK_HR(stmt)        do { hr=(stmt); if (FAILED(hr)) goto CleanUp; } while(0)

void dump_com_error(_com_error &e)
{
    _bstr_t bstrSource(e.Source());
    _bstr_t bstrDescription(e.Description());

    printf("Error\n");
    printf("\a\tCode = %08lx\n", e.Error());
    printf("\a\tCode meaning = %s", e.ErrorMessage());
    printf("\a\tSource = %s\n", (LPCSTR)bstrSource);
    printf("\a\tDescription = %s\n", (LPCSTR)bstrDescription);
}

_bstr_t validateFile(_bstr_t bstrFile)
{
    // Initialize objects and variables.
    MSXML2::IXMLDOMDocument2Ptr pXMLDoc;
    MSXML2::IXMLDOMParseErrorPtr pError;
    _bstr_t bstrResult = L"";
    HRESULT hr = S_OK;

    // Create a DOMDocument and set its properties.
    CHK_HR(pXMLDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60), NULL, CLSCTX_INPROC_SERVER));

    pXMLDoc->async = VARIANT_FALSE;
    pXMLDoc->validateOnParse = VARIANT_TRUE;
    pXMLDoc->resolveExternals = VARIANT_TRUE;

    // Load and validate the specified file into the DOM.
    // And return validation results in message to the user.
    if (pXMLDoc->load(bstrFile) != VARIANT_TRUE)
    {
        pError = pXMLDoc->parseError;

        bstrResult = _bstr_t(L"Validation failed on ") + 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"Validation succeeded for ") + bstrFile +
            _bstr_t(L"\n======================\n") +
            _bstr_t(pXMLDoc->xml) + _bstr_t(L"\n");
    }

CleanUp:
    return bstrResult;
}

wchar_t* charToWChar(const char* text)
{
    size_t size = strlen(text) + 1;
    wchar_t* wa = new wchar_t[size];
    mbstowcs(wa, text, size);
    return wa;
}

int main(int argc, char** argv)
{
    if (argc < 2) {
        printf("Usage: %s <xml file>\n", argv[0]);
        return 0;
    }

    HRESULT hr = CoInitialize(NULL);
    if (SUCCEEDED(hr))
    {
        try
        {
            _bstr_t bstrOutput = validateFile(charToWChar(argv[1]));
            MessageBoxW(NULL, bstrOutput, L"noNamespace", MB_OK);
        }
        catch (_com_error &e)
        {
            dump_com_error(e);
        }
        CoUninitialize();
    }

    return 0;

}

还请注意以下代码段: extern "C" __declspec(dllexport) int main(int argc, char** argv);

从本质上讲,这使我们能够使用target_method的参数,它DynamoRIO将尝试为给定的检索地址符号名所看到这里

我可以按照README使用偏移方法,但是由于ASLR和所有这些东西,我们希望对模糊进行缩放,并将二进制扩展到许多虚拟机,并使用相同的命令来模糊它。该extern "C"指令将解除函数名称的混淆,并使其看起来更漂亮。

要确认DynamoRIO是否可以使用此方法,可以使用以下命令:

dumpbin /EXPORTS xmlvalidate_fuzz.exe

查看导出的功能。

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

从xmlvlidation二进制文件输出。

代码覆盖

WinAFL

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

C:\DRIO\bin32\drrun.exe -c winafl.dll -debug -coverage_module msxml6.dll -target_module xmlvalidate.exe -target_method main -fuzz_iterations 10 -nargs 2 -- C:\xml_fuzz_initial\xmlvalidate.exe C:\xml_fuzz_initial\nn-valid.xml

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

检查WinAFL中的覆盖范围。

从输出我们可以看到一切似乎正常运行!在文件的右侧,点描述了DLL的覆盖范围,如果您向下滚动,您会看到我们确实打了很多功能,因为我们在整个文件中获得更多的点。这是一个非常好的迹象,我们正在搜索很多代码,并且正确地定位到MSXML6库。

灯塔 – IDA Pro的代码覆盖资源管理器

这个插件将帮助我们更好地了解我们正在击中的功能,并使用IDA对覆盖范围进行很好的概述。这是一个很好的插件,非常好的文档,并已由Markus Gaasedelen(@gaasedelen)开发。 请确保下载最新的DynamoRIO版本7,并按照这里的说明进行安装。幸运的是,我们从文档中有两个样本测试用例,一个有效和一个无效。让我们吃一个有效的一个并观察覆盖。为此,请运行以下命令:

C:\DRIO7\bin64\drrun.exe -t drcov -- xmlvalidate.exe nn-valid.xml

下一步启动IDA,拖动msxml6.dll并确保获取符号!现在,检查.log文件是否已创建,并从文件 – >加载文件 – >代码覆盖文件菜单中的IDA上打开它。一旦覆盖文件被加载,它将突出显示测试用例命中的所有功能。

案例最小化

现在是抓住一些XML文件(尽可能小)的时候了。我使用了一个稍微被黑客攻击的joxean的find_samples.py脚本。一旦你得到了几个测试用例,我们可以最小化我们的初始种子文件。这可以使用以下命令完成:

python winafl-cmin.py --working-dir C:\winafl\bin32 -D C:\DRIO\bin32 -t 100000 -i C:\xml_fuzz\samples -o C:\minset_xml -coverage_module msxml6.dll -target_module xmlvalidate.exe -target_method fuzzme -nargs 1 -- C:\xml_fuzz\xmlvalidate.exe @@

您可能会看到以下输出:

corpus minimization tool for WinAFL by <0vercl0k@tuxfamily.org>
Based on WinAFL by <ifratric@google.com>
Based on AFL by <lcamtuf@google.com>
[+] CWD changed to C:\winafl\bin32.
[*] Testing the target binary...
[!] Dry-run failed, 2 executions resulted differently:
Tuples matching? False
Return codes matching? True

我不太确定,但我认为winafl-cmin.py脚本期望初始的种子文件导致相同的代码路径,也就是我们必须运行脚本一次为有效的情况,一个为无效的。我可能错了,可能有一个bug,在这种情况下,我需要ping Axel。

我们来确定使用这个bash脚本的“好”和“坏”XML测试用例:

$ for file in *; do printf "==== FILE: $file =====\n"; /cygdrive/c/xml_fuzz/xmlvalidate.exe $file ;sleep 1; done

以下屏幕截图显示了我的结果:

通过Cygwin的测试用例循环

请随时到期,并查看哪些文件导致此问题 – 您的里程可能会有所不同。一旦设置好,再次运行上面的命令,希望你会得到以下结果:

最小化我们的初始种子文件。

所以看看那个!最初的运动包括76例,最小化后缩小到26。
谢谢Axel!

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

import sys
import os

testcases = []
for root, dirs, files in os.walk(".", topdown=False):
    for name in files:
        if name.endswith(".xml"):
            testcase =  os.path.abspath(os.path.join(root, name))
            testcases.append(testcase)

for testcase in testcases:
    print "[*] Running DynamoRIO for testcase: ", testcase
    os.system("C:\\DRIO7\\bin32\\drrun.exe -t drcov -- C:\\xml_fuzz\\xmlvalidate.exe %s" % testcase)

上面的脚本为我的案例生成了以下输出:

由Lighthouse插件生成的覆盖文件。

如前所述,使用IDA打开File – > Load File – > Code Coverage File(s)菜单下的所有.log文件。

使用灯塔插件和IDA Pro的代码覆盖。

有意思的是,请注意有多少解析函数存在,如果您在覆盖范围内徘徊,您将看到我们已经设法获得了大量有趣的代码。

由于我们确实有一些体面的覆盖,让我们继续前进,最终模糊它!

我所做的只是毛绒,绒毛,绒毛

让我们开始模糊者:

afl-fuzz.exe -i C:\minset_xml -o C:\xml_results -D C:\DRIO\bin32\ -t 20000 -- -coverage_module MSXML6.dll -target_module xmlvalidate.exe -target_method main -nargs 2 -- C:\xml_fuzz\xmlvalidate.exe @@

运行以上产生以下输出:

WinAFL以慢速运行。

你可以看到,初始代码做这个工作 – 但速度非常慢。每秒三次处决将需要很长时间才能给出一些正确的结果。有趣的是,我曾经幸运过这种速度(使用python和radamsa之前的afl / winafl时代)成功地发现错误,并在三天的模糊!

让我们尽力而为,尽可能地减轻疲劳的部分。如果你已经做了一些Windows编程,你会知道以下行初始化一个COM对象,这可能是慢速的瓶颈:

HRESULT hr = CoInitialize(NULL);

这行可能是一个主要问题,所以我们来重构代码,我们将创建一个fuzzme方法,该方法将在COM初始化调用之外接收文件名作为参数。重构的代码应如下所示:

--- cut ---

extern "C" __declspec(dllexport) _bstr_t fuzzme(wchar_t* filename);

_bstr_t fuzzme(wchar_t* filename)
{
    _bstr_t bstrOutput = validateFile(filename);
    //bstrOutput += validateFile(L"nn-notValid.xml");
    //MessageBoxW(NULL, bstrOutput, L"noNamespace", MB_OK);
    return bstrOutput;

}
int main(int argc, char** argv)
{
    if (argc < 2) {
        printf("Usage: %s <xml file>\n", argv[0]);
        return 0;
    }

    HRESULT hr = CoInitialize(NULL);
    if (SUCCEEDED(hr))
    {
        try
        {
            _bstr_t bstrOutput = fuzzme(charToWChar(argv[1]));
        }
        catch (_com_error &e)
        {
            dump_com_error(e);
        }
        CoUninitialize();
    }
    return 0;
}
--- cut ---

您可以在这里抓住重构的版本使用重构的二进制文件让我们再一次运行fuzzer,看看我们是否正确。 这一次,我们将传递fuzzme target_method而不是main,并且只使用一个参数,它是文件名。虽然我们在这里,让我们使用lcamt​​uf的 xml.dic从这里

afl-fuzz.exe -i C:\minset_xml -o C:\xml_results -D C:\DRIO\bin32\ -t 20000 -x xml.dict -- -coverage_module MSXML6.dll -target_module xmlvalidate.exe -target_method fuzzme -nargs 1 -- C:\xml_fuzz\xmlvalidate.exe @@

一旦运行完毕,在VMWare实例的几秒钟内就会出现这个输出:

WinAFL以极大的速度运行。

辉煌!那好多了,现在让它运行并等待崩溃!

调查结果 – 崩溃分类/分析

一般来说,我试图用不同的测试用例来模糊这个二进制文件,不过不幸的是我不断得到NULL指针解引用错误。以下屏幕截图显示了〜12天模糊运动后的发现:

12天后的模糊结果。

请注意,总共执行了33万次处决,发现了26次独特的事件!

为了分类这些发现,我已经使用了错误ID从工具SkyLined,这是一个很好的工具,这将给你就崩溃和死机的可利用性的详细报告。

这是我的python代码:

import sys
import os


sys.path.append("C:\\BugId")

testcases = []
for root, dirs, files in os.walk(".\\fuzzer01\\crashes", topdown=False):
    for name in files:
        if name.endswith("00"):
            testcase =  os.path.abspath(os.path.join(root, name))
            testcases.append(testcase)

for testcase in testcases:
    print "[*] Gonna run: ", testcase
    os.system("C:\\python27\\python.exe C:\\BugId\\BugId.py C:\\Users\\IEUser\\Desktop\\xml_validate_results\\xmlvalidate.exe -- %s" % testcase)

上述脚本给出以下输出:

运行cBugId来分类崩溃..

一旦我运行这一切,我的所有崩溃,它清楚地表明,我们正在打同样的错误。要确认,让我们开火windbg:

0:000> g
(a6c.5c0): Access violation - code c0000005 (!!! second chance !!!)
eax=03727aa0 ebx=0012fc3c ecx=00000000 edx=00000000 esi=030f4f1c edi=00000002
eip=6f95025a esp=0012fbcc ebp=0012fbcc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
msxml6!DTD::findEntityGeneral+0x5:
6f95025a 8b4918          mov     ecx,dword ptr [ecx+18h] ds:0023:00000018=????????
0:000> kv
ChildEBP RetAddr  Args to Child              
0012fbcc 6f9de300 03727aa0 00000002 030f4f1c msxml6!DTD::findEntityGeneral+0x5 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\w7rtm\sql\xml\msxml6\xml\dtd\dtd.hxx @ 236]
0012fbe8 6f999db3 03727aa0 00000003 030c5fb0 msxml6!DTD::checkAttrEntityRef+0x14 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\w7rtm\sql\xml\msxml6\xml\dtd\dtd.cxx @ 1470]
0012fc10 6f90508f 030f4f18 0012fc3c 00000000 msxml6!GetAttributeValueCollapsing+0x43 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\w7rtm\sql\xml\msxml6\xml\parse\nodefactory.cxx @ 771]
0012fc28 6f902d87 00000003 030f4f14 6f9051f4 msxml6!NodeFactory::FindAttributeValue+0x3c (FPO: [Non-Fpo]) (CONV: thiscall) [d:\w7rtm\sql\xml\msxml6\xml\parse\nodefactory.cxx @ 743]
0012fc8c 6f8f7f0d 030c5fb0 030c3f20 01570040 msxml6!NodeFactory::CreateNode+0x124 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\w7rtm\sql\xml\msxml6\xml\parse\nodefactory.cxx @ 444]
0012fd1c 6f8f5042 010c3f20 ffffffff c4fd70d3 msxml6!XMLParser::Run+0x740 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\w7rtm\sql\xml\msxml6\xml\tokenizer\parser\xmlparser.cxx @ 1165]
0012fd58 6f8f4f93 030c3f20 c4fd7017 00000000 msxml6!Document::run+0x89 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\w7rtm\sql\xml\msxml6\xml\om\document.cxx @ 1494]
0012fd9c 6f90a95b 030ddf58 00000000 00000000 msxml6!Document::_load+0x1f1 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\w7rtm\sql\xml\msxml6\xml\om\document.cxx @ 1012]
0012fdc8 6f8f6c75 037278f0 00000000 c4fd73b3 msxml6!Document::load+0xa5 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\w7rtm\sql\xml\msxml6\xml\om\document.cxx @ 754]
0012fe38 00401d36 00000000 00000008 00000000 msxml6!DOMDocumentWrapper::load+0x1ff (FPO: [Non-Fpo]) (CONV: stdcall) [d:\w7rtm\sql\xml\msxml6\xml\om\xmldom.cxx @ 1111]
-- cut --

运行cBugId来分类崩溃..

我们来看看一个凶手:

C:\Users\IEUser\Desktop\xml_validate_results\fuzzer01\crashes>type id_000000_00
<?xml version="&a;1.0"?>
<book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="nn.xsd"
      id="bk101">
   <author>Gambardella, Matthew</author>
   <title>XML Developer's Guide</title>
   <genre>Computer</genre>
   <price>44.95</price>
   <publish_date>2000-10-01</publish_date>
   <description>An in-depth look at creating applications with
   XML.</description>

如你所见,如果我们在xml版本或编码上提供一些垃圾,我们将会得到上述的崩溃。Mitja还将案例减至最低,如下所示:

<?xml version='1.0' encoding='&aaa;'?>

这个图书馆的整个想法是基于在Internet Explorer的上下文中找到一个漏洞,并以某种方式触发它。经过一番谷歌搜索,让我们使用以下PoC(crashme.html),看看它是否会崩溃IE11:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script>

var xmlDoc = new ActiveXObject("Msxml2.DOMDocument.6.0");
xmlDoc.async = false;
xmlDoc.load("crashme.xml");
if (xmlDoc.parseError.errorCode != 0) {
   var myErr = xmlDoc.parseError;
   console.log("You have error " + myErr.reason);
} else {
   console.log(xmlDoc.xml);
}

</script>
</body>
</html>

在Python的SimpleHTTPServer下运行它,提供以下内容:

运行cBugId来分类崩溃..

答对了!正如预期的那样,至少在启用了PageHeap的情况下,我们能够触发与我们的线束完全相同的崩溃。小心不要在Microsoft Outlook中包含该xml,因为它也会崩溃!此外,由于它在图书馆本身,如果一个更性感的碰撞会增加攻击面!

修补

在与Mitja交换了一些电子邮件后,他向我提供了可以在完全更新的x64系统上应用的以下补丁:

;target platform: Windows 7 x64
;
RUN_CMD C:\Users\symeon\Desktop\xmlvalidate_64bit\xmlvalidate.exe C:\Users\symeon\Desktop\xmlvalidate_64bit\poc2.xml
MODULE_PATH "C:\Windows\System32\msxml6.dll"
PATCH_ID 200000
PATCH_FORMAT_VER 2
VULN_ID 9999999
PLATFORM win64


patchlet_start
 PATCHLET_ID 1
 PATCHLET_TYPE 2
 
 PATCHLET_OFFSET 0xD093D 
 PIT msxml6.dll!0xD097D
  
 code_start

  test rbp, rbp ;is rbp (this) NULL?
  jnz continue
  jmp PIT_0xD097D
  continue:
 code_end
patchlet_end

让我们调试和测试这个补丁,我创建了一个帐户并为开发人员安装了0patch代理,并继续右键点击上面的.0pp文件:

使用0patch控制台运行crasher

一旦我使用xml crasher执行了我的线束,我立即点击了断点:

击中Windbg下的断点

从上面的代码中,确实是rbpnull哪个将导致空指针取消引用。由于我们已经部署了0patch代理,实际上它会跳转到msxml6.dll!0xD097D避免崩溃:

Bug完全修补了!

太棒了!我的下一步是再次启动winafl与修补版本不幸失败。由于0patch(函数钩子)的性质,它不会与WinAFL发挥不错,它会崩溃。

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

MSRC回应

我完全同意这个决定,但是我主要感兴趣的是修补烦人的错误,以便我可以继续前进:o)
在调试器上花了几个小时,唯一的“可控制”用户输入将是编码字符串:

eax=03052660 ebx=0012fc3c ecx=00000011 edx=00000020 esi=03054f24 edi=00000002
eip=6f80e616 esp=0012fbd4 ebp=0012fbe4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
msxml6!Name::create+0xf:
6f80e616 e8e7e6f9ff      call    msxml6!Name::create (6f7acd02)
0:000> dds esp L3
0012fbd4  00000000
0012fbd8  03064ff8
0012fbdc  00000003

0:000> dc 03064ff8 L4
03064ff8  00610061 00000061 ???????? ????????  a.a.a...????????

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

结论

如您所见,花费一些时间在微软的API /文档可以是黄金!Morover,重构一些基本功能并精确定位影响性能的问题也可能导致巨大的改进!

在这个说明上,我不能感谢Ivan将afl移植到Windows并创建这个惊人的项目。此外,由于Axel以及谁一直在积极贡献和增加惊人的功能。

向我的同事哈维尔(我们都有一个堆垃圾朋友,对吗?)激励我写这个博客,理查德谁一直在回答我的愚蠢的问题,并帮助我所有这一次,来自0patch团队的Mitja建立这个补丁最后Patroklo教我几年前关于模糊的技巧!

参考

进化核心模糊 – BH2017-rjohnson-FINAL.pdf
超级真棒模糊,第一部分

码字很辛苦,转载请注明来自人生在世《使用WinAFL模糊MSXML6库》

评论