Win下测试高亮
传统的afl在unix上运行的时候,在关键地方通过是否变成绿色来判断是否测试完毕,但是在Windows上没有进行高亮,本来以为是Windows的cmd不支持ansi色彩,后来发现不是。
通过代码可以发现真正的问题出现在源码中他就没开。找到debug.h
文件,在里面的42行左右可以发现一些判断,这里直接把判断去掉就可以在Windows中高亮了。别让他判断就行了,他判断了一个本地的环境是不是tty,不是就不高亮了。
#ifdef USE_COLOR
# define cBLK "\x1b[0;30m"
# define cRED "\x1b[0;31m"
# define cGRN "\x1b[0;32m"
# define cBRN "\x1b[0;33m"
# define cBLU "\x1b[0;34m"
# define cMGN "\x1b[0;35m"
# define cCYA "\x1b[0;36m"
# define cLGR "\x1b[0;37m"
# define cGRA "\x1b[1;30m"
# define cLRD "\x1b[1;31m"
# define cLGN "\x1b[1;32m"
# define cYEL "\x1b[1;33m"
# define cLBL "\x1b[1;34m"
# define cPIN "\x1b[1;35m"
# define cLCY "\x1b[1;36m"
# define cBRI "\x1b[1;37m"
# define cRST "\x1b[0m"
# define bgBLK "\x1b[40m"
# define bgRED "\x1b[41m"
# define bgGRN "\x1b[42m"
# define bgBRN "\x1b[43m"
# define bgBLU "\x1b[44m"
# define bgMGN "\x1b[45m"
# define bgCYA "\x1b[46m"
# define bgLGR "\x1b[47m"
# define bgGRA "\x1b[100m"
# define bgLRD "\x1b[101m"
# define bgLGN "\x1b[102m"
# define bgYEL "\x1b[103m"
# define bgLBL "\x1b[104m"
# define bgPIN "\x1b[105m"
# define bgLCY "\x1b[106m"
# define bgBRI "\x1b[107m"
#else
# define cBLK "\x1b[0;30m"
# define cRED "\x1b[0;31m"
# define cGRN "\x1b[0;32m"
# define cBRN "\x1b[0;33m"
# define cBLU "\x1b[0;34m"
# define cMGN "\x1b[0;35m"
# define cCYA "\x1b[0;36m"
# define cLGR "\x1b[0;37m"
# define cGRA "\x1b[1;30m"
# define cLRD "\x1b[1;31m"
# define cLGN "\x1b[1;32m"
# define cYEL "\x1b[1;33m"
# define cLBL "\x1b[1;34m"
# define cPIN "\x1b[1;35m"
# define cLCY "\x1b[1;36m"
# define cBRI "\x1b[1;37m"
# define cRST "\x1b[0m"
# define bgBLK "\x1b[40m"
# define bgRED "\x1b[41m"
# define bgGRN "\x1b[42m"
# define bgBRN "\x1b[43m"
# define bgBLU "\x1b[44m"
# define bgMGN "\x1b[45m"
# define bgCYA "\x1b[46m"
# define bgLGR "\x1b[47m"
# define bgGRA "\x1b[100m"
# define bgLRD "\x1b[101m"
# define bgLGN "\x1b[102m"
# define bgYEL "\x1b[103m"
# define bgLBL "\x1b[104m"
# define bgPIN "\x1b[105m"
# define bgLCY "\x1b[106m"
# define bgBRI "\x1b[107m"
// # define cBLK ""
// # define cRED ""
// # define cGRN ""
// # define cBRN ""
// # define cBLU ""
// # define cMGN ""
// # define cCYA ""
// # define cLGR ""
// # define cGRA ""
// # define cLRD ""
// # define cLGN ""
// # define cYEL ""
// # define cLBL ""
// # define cPIN ""
// # define cLCY ""
// # define cBRI ""
// # define cRST ""
// # define bgBLK ""
// # define bgRED ""
// # define bgGRN ""
// # define bgBRN ""
// # define bgBLU ""
// # define bgMGN ""
// # define bgCYA ""
// # define bgLGR ""
// # define bgGRA ""
// # define bgLRD ""
// # define bgLGN ""
// # define bgYEL ""
// # define bgLBL ""
// # define bgPIN ""
// # define bgLCY ""
// # define bgBRI ""
#endif /* ^USE_COLOR */
/*************************
* Box drawing sequences *
*************************/
#ifdef FANCY_BOXES
# define SET_G1 "\x1b)0" /* Set G1 for box drawing */
# define RESET_G1 "\x1b)B" /* Reset G1 to ASCII */
# define bSTART "\x0e" /* Enter G1 drawing mode */
# define bSTOP "\x0f" /* Leave G1 drawing mode */
# define bH "q" /* Horizontal line */
# define bV "x" /* Vertical line */
# define bLT "l" /* Left top corner */
# define bRT "k" /* Right top corner */
# define bLB "m" /* Left bottom corner */
# define bRB "j" /* Right bottom corner */
# define bX "n" /* Cross */
# define bVR "t" /* Vertical, branch right */
# define bVL "u" /* Vertical, branch left */
# define bHT "v" /* Horizontal, branch top */
# define bHB "w" /* Horizontal, branch bottom */
#else
# define SET_G1 ""
# define RESET_G1 ""
# define bSTART ""
# define bSTOP ""
# define bH "-"
# define bV "|"
# define bLT "+"
# define bRT "+"
# define bLB "+"
# define bRB "+"
# define bX "+"
# define bVR "+"
# define bVL "+"
# define bHT "+"
# define bHB "+"
#endif /* ^FANCY_BOXES */
/***********************
* Misc terminal codes *
***********************/
#ifdef USE_COLOR
#define TERM_HOME "\x1b[H"
#define TERM_CLEAR TERM_HOME "\x1b[2J"
#define cEOL "\x1b[0K"
#define CURSOR_HIDE "\x1b[?25l"
#define CURSOR_SHOW "\x1b[?25h"
#else
#define TERM_HOME "\x1b[H"
#define TERM_CLEAR TERM_HOME "\x1b[2J"
#define cEOL "\x1b[0K"
#define CURSOR_HIDE "\x1b[?25l"
#define CURSOR_SHOW "\x1b[?25h"
// #define TERM_HOME ""
// #define TERM_CLEAR ""
// #define cEOL ""
// #define CURSOR_HIDE ""
// #define CURSOR_SHOW ""
#endif /* ^USE_COLORS */
优化winafl发现pdf阅读器的漏洞
针对pdf阅读器中的图片解析引擎提出了一种针对性的方案来减少winafl的盲目性。翻译学习的是一篇期刊论文
《Optimizing WinAFL for Image Parsing Engine Vulnerability Discovery in PDF Readers》
疑惑
这个论文开篇就解答了“为什么关于pdf解析器的漏洞很多?”的问题,因为pdf格式复杂,并不像docx或者txt或者md那种格式单一的文档,正因为结构的复杂性所以导致了漏洞的多样性。
fuzz分类
- 基于生成的
需要测试人员提供程序的格式化信息或者其他标识,好帮助更好的fuzz过程
- 基于变异的
不需要规定输入的格式,程序会根据预定义的变异规则进行随机变异,winafl则是基于变异的fuzzer。
DynamoRIO
winafl的关键就是DynamoRIO这个黑盒的模糊测试工具中的drrun.exe
,这个工具可以通过动态插桩来对二进制程序进行动态分析程序的执行过程,还可以进行污点分析的功能。ida安装一个灯塔插件就可以配合使用了。可以将其理解为一个进程虚拟机,结构图如下:
该工具也是通过基本块的定义来监视程序执行的。
AFL
afl作为fuzz模糊测试的一种具体实现形式,他利用遗传算法和编译时检测以发现被测程序内部的覆盖信息,并将有意义的样本进行记录从而变化模糊的策略以此来达到更好的测试效果,流程如图所示
- 使用 afl-cmin 对原始种子文件进行语料提取,并根据路径反馈消除重复文件。主要用于初始文件多的情况。生成的文件集被添加到种子文件队列中。
- 根据种子选择算法从种子队列中选择首选种子集。
- 根据预设的变化选择种子文件。
- 使用多种变异算法对文件进行变异,可以周期性地生成大量测试用例进行测试。
- 一般对afl的修改都是对变异策略的修改,以此来达到更高覆盖率的目的。
- 如果程序崩溃被触发,可能存在潜在的漏洞,存储触发崩溃的文件并记录崩溃。
- 如果找到一个新的路径,将该路径的测试用例添加到种子队列中。
- 当这个种子生成的所有测试用例都被测试后,继续从第3步开始,以此类推。
winafl介绍
afl一般用于unix环境中,处于代码的差异和接口的差异,Windows平台并不能使用上述提到的编译时检测
的方式,而是使用winafl(模糊测试+记录)+dynamoRIO(动态插桩)的组合进行测试。RIO可以动态的通过管道通信的方式将覆盖率反馈给winafl。
流程
afl_fuzz.exe
通过创建命名管道(不是匿名管道)和内存映射与目标进程进行交互。管道用于发送和接收命令,与其他进程进行交互,内存映射主要用于记录覆盖信息。- 覆盖率记录主要是通过
drmgr_register_BB_uments_event
来设置 BB 执行的回调函数。覆盖率由Instrent_bb_overage
或Instrent_edge_overage
记录,如果找到新的执行路径,则将样本放入队列目录中,以进行后续的文件变异,从而提高代码覆盖率。 - 在目标进程执行到目标函数(target_offset或者target_modules参数规定的地址或者名称)之后,
pre_fuzz_handler
将被调用来存储上下文信息,包括寄存器和运行参数。 - 在目标函数启动后,
post_fuzz_handle
函数将被调用来记录应答上下文信息( reply context information),从而执行原始目标函数并返回到第二步。 - 当目标函数的运行次数达到指定的循环调用次数时,进程将被中断并退出。
PDF文件格式
pdf是由一组相互连接的对象按照层次结构构造的,基本上计算机里的所有文件都是按照这种层级模式构架的吧😅
基本由这四个结构体构成。
- header
开头一般是以%PDF-
开始的,后面跟一个数字,代表着pdf代表着版本规范号。
- Body
正文由多个间接对象组成,主要用来存储pdf的内容,同时标记图片的一些特点和文字的一些特征。pdf会对这些内容进行压缩来控制文件大小。
- xref
交叉引用,相当于PE文件里的节区头的作用,记录了每个对象相对于文档起始位置的偏移(byte为单位)
trailer
以
trailer
开始,以%%EOF
结束,预告片存储两个重要的信息片段:
由属性关键字Root
标识的根对象,以及交叉引用表相对于文档开头的字节偏移位置,由关键字startxref
标识。
WinAFL改进
主要对winafl中的write_to_testcase
进行重写劫持来达到对pdf中的图像部分的变异。
write_to_testcase
这个函数的作用是生成根据变异和遗传算法生成后续用来测试的案例文件。这里拿到的论文介绍和我实际上拿到的项目中的代码有出入啊,下文是winafl中的afl_fuzz.c的中的函数代码,这些代码的作用就是生产测试文件,测试文件名称一般是
“Cur_input”文件,这个文件一般存放到in目录中。
static void write_to_testcase(void* mem, u32 len)
{
if (dll_write_to_testcase_ptr)
{
dll_write_to_testcase_ptr(out_file, out_fd, mem, len);
return;
}
else if (use_sample_shared_memory)
{
//this writes fuzzed data to shared memory, so that it is available to harnes program.
uint32_t* size_ptr = (uint32_t*)shm_sample;
unsigned char* data_ptr = shm_sample + 4;
if (len > MAX_SAMPLE_SIZE) len = MAX_SAMPLE_SIZE;
*size_ptr = len;
memcpy(data_ptr, mem, len);
return;
}
s32 fd = out_fd;
if (out_file)
{
fd = open(out_file, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, DEFAULT_PERMISSION);
if (fd < 0)
{
destroy_target_process(0);
fd = open(out_file, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, DEFAULT_PERMISSION);
if (fd < 0) PFATAL("Unable to create '%s'", out_file);
}
}
else lseek(fd, 0, SEEK_SET);
ck_write(fd, mem, len, out_file);
if (!out_file)
{
if (_chsize(fd, len)) PFATAL("ftruncate() failed");
lseek(fd, 0, SEEK_SET);
}
else close(fd);
}
run_target
之后通过“run_target”函数来启动执行测试程序,并且将“Cur_input”文件输入进去
static u8 run_target(char** argv, u32 timeout) {
total_execs++;
if (dll_run_target_ptr) {
return dll_run_target_ptr(argv, timeout, trace_bits, MAP_SIZE);
}
#ifdef INTELPT
if (use_intelpt) {
return run_target_pt(argv, timeout);
}
#endif
//todo watchdog timer to detect hangs
DWORD num_read, dwThreadId;
char result = 0;
if (sinkhole_stds && devnul_handle == INVALID_HANDLE_VALUE) {
devnul_handle = CreateFile(
"nul",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (devnul_handle == INVALID_HANDLE_VALUE) {
PFATAL("Unable to open the nul device.");
}
}
if (dll_init_ptr) {
if (!dll_init_ptr())
PFATAL("User-defined custom initialization routine returned 0");
}
if (!is_child_running()) {
destroy_target_process(0);
create_target_process(argv);
fuzz_iterations_current = 0;
}
if (dll_run_ptr)
process_test_case_into_dll(fuzz_iterations_current);
child_timed_out = 0;
memset(trace_bits, 0, MAP_SIZE);
MemoryBarrier();
if (fuzz_iterations_current == 0 && init_tmout != 0) {
watchdog_timeout_time = get_cur_time() + init_tmout;
}
else {
watchdog_timeout_time = get_cur_time() + timeout;
}
watchdog_enabled = 1;
result = ReadCommandFromPipe(timeout);
if (result == 'K')
{
//a workaround for first cycle in app persistent mode
result = ReadCommandFromPipe(timeout);
}
if (result == 0)
{
//saves us from getting stuck in corner case.
MemoryBarrier();
watchdog_enabled = 0;
destroy_target_process(0);
return FAULT_TMOUT;
}
if (result != 'P')
{
FATAL("Unexpected result from pipe! expected 'P', instead received '%c'\n", result);
}
WriteCommandToPipe('F');
result = ReadCommandFromPipe(timeout); //no need to check for "error(0)" since we are exiting anyway
//ACTF("result: '%c'", result);
MemoryBarrier();
watchdog_enabled = 0;
#ifdef _WIN64
classify_counts((u64*)trace_bits);
#else
classify_counts((u32*)trace_bits);
#endif /* ^_WIN64 */
fuzz_iterations_current++;
if (fuzz_iterations_current == fuzz_iterations_max) {
destroy_target_process(2000);
}
if (result == 'K') return FAULT_NONE;
if (result == 'C') {
ret_exception_code = ReadDWORDFromPipe(timeout);
// ACTF("destroying target process");
destroy_target_process(2000);
return FAULT_CRASH;
}
destroy_target_process(0);
return FAULT_TMOUT;
}
算法框架
一般来说当fuzz PDF的时候,一般就是拿PDF文件作为种子文件进行后续的变异。该文中为了针对性的fuzz PDF李的图片部分,所以种子文件被替换为图像格式。实现的思路就是:
劫持write_to_testcase
函数,用来替换直接输入二进制流到.cur_input
文件的原始操作。将该函数劫持之后,用自定义的脚本来测试用例,这样就可以写入磁盘的每一个测试案例都是一个经过变异之后的图片。
然后调用run_target
函数来嵌入的PDF文件输入到目标程序中。论文中的图片如图:
这个改动的本质就是,不动PDF的整体结构,只对PDF中的图片进行变异,通过这种方式来提高测试的效率和减少系统开支。
总结
论文的最后采用C和python混合变异的方式来修改程序,通过案例测试发现相比于之前的效率显著提高,但是文章里没有给出具体的代码。在论文中提到,还可以通过修改对PDF中的otf或者ttf等格式的图片进行测试。
本文相当于是通过这篇论文来从代码层面对winafl有了一个大概的认识,他的主要代码就集中在afl-fuzz.c
和debug.h
这两个文件中。