fuzz测试之libfuzzer使用小结
- 背景
- 基本原理
- 使用方法
- 主调DEMO
- 参考资料
背景
项目中,为测试算法的鲁棒性,经常会用到fuzz测试进行压力测试。fuzz测试是一种模糊测试方法,本质是通过灌入各种变异的随机数据,去遍历不同函数分支,以暴露程序中可能出现的问题。
*
接下来,本文以安卓平台LLVM自带的libFuzzer工具使用为例,简单介绍其使用方法。
基本原理
在学习使用之前,先了解其基本运行原理。LLVM中首先调用一次初始化接口LLVMFuzzerInitialize
设定参数,然后通过反复调用接口LLVMFuzzerTestOneInput
,不断灌入不同长度的随机数据,直到程序达到最大运行限制,或中途找到bug才中断退出。
使用方法
所需环境
- 电脑硬件
- NDK编译软件,使用NDK-R20及以上的版本,其clang编译器自带地址消毒和fuzz测试模块
- adb软件,用于电脑与手机联调
- 安卓手机(可使用开发者人员选项)
所需文件
- CPP主调:
- xx_fuzz.cc
- 编译脚本:
- andriod.mk
- application.mk
- 待测试算法源码
- xx.c
基本流程
- 电脑用NDK编译源文件,生成可执行文件
- andriod.mk中添加
- 编译选项:
LOCAL_CFLAGS += -fsanitize=fuzzer,address -fomit-frame-pointer
- 链接选项:
LOCAL_LDFLAGS += -fsanitize=fuzzer,address
- address选项是打开asan地址消毒功能,可增强找bug能力
- 编译选项:
- application.mk中添加
- 标准库:
APP_STL := c++_shared
- 因为调度代码是用CPP写的,需用到STL库
- 标准库:
- 打开powershell,在mk文件所在目录下,通过
ndk-build
指令,编译可执行文件
- andriod.mk中添加
- 通过ADB软件push推到手机文件夹内
- 拷贝上一步骤生成的可执行文件和动态库文件(如:
libclang_rt.asan-aarch64-android.so
和libc++_shared.so
) - 将之推到手机内,如
/data/local/tmp/xx_fuzz/
- 指令举例:
adb push xx.so /data/local/tmp/xx_fuzz/
- 指令举例:
- 拷贝上一步骤生成的可执行文件和动态库文件(如:
- 通过ADB输入相关指令在手机上启动运行程序
- 电脑连接安卓手机,手机进入开发者模式
- 电脑命令窗,输入
adb shell
,进入手机环境 - 切到可执行文件对应目录,输入以下指令,运行程序
LD_LIBRARY_PATH=./ ./demo_exe -max_len=65535 -len_control=0 -artifact_prefix=./corpus/ -detect_leaks=0 ./corpus
- 解析报错日志
- 根据手机环境反馈的错误信息,如偏移地址0x226534
- 将ndk自带的llvm-symbolizer.exe所在目录添加到电脑环境变量的path中
- 命令窗输入以下指令,解析报错相关代码上下文,分析算法,修复bug
- 指令:
llvm-symbolizer -e=demo_exe 0x226534 --print-source-context-lines=3
- 模式:llvm-symbolizer -e=库名或EXE名 报错显示的偏移地址
- 指令:
- 重点步骤
- 打开fuzz测试的编译选项,并编译成功
- LLVMFuzzerInitialize/LLVMFuzzerTestOneInput主调代码编写
- 注意事项
- 主调demo中不用写main函数,main由libfuzzer框架提供
- c和cpp代码联合编译时,需用宏区分函数命名
具体MK文件编译脚本说明,见《NDK编译系列:构建C/CPP工程》。
主调DEMO
上面流程中用到的主调xx_fuzz.cc
里的两个接口函数,框架模板如下:
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#ifdef __cplusplus
extern "C" {
#endif
int LLVMFuzzerInitialize(int argc, char **argv)
{
// write your code
return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)
{
// write your code
return 0;
}
#ifdef __cplusplus
}
#endif
进一步的,具体使用可以参考:libfuzzer从入门到放弃。
参考资料
- llvm-libfuzzer官方介绍
- 入门libFuzzer——编译链接
- libFuzzer使用总结教程
- libFuzzer学习
- Android NDK Address Sanitizer地址消毒ASAN
- aac的fuzzer测试工程