- 在 *vte/fuzz 目录下, 配置 config.json.
- 将 radare2/bin 路径添加到系统 PATH
- 用 VTest 打开项目 .vte, 在项目设计页面中:
- 点击 CmdSys, 关闭与 DTP 连接. 目前版本无需 DTP.
- 将 fuzz_dongle.dll 添加到工程中
- 将 fuzz_dongle.dll 和 CmdSys 芯片 RECV_CMD 虚拟端口连接. 用于转发前端测试用例.
- 将 fuzz_dongle.dll 和 BM3803 芯片 IRQ GPIO (如果有) 连接. 用于主动触发中断.
运行时所有信息都放在 *vte/fuzz 目录下.
- VTest 版本至少高于 2.80
- VTest 只支持 DWARF2 及之前版本的 ELF 调试信息
- 在没有 VS2022 环境的机器上 (比如 Win10 早期版本), 需要安装 VC++ Redistributable 2022 来补充依赖的新版标准库.
- 不建议路径有中文, 配置文件中的中文请使用 utf-8 编码
- dongle.dll 或项目配置有更新时, 请重启整个 VTest. VTest 关闭项目不保证刷新 DLL.
- 250911 版本似乎偶尔有线程泄漏? 关闭 VTest 后也无法清除.
- 项目有更新时, 可以删除 fuzz/ 中除
config.json外所有的状态文件, 来刷新整个缓存.
config.json配置文件, 见下.analz.json存放静态分析数据, 由static_analz.h模块自动生成.stats/状态数据文件func_cov.json函数覆盖率等N.json总运行计次
logs/运行时日志, 按 运行计次 + 时间戳 命名bugs/漏洞数据, 按 运行计次 + 时间戳 命名corpus/用于存放测试用例
需要在 *.vte/fuzz 目录下提供 config.json, 用于配置参数. 其中包含如下信息:
{
"log_level":"debug", // 日志级别 (debug, info, warn, error), 推荐设置为 info.
"proj_name": "xxx", // 项目名
"vtestc_path": "xxxxx/vtestc.exe", // 命令行 vtestc 启动路径 (可选). 路径要用 \\ 或 /
"vte_path": "path/to/PRJ.vte", // vte 路径. (可选). 其实就是 proj_dir
"target_path": "/path/to/xxx.elf", // elf 执行文件的路径. 支持相对 proj_dir 的相对路径.
"socket": "tcp://localhost:5555", // 前端 ZeroMQ 通信绑定到的地址. 一般默认值即可.
"socket_enable": false,
// "entry_addr": "0x0000", // 程序入口, 也是向量表入口. 固定式 0x0000, 不再需要
"trap_table_base": "0x0000", // Trap Table 入口地址. 这个不一定是 0x0000, 可用 TBR 修改.
"init_sp": "0x5fffffff", // 初始栈指针值
"irqs": [1, 2, ...], // 使用到的中断, 从 1~15 (对应 TBR TT 0x11~0x1f). 不要有零.
"memory_map": [ // 不再此处声明的内存段, 不会插入内存 HooK
{
"name": "ram", // 内存段名称, 不要超过 20 字符
"base_addr": "0x40000000", // 内存基地址
"size": "0x1ffff", // 字节大小
"flags": "rw-" // 权限
}
],
"sanitizers": { // 检查器开关
"segv_check_enable": true,
"trap_check_enable": true,
"shadow_mem_enable": true,
"shadow_reg_enable": true, // 要使用影子寄存器, 需同时开启 REG_HOOK 宏
"int_overflow_check_enable": true,
"atomv_check_enable": true
}
}规定 config.json 必须是 utf-8 字符编码.
fuzz_analz.exe 是一个命令行工具, 用于静态分析目标文件, 生成数据存储在 analz.json 中.
analz.json 类似数据库文件, 用于存储 fuzz_dongle.dll 需要的分析数据
需要确保 radare2 存在于系统 PATH 中.
fuzz_analz.exe 运行配置项如下:
--tartget: 必须项, 目标文件路径--entry_addr: 程序入口地址. (可选, 裸机二进制需要配置, ELF 一般不用)--load_addr: 程序加载地址. (可选, 同上)--arch: ISA 架构. 目前仅支持 sparc, 默认 sparc.--bits: 硬件字长. 默认 32 (sparc v8)
执行结束后, 执行目录下会出现 analz.json 文件. 手动拷贝至 VTest 项目路径下的 /fuzz 文件夹即可.
fuzz_dongle.dll 和内置了 fuzz_analz.exe 的功能, 但仅支持 SPARC v8.
fuzz_analz.exe 实际的加载命令:
r2 -a arm -b 16 -m 0x08000000r2 -a sparc -b 32 -m 0x400000 -e cfg.bigendian=true
- frontend: 模糊测试工具. 负责迭代和生成协议测试用例.
- vtestc.exe: VTest 虚拟机的命令行版本
- bm3803.dll: 定制化的虚拟 CPU. 支持主中断触发, 等.
- cmdsys: 被测项目发令器.
- fuzz_dongle.dll: 缺陷检查器, 程序状态监控器, 测试用例转发.
流程如下:
- frontend 通过 socket 连接到 fuzz_dongle, 向其发送控制命令.
- frontend 生成一个测试用例 tc, 发送给 fuzz_dongle
- fuzz_dongle 通过虚端口连接到 CmdSys, 将测试用例 tc 转发给 CmdSys
- CmdSys 由被测项目本身配置, 负责将 tc 通过某种串口传递给 CPU, 触发程序.
- 一段时间后, frontend 要求 fuzz_dongle 收集覆盖等信息
所以 虚拟机相关 API 都封装在 vtest/vtest.h 下, 相关概念定义在 vtest/concepts.h 中.
通过 vtest/plugin_registry.h 定义的反向绑定接口 REGISTER_PLUGIN(xxx, VTest) 来将自己注册为虚拟机插件.
没有前端发令时, Dongle 也被设计为能够独立工作, 执行缺陷检查功能.
检查内存访问异常. 包括:
- 内存节错误, 访问了 bss/data/stack 等内存间隙
- 内存段错误, 访问了 rom/ram/falsh 外的未分配内存. 此部分需要手动 config.json 配置段定义.
- 违反了内存 rwx 属性
检查局部变量的未初始化访问.
例外情况:
- 局部变量未声明
volatile, 导致局部变量被优化为寄存器. - 全局变量未初始化. 一般在 startup.S 中清空 .bss/.data, 不能分辨是否为确切错误.
检查整数除零和浮点数除零. 原理是监控 Trap Table 的除零异常和浮点数异常, 须要在 config.json 中准确
定义 trap_table_base.
检查中断导致的原子性违反. 误报较多, 实验性.
检查数据溢出错误. 原理是监控硬件汇编指令, 包括以下种类:
- SLL: 左移指令. 超出 32b 即判错.
- ADD: 加法指令. 由于没办法区分寄存器内数据的实际语义, 包括 位宽 (32b,16b,8b) 和 符号 (signed, unsigned), 所以我们只视为一种情况: 32b 位宽的有符号加法.
- SUB: 减法指令, 我们将
sub a, b视为add a, -b. - ADDX: 用于 64b 数据加法, 视为 64b 有符号加法.
- SUBX: ...
例外情况:
- 无符号 32b/64b 的加减法溢出, 会误报
- 小于 32b 位宽的加减法, 会漏报
- ADDCC / ADDCCX 指令, 因为通常和条件跳转有关, 不检测. 我们只关心算术.
在 VTest 之外的用户作为请求者, 向 VTest 中 dongle.dll 插件发起请求, 并得到回应. dongle.dll 中可能有多个 VTest 插件, 他们复用了同一个 Socket, 通过过滤 Msg::EventT 实现事件分发.
基于 ZMQ.Pair 模式, 但是仅实现了单工单向请求. 即, 前端 fuzzingloop 向其他各个组件发起一个 Request, 各个组件则回应一个 Response. 和每个组件都有独立的 Socket 连接.
{
"type" : "req" | "resp",
"id" : "uuid", // 用于匹配响应
"event" : "hello" | "testcase" | ..., // 包实际类型
"body" : "..." // 实际载荷, 可能为空
}实际消息中, type, event 都是用枚举整数值实现的, 详见 ./socket.h 定义.
| type | event | 方向 | 数据含义 | 数据 |
|---|---|---|---|---|
| req | testcase | Frontend --> Dongle | 单次测试用例数据 | base64 编码的二进制数据 |
| resp | 测试用例派发结果 | "ok"/"error" | ||
| req | hello | Frontend --> Dongle | 心跳检测 | "yyy" |
| resp | "it's me, xxx" | |||
| req | bug | Dongle --> Frontend | 询问上一轮是否有漏洞发现 | |
| resp | 漏洞类型 | "{bug_code}" | ||
| req | cov_edge | Frontend --> Dongle | 询问覆盖的基本块边总数 (含历史) | |
| resp | "133" | |||
| req | cov_func | Frontend --> Dongle | 询问上一轮覆盖的函数总数 | |
| resp | "111" | |||
| req | start_round | Frontend --> Dongle | 开启新一轮 fuzz | |
| resp | "ok"/"error" | |||
| req | finish_round | Frontend --> Dongle | 结束单轮 fuzzing, 保存状态 | |
| resp | "ok" |
对于 req/resp, msg.id 是一致的.
操控 Fuzz 声明周期时, 消息顺序是: hello --> start_round --> finish_round --> ... --> start_round --> finish_round
想要保存状态, 请使用 finish_round, 之后稍作等待后, 再杀死虚拟机进程. 类似地, 请使用 start_round 让各组件
正确完成状态转移, 否则组件可能认为自身处于非法状态而拒绝执行功能.
- Frontend: 消息中转, 模糊测试, Python.
- Dongle: 覆盖率检测, 缺陷监控, 转发测试用例, C++.
Frontend 负责发送 REQ, 阻塞等待 Dongle 响应 RESP. 不支持其他通信模式.
状态转移失败时, 返回 "error". 可能的问题如下:
- "run" 返回 "error": 发送的 round_num 无法解析为整型数字, 应该发送一个递增的整型数字
- "testcase" 返回 "error": Issuer 中外设未成功向芯片中写入数据
注意, 没有 bug 时返回的是代码 0. 详见 ./fuzz.h
enum BUG {
NONE = 0,
SECTION_ERROR,
MEMORY_UNINIT,
USE_AFTER_FREE,
STACK_UNDERFLOW,
DIV_BY_ZERO,
FDIV_BY_ZERO, // 浮点数除零
DATA_ACCESS_ERROR,
ADDR_NOT_ALIGNED,
WIN_UNDERFLOW,
WIN_OVERFLOW,
BUG_OUT_OF_BOUNDS,
ILL_INSN
};- 代码风格基于 Google C++ 风格, 但有出入:
- 变量 / 函数 / 方法 使用蟒蛇型命名
snake_case - 常量以
k_开头, 全局变量以g_开头. - 类, 结构体使用驼峰型命名:
CamelCase - 宏为全大写蟒蛇型命名
SNAKE_CASE - 行宽度为 100 字符, 中文应占两字符
- 变量 / 函数 / 方法 使用蟒蛇型命名
- 使用 PEP8 python 代码风格.
- 常用标准库和第三方库包含在
include/common.h中, 开发时可以使用该文件. - 引用本项目的内部头文件时, 需要用相对项目根的引用路径, 如
#include "vtest/vtest.h" - 源码文件必须使用 UTF-8 编码, 这是 SPDLOG 库的要求.
- 使用 C++20 标准库版本, 但禁用一些功能: modules + ranges. modules 支持不稳定; ranges 一些功能在 c++23 才完善, 因此改用第三方的 range-v3
- 注意, 尽量减少对 Windows API, Windows 平台宏和系统调用的依赖, 优先使用标准库 / 第三方库替代方案.
远程仓库必须为 Private 状态. 请提交 PR, 不要直接覆盖和修改我的代码.
由于 VTest 兼容性问题, 使用 x86-windows 平台, C++20 + vcpkg + CMake(>3.25) + MSVC2022 工具链.
外部依赖项: 通过 vcpkg 提供, x86-windows-static-md 平台
cppzmq:x86-windows-static-md catch2:x86-windows-static-md spdlog:x86-windows-static-md elfio:x86-windows-static-md capstone:x86-windows-static-md cereal:x86-windows-static-md cxxopts:x86-windows-static-md nlohmann-json:x86-windows-static-md zlib:x86-windows-static-md nng:x86-windows-static-md新版本实际不再需要的库:
- capstone: 源码已经合并到本仓库
- cppzmq: 提供了 nng/zmq 两种功能选择: Go 绑定默认 NNG, Python 则拿 ZeroMQ 实现.
- jsoncpp: 完全迁移到 nlohmann-json
- cereal: 序列化已经全部换为 nlohmann-json.
- range-v3: 改为标准库 c++20 ranges, 部分未支持函数自己实现. range-v3 IDE 总报错.
命令行程序依赖:
- radare2: 用于控制流分析和调试信息解析, 需要添加到 PATH 中.
- addr2line: 可选.
cmake -S . -B ./build --preset=xxxcmake --build ./build --preset=xxxx- 需要调试时, 进入
./build内点击xxx.sln进入 VS 界面, 配置启动项目及调试参数即可.
需要按需配置 CMakePresets.json. 目前提供两种基于 vcpkg (记得配置 VCPKG_ROOT)
- vs-x86-release
- vs-x86-debug
- 获取源码
- 获取并解压提供的 vcpkg 导出包, 并配置 VCPKG_ROOT
- 获取第三方工具 radare2, 将 /bin 目录添加到 PATH 中.
- 获取第三方工具 cmake (> 3.25)
- 轩宇 VTest2.80 及之后版本, 详见 vtest/sunrise/vttDevice.h 要求
- 项目使用 CMake + VS22 编译工具链, 请确保 VS22 中的 C++ CMake Tools for Windows 组件启用.
- CMakePresets.json 中, 提供了 Ninja 和 VS2022 两种编译链, 推荐使用 VS2022, Ninja 仅用于调试.
- VTest 要求 MainProc 符号, 我们定义在 vtest/vtest_mainproc.cc 中, 但是需要用户手动在 DLL 中
导出. 通过
/EXPORT参数.
.\build\tests\Debug\all_tests.exe --list-tests
.\build\tests\Debug\all_tests.exe -s