hu1y40's blog

hu1y40'blog
天堂的穹空遍布地狱之火的颜色,但也是天堂。
  1. 首页
  2. FUZZ
  3. 正文

AFL代码阅读 II

2023年10月7日 1771点热度 0人点赞 0条评论

0x00 前言

为了解决使用AFL++进行Fuzzing的部分问题,个人决定对AFL的代码进行阅读,此为第二部分。

0x01 LLVM

1.1 起源

LLVM是底层虚拟机的英文缩写。

根据不同的场景,LLVM可能指示以下内容:

  1. LLVM项目/基础架构。此时,LLVM指代多个构建一个完整编译的项目,包括前端后端,优化器,汇编器,链接器,libc++。compiler-rt以及JIT引擎。
  2. 基于LLVM的编译器。此时LLVM指代部分或者完全采用LLVM基础架构构建的编译器。例如某个编译器可以采用LLVM作为前端或者后端,但是使用GCC以及GNU系统库函数进行最后的链接。
  3. LLVM库,此时,LLVM指代LLVM基础架构种可复用的代码部分。
  4. LLVM内核。此时LLVM指代在中间表示级别上所进行的优化以及后端算法。
  5. LLVM中间表示(LLVM IR)。此时,LLVM指代LLVM编译器的中间表示。

fig:

0x02 文件分析

2.1 afl-clang-fast.c

2.1.1 简介

该文件位于AFL的llvm_mode目录下 。AFL的 llvm_mode 可以实现编译器级别的插桩,可以替代 afl-gcc 或 afl-clang 使用的比较“粗暴”的汇编级别的重写的方法,且具备如下几个优势:

  1. 编译器可以进行很多优化以提升效率;
  2. 可以实现CPU无关,可以在非 x86 架构上进行fuzz;
  3. 可以更好地处理多线程目标。

在AFL的 llvm_mode 文件夹下包含3个文件: afl-clang-fast.c ,afl-llvm-pass.so.cc, afl-llvm-rt.o.c。

afl-llvm-rt.o.c 文件主要是重写了 afl-as.h 文件中的 main_payload 部分,方便调用;

afl-llvm-pass.so.cc 文件主要是当通过 afl-clang-fast 调用 clang 时,这个pass被插入到 LLVM 中,告诉编译器添加与 `afl-as.h 中大致等效的代码;

afl-clang-fast.c 文件本质上是 clang 的 wrapper,最终调用的还是 clang 。但是与 afl-gcc 一样,会进行一些参数处理。

llvm_mode 的插桩思路就是通过编写pass来实现信息记录,对每个基本块都插入探针,具体代码在 afl-llvm-pass.so.cc 文件中,初始化和forkserver操作通过链接完成。

2.1.2 函数

1.edit_params

该函数有两种插桩模式,一种是afl-llvm-pass.so注入插桩,一种是trace-pc-guard模式插桩,使用本地LLVM插桩回调来实现代码覆盖率。

首先判断传入第一个参数来选择编译器。然后遍历参数,根据参数设置bit_mode(bitmode决定调用的afl-llv-rt的位数,这里不知道是否和Compiler-RT项目有关,该项目是一个对目标架构提供硬件不支持的部分底层功能),x_set(有-x的话最后会设置-x none也即是不根据文件扩展名),asan_set,fortify_set参数,参数中如果有-Wl,-z,defs或 -Wl,--no-undefined则跳过,从而无视undefined reference to错误。

随后检查环境变量是否有AFL_HARDEN,如果有,添加 -fstack-protector-all 选项,同时如果设置了FORTIFY_SOURCE,添加-D_FORTIFY_SOURCE=2选项。

然后检查AFL_USET_ASAN,AFL_USE_MSAN环境变量,选择开启ASAN或者MSAN,ASAN,MSAN,AFL_HARDEN是互斥关系。

再然后根据环境变量AFL_DONT_OPTIMIZE设置编译优化选项,根据环境变量AFL_NO_BUILTIN设置内建函数优化。

接着设置宏变量__AFL_HAVE_MANUAL_CONTROL=1,__AFL_COMPILER=1,FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1。并且定义了__AFL_LOOP(),__AFL_INIT()两个宏函数,其中有意思的就是宏函数使用了__attribute__((used))防止了变量被编译器优化,volatile修饰变量防止被链接器优化,

/* When the user tries to use persistent or deferred forkserver modes by
appending a single line to the program, we want to reliably inject a
signature into the binary (to be picked up by afl-fuzz) and we want
to call a function from the runtime .o file. This is unnecessarily
painful for three reasons:

1) We need to convince the compiler not to optimize out the signature.
This is done with __attribute__((used)).

2) We need to convince the linker, when called with -Wl,--gc-sections,
not to do the same. This is done by forcing an assignment to a
'volatile' pointer.

3) We need to declare __afl_persistent_loop() in the global namespace,
but doing this within a method in a class is hard - :: and extern "C"
are forbidden and __attribute__((alias(...))) doesn't work. Hence the
__asm__ aliasing trick.

*/
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
"({ static volatile char *_B __attribute__((used)); "
" _B = (char*)\"" PERSIST_SIG "\"; "
"_L(_A); })";

没读懂第三条,可能与下面的链接有关

https://dmalcolm.fedorapeople.org/gcc/2015-08-31/rst-experiment/how-to-use-inline-assembly-language-in-c-code.html

2.find_obj

该函数寻找运行时库(runtime libraries)具体是afl-llvm-rt.o文件

3.main

首先如果有宏定义__ANDROID__就调用find_obj,然后调用edit_params,最后execvp执行clang并传入处理完的参数。

2.2 afl-llvm-pass.so.cc

2.2.1 简介

实现一个遍。

2.2.2 前置知识

LLVM的每一遍处理都变成实现为Pass类的派生类。大多数的遍变成实现为单个cpp文件,Pass类的子类再匿名空间进行定义。为了使每一遍扫描的发挥实际作用,文件之外的代码也能够使用该类,文件中需要导出一个函数(创建Pass)。如图是一个创建遍的实例。

fig:

2.2.3 函数

1.registerAFLPass

static void registerAFLPass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {

PM.add(new AFLCoverage());

}

static RegisterStandardPasses RegisterAFLPass(
PassManagerBuilder::EP_ModuleOptimizerEarly, registerAFLPass);

static RegisterStandardPasses RegisterAFLPass0(
PassManagerBuilder::EP_EnabledOnOptLevel0, registerAFLPass);

注册Pass

RegisterAFLPass和RegisterAFLPass0在不同的编译器优化级别(Optimization Level)下注册 AFLCoverage Pass。

2.runOnModule

该函数为关键函数,传入参数为Module &M。

其首先获取环境变量AFL_INST_RATIO,默认的插入比率为100。随后获取共享内存的指针和上一次插桩的ID。

接着循环遍历模块中的每一个函数,对函数中的每一个基本块(BB)进行如下操作:

  1. 在每一个BB的开头获取插入点
  2. 生成一个随机值,标识当前位置
  3. 然后获取上一个插桩id,共享内存,将上一个插桩id与当前的异或
  4. 然后再共享内存的对应地方+1
  5. 最后将现在的id右移一位并且设置为pre_loc

本质上就是和之前的add_instrumentation函数中插入的代码差不多。

2.3 afl-llvm-rt.o.c

2.3.1 简介

LLVM的插桩引导代码。重写afl-as.h中的main_payload部分。

2.3.2 全局变量

u8 __afl_area_initial[MAP_SIZE];
u8* __afl_area_ptr = __afl_area_initial; // 共享内存区域

__thread u32 __afl_prev_loc; // 上一个插桩ID
static u8 is_persistent; // 是否持续模式

2.3.3 函数

1.__afl_map_shm

该函数的目的是设置共享内存,首先获取环境变量__AFL_SHM_ID。然后将该id的共享内存附加进当前进程,并且返回给__afl_area_ptr。最后将共享内存区域第一个字节设置为1,防止插桩比率过低的时候,父进程忽略当前进程。

2.__afl_start_forkserver

该函数是fork server的逻辑。

首先向FORKSRV_FD + 1写入四字节的tmp数据,如果写入字节不等于4直接返回。告知fuzzer已准备完成。

随后进入一个循环首先从FORKSRV_FD读取4字节数据到was_killed。如果在持久模式下停止子进程,但存在条件竞争并且afl-fuzz已经发出了SIGKILL信号,那么就注销就进程,如果在持久模式下子进程只是暂停了就发送SIGCONT信号重启进程。

具体表现为如果child_stopped=1且was_killed=1(子进程被杀死)就会等待子进程结束,然后重新fork。否则重启子进程。

static void __afl_start_forkserver(void) {

static u8 tmp[4];
s32 child_pid;

u8 child_stopped = 0;

/* Phone home and tell the parent that we're OK. If parent isn't there,
assume we're not running in forkserver mode and just execute program. */

if (write(FORKSRV_FD + 1, tmp, 4) != 4) return;

while (1) {

u32 was_killed;
int status;

/* Wait for parent by reading from the pipe. Abort if read fails. */

if (read(FORKSRV_FD, &was_killed, 4) != 4) _exit(1);

/* If we stopped the child in persistent mode, but there was a race
condition and afl-fuzz already issued SIGKILL, write off the old
process. */

if (child_stopped && was_killed) {
child_stopped = 0;
if (waitpid(child_pid, &status, 0) < 0) _exit(1);
}

if (!child_stopped) {

/* Once woken up, create a clone of our process. */

child_pid = fork();
if (child_pid < 0) _exit(1);

/* In child process: close fds, resume execution. */

if (!child_pid) {

close(FORKSRV_FD);
close(FORKSRV_FD + 1);
return;

}

} else {

/* Special handling for persistent mode: if the child is alive but
currently stopped, simply restart it with SIGCONT. */

kill(child_pid, SIGCONT);
child_stopped = 0;

}

/* In parent process: write PID to pipe, then wait for child. */

if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) _exit(1);

if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0)
_exit(1);

/* In persistent mode, the child stops itself with SIGSTOP to indicate
a successful run. In this case, we want to wake it up without forking
again. */

if (WIFSTOPPED(status)) child_stopped = 1;

/* Relay wait status to pipe, then loop back. */

if (write(FORKSRV_FD + 1, &status, 4) != 4) _exit(1);

}

}

然后forkserver向状态管道 FORKSRV_FD + 1 写入子进程的pid(告知子进程已启动),然后等待子进程结束(持久模式下子进程暂停也会立即返回)。如果子进程的返回状态时暂停就将child_stopped设置为1,随后将子进程的状态写入 FORKSRV_FD + 1 。

3.__afl_persistent_loop

该函数是持久模式的处理。

首先如果是第一次循环,清空共享内存空间,并将共享内存空间的第一个值设置为1,然后将__afl_pre_loc设置为0。cycle_cnt为max_cnt,由宏控制。然后返回1。

如果不是第一次循环则raise(SIGSTOP)停止当前进程,以便在下一次迭代开始时重新设置状态,并将共享内存空间的第一个值设置为1,然后将__afl_pre_loc设置为0。

当循环达到最大迭代次数后,会将覆盖率地图切换回初始状态以退出循环。

4.__afl_manual_init

该函数是初始化afl的一些内容,首先附加共享内存到该进程,然后启动fork server。

5.__afl_auto_init

/* Proper initialization routine. */

__attribute__((constructor(CONST_PRIO))) void __afl_auto_init(void) {

is_persistent = !!getenv(PERSIST_ENV_VAR);

if (getenv(DEFER_ENV_VAR)) return;

__afl_manual_init();

}

该函数使用了 __attribute__((constructor(CONST_PRIO))) 注释,表示它将在程序启动时自动执行。

首先获取环境变量__AFL_PERSISTENT。

然后获取环境变量__AFL_DEFER_FORKSRV的值,如果有,那么代表会延迟forkserver的初始化,直接return,如果没有那么就执行初始化。

6.__sanitizer_cov_trace_pc_guard

/* The following stuff deals with supporting -fsanitize-coverage=trace-pc-guard.
It remains non-operational in the traditional, plugin-backed LLVM mode.
For more info about 'trace-pc-guard', see README.llvm.

The first function (__sanitizer_cov_trace_pc_guard) is called back on every
edge (as opposed to every basic block). */

void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {
__afl_area_ptr[*guard]++;
}

该函数是为了支持-fsanitize-coverage=trace-pc-guard,目的是让传入的参数所对应共享内存的地址处的值+1。

7.__sanitizer_cov_trace_pc_guard_init

该函数也是和trace-pc-guard mode有关,对于该模式并不特别了解,所以暂且放置。

标签: FUZZ 代码阅读
最后更新:2023年10月7日

hu1y40

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
文章目录
  • 0x00 前言
  • 0x01 LLVM
    • 1.1 起源
  • 0x02 文件分析
    • 2.1 afl-clang-fast.c
      • 2.1.1 简介
      • 2.1.2 函数
    • 2.2 afl-llvm-pass.so.cc
      • 2.2.1 简介
      • 2.2.2 前置知识
      • 2.2.3 函数
    • 2.3 afl-llvm-rt.o.c
      • 2.3.1 简介
      • 2.3.2 全局变量
      • 2.3.3 函数

分类目录

  • 0day安全
  • Bypass
  • C++Prime
  • CTF
  • DoS
  • DoS
  • FUZZ
  • iot
  • JSONP
  • MISC
  • MISC
  • PHP伪协议
  • Python
  • REVERSE
  • sqli-labs
  • SQL注入
  • Trick
  • UAF
  • WEB
  • WEB
  • XXE
  • 书籍阅读
  • 二进制
  • 代码阅读
  • 信息搜集
  • 信息泄露
  • 加密与解密
  • 双重释放漏洞
  • 反序列化
  • 命令执行
  • 命令执行
  • 堆溢出
  • 密码学
  • 弱加密
  • 提权漏洞
  • 整数溢出
  • 文件上传
  • 未分类
  • 栈溢出
  • 格式化字符串漏洞
  • 模型
  • 汇编语言
  • 渗透测试
  • 漏洞分析
  • 漏洞利用
  • 漏洞战争
  • 漏洞挖掘
  • 病毒分析
  • 越界读取
  • 路径遍历
  • 逻辑漏洞
  • 配置不当
  • 钓鱼
  • 靶场
最新 热点 随机
最新 热点 随机
加密算法 2023年度总结 RTSPServer StackOverflow Vulnerability FUZZ 总览篇 MP4Box 无限循环漏洞 CVE-2023-40477 Winrar RCE漏洞分析
nmap流量特征及其用法详细 CVE-2023-32784 KeePass主密码泄露漏洞分析 加密与解密 第4章实验 CVE-2013-1347 Microsoft IE CGenericElement UAF漏洞 0day安全 第10章实验 CVE-2022-4565 Hutool资源消耗漏洞

COPYRIGHT © 2023 hu1y40's blog. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

鄂ICP备2021009673号-1