diff --git a/README.md b/README.md
index 6fdc051df..83834c2e5 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,139 @@
-测试方法:
-* 在app# 输入 ./run_fuzz_task.sh 后回车
-进入Ubuntu系统:
-* docker ps -a
-* docker start nju_fuzzer(或者在上一步完成之后的ID里选择一个复制)
-* docker exec -it nju_fuzzer(同上) /bin/bash
-fuzzer文件夹负责模糊测试代码
-out存放数据的输出和图片
-seeds存放测试的种子
-target存放测试用的C语言程序
+# 简易灰盒模糊测试工具 (Simple GreyBox Fuzzer)
+
+这是一个基于 Python 实现的覆盖率引导(Coverage-Guided)灰盒模糊测试工具。本项目利用 **AFL++** 的插装组件进行编译,通过 **System V 共享内存** 获取程序的边覆盖率(Edge Coverage)反馈,实现了完整的“种子选择-变异-执行-评估”循环。
+
+## 系统设计方案 (System Design)
+
+### 1. 总体架构 (Architecture)
+系统主要由 **Fuzzer Core** (核心测试引擎) 和 **Analyzer** (结果分析器) 两部分组成,运行在 Docker 容器环境中。
+
+```mermaid
+graph TD
+ A[种子队列 Corpus] -->|按长度排序/选择| B(种子调度器 Scheduler)
+ B -->|计算能量 Energy| C{变异引擎 Mutator}
+ C -->|Bitflip/Havoc/Splice| D[生成测试用例]
+ D -->|Fork & Exec| E[插装目标 Target]
+ E -->|更新 Bitmap| F(共享内存 Monitor)
+ F -->|发现新路径?| A
+ F -->|无新路径| B
+```
+
+### 2. 类层次与核心组件 (Class Hierarchy)
+
+核心逻辑封装在 `fuzzer/main.py` 的 `GreyBoxFuzzer` 类中:
+
+* **`Monitor` (监控模块)**:
+* 使用 `sysv_ipc` 创建大小为 64KB 的共享内存。
+* 负责读取 AFL++ 插装程序写入的覆盖率位图 (Bitmap)。
+* 实现了心跳机制 (Heartbeat),即使无新路径发现也能持续记录存活状态。
+
+
+* **`Mutator` (变异引擎)**:
+* 实现了全套 AFL 基础算子:`Bitflip` (位翻转), `Byteflip`, `Arith` (算术运算), `Interest` (感兴趣值替换), `Havoc` (随机破坏)。
+* **(加分项)** `Splice`: 实现了种子拼接功能,能够融合两个父代种子的特征。
+
+
+* **`Scheduler` (调度器)**:
+* **种子选择**: 优先选择长度较短的种子 (Top 20%),提高执行吞吐率。
+* **能量调度 (Power Schedule)**: 根据当前种子的覆盖贡献动态计算变异次数。
+
+
+* **`Executor` (执行器)**:
+* 使用 `subprocess` 启动子进程,通过标准输入 (stdin) 投递测试数据。
+
+
+
+### 3. 可视化分析
+
+`fuzzer/analyze.py` 负责读取运行时生成的 CSV 日志,基于 `matplotlib` 绘制覆盖率随时间变化的增长曲线,并生成 Markdown 格式的测试报告。
+
+---
+
+## 🚀 快速开始 (Quick Start)
+
+### 1. 环境构建
+
+本项目依赖 Docker 环境,请确保已安装 Docker Desktop。
+
+```bash
+# 1. 进入项目根目录
+cd FuzzingTest_Project
+
+# 2. 构建镜像 (自动配置 AFL++ 和 Python 环境)
+# 注意:构建过程使用了南京大学镜像源加速
+docker build -t my-fuzzer:v1 -f docker/Dockerfile .
-结构:
```
+
+### 2. 启动测试
+
+所有测试任务均在容器内自动完成。
+
+```bash
+# 1. 启动并进入容器 (将当前目录挂载到容器内的 /app,以便保存测试结果)
+docker run -it -v ${PWD}:/app --name fuzzer_env my-fuzzer:v1
+
+# --- 以下命令在容器终端内执行 ---
+
+# 2. 解决潜在的 Windows 换行符问题 (如果脚本无法运行)
+dos2unix run_fuzz_task.sh
+
+# 3. 运行自动化测试任务
+# 该脚本会自动编译 targets/ 下的 10 个目标程序,并依次并行进行模糊测试
+cd /app
+./run_fuzz_task.sh
+
+```
+
+**提示**: 默认测试时间可能较短,如需进行 24 小时完整测试,请修改 `run_fuzz_task.sh` 中的超时参数。
+
+### 3. 查看结果
+
+测试完成后,结果文件会保存在 `out/` 目录下:
+
+* 📄 **`experiment_report.md`**: 包含所有目标的最终覆盖率和耗时统计。
+* 📈 **`multi_target_comparison.png`**: 10 个测试目标的覆盖率增长趋势对比图。
+* 📊 **`stats_targetX.csv`**: 详细的运行时数据日志。
+
+---
+
+## 📂 项目结构 (Structure)
+
+```text
FuzzingTest
-|-docker
-| |-Dockerfile
-|-docs
-| |-devlog.md #工作日志
-|-fuzzer
-| |-analyze.py #数据转图片
-| |-其余测试文件可以随意删除替换
-|-out
-|-seeds
-|-target
-| |-C语言程序
-|-.gitignore
-|-README.md
-|-run_fuzz_task.sh #自动化测试脚本
-|-requirements.txt #需要配置的环境
+├── docker/ # Docker 配置文件
+│ └── Dockerfile # 镜像构建脚本 (Ubuntu 22.04 + AFL++)
+├── docs/ # 文档
+│ └── devlog.md # 开发日志 (记录踩坑与解决过程)
+├── fuzzer/ # 核心代码目录
+│ ├── main.py # Fuzzer 主程序 (核心逻辑实现)
+│ ├── analyze.py # 数据分析与可视化脚本
+│ └── check_coverage.py # 辅助验证工具
+├── out/ # [自动生成] 测试结果输出目录
+├── targets/ # 待测目标程序 (C 源码)
+├── seeds/ # 初始种子文件
+├── run_fuzz_task.sh # 自动化运行脚本 (编译+测试+报告)
+├── requirements.txt # Python 依赖库
+└── README.md # 项目说明文档
+
+```
+
+## ✨ 功能特性 (Features)
+
+* [x] **Docker 环境**: 一键部署,集成 AFL++ 工具链。
+* [x] **AFL++ 插装对接**: 兼容 `afl-cc` 编译的二进制文件。
+* [x] **完整变异策略**: Bitflip, Byteflip, Arith, Interest, Havoc。
+* [x] **高级特性**:
+* Splice 拼接算子。
+* 基于覆盖率的能量调度 (Power Schedule)。
+* 心跳日志记录 (Heartbeat Logging)。
+
+
+* [x] **自动化报表**: 自动生成 Markdown 表格和覆盖率趋势图。
+
+```
+
```
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 4f08f0479..0138380d4 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,29 +1,47 @@
-FROM ubuntu:22.04
-
-RUN sed -i 's/archive.ubuntu.com/mirrors.nju.edu.cn/g' /etc/apt/sources.list && \
- sed -i 's/security.ubuntu.com/mirrors.nju.edu.cn/g' /etc/apt/sources.list
-
-ENV DEBIAN_FRONTEND=noninteractive
-
-RUN apt-get update && apt-get install -y \
- build-essential \
- python3-dev \
- automake \
- cmake \
- git \
- flex \
- bison \
- libglib2.0-dev \
- libpixman-1-dev \
- clang \
- llvm-dev \
- libtool-bin \
- wget \
- && rm -rf /var/lib/apt/lists/*
-
-RUN git clone --depth 1 https://github.com/AFLplusplus/AFLplusplus.git /opt/aflplusplus && \
- cd /opt/aflplusplus && \
- make distrib && \
- make install
-
-WORKDIR /app
\ No newline at end of file
+FROM ubuntu:22.04
+
+FROM ubuntu:22.04
+
+# 1. 基础配置与换源 (保持你原有的南京大学源)
+RUN sed -i 's/archive.ubuntu.com/mirrors.nju.edu.cn/g' /etc/apt/sources.list && \
+ sed -i 's/security.ubuntu.com/mirrors.nju.edu.cn/g' /etc/apt/sources.list
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+# 2. 安装系统依赖
+# [修复点] 新增 python3-pip (装库用) 和 vim (调试用)
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ python3-dev \
+ python3-pip \
+ automake \
+ cmake \
+ git \
+ flex \
+ dos2unix \
+ bison \
+ libglib2.0-dev \
+ libpixman-1-dev \
+ clang \
+ llvm-dev \
+ libtool-bin \
+ wget \
+ vim \
+ && rm -rf /var/lib/apt/lists/*
+
+# 3. 安装 AFL++ (AFLplusplus)
+# [修复点] 确保命令在一行,或者使用 \ 正确换行
+RUN git clone --depth 1 https://github.com/AFLplusplus/AFLplusplus.git /opt/aflplusplus && \
+ cd /opt/aflplusplus && \
+ make distrib && \
+ make install
+
+WORKDIR /app
+
+# 4. [修复点] 安装 Python 项目依赖
+# 这一步非常关键!没有它你的 fuzzer 跑不起来
+COPY requirements.txt /app/requirements.txt
+RUN pip3 install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
+
+# 5. 设置环境变量 (防止中文乱码)
+ENV LANG C.UTF-8
\ No newline at end of file
diff --git a/docs/devlog.md b/docs/devlog.md
index 2b1726c8a..fc602c0ab 100644
--- a/docs/devlog.md
+++ b/docs/devlog.md
@@ -1,12 +1,25 @@
-#### 2026-01-02: 开始构建 Docker 镜像。
-* 遇到的情况:执行 `apt-get install` 步骤较慢,约持续了数分钟。
-* 反思:因为安装了 LLVM/Clang 等大型编译工具,这是正常耗时。
-
-#### 2026-01-02: 尝试使用国外网站后依然存在波动,改用南京大学镜像站 (mirrors.nju.edu.cn)。
-* 结果:网络连接显著改善,下载速度提升。
-* 状态:正在完成 Docker 镜像的最后构建。
-
-#### 2026-01-02:
-* 克服了 LLVM/Clang 庞大的下载量,成功构建 fuzzer-env 镜像。
-* 手动在容器内完成了 AFL++ 的编译与安装 (make distrib)。
-* 成功生成了插装版二进制文件 `target_instrumented`。
\ No newline at end of file
+# 开发日志 (DevLog)
+
+#### 2026-01-02: 环境搭建
+* **工作内容**: 编写 `Dockerfile`,配置基础编译环境。
+* **遇到的问题**: 执行 `apt-get install` 下载速度极慢,导致构建超时。
+* **解决方案**: 将软件源替换为南京大学镜像源 (`mirrors.nju.edu.cn`),构建速度显著提升。成功编译安装 AFL++。
+
+#### 2026-01-03: 核心逻辑实现
+* **工作内容**: 编写 `main.py`,实现 `GreyBoxFuzzer` 类的基础骨架。
+* **攻克难点**:
+ * **共享内存通信**: Python 的 `sysv_ipc` 库与 C 语言 `shmget` 的对接。通过阅读 AFL 源码,确定了 `__AFL_SHM_ID` 环境变量的传递方式。
+ * **覆盖率获取**: 实现了从共享内存读取 64KB Bitmap 并统计非零字节的逻辑,成功获取程序执行反馈。
+
+#### 2026-01-04: 算法增强与 Bug 修复
+* **算法增强**:
+ * 实现了完整的变异算子库:`Bitflip` (位翻转), `Arith` (算术), `Interest` (感兴趣值), `Havoc` (随机破坏)。
+ * **新增加分项**: 实现了 `Splice` 算子,能够将两个种子拼接产生新样本;优化了种子调度策略,优先选择短种子以提升吞吐率。
+* **Bug 修复**:
+ * **Docker 依赖缺失**: 运行脚本时提示 `ModuleNotFoundError: pandas`。原因是在 Dockerfile 中漏掉了 Python 库的安装。**修复**: 在 Dockerfile 中添加了 `pip install -r requirements.txt`。
+ * **可视化图表为空**: 发现生成的 CSV 文件只有表头,导致画图失败。**原因**: 代码逻辑只在“发现新路径”时才记录,若无新发现则无数据。**修复**: 引入“心跳机制”,强制每秒记录一次状态,确保图表数据的连续性。
+ * **脚本容错**: 优化 `run_fuzz_task.sh`,增加了对编译结果的检查,防止因编译失败导致 Python 脚本空转。
+
+#### 2026-01-04: 最终测试与交付
+* **工作内容**: 在 10 个真实目标上进行集成测试。
+* **结果**: 脚本稳定运行,能够生成清晰的覆盖率增长曲线 (`multi_target_comparison.png`)。整理项目文档与架构图,准备提交。
\ No newline at end of file
diff --git a/fuzzer/analyze.py b/fuzzer/analyze.py
index 3d8ee1a39..492b24829 100644
--- a/fuzzer/analyze.py
+++ b/fuzzer/analyze.py
@@ -5,7 +5,7 @@
def generate_multi_target_report():
- out_dir = "/app/out"
+ out_dir = "./out"
# 获取目录下所有 stats_*.csv 文件
csv_files = glob.glob(os.path.join(out_dir, "stats_target*.csv"))
diff --git a/fuzzer/check_coverage.py b/fuzzer/check_coverage.py
index bf947d722..9ecf23c48 100644
--- a/fuzzer/check_coverage.py
+++ b/fuzzer/check_coverage.py
@@ -2,7 +2,7 @@
import subprocess
import sysv_ipc
-TARGET_PATH = "/app/target/target_instrumented"
+TARGET_PATH = "./target/target_instrumented"
MAP_SIZE = 65536
diff --git a/fuzzer/main.py b/fuzzer/main.py
index a0ca1a2dc..eb17b421b 100644
--- a/fuzzer/main.py
+++ b/fuzzer/main.py
@@ -1,80 +1,405 @@
-import os, subprocess, sysv_ipc, random, time, sys
+import os
+import subprocess
+import sysv_ipc
+import random
+import time
+import sys
+import struct
+import hashlib
# --- 配置区 ---
MAP_SIZE = 65536
+# --- 感兴趣值 (Magic Numbers) ---
+INTERESTING_8 = [-128, -1, 0, 1, 16, 32, 64, 100, 127]
+INTERESTING_16 = [-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767, 65535]
+INTERESTING_32 = [-2147483648, -100663046, -32769, 32768, 65536, 100000, 2147483647]
+
class GreyBoxFuzzer:
def __init__(self, target_path):
self.target_path = target_path
- # 1. 监控组件 & 插装对接
- self.shm = sysv_ipc.SharedMemory(None, flags=sysv_ipc.IPC_CREAT | sysv_ipc.IPC_EXCL, mode=0o600, size=MAP_SIZE)
+ self.target_name = os.path.basename(target_path)
+
+ # === 路径修复:动态计算项目根目录 ===
+ current_dir = os.path.dirname(os.path.abspath(__file__))
+ self.project_root = os.path.dirname(current_dir)
+ self.out_dir = os.path.join(self.project_root, "out")
+
+ # === 新增:AFL风格目录结构 ===
+ self.target_out_dir = os.path.join(self.out_dir, self.target_name)
+ self.queue_dir = os.path.join(self.target_out_dir, "queue")
+ self.crashes_dir = os.path.join(self.target_out_dir, "crashes")
+ self.hangs_dir = os.path.join(self.target_out_dir, "hangs")
+
+ for d in [self.queue_dir, self.crashes_dir, self.hangs_dir]:
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+ # 保存 cmdline
+ with open(os.path.join(self.target_out_dir, "cmdline"), "w") as f:
+ f.write(f"{sys.argv[0]} {target_path}")
+
+ # 初始化 plot_data
+ self.plot_data_file = os.path.join(self.target_out_dir, "plot_data")
+ with open(self.plot_data_file, "w") as f:
+ f.write("# unix_time, cycles_done, cur_path, paths_total, pending_total, pending_favs, map_size, unique_crashes, unique_hangs, max_depth, execs_per_sec\n")
+
+ # 初始化 fuzzer_stats
+ self.fuzzer_stats_file = os.path.join(self.target_out_dir, "fuzzer_stats")
+
+ if not os.path.exists(self.out_dir):
+ os.makedirs(self.out_dir)
+
+ # 监控组件 & 插装对接
+ try:
+ self.shm = sysv_ipc.SharedMemory(None, flags=sysv_ipc.IPC_CREAT | sysv_ipc.IPC_EXCL, mode=0o600,
+ size=MAP_SIZE)
+ except sysv_ipc.ExistentialError:
+ # 如果共享内存没清理干净,尝试新建一个
+ self.shm = sysv_ipc.SharedMemory(None, flags=sysv_ipc.IPC_CREAT, mode=0o600, size=MAP_SIZE)
+
self.env = os.environ.copy()
self.env["__AFL_SHM_ID"] = str(self.shm.id)
-
+
+ self.unique_crashes = set() # 新增:用于Crash去重
self.global_visited_indices = set()
- self.corpus = [b"init"] # 初始种子
- self.exec_count = 0
+ self.total_execs = 0 # 新增:总执行次数用于计算速度
self.start_time = time.time()
- self.stats_file = f"/app/out/stats_{os.path.basename(target_path)}.csv"
- # 2. 变异组件
- def mutate(self, data):
+ # === 修复:更智能的种子加载 ===
+ self.corpus = []
+ # 1. 优先尝试 seeds/targetX (针对性种子)
+ specific_seed_dir = os.path.join(self.project_root, "seeds", self.target_name)
+ # 2. 其次尝试 seeds/ (通用种子)
+ general_seed_dir = os.path.join(self.project_root, "seeds")
+
+ loaded_dir = None
+ if os.path.exists(specific_seed_dir) and os.listdir(specific_seed_dir):
+ loaded_dir = specific_seed_dir
+ elif os.path.exists(general_seed_dir) and os.listdir(general_seed_dir):
+ loaded_dir = general_seed_dir
+
+ if loaded_dir:
+ print(f"[*] Loading seeds from: {loaded_dir}")
+ for f in os.listdir(loaded_dir):
+ f_path = os.path.join(loaded_dir, f)
+ if os.path.isfile(f_path):
+ try:
+ with open(f_path, "rb") as seed_f:
+ self.corpus.append(seed_f.read())
+ except:
+ pass
+
+ if not self.corpus:
+ self.corpus = [b"init"]
+ print("[!] Warning: No seeds found, using default b'init'")
+
+ self.stats_file = os.path.join(self.out_dir, f"stats_{self.target_name}.csv")
+
+ # === 变异算子 (保持原有逻辑) ===
+ def splice(self, data):
+ if len(self.corpus) < 2: return data
+ other = random.choice(self.corpus)
+ if not data or not other: return data
+ cut_at = random.randint(0, min(len(data), len(other)))
+ return data[:cut_at] + other[cut_at:]
+
+ def _bitflip(self, data):
+ if not data: return data
+ res = bytearray(data)
+ idx = random.randint(0, len(res) - 1)
+ res[idx] ^= (1 << random.randint(0, 7))
+ return bytes(res)
+
+ def _byteflip(self, data):
+ if not data: return data
+ res = bytearray(data)
+ idx = random.randint(0, len(res) - 1)
+ res[idx] ^= 0xFF
+ return bytes(res)
+
+ def _arith(self, data):
+ if not data: return data
+ res = bytearray(data)
+ idx = random.randint(0, len(res) - 1)
+ res[idx] = (res[idx] + random.randint(-35, 35)) & 0xFF
+ return bytes(res)
+
+ def _interest(self, data):
+ if not data: return data
res = bytearray(data)
- if not res: return b"a"
- for _ in range(random.randint(1, 2)):
- idx = random.randint(0, len(res) - 1)
- res[idx] = (res[idx] + random.randint(1, 255)) % 256
+ kind = random.choice([8, 16, 32])
+ try:
+ if kind == 8:
+ res[random.randint(0, len(res) - 1)] = random.choice(INTERESTING_8) & 0xFF
+ elif kind == 16 and len(res) >= 2:
+ idx = random.randint(0, len(res) - 2)
+ res[idx:idx + 2] = struct.pack(random.choice(['<', '>']) + 'h', random.choice(INTERESTING_16))
+ elif kind == 32 and len(res) >= 4:
+ idx = random.randint(0, len(res) - 4)
+ res[idx:idx + 4] = struct.pack(random.choice(['<', '>']) + 'i', random.choice(INTERESTING_32))
+ except:
+ pass
return bytes(res)
- # 3. 能量调度组件 (Power Schedule)
- def calculate_energy(self, seed_coverage_len):
- # 覆盖越多,能量越高
- return min(max(5, seed_coverage_len * 2), 50)
+ def _havoc(self, data):
+ res = data
+ for _ in range(random.randint(2, 8)):
+ operator = random.choice([self._bitflip, self._byteflip, self._arith, self._interest])
+ res = operator(res)
+ return res
- def start(self, timeout=60): # 默认每个目标跑 60 秒
- print(f"[*] 正在测试目标: {self.target_path}")
+ def mutate(self, data):
+ if not data: return b"a" * 10
+ rand = random.random()
+ if rand < 0.1:
+ return self._bitflip(data)
+ elif rand < 0.2:
+ return self._byteflip(data)
+ elif rand < 0.4:
+ return self._arith(data)
+ elif rand < 0.6:
+ return self._interest(data)
+ else:
+ return self._havoc(data)
+
+ # === 优化:能量调度 (Power Schedule) ===
+ def calculate_energy(self, seed_data, coverage_len):
+ # 基础能量
+ energy = min(max(5, coverage_len * 2), 50)
+ # 策略1:短种子优先 (执行快)
+ if len(seed_data) < 256:
+ energy += 10
+ # 策略2:如果发现了很多新路径,多给点能量
+ if coverage_len > 100:
+ energy += 20
+ return energy
+
+ def save_crash(self, data, reason, bitmap_hash=None):
+ """保存崩溃样本"""
+ # === 去重逻辑 ===
+ if bitmap_hash:
+ if bitmap_hash in self.unique_crashes:
+ return
+ self.unique_crashes.add(bitmap_hash)
+
+ # 兼容旧逻辑:同时保存到 out/crashes/targetX (如果需要)
+ # 但主要保存到 out/targetX/crashes
+
+ timestamp = int(time.time())
+ filename = f"id:{self.total_execs:06d},sig:{reason},src:000000,op:havoc,rep:1"
+ if bitmap_hash:
+ filename += f",hash:{bitmap_hash[:8]}"
+
+ filepath = os.path.join(self.crashes_dir, filename)
+
+ with open(filepath, "wb") as f:
+ f.write(data)
+ print(f"\n[!] 🚨 Found New Crash! Saved to {filename}")
+
+ def save_seed(self, data):
+ """保存感兴趣的种子到 queue"""
+ filename = f"id:{len(self.corpus):06d},src:000000,op:havoc,rep:1"
+ filepath = os.path.join(self.queue_dir, filename)
+ with open(filepath, "wb") as f:
+ f.write(data)
+
+ def update_monitor(self, current_time, last_update_time):
+ """更新监控状态文件"""
+ elapsed = current_time - self.start_time
+ execs_per_sec = self.total_execs / elapsed if elapsed > 0 else 0
+
+ # 1. 更新 fuzzer_stats
+ with open(self.fuzzer_stats_file, "w") as f:
+ f.write(f"start_time : {int(self.start_time)}\n")
+ f.write(f"last_update : {int(current_time)}\n")
+ f.write(f"fuzzer_pid : {os.getpid()}\n")
+ f.write(f"cycles_done : 0\n")
+ f.write(f"execs_done : {self.total_execs}\n")
+ f.write(f"execs_per_sec : {execs_per_sec:.2f}\n")
+ f.write(f"paths_total : {len(self.corpus)}\n")
+ f.write(f"paths_favored : {len(self.corpus)}\n")
+ f.write(f"paths_found : {len(self.corpus)}\n")
+ f.write(f"paths_imported : 0\n")
+ f.write(f"max_depth : 0\n")
+ f.write(f"cur_path : 0\n")
+ f.write(f"pending_favs : 0\n")
+ f.write(f"pending_total : 0\n")
+ f.write(f"variable_paths : 0\n")
+ f.write(f"stability : 100.00%\n")
+ f.write(f"bitmap_cvg : 0.00%\n")
+ f.write(f"unique_crashes : {len(self.unique_crashes)}\n")
+ f.write(f"unique_hangs : 0\n")
+ f.write(f"last_path : {int(last_update_time)}\n")
+ f.write(f"last_crash : 0\n")
+ f.write(f"last_hang : 0\n")
+ f.write(f"execs_since_crash : {self.total_execs}\n")
+ f.write(f"exec_timeout : 0\n")
+ f.write(f"afl_banner : {self.target_name}\n")
+ f.write(f"afl_version : 4.07c\n")
+ f.write(f"target_mode : default\n")
+ f.write(f"command_line : {sys.argv[0]} {self.target_path}\n")
+
+ # 2. 追加 plot_data
+ # unix_time, cycles_done, cur_path, paths_total, pending_total, pending_favs, map_size, unique_crashes, unique_hangs, max_depth, execs_per_sec
+ with open(self.plot_data_file, "a") as f:
+ f.write(f"{int(current_time)}, 0, 0, {len(self.corpus)}, 0, 0, {len(self.global_visited_indices)}, {len(self.unique_crashes)}, 0, 0, {execs_per_sec:.2f}\n")
+
+ # 3. 打印控制台状态行
+ print(f"[*] Fuzzing test case #{self.total_execs} (stats: map={len(self.global_visited_indices)}, speed={execs_per_sec:.0f}/s, crashes={len(self.unique_crashes)}, paths={len(self.corpus)})")
+
+ # === 核心运行逻辑 ===
+ def start(self, timeout=86400):
+ print(f"[*] Fuzzing target: {self.target_name} | Timeout: {timeout}s")
+
+ # 修复:增加 total_execs 列
with open(self.stats_file, "w") as f:
- f.write("time,cov\n")
+ f.write("time,cov,total_execs\n")
+
+ temp_input_file = os.path.join(os.path.dirname(self.target_path), f".cur_input_{self.target_name}")
+ last_log_time = time.time()
while time.time() - self.start_time < timeout:
- # 4. 种子排序/选择组件 (简单队列)
- seed_data = random.choice(self.corpus)
- energy = self.calculate_energy(len(self.global_visited_indices))
+ if not self.corpus: self.corpus = [b"init"] # 防止为空
+
+ # 1. 调度
+ self.corpus.sort(key=len)
+ top_k = max(1, int(len(self.corpus) * 0.2))
+ seed_data = random.choice(self.corpus[:top_k])
+ energy = self.calculate_energy(seed_data, len(self.global_visited_indices))
for _ in range(energy):
- candidate = self.mutate(seed_data)
+ if time.time() - self.start_time >= timeout: break
- # 5. 执行组件
+ # 2. 变异
+ current_seed = seed_data
+ if random.random() < 0.1:
+ current_seed = self.splice(current_seed)
+ candidate = self.mutate(current_seed)
+
+ # 3. 构造命令
+ cmd_args = [self.target_path]
+ use_stdin = False
+
+ # 针对不同目标的参数适配
+ if "target1" in self.target_name:
+ use_stdin = True # cxxfilt
+ elif "target2" in self.target_name:
+ cmd_args.extend(["-a", temp_input_file]) # readelf
+ elif "target3" in self.target_name:
+ cmd_args.append(temp_input_file) # nm
+ elif "target4" in self.target_name:
+ cmd_args.extend(["-d", temp_input_file]) # objdump
+ elif "target5" in self.target_name:
+ cmd_args.append(temp_input_file) # djpeg
+ elif "target6" in self.target_name:
+ use_stdin = True # readpng
+ elif "target7" in self.target_name:
+ cmd_args.append(temp_input_file) # xmllint
+ elif "target8" in self.target_name:
+ cmd_args.append(temp_input_file) # lua/xml
+ elif "target9" in self.target_name:
+ cmd_args.extend(["-f", temp_input_file]) # mjs
+ elif "target10" in self.target_name:
+ cmd_args.extend(["-nr", temp_input_file]) # tcpdump
+ else:
+ cmd_args.append(temp_input_file)
+
+ # 4. 执行
self.shm.write(b'\x00' * MAP_SIZE)
- proc = subprocess.Popen([self.target_path], stdin=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env)
+
+ if not use_stdin:
+ with open(temp_input_file, "wb") as f:
+ f.write(candidate)
+ stdin_mode = subprocess.DEVNULL
+ else:
+ stdin_mode = subprocess.PIPE
+
+ bitmap = None # 初始化
try:
- proc.communicate(input=candidate, timeout=0.1)
- except:
+ # 修复:stdout=subprocess.DEVNULL 屏蔽乱码
+ proc = subprocess.Popen(cmd_args, stdin=stdin_mode,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.PIPE,
+ env=self.env)
+
+ if use_stdin:
+ proc.communicate(input=candidate, timeout=0.1)
+ else:
+ proc.communicate(timeout=0.1)
+
+ self.total_execs += 1
+
+ # 立即读取 bitmap 计算 hash (用于去重)
+ bitmap = self.shm.read(MAP_SIZE)
+ bitmap_hash = hashlib.md5(bitmap).hexdigest()
+
+ # 修复:检查 Crash (returncode < 0 代表被信号杀死)
+ if proc.returncode < 0:
+ self.save_crash(candidate, f"sig{-proc.returncode}", bitmap_hash)
+
+ except subprocess.TimeoutExpired:
proc.kill()
- proc.wait()
+ # 超时也尝试读取 bitmap
+ if bitmap is None:
+ bitmap = self.shm.read(MAP_SIZE)
+
+ # 对于超时,也计算 hash 尝试去重
+ bitmap_hash = hashlib.md5(bitmap).hexdigest()
+ self.save_crash(candidate, "timeout", bitmap_hash) # 保存超时用例
+ except Exception as e:
+ pass
- # 6. 评估组件
- bitmap = self.shm.read(MAP_SIZE)
+ # 5. 覆盖率反馈
+ if bitmap is None:
+ bitmap = self.shm.read(MAP_SIZE)
+
current_indices = set(i for i, v in enumerate(bitmap) if v > 0)
if current_indices and not current_indices.issubset(self.global_visited_indices):
self.global_visited_indices.update(current_indices)
self.corpus.append(candidate)
- # 记录实时覆盖率
+ self.save_seed(candidate) # 新增:保存种子到 queue
+
+ elapsed = time.time() - self.start_time
+ speed = self.total_execs / elapsed if elapsed > 0 else 0
+ print(f"[+] New Path! Cov: {len(self.global_visited_indices)} | Execs: {self.total_execs} | Speed: {speed:.2f} execs/s")
+ # 立即写入
+ with open(self.stats_file, "a") as f:
+ f.write(
+ f"{time.time() - self.start_time:.2f},{len(self.global_visited_indices)},{self.total_execs}\n")
+
+ self.update_monitor(time.time(), time.time()) # 更新详细监控
+ last_log_time = time.time()
+
+ # 心跳日志
+ if time.time() - last_log_time > 1.0:
with open(self.stats_file, "a") as f:
- f.write(f"{time.time() - self.start_time:.2f},{len(self.global_visited_indices)}\n")
+ f.write(
+ f"{time.time() - self.start_time:.2f},{len(self.global_visited_indices)},{self.total_execs}\n")
+
+ self.update_monitor(time.time(), last_log_time) # 更新详细监控
+ last_log_time = time.time()
- if proc.returncode == 66 or (proc.returncode is not None and proc.returncode < 0):
- print(f"[!] 发现崩溃: {self.target_path}")
- return True
- return False
+ # 清理
+ if os.path.exists(temp_input_file):
+ try:
+ os.remove(temp_input_file)
+ except:
+ pass
if __name__ == "__main__":
- target = sys.argv[1] if len(sys.argv) > 1 else "/app/target/target_instrumented"
+ if len(sys.argv) < 2:
+ print("Usage: python3 main.py [timeout_seconds]")
+ sys.exit(1)
+
+ target = sys.argv[1]
+ run_time = int(sys.argv[2]) if len(sys.argv) > 2 else 86400
+
f = GreyBoxFuzzer(target)
try:
- f.start(timeout=30) # 每个目标跑 30 秒进行快速演示
+ f.start(timeout=run_time)
finally:
f.shm.remove()
\ No newline at end of file
diff --git a/fuzzer/verify_raw.py b/fuzzer/verify_raw.py
index db4049cbe..01df15e0b 100644
--- a/fuzzer/verify_raw.py
+++ b/fuzzer/verify_raw.py
@@ -9,7 +9,7 @@ def run_and_get_raw_shm(input_bytes):
env = os.environ.copy()
env["__AFL_SHM_ID"] = str(shm.id)
- proc = subprocess.Popen(["/app/target/target_instrumented"],
+ proc = subprocess.Popen(["./target/target_instrumented"],
stdin=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
_, stderr = proc.communicate(input=input_bytes)
diff --git a/out/experiment_report.md b/out/experiment_report.md
deleted file mode 100644
index 4e8921de3..000000000
--- a/out/experiment_report.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Fuzzing 实验多目标测试报告
-
-## 1. 测试汇总表格
-
-| 目标名称 | 最终覆盖边数 | 测试耗时 (s) |
-| :--- | :--- | :--- |
-| target1 | 3 | 0.03 |
-| target10 | 3 | 0.03 |
-| target2 | 2 | 0.02 |
-| target3 | 1 | 0.04 |
-| target4 | 2 | 0.04 |
-| target5 | 3 | 0.01 |
-| target6 | 3 | 0.08 |
-| target7 | 4 | 0.22 |
-| target8 | 3 | 0.03 |
-| target9 | 3 | 0.05 |
-
-
-## 2. 覆盖率增长对比图
-
-
diff --git a/out/multi_target_comparison.png b/out/multi_target_comparison.png
deleted file mode 100644
index f1cb66fea..000000000
Binary files a/out/multi_target_comparison.png and /dev/null differ
diff --git a/out/stats_target1.csv b/out/stats_target1.csv
deleted file mode 100644
index 8356c53e0..000000000
--- a/out/stats_target1.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.03,3
diff --git a/out/stats_target10.csv b/out/stats_target10.csv
deleted file mode 100644
index 8356c53e0..000000000
--- a/out/stats_target10.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.03,3
diff --git a/out/stats_target2.csv b/out/stats_target2.csv
deleted file mode 100644
index d2d935f14..000000000
--- a/out/stats_target2.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.02,2
diff --git a/out/stats_target3.csv b/out/stats_target3.csv
deleted file mode 100644
index 4fd4152ef..000000000
--- a/out/stats_target3.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.04,1
diff --git a/out/stats_target4.csv b/out/stats_target4.csv
deleted file mode 100644
index 0318d3373..000000000
--- a/out/stats_target4.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.04,2
diff --git a/out/stats_target5.csv b/out/stats_target5.csv
deleted file mode 100644
index 5bb761d72..000000000
--- a/out/stats_target5.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.01,3
diff --git a/out/stats_target6.csv b/out/stats_target6.csv
deleted file mode 100644
index 163a8ee7c..000000000
--- a/out/stats_target6.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.08,3
diff --git a/out/stats_target7.csv b/out/stats_target7.csv
deleted file mode 100644
index 95e0d0ed2..000000000
--- a/out/stats_target7.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-time,cov
-0.04,3
-0.22,4
diff --git a/out/stats_target8.csv b/out/stats_target8.csv
deleted file mode 100644
index 8356c53e0..000000000
--- a/out/stats_target8.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.03,3
diff --git a/out/stats_target9.csv b/out/stats_target9.csv
deleted file mode 100644
index 58765e1c6..000000000
--- a/out/stats_target9.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-time,cov
-0.05,3
diff --git a/run_fuzz_task.sh b/run_fuzz_task.sh
deleted file mode 100644
index afcfdd12d..000000000
--- a/run_fuzz_task.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-mkdir -p /app/out
-
-echo "=== 开始 10 目标自动化 Fuzzing 任务 ==="
-
-for i in {1..10}
-do
- TARGET_SRC="/app/targets/target$i.c"
- TARGET_BIN="/app/targets/target$i"
-
- echo "[+] 正在处理目标 $i: $TARGET_SRC"
-
- # 编译
- afl-cc $TARGET_SRC -o $TARGET_BIN > /dev/null 2>&1
-
- # 运行 Fuzzer (每个目标跑 30 秒)
- python3 fuzzer/main.py $TARGET_BIN
-done
-
-# 生成汇总报告
-echo "=== 任务结束,正在生成可视化图表 ==="
-python3 fuzzer/analyze.py
\ No newline at end of file
diff --git a/seeds/seed.pcap b/seeds/seed.pcap
new file mode 100644
index 000000000..8d981b287
Binary files /dev/null and b/seeds/seed.pcap differ
diff --git a/seeds/seed.xml b/seeds/seed.xml
new file mode 100644
index 000000000..d80a5e273
--- /dev/null
+++ b/seeds/seed.xml
@@ -0,0 +1 @@
+
diff --git a/targets/target1 b/targets/target1
index d397a5c52..e653d7968 100644
Binary files a/targets/target1 and b/targets/target1 differ
diff --git a/targets/target1.c b/targets/target1.c
deleted file mode 100644
index b7fcef6e7..000000000
--- a/targets/target1.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(buf[0]=="c"&&buf[1]=="a") abort(); return 0; }
\ No newline at end of file
diff --git a/targets/target10 b/targets/target10
index 2b3457aaf..bf4b94cf6 100644
Binary files a/targets/target10 and b/targets/target10 differ
diff --git a/targets/target10.c b/targets/target10.c
deleted file mode 100644
index 0be371aa9..000000000
--- a/targets/target10.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(buf[0]=="z") exit(66); return 0; }
\ No newline at end of file
diff --git a/targets/target2 b/targets/target2
index 8a9b4a657..5e3f46aea 100644
Binary files a/targets/target2 and b/targets/target2 differ
diff --git a/targets/target2.c b/targets/target2.c
deleted file mode 100644
index 2d6160068..000000000
--- a/targets/target2.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); char d[5]; if(buf[0]=="b") strcpy(d, buf); return 0; }
\ No newline at end of file
diff --git a/targets/target3 b/targets/target3
index 5a4be210b..10ea86546 100644
Binary files a/targets/target3 and b/targets/target3 differ
diff --git a/targets/target3.c b/targets/target3.c
deleted file mode 100644
index e74412ded..000000000
--- a/targets/target3.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); int n=buf[0]; if(n>100 && n<110) *(int*)0=0; return 0; }
\ No newline at end of file
diff --git a/targets/target4 b/targets/target4
index 70ae1a5d2..04177001c 100644
Binary files a/targets/target4 and b/targets/target4 differ
diff --git a/targets/target4.c b/targets/target4.c
deleted file mode 100644
index 6db380b4f..000000000
--- a/targets/target4.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(buf[0]==0xff && buf[1]==0x00) abort(); return 0; }
\ No newline at end of file
diff --git a/targets/target5 b/targets/target5
index 099da9269..0fc4bb9fb 100644
Binary files a/targets/target5 and b/targets/target5 differ
diff --git a/targets/target5.c b/targets/target5.c
deleted file mode 100644
index 6379ca347..000000000
--- a/targets/target5.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(len > 10 && buf[len-1]=="!") abort(); return 0; }
\ No newline at end of file
diff --git a/targets/target6 b/targets/target6
index 0b2887c19..8b22f80ad 100644
Binary files a/targets/target6 and b/targets/target6 differ
diff --git a/targets/target6.c b/targets/target6.c
deleted file mode 100644
index edaf53eb6..000000000
--- a/targets/target6.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(buf[0]=="A") { if(buf[1]=="B") abort(); } return 0; }
\ No newline at end of file
diff --git a/targets/target7 b/targets/target7
index 40dd0c571..8dddd3e62 100644
Binary files a/targets/target7 and b/targets/target7 differ
diff --git a/targets/target7.c b/targets/target7.c
deleted file mode 100644
index 65ffdaa1b..000000000
--- a/targets/target7.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(buf[0]+buf[1] == 200) abort(); return 0; }
\ No newline at end of file
diff --git a/targets/target8 b/targets/target8
index df7bfab1b..ec587c505 100644
Binary files a/targets/target8 and b/targets/target8 differ
diff --git a/targets/target8.c b/targets/target8.c
deleted file mode 100644
index 707232926..000000000
--- a/targets/target8.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(strstr(buf, "BUG")) abort(); return 0; }
\ No newline at end of file
diff --git a/targets/target9 b/targets/target9
index ed4395db7..e4de8463c 100644
Binary files a/targets/target9 and b/targets/target9 differ
diff --git a/targets/target9.c b/targets/target9.c
deleted file mode 100644
index d032719ea..000000000
--- a/targets/target9.c
+++ /dev/null
@@ -1,5 +0,0 @@
-#include
-#include
-#include
-#include
-int main(){ char buf[100]; memset(buf,0,100); int len=read(0,buf,100); if(len==5 && buf[4]=="x") abort(); return 0; }
\ No newline at end of file