论坛首页 安全工具分享区 阅读主题

[原创]AFL源码分析(1)

275 浏览 0 回复
#1 楼主 2026-06-01 21:09:06
basic block(基本块)、edge(边)、代码覆盖率edge就被用来表示在基本块之间的跳转,知道了每个基本块和跳转的执行次数,就可以知道程序中的每个语句和分支的执行次数,从而获得比记录BB更细粒度的覆盖率信息代码覆盖率是一种度量代码的覆盖程度的方式,也就是指源代码中的某行代码是否已执行;对二进制程序,还可将此概念理解为汇编代码中的某条指令是否已执行。覆盖率劫持:劫持汇编器(afl-gcc、afl-clang、afl-g++),通过识别跳转指令,然后在其中插入一段汇编用于与AFL之间的通信(__afl_maybe_log)clang内置,LLVM 内置了一个简单的代码覆盖率检测 (SanitizerCoverage),它在函数、基本块和边缘级别插入对用户定义函数的调用。提供了这些回调的默认实现,并实现了简单的覆盖率报告和可视化编译器将在 -fsanitize-coverage=trace-pc-guard 每个边缘插入以下代码:每条边都有自己的 guard_variable,每当对应的edge被执行到时,都会向共享内存中对list[*guard_variable]进行++操作这个函数会在可执行文件开始执行时会调用一次,每一个有向边对应一个*guard值,参数start是第一个guard指针,代表第一条边;stop是最后一个guard指针,代表最后一条边,遍历start到stop就可以给每个guard指针指向的值初始化一个随机数(但是实际上这个值可以由用户来决定)但是不可避免的,会有概率出现冲突的情况,因此Sakura师傅对其进行了替换:要想对每条edge进行插桩,需要先在make afl时传入AFL_TRACE_PC=1来定义DUSE_TRACE_PC宏(make AFL_TRACE_PC=1),然后在执行afl-clang-fast的时候传入-fsanitize-coverage=trace-pc-guard参数即可。正常make会启用afl-llvm-pass而非afl-llvm-rtGCOV、LCOV覆盖率记录的实现除了前两种方式之外还可以使用GCOV、LCOV这两个东西可视化展示代码覆盖率,但是不能使用到fuzzer上。一些fuzz的覆盖率反馈和引导变异的一些概念:fuzz有生成式fuzz和变异式fuzz两种,生成式fuzz就是一开始什么种子也没有,纯根据fuzzer的逻辑来生成种子(输入),fuzz过程中没有任何的反馈,也不会根据某时某刻的代码覆盖率来把interesting case变异发现新的路径。 AFL采用的是变异式fuzz进行fuzz时,对样本进行变异,然后在找到新的路径时,将当前变异后数据当成一个新的样本继续变异。经过逐代的变异杂交选优,直到达到一个局部最优解为止(已发现的路径达到max),这就是所谓的**遗传算法**。因为遗传算法的特征总是来自于初始种子样本和变异策略,所以改进也主要在这两方面。种子也可以是一些代码语句:如果变异器不会引入新的token(特征),那永远就只会生成select这一种特征,而不会生成如SQL语句中的insert、delete等。所以种子的变异策略也十分的重要,它极大的影响着fuzz的代码覆盖率。除了对种子的不断变异,我们还需要了解AFL如何将自动或半自动生成的随机数据输入到被fuzzer的程序中并监视如崩溃,断言(assertion)等程序异常这个过程,也就是进行输入和捕获crash。AFL采用的是fork server这种机制,具体方式如下:进程关系:fuzzer进程 -> fork server -> 被fuzz的程序常用的AFL魔改技巧有哪些?write_to_testcase函数会将变异生成的新样本写入到.cur_input隐藏文件夹中,所以我们在文件写入之前可以在外面套上一层函数以便让AFL的原始字节码的变异适用于更多的场景。afl-fuzz适合基于字节粒度变异的fuzz(如fuzz一个简单的输入输出程序),但并不是所有目标都可以直接进行字节粒度的fuzz。有些是因为文件解析部分的代码不能单独抽出来,有些是因为文件解析仅仅是逻辑的开始。那么为变异器加层就是在这方面扩展afl-fuzz的最简单方法,最经典的例子如webassembly。为了fuzz这些结构化的东西(类似SQL)我们需要进行结构感知(structure-aware),即针对特定输入类型的语法进行感知如何使用AFL fuzz client-server模式的程序,或者如何使用AFL去fuzz网络协议呢?lient-server模式中的client -> server大致流程如下:这篇文章值得参考:008K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2X3j5i4y4@1L8s2W2Q4x3X3g2U0L8$3#2Q4x3V1k6T1L8r3!0Y4i4K6u0r3K9r3!0%4i4K6u0V1k6Y4g2*7P5W2)9J5k6s2y4W2M7Y4k6W2M7W2)9J5k6r3q4E0k6i4u0A6j5$3q4F1i4K6u0V1k6Y4g2*7P5Y4W2Q4x3X3c8D9L8%4l9`.文章中提到一个为Persistent mode(持久模式)的概念,如果要向AFL集成这种功能,我们可以按照如下流程编写自己的代码:该方法需要用afl直接启动server程序,patch server程序接收请求包的代码,改为直接从标准输入里读取,server执行足够多次请求后退出,AFL再启动一个新的server再次fuzz。fuzz要真正解决的问题和一些Sakura师傅的建议Address Sanitizer又名ASan,是一个快速的C/C++内存错误检查器。它可以检测到:UAF、Heap buffer overflow、Stack buffer overflow、Global buffer overflow、Use after return、Use after scpoe、Initialization order bugs、Memory leak等漏洞类型。asan可以简单理解成对malloc和free以及存取指令等的hook,从而在发现分配出来的内存的大小,小于要存取的index的大小时,检测出越界读写问题。要理解被测试程序的代码,并不是说随便拿到一个程序拿fuzz跑起来是有意义的,例如传入数据的parser(解析器、语法分析器),必要时通过逆向来单独取出一部分代码来测试功能。比如Windows的media player,这是一个图形化的应用程序,我们不能通过命令行窗口来对该程序进行流程的控制与输入,但是这些功能的实现肯定都是存在于某一个dll(动态链接库)中的,所以可以利用dlopen函数写harness来加载dll:harness作用:如果我们想fuzz dll (加载的库)中的函数输入,因为没有定义入口点,我们需要编写测试工具来将输入从命令行传递到 DLL 函数。善于根据场景改进fuzz,以将只用与文件格式fuzz的AFL利用加层或映射让其扩展到更多的场景中,实现自己的自定义编译。根据不同的目标

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-288070.htm

暂无回复,快来抢沙发吧!

请登录后参与讨论

立即登录 注册账号