【模糊测试】fuzz Unacev2.dll - Harness编写

fuzz Unacev2.dll Part1 - Harness 编写

前一篇文章 【CVE分析】CVE-2018-20250 WinRAR目录穿越漏洞 中分析了 WinRAR 的路径穿越漏洞,作者的博客中提到他是使用 fuzz 来发现这个漏洞的。所以我也想研究一下 fuzz 的使用。

分成两个部分:

  1. harness 编写
  2. winafl 测试

Winafl 环境搭建

参考了 FreeBuf: 模糊测试工具WinAFL使用指南

使用 WinAFL + DynamoRIO 动态插桩来实现。安装过程完全参照博客即可。

Fuzz Unacev2.dll

编写 harness

原博客中作者虽然提到是用 fuzz 来挖掘出该漏洞的,但是并没有给出编写的 harness。所以我参考了该作者的另外一篇博客来学习如何编写 harness(参考:【模糊测试】如何编写一个Harness? )。

信息来源主要有两个点:

  1. 参考 winrar.exe :winrar.exe 调用了 unacev2.dll 对 ace 格式对压缩文件进行解压。所以通过逆向分析 winrar 对 unacev2.dll 的调用逻辑,就能够大致还原调用流程;
  2. 参考 unacev2.dll 的部分代码:在 【模糊测试】如何编写一个Harness? 中提到,试图逆向一个程序之前,先寻找是否存在开源信息。虽然 unacev2.dll 并不是开源项目,但是在 github 上仍然找到了相关数据结构的头文件。这对逆向 unacev2.dll 和编写 harness 起到了重要作用。

将 winrar 拖入 windbg 中,打开 test.rar 程序,在 windbg 中输入 lm 指令,能看到所有加载的模块:(也可以用 lm m *unacev2.dll* 仅查看 unacev2.dll)

image-20201104104145620

bm UnAceV2!* ".echo ; kb 3; gc" 不可用,因为 UnAceV2.dll 没有符号表。

sxe ld:UnAceV2,在加载链接库 UnAceV2.dll 的时候,程序会中断。此时,使用 WinDbg 调试,现场如图:

image-20201104110809953

可以看到,程序在 WinRAR+0x16916 处经过调用 LoadLibraryW() 等函数加载了 UnAceV2.dll 。在 IDA 中查找对应的地址(0x400000+0x16916)如图:

image-20201104111059162

在 IDA 中通过 alt+k 调节了栈帧,f5 得到伪 C 语言代码:

image-20201104113648398

重命名一些函数和变量地址:

image-20201104121504472

查找 ACEInitDll_addr 作为函数被调用的位置,向上回溯两层调用,可以看到有两个位置调用了这个函数:

image-20201104121633593

将其记录为 interesting_func1()interesting_func2()。对应的地址是 0x00415F20 和 0x00415C30。经过检查,其它函数包括 ACEExtractACEExtract() 函数也是在这两个函数里被调用的。(比较特别的是,ACEReadArchiveData 和 AceList 函数似乎没有被调用)。

重新打开 WinDbg 调试,在这两个函数上下断点:

1
2
bp 0x00415C30
bp 0x00415F20

在 WinRAR 打开 test.rar 文件时(仅打开,还未解压),调用了 0x00415F20 函数。可以看到,该函数由 WinRAR+0x506e 调用。

image-20201104135900076

而解压 test.rar 文件时,调用了 0x00415c30:(感觉函数栈显示得不全?)

image-20201104142409416

在 0x00415c30 函数中,能够发现,先调用了函数 ACEInitDll() 函数,再调用了 ACEExtract() 函数。

强行反汇编失败。尝试从 github 中寻找有关 ACEInitDll() 函数的信息。找到了项目 https://github.com/Firebie/FarManager。该项目引用了 UnAceV2.dll,甚至给出了编译的工程。

可以向 IDA 中导入头文件:

image-20201104150648303

先后导入 :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)
{
//printf("load unacev2.dll failed\n");
return -1;
}

ACEInitDll_addr = (int (*)(pACEInitDllStruc))GetProcAddress(module, "ACEInitDll");
ACEExtract_addr = (int (*)(LPSTR, pACEExtractStruc))GetProcAddress(module, "ACEExtract");

//printf("%p, %p\n", ACEInitDll_addr, ACEExtract_addr);
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;
//printf("successfully.\n");
return 0;
}

void fuzzfunction(char *rarpath, char *extractpath)
{
//puts(filename);

memset(unknowstr1, 0, 1024);
memset(passwd, 0, 1024);
memset(unknowstr2, 0, 1024);
memset(comment, 0, 0x2000);

char tmpdir[] = "C:\\tmp";
//nop2();

/* start calling ACEInitDll */
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; // important
struc.GlobalData.StateCallbackProc = (INT (__stdcall *)(pACEStateCallbackProcStruc))nop3;

ACEInitDll_addr(&struc);
//printf("end calling ACEInitDll.\n");
/* end ACEInitDll */

/* start calling ACEExtract */
tACEExtractStruc extract;
char fl[] = "*";
//char dst[] = "C:\\tmp\\test";
//char rarpath[] = "C:\\tmp\\test.rar";


extract.Files.FileList = fl;
extract.ExcludePath = NULL;
extract.Files.SourceDir = (LPSTR)unknowstr1; // what's this?
extract.Files.ExcludeList = (LPSTR)unknowstr2; // what's this?
extract.Files.FullMatch = 1;
extract.DestinationDir = extractpath;
extract.DecryptPassword = passwd;
//printf("start calling ACEExtract.\n");
ACEExtract_addr(rarpath, &extract);
//printf("end calling ACEExtract.\n");
/* end ACEExtract */


//printf("end fuzzfunction.\n");
return;
// comment = NULL;
}

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 编译这个程序。中间在执行的时候各种报错,最后把优化关掉之后就好了。

image-20210815222130314

总之可以正常解压一个 ace 文件:

image-20210815222346923