欢迎来到编写fuzzer系列的第三部分,我收到了一些读者的来信,大家对模糊测试技术非常感兴趣并且想要了解更多的技术细节,在这里我非常感谢大家的支持与鼓励。
在本篇中我将致力于优化我在上一篇中写好的fuzzer,为了能让更多的人更好的理解文章中的内容,我将接下来的内容分为两篇,在这篇文章中我们将讨论覆盖率的跟踪与实现,下一篇文章中我们将讨论这些优化如何指导fuzzer并使其有更好的返回结果。
让我们先从一些理论基础开始讲起:“覆盖率引导fuzzing(greybox fuzzing),使用程序检测跟踪已经喂给fuzz目标的样本数据并检测变异后的样本的覆盖率,fuzzing引擎使用检测跟踪后的信息来判断那种变异策略可以使覆盖率最大化”正如链接文档解释的那样,这种技术适用于对非结构化数据具有适当容忍度的确定性目标,比如第一篇的jpeg解析器;但是要注意,像编程语言这样的高度结构化输入,随机变异很可能无法生成有效的样本,因此fuzzer的运行深度会受到影响。可惜的是有关于覆盖率跟踪这样庞大的实验,我懂得并不多,正因如此才有必要学习并亲自编写fuzzer;最后,关于这次的代码可以再GitHub上找到。
为了能让大多数人都能理解文章中的内容,我使用了Python,但是因为Python执行效率的原因,最后我将放弃Python并用rust重写所有内容。我准备写一些简单的代码来了解我的fuzzer每秒能执行多少次迭代并从中计算出fuzzer的性能,但当我看到中间的某个地方有一个自定义的线程类,并在固定的时间间隔生成其他对象,这意味着“简单”的代码变成了“如何在线程之间传值”这种复杂的问题,于是我果断使用了另一种方法来实现性能的计算:
我使用运行的迭代书除以运行时间这种简单的方法来计算性能,目前这已经够用了,在下一阶段我将以不同的方式实现它,特别是能进行连续的模糊测试时。
如果你真的对如何配置程序感兴趣(改变性能),有两篇Python模块的文章可以帮到你——profile和memory_profiler。
我有一些关于测试程序执行时所花费的成本的想法,我之前略过了一个重要的概念——覆盖率粒度,我们可以跟踪已执行的函数、块甚至指令,目前我的fuzzer的粒度保持在函数级,但我决定挑战将粒度上升到块级。我依然要使用ptrace来达到目的,我最初的想法是在代码执行过程中检查指令,这样不论程序做了什么操作、调用了哪些函数我都能准确的了解,但是这个方法有严重的缺陷,没办法了解覆盖了多少程序(覆盖率无法计算),另一个就是因为每条指令都要检查,所以执行速度从原来的300exec/sec降到了1exec/sec。显然这种方法行不通。AFL的做法是函数插桩,但是我并不想直接深入编译器内部,所以我开始考虑ptrace
我们的fuzzer已经能够处理信号了,再添加一些代码来处理SIGTRAP也没什么问题,所以我决定在每个函数开始处设置断点,但是理想很丰满,现实很骨感
生成函数列表
第一个问题就是在哪里下断点,这只需要反汇编程序,找到以标识的函数起始地址就可,我写了个binary ninja的自动下断脚本:
你可能很好奇为什么不在fuzzer运行时下断,在无头模式(”headless mode”译者没有使用过binary ninja,不知道这个模式的正确名称。。。)下工作是需要付费的,并不是每个读者都有这个付费能力,考虑到新人的感受,我设计了一种工具可以直接输出一个地址列表供fuzzer使用,你可以使用objdump或者awk/sed生成地址列表,喜欢挑(作)战(死)的,也可以使用Radare2。
关于脚本,有两点需要解释一下,计算下断位置的地址,首先我们需要得到.text段(代码所在位置)的偏移,在二进制文件运行时.text段会被加载到一个虚拟地址中,所以不要直接把地址写死,但是如果将偏移放到正确的内存区域的开始位置时,就能在函数开始处直接下断。
之前我们提到了跟踪所有函数,但是我们并不需要跟踪libc的函数以及一些设置函数,下面是要跳过的函数列表:
事实上我是看到了一些同事的文章中提到了函数筛选,所以我才做的,这里需要跳过多少函数要取决于你的目标是什么。
Fuzzer加载了断点列表后可以实现断点插入和信号处理了,这是完整的函数:
关于这个函数我们只讨论重要的部分,要想在函数下断,我们需要知道代码段的基地址,可以通过执行下面的get_base()函数(CV大法好)获得:
[招生]系统0day安全班,企业级设备固件漏洞挖掘,Linux平台漏洞挖掘!