---
name: re-dynamic
description: 让二进制跑起来观察行为（dynamic analysis）：直接运行（ELF 走 WSL、PE 走本机）、函数级 Oracle（单函数调用+观察输入输出）、Frida 插桩、gdb/qiling 入口指引。触发：静态看不懂/需要验证函数行为/拿到 check 函数要试输入/想"先跑起来看看"。不做静态分析——那是 ghidra-static。动手前必须先用 skill 工具加载本 skill 全文并遵守其流程；一切观察/结论用 ledger.py 落账。
whenToUse: 静态分析卡住要运行时事实时；验证 check 函数/算法猜想（喂输入看返回）；样本行为观察（先跑起来看）；需要 Frida/gdb/qiling 动态手段的入口指引
---

# RE Dynamic（场景 4：跑起来看）

前置：静态卡住或要验证猜想 → 从 `~/.dsh/skills/ghidra-static` 来；判型不明先 `~/.dsh/skills/re-triage`／后继：拿到运行时事实后回 ghidra-static 标注进台账

> **先跑起来看**：对可执行样本，静态深挖前先花 2 分钟跑一遍观察 I/O——运行行为常常直接告诉你程序结构（菜单、校验提示、魔数），省掉半小时静态。（铁律全文见 ghidra-core §1）

## 路径约定

```bash
ORACLE="$HOME/.dsh/skills/re-dynamic/scripts/oracle.py"
# 后端：qiling（Windows 走 WSL Ubuntu /root/re-pwn-venv；WSL/Linux 下同一 venv 原生可用，
# rootfs /root/qiling-rootfs，均已装）
# re-tools venv：Windows ~/Desktop/src/re-tools-venv/Scripts/python.exe；Linux ~/re-tools-venv/bin/python
# win_gui_drive.py 为 Windows-only；frida_time_hook.py 双平台
```

## 1. 直接运行（第一选择，最便宜）

```bash
wsl -d Ubuntu -- /mnt/c/.../sample          # ELF（含加壳判断后的产物）
./sample.exe                                 # PE 本机直接跑
```

来源不明/疑似恶意的样本先评估再跑（快照 VM 更佳）；程序等输入就给输入，看输出猜结构。**观察到的每一个行为事实都用 ghidra-core `scripts/ledger.py observe` 回写台账**（机制见 ghidra-core references/evidence-ledger.md）。

## 2. 函数级 Oracle（oracle.py）

给定二进制 + 函数地址（Ghidra listing 里的地址）+ 参数，真实调用该函数并返回 JSON（retval/stdout/regs/stop_reason）。CTF check 函数验证的标准动作：喂候选输入，看返回 0/1。

```bash
python "$ORACLE" <binary> 0x101064 1 0x20              # 整参（SysV 前 6 / Win64 前 4）
python "$ORACLE" <binary> 0x1020a0 str:flag{guess}     # 字符串参数（自动写内存传指针）
python "$ORACLE" <binary> 0x1020a0 str:test @out.json  # @out 落盘约定
```

**能力与边界（v2，实测）**：

| 场景 | 状态 |
|---|---|
| 静态链接 ELF，任意函数（含 libc 调用） | ✅ 加 `--init-until <main 的 Ghidra 地址>` 先跑 CRT 初始化 |
| 动态链接 ELF，**leaf 函数**（不调 libc：XOR/比较/数学） | ✅ 直接调（PIE/非 PIE 地址自动换算） |
| 动态链接 ELF，调 libc 的函数 | ⚠️ `--init-until` 供使用，但 glibc 版本与 rootfs 不符时初始化可能崩（UC_ERR_FETCH_UNMAPPED）→ 升 Frida/gdb |
| **i386 ELF（32 位）** | ✅ v2 已支持（参数按 cdecl 压栈；rootfs x86_linux） |
| PE（qiling windows rootfs） | 实验性：leaf 函数可调；复杂 PE 可能撞 API stub 缺失 → 升 Frida |
| 浮点参数 / 结构体参数 | 未支持（整型+字符串） |

**中间态取证（`--break` + `--dump`，v2 新增）**：在指定 PC 停下 dump 内存表达式，不用手写 gdb 脚本抓流水线中间态（base64 产物、XOR 中间值、比较前的密文缓冲区）：

```bash
python "$ORACLE" ./encode 0x804887c --init-until 0x804887c \
    --break 0x8048a10 --dump ebp-0x8c:28 --dump 0x804a020:32 --dump eax
```

dump 表达式：`reg+/-0xoff:len`（寄存器相对）、`0x地址:len`（Ghidra 地址，自动换算运行时）、裸 `reg`（只读寄存器值）。`--max-hits N` 控制断点命中几次后停录（默认 1）。断在函数入口时 prologue 尚未执行——`ebp` 还是调用者的，栈参数用 `esp+4:8`（32 位）/寄存器（64 位）读。

已知行为：libc stdout 全缓冲——oracle 的 `stdout` 可能为空但 retval 正确，需要输出证据时在函数里找 write/puts 直接调用或看 retval。

## 3. 升级阶梯（oracle 不够用时）

① **Frida**（re-tools-venv 已装）：真实进程 hook——反仿真/复杂 API/要看中间状态时用；`frida`/`frida-trace` 入口见 ghidra-static references/ctf-patterns.md §6
② **gdb + pwndbg**（WSL 已装）：断点单步、内存断点；`wsl -d Ubuntu -- gdb /mnt/c/.../sample`
③ **angr**（re-tools-venv 已装）：符号执行求输入——"什么输入能让 check 返回 1"的直接求法，路径爆炸时慎用

## 3.5 两个高价值套路（五题复盘实战）

**① 外部随机/时间源是输入，不是逻辑**：校验依赖 `time()`/随机数/环境时，先判它是"输入"还是"逻辑"。是输入 → **合法地构造/覆写它**，把概率型 oracle 变成确定性 oracle——且不需要 patch 任何字节，天然满足铁律 10 的验证独立性（复盘实例：snake.exe 的 4 个关键格子由 `time.time()` 播种，命中概率 1/40320；枚举 FILETIME 后 frida hook `GetSystemTimeAsFileTime` 覆写返回值，未修改的二进制自己打印 flag）。覆写模板：`scripts/frida_time_hook.py`（Windows `GetSystemTimeAsFileTime` / Linux `time`/`gettimeofday` / Java `System.currentTimeMillis`）。

**② Windows GUI 消息驱动（模态对话框程序的 oracle）**：用 pywin32 驱动 GUI 程序批量试输入。实测两条铁律：
- **打开窗口用 `PostMessageA`**：模态对话框（`DialogBoxParam`）里 `SendMessageA` 会死锁
- **高频连点回 `SendMessageA`**：`PostMessage` 队列约 10000 条后静默丢弃（实测发 19999 次只到 12205 次）

组合记忆：**打开用 Post、连点用 Send**。驱动脚本：`scripts/win_gui_drive.py`。

## 4. 验证可信度（铁律 10 细则，encode 复盘 E2/E3）

**① patch 态禁区**：被 patch 过的运行态（`write-bytes`/`assemble`/调试器改字节/改寄存器/跳过长度门）
只能用于**探索控制流**，禁止用于**验证数据模型**。验证数据模型必须同时满足：

- 进程未修改，或修改点与测量点**无数据依赖**（patch 了长度门再去读缓冲区 = 溢出直接污染测量对象，同源）
- 至少一个**独立来源**交叉印证：静态常量（read_views）/ 第二输入 / 已知明文

被迫在 patch 后测量的结论，落账必须 `ledger.py conclude --independent no`（render 标 ⚠UNVERIFIED），
交付时显式声明「自我一致，未独立验证」。**模型与 trace 同源于「我的理解」，二者一致不构成证据。**

**② harness 自检门**：任何自建投喂/读数 harness（subprocess 管道、gdb 批处理、输入文件重定向），
在用于**支撑结论**之前必须用已知答案的输入自检——例如喂 10 字节，断言程序实际收到 10 字节、
NUL 在 +10。自检失败或同输入多次运行结果不同 → **该 harness 全部输出作废**，不得作为证据，
并在台账 `--harness <路径:版本>` 标注。已知坑：`subprocess`+文件重定向竞态（fd 未同步就 fork）、
`gdb < input.txt`（gdb 与 inferior 抢同一 fd，输入多/少 1 字节）。

**③ 偶发命中不是发现**：「随机 N 个输入里 K 个命中」必须复跑 ≥100 次确认可复现，
否则按 harness 不可信处理（回 ②）。幽灵命中率（12/2000 但复跑 0/300）是工具 bug 的签名，不是程序的。

**④ 矛盾读数的第一动作**：`strlen` 忽大忽小、同一缓冲两次读出不同值 → 先交叉验证工具
（独立 harness / strace / oracle.py --dump），禁止为矛盾编造机制（「IFUNC 怪癖」之类）。

## 防死循环

oracle 搭不起来（缺 stub/反仿真/地址算不对）→ 按阶梯升，**每级 ≤15 分钟**；禁止反复重试同一层（铁律 6/7，全文见 ghidra-core §1；卡点写台账）。

## References

| 文件 | 何时读 |
|---|---|
| `references/js-antidebug.md` | CTF web/misc 遇到混淆 JS / 浏览器反调试时（混淆分类、反调试四件套中和模板、Node vm 沙箱脱 eval 链） |
