---
name: re-crash-triage
description: >
  崩溃/漏洞样本分析：确定性复现、ASAN/UBSAN 报告解读、输入最小化
  (afl-tmin/cmin)、gdb 回溯定位、rr 录制重放、PoC 产出。
  触发词：崩溃、crash、ASAN、UBSAN、未定义行为、UAF、堆溢出、越界、段错误、segfault、
  core dump、PoC、漏洞分析、崩溃分析。
capabilities: [debugging]
---

# 崩溃分析与漏洞定位（crash triage）

## 何时使用 / 何时不用

- 用：fuzz 产出崩溃输入；程序段错误/abort；需要判断崩溃是否真实漏洞（可达性）并产出最小 PoC；多线程/非确定性崩溃需复现
- 不用：还没崩溃、要主动找 bug（→ [[re-fuzzing]]）；崩溃已定位、需要深挖函数逻辑（→ [[re-binary-core]] 底座）
- 不用：只需要看符号栈（直接 [[re-gdb]] 亦可，但本技能含完整"复现→最小化→定位→PoC"流程）

## 工具准备

所有工具先验证再使用。复现崩溃是动态执行，默认沙箱内跑（[[re-analyze/platform-tips]] 最高原则，见 [[re-sandbox]]）。

### ASAN / UBSAN —— 内存与未定义行为检测（clang/gcc 内置）

- clang: `sudo apt install clang` / `sudo dnf install clang` / `sudo pacman -S clang` / macOS Xcode 自带
- gcc: Debian/Ubuntu `sudo apt install gcc`（ASAN 内置在 gcc，无独立包）；Fedora `sudo dnf install gcc`；Arch `sudo pacman -S gcc`
- 编译: `gcc -fsanitize=address,undefined -g -fno-omit-frame-pointer -o target_asan target.c`（clang 参数相同）
- 验证: 写一个数组越界小程序编译运行，确认输出含 `AddressSanitizer` 字样

### gdb —— 回溯与 core 分析

- Debian/Ubuntu: `sudo apt install gdb`
- Fedora/RHEL: `sudo dnf install gdb`
- Arch: `sudo pacman -S gdb`
- macOS: `brew install gdb`（需 codesign 授权）；Xcode 自带 lldb 亦可（见 [[re-lldb]]）
- Windows: WSL 内 Linux 版；Windows 本机用 WinDbg（[[re-windbg]]）
- 验证: `gdb --version`

### rr —— 录制/重放（非确定性崩溃）

- Debian/Ubuntu: `sudo apt install rr`（universe 仓库）
- Fedora/RHEL: `sudo dnf install rr`
- Arch: `paru -S rr`（AUR 包；官方仓库无 rr）
- macOS: 无支持（rr 仅支持 Linux，官方原话 "Apple hardware is fine, Apple software is not"）；Apple Silicon 用户用 Asahi Linux 或 Linux 虚拟机（需 PMU 透传）
- Windows: WSL2 内 Linux 版
- 源码编译:
  ```sh
  git clone https://github.com/rr-debugger/rr && mkdir rr/obj && cd rr/obj
  cmake -DCMAKE_BUILD_TYPE=Release ../rr && make -j$(nproc) && sudo make install
  ```
- 前置: Linux 内核 3.11+（建议 4.7+）；`perf_event_paranoid` > 1 时超慢，先放开：
  ```sh
  sudo sysctl kernel.perf_event_paranoid=1
  ```
- 虚拟机内需 PMU 透传（VMware/KVM 可，VirtualBox 不可）
- **容器/受限环境先自检**: `rr check` 会检查 PMU/`perf_event` 可用性与已知限制，通过后再用；嵌套虚拟化、未透传 PMU 的容器里 rr 无法录制
- **PMU 不可用时的回退**（不要卡在 rr 上）: 改用「同一输入多次重跑 + 日志/条件断点」缩小竞态窗口；数据竞争类问题用 `valgrind --tool=helgrind` 或 `-fsanitize=thread` 构建定位；纯 ASLR 抖动可先 `set disable-randomization on` 排除
- 验证: `rr --version`

### afl-tmin / afl-cmin —— 输入最小化与去重（随 AFL++ 安装）

- 安装见 [[re-fuzzing]] 的 AFL++ 一节（`afl-tmin` / `afl-cmin` 随包安装）
- **前置（`afl-cmin` 尤其重要）**: `afl-cmin` 需要目标可被 AFL 观测——通常要求它由 AFL 插桩构建（`afl-clang-fast` / `afl-gcc`），否则会报「无插桩 / not instrumented」而失败。`afl-tmin` 的约束弱得多（可按纯崩溃判据工作），未插桩目标通常也能跑
- **常见踩空**: 目标是独立 ASAN 构建（`gcc -fsanitize=address -g`）而**没有**走 `afl-clang-fast` 时，`afl-cmin` 不保证能跑。先确认构建方式；不是 AFL 编译器产出时，改用 `afl-tmin` 逐个最小化，或回 [[re-fuzzing]] 重建插桩版本
- 验证: `afl-tmin -h` / `afl-cmin -h`

## 操作步骤

按顺序执行；崩溃输入与 PoC 先 sha256 存证（[[re-triage]] 方法）。

1. **确定性复现**：
   ```sh
   sha256sum crash_input > crash_input.sha256
   ./target < crash_input          # stdin 输入
   ./target crash_input            # 文件输入
   ./target -f crash_input         # 按目标实际参数形态
   ```
   - 连续跑 3 次都崩 = 确定性崩溃，继续第 2 步；时崩时不崩 = 非确定性（多线程/未初始化/ASLR），跳到第 4 步用 rr
   - core dump 开启（崩溃后留 core 供分析）: `ulimit -c unlimited`；`cat /proc/sys/kernel/core_pattern` 若指向 systemd-coredump/apport 则 core 会被系统收走（见坑 4）
2. **ASAN 报告解读**（目标须为 `-fsanitize=address -g -fno-omit-frame-pointer` 构建，见工具准备）：
   - `heap-buffer-overflow` / `stack-buffer-overflow` / `global-buffer-overflow`：越界读写——报告含越界方向（`READ of size N` / `WRITE of size N`）与越界偏移
   - `heap-use-after-free`：UAF——看 `freed by` 与 `allocated by` 两段栈，找释放点与再使用点
   - `attempting double-free`：重复释放
   - `SEGV`：空指针/野指针访问，细节需 gdb 补（第 4 步）
   - 关键：`#0` 帧是崩溃点，**别只看行号**——结合数据流向判断是哪个输入字节导致
3. **输入最小化**：
   ```sh
   afl-tmin -i crash_input -o crash.min -- ./target @@
   # 多个崩溃输入先按路径去重（需 AFL 插桩，见工具准备）：
   afl-cmin -i crashes_dir -o minimized -- ./target @@
   ```
   - **`@@` 只适用于「文件参数」目标**（AFL 把 `@@` 替换成输入文件路径）。目标是 **stdin** 读输入时不要用 `@@`，把「文件 → stdin」这一步补进命令行，例如：
     ```sh
     afl-tmin -i crash_input -o crash.min -- sh -c './target < "$1"' _ @@
     ```
     或在目标自带 `-f <file>` 之类文件参数时用它替代 `@@`；先按第 1 步确认过的真实输入形态选写法
   - afl-tmin 产出保持崩溃的最短输入；可再手工删头/尾字节验证（二分查找最小边界）
   - 最小化后输入即 PoC 底稿；崩溃类型保持 = 同一 bug 的判定依据
4. **gdb 回溯定位**：
   ```sh
   gdb -q ./target
   (gdb) set disable-randomization on     # 关 ASLR 便于地址对照
   (gdb) run crash.min
   (gdb) bt                                # 完整回溯
   (gdb) info registers                    # 崩溃点寄存器
   (gdb) x/16i $pc                         # 崩溃点反汇编
   ```
   - core 文件分析：`gdb -q ./target /path/to/core`
   - `bt` 全是 `??`（无符号）：记模块基址 + 偏移，进 [[re-ghidra]] / [[re-ida]] 反编译补帧（见坑 5）
   - 非确定性崩溃 → rr 录制重放：
     ```sh
     rr record ./target crash_input
     rr replay                            # 打开 gdb 会话
     (rr) continue                         # 跑回崩溃点
     (rr) bt
     (rr) reverse-continue                 # 崩溃前反向执行找根因
     ```
5. **PoC / 报告**：
   - PoC：`crash.min` + sha256 + 复现命令（环境：ASAN 构建参数、运行参数、输入通道）
   - 报告要点：崩溃类型（ASAN 类别）、崩溃点函数/行/偏移、数据流向（哪个输入字节 → 越界下标）、根因（长度校验缺失 / 索引未验证 / 释放后使用）、**可达性**（见坑 3：崩溃路径真实可触达、输入可控）、影响评估（越界读→信息泄露、越界写→RCE、纯崩溃→DoS）、修复建议
   - 产出：PoC hash 与崩溃特征可进 [[re-ioc]]

## 跨域联合

- [[re-vuln]]：本网关工作流第 3 步固定调用本技能（崩溃分析是漏洞挖掘链路的核心环节）
- [[re-fuzzing]]：其崩溃产出（`out/crashes/`）交本技能分析（本技能也反向使用其 afl-tmin/cmin 工具链）
- [[re-binary-core]]：定位后补符号/读函数逻辑走底座（[[re-ghidra]] / [[re-ida]] / [[re-radare2]]）；[[re-triage]] 提供存证方法
- [[re-memdump]]：进程已死 / 已有 core 文件需更多上下文时转储分析（崩溃 core → memdump）
- [[re-sandbox]] / [[re-analyze/platform-tips]]：复现是动态执行，默认沙箱内跑

## 常见坑与陷阱

- **非确定性崩溃（多线程）**：现象——同一输入 10 次里崩 3 次，gdb 重跑复现不了；原因——线程竞态 / 未初始化内存 / ASLR 地址差异；对策——rr 录制（`rr record ./target input`）在崩溃点自动停止，`rr replay` + `reverse-continue` 反向定位根因；先 `set disable-randomization on` 排除 ASLR 因素
- **ASAN 与优化差异**：现象——ASAN（-O0/-O1）崩溃但 release（-O2）不崩，或反过来 release 才崩；原因——优化改变内存布局/时序，掩盖或暴露未定义行为；对策——两种构建都试；补 `-fsanitize=undefined` 抓 UB；影响与可达性判断以真实 release 行为为准（ASAN 报出的是潜在漏洞，需确认）
- **崩溃≠漏洞（可达性）**：现象——ASAN 报越界但在死代码/不可达分支，或崩溃由 harness 引入；原因——fuzzer 触发的输入未必对应真实攻击面；对策——gdb 回溯 + 反编译确认崩溃路径可触达、输入可控；区分 harness bug 与目标 bug（[[re-vuln]] 工作流第 4 步兜底确认）
- **core 被系统收走**：现象——`ulimit -c unlimited` 后崩溃仍无 core 文件；原因——`core_pattern` 指向 systemd-coredump（管道方式）/apport 拦截；对策——先 `cat /proc/sys/kernel/core_pattern` 确认落点，再按落点取回：
  - `core_pattern` 形如 `|/usr/lib/systemd/systemd-coredump ...`：core 已被 systemd 接管，**不要急着改全局设置**，直接取用即可——`coredumpctl list` 找条目（按可执行名/时间），`coredumpctl info <PID|可执行名>` 看元数据，`coredumpctl dump <PID> -o core` 导出，或 `coredumpctl gdb <PID>` 直接进 gdb 调试。这比改 `core_pattern` 更完整，也不影响系统上其它程序的 core 行为
  - apport 接管（Ubuntu）：core 落在 `/var/crash/`，用 `apport-unpack` 展开
  - 确实需要原始 core 文件时再临时改：`sudo sysctl kernel.core_pattern=core`（core 落到进程工作目录，注意只对之后启动的进程生效）
  - 兜底：`gdb --args ./target crash.min` 直接跑，不依赖 core 文件
- **符号缺失**：现象——`bt` 全 `??`、地址对不上；原因——strip / 静态链接 / 二进制与 core 版本不一致；对策——core 与二进制必须同版本（对比 sha256）；记录崩溃模块基址与偏移，在 [[re-ghidra]] / [[re-ida]] 按偏移定位函数，换算回 gdb 地址
