fuzz Unacev2.dll Part1 - Harness 编写
前一篇文章 【CVE分析】CVE-2018-20250 WinRAR目录穿越漏洞 中分析了 WinRAR 的路径穿越漏洞,作者的博客中提到他是使用 fuzz 来发现这个漏洞的。所以我也想研究一下 fuzz 的使用。
分成两个部分:
- harness 编写
- winafl 测试
Winafl 环境搭建
参考了 FreeBuf: 模糊测试工具WinAFL使用指南 。
使用 WinAFL + DynamoRIO 动态插桩来实现。安装过程完全参照博客即可。
Fuzz Unacev2.dll
编写 harness
在原博客中作者虽然提到是用 fuzz 来挖掘出该漏洞的,但是并没有给出编写的 harness。所以我参考了该作者的另外一篇博客来学习如何编写 harness(参考:【模糊测试】如何编写一个Harness? )。
信息来源主要有两个点:
- 参考 winrar.exe :winrar.exe 调用了 unacev2.dll 对 ace 格式对压缩文件进行解压。所以通过逆向分析 winrar 对 unacev2.dll 的调用逻辑,就能够大致还原调用流程;
- 参考 unacev2.dll 的部分代码:在 【模糊测试】如何编写一个Harness? 中提到,试图逆向一个程序之前,先寻找是否存在开源信息。虽然 unacev2.dll 并不是开源项目,但是在 github 上仍然找到了相关数据结构的头文件。这对逆向 unacev2.dll 和编写 harness 起到了重要作用。
将 winrar 拖入 windbg 中,打开 test.rar 程序,在 windbg 中输入 lm
指令,能看到所有加载的模块:(也可以用 lm m *unacev2.dll*
仅查看 unacev2.dll)
bm UnAceV2!* ".echo ; kb 3; gc"
不可用,因为 UnAceV2.dll 没有符号表。
sxe ld:UnAceV2
,在加载链接库 UnAceV2.dll 的时候,程序会中断。此时,使用 WinDbg 调试,现场如图:
可以看到,程序在 WinRAR+0x16916 处经过调用 LoadLibraryW()
等函数加载了 UnAceV2.dll 。在 IDA 中查找对应的地址(0x400000+0x16916)如图:
在 IDA 中通过 alt+k 调节了栈帧,f5 得到伪 C 语言代码:
重命名一些函数和变量地址:
查找 ACEInitDll_addr
作为函数被调用的位置,向上回溯两层调用,可以看到有两个位置调用了这个函数:
将其记录为 interesting_func1()
和 interesting_func2()
。对应的地址是 0x00415F20 和 0x00415C30。经过检查,其它函数包括 ACEExtract
,ACEExtract()
函数也是在这两个函数里被调用的。(比较特别的是,ACEReadArchiveData 和 AceList 函数似乎没有被调用)。
重新打开 WinDbg 调试,在这两个函数上下断点:
1 2
| bp 0x00415C30 bp 0x00415F20
|
在 WinRAR 打开 test.rar 文件时(仅打开,还未解压),调用了 0x00415F20 函数。可以看到,该函数由 WinRAR+0x506e 调用。
而解压 test.rar 文件时,调用了 0x00415c30:(感觉函数栈显示得不全?)
在 0x00415c30 函数中,能够发现,先调用了函数 ACEInitDll()
函数,再调用了 ACEExtract()
函数。
强行反汇编失败。尝试从 github 中寻找有关 ACEInitDll()
函数的信息。找到了项目 https://github.com/Firebie/FarManager。该项目引用了 UnAceV2.dll,甚至给出了编译的工程。
可以向 IDA 中导入头文件:
先后导入 :FarManager-master\plugins\newarc\src\plugins\ace\includes\STRUCS.H 和 UNACEFNC.H。
1 2 3
| INT __stdcall ACEInitDll(pACEInitDllStruc DllData); INT __stdcall ACEExtract(LPSTR ArchiveName, pACEExtractStruc Extract); INT __stdcall ACETest(LPSTR ArchiveName, pACETestStruc Test);
|
此外,在逆向数据结构时,出现了难以确定含义的函数指针。因此使用了作者提到的构建空函数的方法(简单理解为先糊弄过去,等到真正调用这个回调函数时再分析。)
1 2 3 4 5 6
| #define NOP(x) \ int _nop##x(){ \ printf("==>nop%d called\n", x ); \ return (int)x; \ } \ int (*nop##x)(void) = _nop##x;
|
总之最后编写了一个 harness:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| #include <stdio.h> #include <stdlib.h> #include <Windows.h>
#include "STRUCS.H" #include "UNACEFNC.H"
#define NOP(x) \ int _nop##x(){ \ printf("==>nop%d called\n", x ); \ return (int)x; \ } \ int (*nop##x)(void) = _nop##x; NOP(0) NOP(1) NOP(2) NOP(3) HMODULE module; char unknowstr1[1024]; char unknowstr2[1024]; char passwd[1024]; char comment[0x2000];
int (*ACEInitDll_addr)(pACEInitDllStruc) = NULL; int (*ACEExtract_addr)(LPSTR, pACEExtractStruc) = NULL;
void fuzzfunction(char *rarpath, char *extractpath); BOOL isEmptyDirectory(const char * dir); wchar_t* charToWChar(const char* text);
int main(int argc, char **argv) { if (argc < 2) { printf("Usage: %s <ace-format-file>\n", argv[0]); return 1; } wchar_t* PathName = charToWChar(argv[1]);
module = LoadLibrary("unacev2.dll"); if (module == NULL) { return -1; } ACEInitDll_addr = (int (*)(pACEInitDllStruc))GetProcAddress(module, "ACEInitDll"); ACEExtract_addr = (int (*)(LPSTR, pACEExtractStruc))GetProcAddress(module, "ACEExtract"); char filename[100] = { 0, }; _splitpath(argv[1], NULL, NULL, filename, NULL); char extractpath[1024]; sprintf(extractpath, "C:\\tmp\\%s", filename); puts(extractpath); int i = CreateDirectory(extractpath, NULL); printf("%d", &i); fuzzfunction(argv[1], extractpath); RemoveDirectory(extractpath);
FreeLibrary(module); module = NULL; return 0; }
void fuzzfunction(char *rarpath, char *extractpath) {
memset(unknowstr1, 0, 1024); memset(passwd, 0, 1024); memset(unknowstr2, 0, 1024); memset(comment, 0, 0x2000);
char tmpdir[] = "C:\\tmp"; tACEInitDllStruc struc = {0,}; struc.GlobalData.MaxArchiveTestBytes = 0x3FFFF; struc.GlobalData.MaxFileBufSize = 0x2FFFF; struc.GlobalData.Comment.Buf = comment; struc.GlobalData.Comment.BufSize = 0x2000; struc.GlobalData.TempDir = tmpdir;
struc.GlobalData.InfoCallbackProc = (INT (__stdcall *)(pACEInfoCallbackProcStruc))nop0; struc.GlobalData.ErrorCallbackProc = (INT (__stdcall *)(pACEErrorCallbackProcStruc))nop1; struc.GlobalData.RequestCallbackProc = (INT (__stdcall *)(pACERequestCallbackProcStruc))nop2; struc.GlobalData.StateCallbackProc = (INT (__stdcall *)(pACEStateCallbackProcStruc))nop3; ACEInitDll_addr(&struc); tACEExtractStruc extract; char fl[] = "*";
extract.Files.FileList = fl; extract.ExcludePath = NULL; extract.Files.SourceDir = (LPSTR)unknowstr1; extract.Files.ExcludeList = (LPSTR)unknowstr2; extract.Files.FullMatch = 1; extract.DestinationDir = extractpath; extract.DecryptPassword = passwd; ACEExtract_addr(rarpath, &extract); return;
}
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; }
|
我在 Windows 7 下的 Visual Studio 2013 编译这个程序。中间在执行的时候各种报错,最后把优化关掉之后就好了。
总之可以正常解压一个 ace 文件: