atheris从安装到fuzz输入输出解读
1. 引入
模糊测试是一种自动化的软件测试技术,它通过自动生成大量随机数据作为输入来测试程序,以发现潜在的错误、漏洞或崩溃。atheris是一个专门用于CPython(Python的C语言实现)的模糊测试框架。
2. 安装atheris
参考1,笔者在Ubuntu 22.04.3 LTS下( gcc version 11.4.0,cmake version 3.22.1,不需要clang也不需要llvm ),直接用pip install atheris
就能安装好atheris。但在centos7下,gcc版本较低的情况下,安装报错较多且难以正确安装。
3. fuzz过程
- 假设需要对如下example_library.py中的代码进行fuzz
def CodeBeingFuzzed(number):
"""Raises an exception if number is 17."""
if number == 17:
raise RuntimeError('Number was seventeen!')
- fuzz驱动例子(fuzzing_example.py)如下
import atheris
import sys
# This tells Atheris to instrument all functions in the `struct` and
# `example_library` modules.
with atheris.instrument_imports():
import struct
import example_library
@atheris.instrument_func # Instrument the TestOneInput function itself
def TestOneInput(data):
if len(data) != 4:
return # Input must be 4 byte integer.
number, = struct.unpack('<I', data)
example_library.CodeBeingFuzzed(number)
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
这里有几个重点:
(1)使用atheris.instrument_imports()上下文管理器:
- 这个上下文管理器用于告诉Atheris框架,它应该为在上下文内导入的模块中的所有函数添加模糊测试的支持(即“instrument”这些函数)。
- 在这个例子中,struct和example_library模块被导入,并且它们的函数会被Atheris框架进行模糊测试时的跟踪和修改。
(2)定义TestOneInput函数:
这个函数是模糊测试的核心,每个测试用例的输入数据都会传递给这个函数。
- 使用@atheris.instrument_func装饰器,这个函数本身也被Atheris框架“instrument”了,意味着Atheris可以跟踪函数内部发生的各种事件,比如异常和返回值的改变。
- 函数内部首先检查输入数据的长度是否为4字节,如果不是,则直接返回。这是因为后面的struct.unpack调用期望的是一个4字节的整数。
- 使用struct.unpack(‘<I’, data)从输入数据中解包出一个无符号整数(<I表示小端字节序的4字节无符号整数)。
然后,这个整数被传递给example_library.CodeBeingFuzzed函数进行实际的模糊测试。
- 直接运行fuzz驱动进行测试
python fuzzing_example.py
- 命令行输出
(env_aa_atheris_py311) bbb@ubuntu:/data/ccc/atheris/example_fuzzers$ python fuzzing_example.py
INFO: Instrumenting struct
INFO: Instrumenting example_library
INFO: Using built-in libfuzzer
WARNING: Failed to find function "__sanitizer_acquire_crash_state".
WARNING: Failed to find function "__sanitizer_print_stack_trace".
WARNING: Failed to find function "__sanitizer_set_death_callback".
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 4169936481
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 38Mb
#21 NEW cov: 5 ft: 5 corp: 2/5b lim: 4 exec/s: 0 rss: 38Mb L: 4/4 MS: 4 CopyPart-CopyPart-ShuffleBytes-CopyPart-
=== Uncaught Python exception: ===
RuntimeError: Number was seventeen!
Traceback (most recent call last):
File "/data/abcd/atheris/example_fuzzers/fuzzing_example.py", line 45, in TestOneInput
example_library.CodeBeingFuzzed(number)
File "/data/abcd/atheris/example_fuzzers/example_library.py", line 19, in CodeBeingFuzzed
raise RuntimeError('Number was seventeen!')
RuntimeError: Number was seventeen!
==1066609== ERROR: libFuzzer: fuzz target exited
SUMMARY: libFuzzer: fuzz target exited
MS: 2 CMP-ChangeBit- DE: "\001\000\000\000"-; base unit: 3f3d2d8955322f325af6db2238355fa07007ebd9
0x11,0x0,0x0,0x0,
\021\000\000\000
artifact_prefix='./'; Test unit written to ./crash-498bcbf6cbffcc8dd2623f388d81f44cfad1014d
Base64: EQAAAA==
(env_aa_atheris_py311) bbb@ubuntu:/data/ccc/atheris/example_fuzzers$
异常之前的输出解读:
(1)INFO: Instrumenting struct
和 INFO: Instrumenting example_library
:
这两条信息表明 Atheris 正在对特定的结构体和库(example_library)进行代码插桩(Instrumentation),这是模糊测试的一个步骤,用于收集代码覆盖率等信息。
(2)INFO: Using built-in libfuzzer
:
表示 Atheris 使用内置的 libFuzzer 引擎来执行模糊测试。libFuzzer 是一个由 LLVM 提供的库,用于自动化模糊测试。
(3)WARNING: Failed to find function "__sanitizer_acquire_crash_state", "__sanitizer_print_stack_trace", "__sanitizer_set_death_callback"
:
这些警告表明 Atheris 或 libFuzzer 在尝试使用某些与 AddressSanitizer(ASan)相关的函数时失败了。这可能是因为当前的测试环境或配置不支持这些功能。
(4)INFO: Running with entropic power schedule (0xFF, 100).
:
这表明模糊测试正在使用一种特定的能量调度策略(Entropic Power Schedule)。“entropic power schedule” 是一种基于熵理论的调度策略,主要用于优化模糊测试(fuzzing)中的能量分配。具体来说,这种策略通过为那些能够最大化信息的种子分配更多的能量来提高模糊测试的效率。这种方法被称为 Entropic,并已被集成到流行的灰盒模糊测试工具 LibFuzzer 中。
(5)INFO: Seed: 4169936481
:
显示当前模糊测试的随机种子值。种子值用于确保测试的可重复性。
(6)INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
:
表示没有指定最大输入长度,因此 libFuzzer 将不会生成大于 4096 字节的输入。
(7)INFO: A corpus is not provided, starting from an empty corpus
:
表明没有提供初始测试用例集(corpus),因此模糊测试将从空白的测试用例集开始。
(8)#2 INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 38Mb 和 #21 NEW cov: 5 ft: 5 corp: 2/5b lim: 4 exec/s: 0 rss: 38Mb L: 4/4 MS: 4 CopyPart-CopyPart-ShuffleBytes-CopyPart-
:
这些是模糊测试过程中的状态更新。#2 和 #21 表示测试的不同阶段或迭代。cov 表示代码覆盖率,ft 表示特征数(可能指特定的测试路径或条件),corp 表示测试用例集的大小和当前状态,exec/s 表示每秒执行的测试数,rss 表示使用的常驻集大小(内存使用量),L 表示当前测试用例的长度,MS 表示生成当前测试用例所使用的变异策略(Mutation Strategy)。
Uncaught Python exception
:
表示触发了一个异常。
异常之后的输出解读:
(1)==1066609== ERROR: libFuzzer: fuzz target exited
:
这条错误信息表明模糊测试的目标程序(fuzz target)异常退出了
(2)SUMMARY: libFuzzer: fuzz target exited
:
这是对上一条错误信息的总结,再次强调模糊测试目标程序已经退出。
(3)MS: 2 CMP-ChangeBit-
:
这表示在生成导致程序退出的测试用例时,使用了两个变异策略(Mutation Strategies):CMP(可能是指比较操作的某种变异)和ChangeBit(改变位)。
(4)DE: "\001\000\000\000"-; base unit: 3f3d2d8955322f325af6db2238355fa07007ebd9
:
DE 表示数据变异(Data Mutation)的结果。这里显示了一个具体的变异数据(\001\000\000\000),以及一个基础单元(base unit),这个基础单元可能是指变异操作之前的数据或是一个特定的种子值。
(5)0x11,0x0,0x0,0x0,
:
0x11正好是十进制数的17,这个数据能对要fuzz的函数CodeBeingFuzzed产生异常。
(6)\021\000\000\000
:
这是另一个变异后的数据示例,表明在模糊测试过程中,数据被进一步修改以尝试触发不同的程序行为。
(7)artifact_prefix='./'; Test unit written to ./crash-498bcbf6cbffcc8dd2623f388d81f44cfad1014d
:
这条信息表明,当模糊测试目标程序退出时,libFuzzer 自动将触发退出的测试用例保存到了一个文件中。artifact_prefix=‘./’ 表示保存的文件位于当前目录下,文件名是 crash-498bcbf6cbffcc8dd2623f388d81f44cfad1014d。这个文件包含了导致程序退出的具体输入数据。
(8)Base64: EQAAAA==
:
这是触发退出的测试用例的 Base64 编码表示。Base64 是一种编码方案,用于将二进制数据转换为 ASCII 字符串。这个编码可以用于在文本环境中传输二进制数据,或者在不支持二进制数据的系统中存储二进制数据。
- 输出crash文件
$ hexdump crash-498bcbf6cbffcc8dd2623f388d81f44cfad1014d
0000000 0011 0000
0000004
从上面输出数据的分析可以看到,0x11,0x0,0x0,0x0,
,0x11正好是十进制数的17,这个数据能对要fuzz的函数CodeBeingFuzzed产生异常。
4. 总结
本文给出了atheris从安装到fuzz输入输出解读的过程,例子来源参考1。
参考
- https://github.com/google/atheris/tree/master
- https://yiyan.baidu.com/