Commit f367d764 by 文周繁

更新s2fuzzer说明文档

parent 6b019685
# S2fuzzer说明
# S2fuzzer说明
## 工具简介
`s2fuzzer`是一款针对网络协议程序的模糊测试工具,它基于`stateafl`模糊测试器,并在此基础上进行了创新性的扩展。工具通过引入共享内存来替换原始的网络I/O接口来传输网络协议数据包,实现执行速率的提升,从而发现更多潜在的安全漏洞;同时实现针对无源码网络协议二进制程序的插桩,从而对无源码网络协议二进制程序开展灰盒模糊测试。
## 技术介绍
### 1.共享内存优化
#### 1.1 定义
共享内存本质上就是内存中的一块区域,用于进程间通信使用。**共享内存允许两个或多个进程访问同一块物理内存区域**,从而使这些进程能够通过直接读取/写入该内存来交换数据,而无需通过内核在进程间复制数据。`s2fuzzer`通过hook原始的网络I/O接口函数,使用基于共享内存的数据交互传输函数来在客户端与服务端之间传输网络协议数据包,实现执行速率的提升。
#### 1.2 实现
##### 1.2.1 基于共享内存的高效传输技术
为了实现基于共享内存的消息传输,通过分析测试程序收集网络 I/O 接口相关的库函数,hook 相关的库函数干预客户端与服务器之间的消息传输。收集程序中的网络套接字接口相关的库函数如 `send/recv``sendto/recvfrom`,以及读 / 写文件描述符的函数如 `write/read`,因为套接字就是文件描述符,也可通过读 / 写文件描述符的函数来进行数据传输。hook 以上网络传输相关的库函数,来干预客户端与服务器之间的消息交互传输,并设计客户端与服务器之间基于共享内存的数据交互传输函数,来替换原始程序中相关的套接字传输的库函数。
![image-20250624093155032](./images/image-20250624093155032-1750818033311-1.png)
##### 1.2.2 基于共享内存的会话状态传输技术
在实际模糊测试时,由于模糊测试器并不知道 `SUT` 是否准备好发送或接收信息,网络协议模糊测试器如 `AFLNet``StateAFL``send()/recv()`之前使用 `poll()`和手动设置超时阈值来处理这个问题。但这种模式下,如果设置的超时时间过长会给模糊测试迭代带来了额外的时间延迟,如果设置得过短,则会导致有状态模糊测试器可能会与其状态机不同步。上述问题的根本原因是通信双方,即客户端和服务器端,无法预测对方下一步计划做什么。
为了解决这个问题,将会话状态引入到方案中,会话状态也存储在共享内存中。会话状态可以是 C 或 S,状态 C 表示客户端正在发送或接收消息,如果服务器即将发送或接收消息,则应该阻塞。类似地,状态 S 表示服务器正在发送或接收消息,如果客户端即将发送或接收消息,则也应该阻塞。一旦任何一方完成了当前的任务,或者无法继续运行,它应该更改会话状态,以通知另一方执行下一个任务。这个动作实际上就是状态迁移,整个交互过程中,用于同步的会话状态迁移也可以用状态转换图来表示,如下图:
![image-20250624093035961](./images/image-20250624093035961-1750759463479-2.png)
### 2.状态感知
#### 2.1 定义
`s2fuzzer`中,协议状态的定义与传统协议设计不同,`s2fuzzer`将跨越整个会话的长期存在的数据定义为协议状态(例如,客户端的当前认证状态),**通过监控程序内存中特定数据结构的演化过程,自动化推断协议状态**,而非依赖预设的状态机。其核心思想是:**协议状态的变化会反映在程序内存的结构化数据中**,通过跟踪这些关键数据的变化即可识别状态变迁。
#### 2.2 实现
`s2fuzzer` 通过在接收-处理-回复循环的每次迭代时检查进程内存的内容来推断当前的协议状态。当前的协议状态必然存储在数据结构中,例如堆和栈内存中,这些结构在每个请求-回复交换时更新。协议状态由**长期存在的数据**表示,其生命周期跨越单个请求-回复交换,并跨越整个会话。此类数据的例子包括客户端的当前认证状态、当前工作目录以及待处理的输入队列。相反,**短期存在的数据**具有较短的生命周期,因为它们仅存储一个或几个请求-回复交换所需的数据。`s2fuzzer` 跟踪整个会话中长期存在的数据结构的演变,并丢弃短期存在的数据。当成功达到一个新的协议状态时,新状态会导致长期数据结构的内容发生变化。因此,在每次请求-回复交换结束时获取此类数据的快照。然后,通过使用模糊哈希将每个唯一的内存状态映射到唯一的状态标识符,将此快照作为当前协议状态的代理。
### 3.静态二进制重写
#### 3.1 定义
静态二进制重写是指在不执行程序的情况下,对目标程序(通常是已编译好的可执行文件)的二进制代码进行修改。这种技术通常用于安全加固、功能增强、代码优化等目的。它的核心在于分析和修改目标程序的机器码,使其行为发生变化,而无需访问源代码。`s2fuzzer`通过`Ghidra`对无源码网络协议二进制程序进行**静态分析**获取插桩辅助信息,随后二进制重写器 `E9Patch`对目标插桩指令通过重写写入插桩功能函数,实现对无源码网络协议二进制程序的插桩,从而对无源码网络协议二进制程序开展灰盒模糊测试。
#### 3.2 实现
首先利用 `Ghidra` 对二进制程序进行`静态分析`获取插桩辅助信息,随后二进制重写器 `E9Patch`对目标插桩指令通过重写写入插桩功能函数,输出插桩后的二进制程序。
![](./images/%E5%9F%BA%E4%BA%8E%E9%9D%99%E6%80%81%E9%87%8D%E5%86%99%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%8F%92%E6%A1%A9%E6%A1%86%E6%9E%B6.png)
## 使用说明
### 1.S2fuzzer工具编译
```sh
# Install clang (as required by AFL/AFLNet to enable llvm_mode)
sudo apt-get install clang llvm
# Install graphviz development
sudo apt-get install graphviz-dev libcap-dev
```
```sh
cd s2fuzzer
make clean all
cd llvm_mode
# The following make command may not work if llvm-config cannot be found
# To fix this issue, just set the LLVM_CONFIG env. variable to the specific llvm-config version on your machine
make
```
编译完成后,工具目录生成如下程序:
| 名称 | |
| :--------------: | :-------------------: |
| afl-analyze | 输入数据模式分析器 |
| afl-as | 汇编器包装器 |
| afl-gcc | `gcc`编译器封装器 |
| afl-g++ | `g++`编译器封装器 |
| afl-clang | `Clang`编译器封装器 |
| afl-clang++ | `Clang++`编译器封装器 |
| afl-clang-fast | 高性能`Clang`封装器 |
| afl-clang-fast++ | 高性能`Clang++`封装器 |
| afl-showmap | 覆盖率地图生成器 |
| afl-tmin | 崩溃用例最小化工具 |
| s2fuzzer | 模糊测试主引擎 |
| afl-replay | 通用输入重放器 |
| aflnet-replay | 有状态网络协议重放器 |
| afl-llvm-pass.so | LLVM插桩插件 |
| sock2shm.so | 共享内存加速库 |
### 2.工具参数说明
- ***-N netinfo***: 服务器信息 (e.g., tcp://127.0.0.1/8554)
- ***-P protocol***: (optional)用于指定测试的协议类型 (e.g., RTSP, FTP, DTLS12, DNS, DICOM, SMTP, SSH,)
- ***-D usec***: (optional) 服务器完成初始化的等待时间(单位为微秒)
- ***-K*** : (optional) send SIGTERM signal to gracefully terminate the server after consuming all request messages
- ***-E*** : (optional) 开启状态感知模式
- ***-R*** : (optional) 开启区域块变异
- ***-c script*** : (optional) 用于服务器清理的脚本的名称或完整路径
- ***-q algo***: (optional) 状态选择策略 (e.g., 1. RANDOM_SELECTION, 2. ROUND_ROBIN, 3. FAVOR)
- ***-s algo***: (optional) 种子选择策略 (e.g., 1. RANDOM_SELECTION, 2. ROUND_ROBIN, 3. FAVOR)
- ***-x dictionary file***: (optional) fuzzer字典
```sh
s2fuzzer -d -i in -o out -N <server info> -x <dictionary file> -P <protocol> -D 10000 -q 3 -s 3 -E -K -R <executable binary and its arguments (e.g., port number)>
```
### 3.目标程序插桩
#### 3.1 有源码程序
`Crow`为例。
```sh
# 获取源代码
$ git clone https://github.com/CrowCpp/Crow
$ cd Crow
# 安装依赖
$ apt install libasio-dev
# 设置编译器为高性能Clang封装器和高性能Clang++封装器
$ cmake . build -DCMAKE_C_COMPILER=/path/to/s2fuzzer/afl-clang-fast -DCMAKE_CXX_COMPILER=/path/to/s2fuzzer/afl-clang-fast++
$ make
# 得到可执行程序/path/to/Crow/example/basic_example
```
#### 3.2 无源码程序
##### 3.2.1 搭建ghidra环境
###### 3.2.1.1 Ubuntu系统安装java 17
终端输入`sudo apt install openjdk-17-jdk`,完成下载后在终端输入`java --version`查看是否成功,如下图:
![image-20250529103031355](./images/image-20250529103031355.png)
###### 3.2.1.2 Ghidra 10.1.2下载
浏览器访问`https://github.com/NationalSecurityAgency/ghidra/releases?page=3`,选择`Ghidra 10.1.2`下载,如下图:
![image-20250529102026867](./images/image-20250529102026867.png)
将下载好的`ghidra_10.1.2_PUBLIC_20220125.zip`文件解压到某个目录,如:`/home/hunter/`目录下。
###### 3.2.1.3 IntelliJ IDEA 2023.2.8 (Community Edition)下载(可选)
`ubuntu`系统在应用中心中搜索`idea`,可以找到`IntelliJ IDEA Community Edition`,在`频道`中选择`2023.2/stable 2023.2.8`下载,如下图:
![image-20250529100847024](./images/image-20250529100847024.png)
`IntelliJ IDEA`下载`Ghidra插件`
浏览器访问`https://plugins.jetbrains.com/plugin/18086-ghidra`页面,下载最新版本插件,如下图:
![image-20250529101047295](./images/image-20250529101047295.png)
`idea`导入插件,idea打开`设置/settings`,选择`Plugins`,再点击`Install Plugin from Disk...`,如下图:
![image-20250529101420661](./images/image-20250529101420661.png)
选择下载好的`idea ghidra`插件,点击`OK`,如下图:
![image-20250529101604675](./images/image-20250529101604675.png)
安装成功后,显示如下图:
![image-20250529101643029](./images/image-20250529101643029.png)
使用`idea`打开`testghidra`文件夹,再选择`Project Structure...`,操作如下图:
![image-20250529102555990](./images/image-20250529102555990.png)
配置`Paht to Ghidra installation`,如下图所示,配置完成后点击`Apply`
![image-20250529102719567](./images/image-20250529102719567.png)
配置`JRE`版本,如下图:
![image-20250529103211601](./images/image-20250529103211601.png)
选择下载好的`java 17`
![image-20250529103220873](./images/image-20250529103220873.png)
如果找不到`java`的位置,如下图操作:
![image-20250529103507254](./images/image-20250529103507254.png)
##### 3.2.2 修改HelloIdeaGhidra.java
修改`e9patch/testghidra/src/HelloIdeaGhidra.java`文件,将**192行****193行**修改为**自定义路径**,如下图:
![](./images/截图 2025-06-23 18-17-09.png)
##### 3.2.3 使用Ghidra
`Ghidra`目录下找到`ghidraRun`文件后在当前目录打开终端,运行`ghidraRun`,如下图:
![image-20250529103716970](./images/image-20250529103716970.png)
启动成功后,出现如下界面:
![image-20250529103816510](./images/image-20250529103816510.png)
点击`File` --> `New Project`,创建项目,输入`Project Directory``Project Name`,创建完成后,可以在中间看到项目`ghidra_test`,如上图。
点击`File` --> `Import File`,选择**要分析的二进制可执行程序**,确定后,可以在项目下看到二进制可执行程序,如下图:
![image-20250626174130774](./images/image-20250626174130774.png)
双击二进制程序,如`wftpserver`,出现如下界面,点击`Yes`后点击`Analyze`
![image-20250529104328910](./images/image-20250529104328910.png)
在上方菜单栏选择`Window` --> `Script Manager`,出现如下界面:
![image-20250529104535851](./images/image-20250529104535851.png)
点击右上角`Manager Script Directories`,出现如下界面:
![image-20250529104727105](./images/image-20250529104727105.png)
点击右上角`Display file chooseer to add bundles to list`,选择`testghidra/src`文件夹,点击`OK`,可以看到被正确添加到列表,如上图。
回到`Script Manager`窗口,下方`Filter`输入`HelloIdeaGhidra`,可以看到我们写的脚本文件,如下图:
![image-20250529105219398](./images/image-20250529105219398.png)
点击`Run script`按钮,会生成`HelloIdeaGhidra.java`中定义的文件(),如下图:
![image-20250529105437755](./images/image-20250529105437755.png)
##### 3.2.4 使用E9patch进行插桩
###### 3.2.4.1 代码覆盖率插桩
搭配`s2fuzzer`使用,在`e9patch`目录下,使用`e9tool`工具进行代码覆盖率插桩。
```bash
./e9tool -M 'plugin(e9AFLPlugin).match()' -P 'entry(random)@afl-rt-bk-2' ./二进制程序 -o ./插桩后的二进制程序
```
通过e9AFLPlugin代码中的match函数确定所有基本块的插桩点位,在所有的插桩点位插入afl-rt中的entry函数,每次调用时,输出信息如下:
```bash
-----------------------------------------------
mode = Linux ELF executable
input_binary = ./二进制程序
output_binary = ./插桩后的二进制程序
num_patched = 1464 / 1464 (100.00%)
num_patched_B1 = 1312 / 1464 (89.62%)
num_patched_B2 = 124 / 1464 (8.47%)
num_patched_T1 = 27 / 1464 (1.84%)
num_patched_T2 = 0 / 1464 (0.00%)
num_patched_T3 = 1 / 1464 (0.07%)
num_virtual_mappings = 144
num_physical_mappings = 68 (47.22%)
num_virtual_bytes = 589824
num_physical_bytes = 278528 (47.22%)
input_file_size = 96848
output_file_size = 434173 (448.30%)
time_elapsed = 40ms
memory_used = 4984KB
----------------------------------------------
```
**输出的二进制程序**可直接作为`s2fuzzer`的输入,进行测试。
###### 3.2.4.2 覆盖率插桩+状态推断插桩
搭配`s2fuzzer`使用,在`e9patch`目录下,使用`e9tool`工具进行代码覆盖率+状态推断插桩。
```bash
./e9tool -M 'plugin(e9AFLPlugin).match()' -P 'entry(random)@afl-rt-bk-2' \
-M 'plugin(funcMatch).match()' -P 'state_tracer(state,asm,size,offset)@afl-rt-bk-2' \
./二进制程序 -o ./插桩后的二进制程序
```
输出信息如下:
```bash
-----------------------------------------------
mode = Linux ELF executable
input_binary = ./二进制程序
output_binary = ./插桩后的二进制程序
num_patched = 9263 / 9263 (100.00%)
num_patched_B1 = 8338 / 9263 (90.01%)
num_patched_B2 = 658 / 9263 (7.10%)
num_patched_T1 = 230 / 9263 (2.48%)
num_patched_T2 = 22 / 9263 (0.24%)
num_patched_T3 = 15 / 9263 (0.16%)
num_virtual_mappings = 723
num_physical_mappings = 256 (35.41%)
num_virtual_bytes = 2961408
num_physical_bytes = 1048576 (35.41%)
input_file_size = 349264
output_file_size = 1645345 (471.09%)
time_elapsed = 444ms
memory_used = 11604KB
-----------------------------------------------
```
输出的二进制文件可直接交给`s2fuzzer`进行测试。
### 4.使用示例
#### 4.1 使用共享内存
```sh
export AFL_PRELOAD=/path/to/s2fuzzer/sock2shm.so
```
`s2fuzzer`模糊测试工具中,设置`LD_PRELOAD`环境变量为`AFL_PRELOAD`的值,这样,当目标程序启动时,系统加载器就会首先加载`AFL_PRELOAD`指定的库。值得注意的是,通过这种方式设置的`LD_PRELOAD`环境变量,仅作用于`s2fuzzer`模糊测试工具本身及其子进程,不会影响其它程序。
> [!TIP]
>
> `LD_PRELOAD`是一个环境变量,它允许用户指定在程序运行前优先加载的动态链接库。这些库中的函数可以覆盖后续加载库中的同名函数,从而达到钩子(hook)的效果。为 了 实 现 一 个 非 侵 入 式 与 测 试 程 序 无 关 的 共 享 内 存 传 输 库, 使 用``LD_PRELOAD`` hook 机制来 hook 网络传输相关的标准库函数。
#### 4.2 开启状态感知模式
开启**状态感知模式**的程序启动命令示例:
```sh
s2fuzzer -d -i /path/to/in -o out -N tcp://127.0.0.1/2200 -t 1500+ -m none -D 10000 -q 3 -s 3 -E -K -R <executable binary and its arguments (e.g., port number)>
```
> [!TIP]
>
> 在命令中增加 -E 参数开启状态感知模式
#### 4.3 运行S2fuzzer模糊测试
```sh
# 以Crow为例,编译生成的可执行程序为:basic_example
$ cd /path/to/Crow/example/
$ /path/to/s2fuzzer -d -i -m none -d -i /path/to/Crow-in-replay/ -o ./output -N tcp://127.0.0.1/18080 -D 10000 -q 3 -s 3 -E -K -R ./basic_example
# 注意将以上命令中的 /path/to 改为自定义的路径
```
![image-20250624175300187](./images/image-20250624175300187.png)
### 5.输出文件说明
- plot_data可以用以可视化画图
- queue中保存所有的种子,每个种子覆盖一条路径
- crashes文件夹中保存所有触发漏洞的POC
- ipsm.dot 为模糊测试器推断的协议状态图
- new-ipsm-paths 为记录的推断状态路径序列
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment