feat(v1.1+v1.2): context-aware MCP decompile/disasm (fold/branch/summary), seep_r2_xrefs paginator, MANUAL anti-debug/unpacking/poc-validation SOP, 7-section verifier

This commit is contained in:
angusdevgo committed 2026-09-24 14:48:05 +08:00
1 parent 55afe5e613
commit 8f3c49c84b
6 files changed
+733 -10

No files matched your search

+190
View File
@@ -0,0 +1,190 @@
# MANUAL — 反调试对抗与强反调试绕过 (Anti-Debug Bypass SOP)
> 本文件是当 Agent 在 G0/G1 阶段检测到目标存在**反调试或强对抗机制**时的标准化处置手册。
> 覆盖 Windows 用户态全量反调试技法、通用补丁模板与代理 DLL 框架接入指引。
>
> 读取顺序:先通过 `seep_auto_triage` 识别壳类型 → 与本文档对比签名 → 选择对应打桩策略。
---
## 一、 快速诊断表:常见反调试信号 → 处置路由
| 检测到的信号 | 最可能机制 | 首选处置策略 |
|---|---|---|
| `IsDebuggerPresent` 出现在导入表 | PEB.BeingDebugged | NOP / 硬编码返回 0 |
| `NtQueryInformationProcess` + 0x7 | ProcessDebugPort | Syscall Stub 替换 |
| `CheckRemoteDebuggerPresent` | 同上 | Hook 返回 FALSE |
| `GetTickCount` / `QueryPerformanceCounter` 频繁调用 | 时序反调试 | 钩子固定返回值 |
| `CreateToolhelp32Snapshot` + 进程名比对 | 调试器进程名检测 | Hook 过滤快照列表 |
| `OutputDebugString` 后检测错误码 | ODS 陷阱 | 无操作(正常运行不受影响) |
| `NtSetInformationThread` 0x11 | ThreadHideFromDebugger | Hook 丢弃该调用 |
| 注册表 `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug` | AeDebug 检测 | 值打补丁 / Hook RegQueryValueEx |
| 断点扫描 / 自校验 CRC | 内存完整性自检 | 修正 CRC 或 NOP 校验逻辑 |
| 驱动 `\Device\DebuggerDetect` / ObRegisterCallbacks | 内核级反调试 | → 参考第五节:内核层对抗 |
---
## 二、 用户态 PEB 系列反调试 —— 最常见(通用补丁)
### 2.1 `IsDebuggerPresent` → 返回值强制为 0
**原理**:`kernel32!IsDebuggerPresent` 本质只读 `PEB.BeingDebugged (offset 0x2)`。
**代理 DLL / Frida 修补(推荐)**:
```javascript
// Frida 方案
Interceptor.attach(Module.getExportByName('kernel32.dll', 'IsDebuggerPresent'), {
onLeave: function(retval) { retval.replace(0); }
});
```
```cpp
// 内联汇编补丁(静态,适用于代理 DLL)
// 定位 IsDebuggerPresent 首地址,写入:
// XOR EAX, EAX
// RET
// 原始字节(x64)通常是: 65 48 8B 04 25 60 00 00 00 → 替换前 3 字节
BYTE patch[] = { 0x33, 0xC0, 0xC3 }; // xor eax,eax; ret
WriteProcessMemory(hProc, pIsDebuggerPresent, patch, sizeof(patch), nullptr);
```
### 2.2 `NtQueryInformationProcess` 信息类 7 (ProcessDebugPort) / 30 (ProcessDebugObjectHandle)
**原理**:成功连接调试器时 Port != 0。
```javascript
// Frida 方案
const NtQIP = Module.getExportByName('ntdll.dll', 'NtQueryInformationProcess');
Interceptor.attach(NtQIP, {
onEnter(args) { this.cls = args[1].toInt32(); this.out = args[2]; },
onLeave(retval) {
if (this.cls === 7 || this.cls === 30) {
this.out.writePointer(ptr(0)); // 清零 DebugPort / DebugObject
}
}
});
```
### 2.3 `NtSetInformationThread` → ThreadHideFromDebugger (0x11)
调用此系统调用后,调试器事件将对该线程不可见,导致断点失效。
```javascript
const NtSIT = Module.getExportByName('ntdll.dll', 'NtSetInformationThread');
Interceptor.attach(NtSIT, {
onEnter(args) {
if (args[1].toInt32() === 0x11) {
args[1] = ptr(0); // 替换为无害的 ThreadBasicInformation
}
}
});
```
---
## 三、 时序反调试 —— 高频坑(修复固定返回值)
调试状态下,`GetTickCount` / `QueryPerformanceCounter` 的推进速度远慢于正常运行,常被用来识别单步调试。
```javascript
// Frida 方案:固定时钟
let baseTick = Date.now();
Interceptor.replace(Module.getExportByName('kernel32.dll', 'GetTickCount'),
new NativeCallback(() => {
baseTick += 15; // 模拟正常 15ms 系统时钟步进
return baseTick;
}, 'uint32', [])
);
```
---
## 四、 进程名 / 窗口名 / 文件路径特征检测 —— 改名或 Hook 快照
目标程序扫描进程快照(`CreateToolhelp32Snapshot + Process32Next`)比对调试器进程名:
`x64dbg.exe`、`ollydbg.exe`、`ida.exe`、`ida64.exe`、`cheatengine.exe` 等。
**最简单处置**:把调试器改名为 `explorer2.exe`、`chrome_x.exe` 等。
**进阶 Hook 方案**:
```javascript
// 过滤 Process32Next 快照,抹去调试器进程条目
const blacklist = ['x64dbg.exe', 'ida64.exe', 'ollydbg.exe', 'wireshark.exe'];
const Process32Next = Module.getExportByName('kernel32.dll', 'Process32NextW');
Interceptor.attach(Process32Next, {
onLeave(retval) {
if (retval.toInt32() !== 0) {
const entry = this.context.rdx; // PROCESSENTRY32W 结构
const exeName = entry.add(44).readUtf16String();
if (blacklist.some(b => exeName && exeName.toLowerCase().includes(b))) {
// 递归调用直到返回非黑名单进程(简化处理:置为无害进程名)
entry.add(44).writeUtf16String('System');
}
}
}
});
```
---
## 五、 内核级反调试对抗(Ring0 / 驱动层)
> **适用场景**:游戏反作弊(ACE/EAC/BattlEye)、强保护商业软件、带 Ring0 驱动的许可校验模块。
> **注意**:内核对抗超出 Agent 全自动范畴,以下为指导性 SOP,需结合 WinDbg + 内核调试器手工处理。
### G0 阶段主动识别信号:
- `triage` 输出中存在 `.sys` 驱动签名或服务注册(HKLM\SYSTEM\CurrentControlSet\Services)
- 导入表出现 `NtLoadDriver`、`ZwCreateSection` 等内核接口
- 运行时观察到 `ObRegisterCallbacks` / `PsSetCreateProcessNotifyRoutine` 回调注册
### 处置策略(按难度升序):
| 方案 | 适用条件 | 操作要点 |
|---|---|---|
| **Kernel Patch Guard 绕过** | 测试用 VM / Hyper-V | 关闭 Driver Signature Enforcement,加载调试补丁驱动 |
| **VirtualBox + DKOM** | 沙箱分析 | 修改 `EPROCESS.DebugPort = NULL` 绕过内核调试器检测 |
| **全量内存 Dump + 用户态重放** | 驱动已运行 | 在内核加载完毕后 dump 进程内存,在用户态 Loader 中重放 |
| **Emulation (Unidbg/QEMU)** | 需脱离真实 OS | 在模拟环境中重跑关键算法,完全规避内核对抗 |
---
## 六、 代理 DLL 注入框架模板(统一打桩方式)
> 适用于不能直接修改宿主 EXE(如有数字签名)的场景。
所有上述 Hook 均可集成在以下代理 DLL 框架中,加载即自动注入,宿主签名完好:
```cpp
// anti_debug_patch.cpp — 代理 DLL 打桩框架(以 version.dll 为例)
#include <Windows.h>
// 实际 version.dll 导出函数转发(保留原功能)
#pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA")
// ... 其他导出
void ApplyAntiDebugPatches() {
// 1. 清除 PEB.BeingDebugged
__asm {
mov eax, fs:[30h] // PEB
mov byte ptr [eax+2], 0 // BeingDebugged = 0
}
// 2. Hook NtQueryInformationProcess (如上 Frida 方案的 C++ 等效)
// ...
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpvReserved) {
if (reason == DLL_PROCESS_ATTACH) ApplyAntiDebugPatches();
return TRUE;
}
```
---
## 七、 两击熔断纪律 —— 反调试对抗的止损规则
| 情形 | 触发条件 | 处置 |
|---|---|---|
| 用户态 Hook 无效(程序仍检测到调试器)| 同构打桩 2 次失败 | 停止,升级为内核层分析 |
| 内核层方案部署失败 | 同构 2 次失败 | 停止,改用 Emulation(Unidbg/QEMU)路线 |
| Emulation 行为无法覆盖 | 无法还原算法 | 输出"该目标超出当前工具链全自动范畴",转人工分析 |
+171
View File
@@ -0,0 +1,171 @@
# MANUAL — PoC 闭环自动化验证 SOP (Automated PoC Validation & Self-Healing)
> 本文件定义了从 Agent 生成 PoC(Frida 脚本 / Smali 补丁 / 代理 DLL)到
> **自动化执行、捕获回显、报错自愈** 的完整端到端验证流程。
>
> 目标:让 Agent 交付的 PoC 从"推测可行的代码"进化为"**100% 执行验证过的有效 PoC**"。
---
## 一、 三阶段闭环验证模型
```
G4/G5:生成 PoC
│
▼
[阶段 1] 静态自检 —— 语法解析、类名/方法签名格式校验
│ 通过
▼
[阶段 2] 沙箱执行 —— 注入目标进程 / 模拟器,捕获 stdout/stderr
│ 通过 失败
▼ ▼
[阶段 3] [自愈循环(最多 2 次)]
结论确认 解析报错 → Agent 定位错误类型 → 修正代码 → 重跑
输出验证 若 2 次自愈失败 → 标注"需人工校准",输出 diff 报告
闭环报告
```
---
## 二、 Frida 脚本静态自检 → 自动执行 → 报错自愈(P0 优先实现)
### 2.1 Agent 执行序列(写入 AGENTS.md 执行纪律)
在 Agent 生成 Frida 脚本(`hook_verify.js`)后,必须立即执行以下序列,不得跳过直接进报告:
```powershell
# Step 1:Node.js 语法快检(不启动 Frida,仅校验 JS 语法)
node --check hook_verify.js
if ($LASTEXITCODE -ne 0) {
Write-Error "[自愈 Step 1] JS 语法错误,Agent 自动修正后重跑"
# → 回传报错给 Agent,Agent 修正后重新生成并再次检查
}
# Step 2:连接模拟器/测试机 (adb),注入目标包并捕获输出
$pkg = "com.example.target" # 从 seep_apk_info 获取
frida -U -f $pkg -l hook_verify.js --no-pause 2>&1 | Tee-Object -FilePath frida_run.log &
Start-Sleep -Seconds 10 # 给 Frida 10 秒采集输出
Stop-Process -Name frida -ErrorAction SilentlyContinue
# Step 3:分析 frida_run.log,由 Agent 读取并判定结果
Get-Content frida_run.log
```
### 2.2 常见报错 → 自愈映射表
| 报错特征 | 根因 | 自愈操作 |
|---|---|---|
| `Java.ClassNotFoundException: com.x.y.Auth` | 类名拼写错误或混淆 | 重新调 `seep_apk_smali_search` 找正确类路径 |
| `Error: expected a pointer` | 参数类型声明错误 | 修正 `Java.use` 链中的类型签名 |
| `Process not found` | 目标 App 未前台运行 | 先启动目标 App 再 attach:改 `-f` 为 `-n` |
| `Error: unable to find module` | so 库未加载 | 加 `waitForModule()` 等待动态加载后 hook |
| `Script terminated` 无任何 console 输出 | onEnter/onLeave 挂钩点在 Dalvik 解释模式 | 改用 `Java.deoptimizeEverything()` 强制解释执行 |
| `Access violation` / 程序崩溃 | 内存写越界或返回类型错误 | 检查 `retval.replace()` 的参数类型一致性 |
---
## 三、 Windows Smali / Binary Patch 本地加载器验证
### 3.1 Smali 补丁验证流程
```powershell
# 前置:已用 apktool d 解包并修改 smali,现在重打包
cd task_sandbox\<task_name>
# 重打包
.\Tool\mcp\Tool\safe\apktool\apktool.bat b smali_patched -o target_repacked.apk
# Zipalign 4 字节对齐(必须,否则 ART 拒绝加载)
zipalign -v 4 target_repacked.apk target_aligned.apk
# 签名(测试用 debug 密钥)
apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android `
--out target_signed.apk target_aligned.apk
# 安装到测试机并捕获 logcat
adb install -r target_signed.apk
adb logcat -c && adb logcat | Select-String "VIP|Premium|License|granted|unlocked|ERROR"
```
### 3.2 Windows 代理 DLL 本地加载验证
```powershell
# 在隔离沙箱副本中加载代理 DLL,捕获函数返回值
# 前提:已编译生成 anti_debug_patch.dll
# 创建进程副本(绝对不要在生产环境直接测试)
$targetCopy = ".\sandbox\target_copy.exe"
Copy-Item "target.exe" $targetCopy
# 将代理 DLL 放置在 target_copy.exe 同目录(version.dll 劫持)
Copy-Item ".\patches\version.dll" ".\sandbox\version.dll"
# 启动进程并监控退出码与关键窗口
$proc = Start-Process $targetCopy -PassThru
Start-Sleep -Seconds 5
if (-not $proc.HasExited) {
Write-Host "[PASS] 进程正常运行,代理 DLL 注入无崩溃"
# 截图或 spy++ 检查目标窗口状态(标题栏是否出现"专业版"字样)
} else {
Write-Host "[FAIL] 进程崩溃,退出码 $($proc.ExitCode)"
# → 回传报错,Agent 检查 DLL 导出表是否与 system32\version.dll 一致
}
```
---
## 四、 离线确证(断网铁证 — CWE-602 终极验证)
PoC 执行成功后,**必须在飞行模式 / 断网状态下补做终极确证**,否则无法区分本地决策与服务端下发:
```text
验证步骤:
1. 关闭所有网络接口(或开飞行模式)
2. 重启目标 App(清除任何本地缓存)
3. 在断网状态下重新触发受限功能
4. 观察是否依然解锁/生效
结论判定:
✓ 断网后受限功能依然生效 → 确认为 CWE-602 本地决策脆弱性,PoC 有效
✗ 断网后功能失效 → 服务端权威强依赖,本地 PoC 无法构成完整绕过,报告需标注"需服务端协同"
```
---
## 五、 PoC 自愈循环上限与熔断规则
| 轮次 | 操作 | 若失败 |
|---|---|---|
| 第 1 次 | 生成 PoC → 静态自检 → 沙箱执行 | 解析报错 → 进入自愈 |
| 第 2 次 | Agent 分析报错 → 修正代码 → 重跑 | 解析报错 → 再试一次 |
| 第 3 次 | 最终修正 → 重跑 | **触发熔断:停止自动化,输出 diff 报告,标注"需人工校准"** |
熔断后 Agent 必须输出:
```markdown
## PoC 自动化验证受阻通知
- **PoC 文件**:`task_sandbox/<name>/hook_verify.js`
- **失败原因**:[具体报错摘要]
- **自愈尝试次数**:3 次(已达上限)
- **建议**:将 `frida_run.log` 与当前 PoC 提交人工校准,或改用 Unidbg 模拟路线。
```
---
## 六、 验证成功后的三段式报告触发
PoC 通过所有验证后,调用 `seep_gen_security_report` 自动生成结构化交付报告:
```python
# Agent 内部调用
seep_gen_security_report(
task_name="项目X_CWE602_验证",
vuln_title="客户端本地鉴权旁路 (CWE-602)",
cwe_id="CWE-602",
severity="HIGH",
affected_component="LicenseManager.checkPremium()",
root_cause="受限功能完全由本地布尔标志位控制,服务端无二次校验",
poc_summary="Frida Hook 拦截 checkPremium() 返回值,强制返回 true,断网状态下受限功能正常解锁,确认为本地决策脆弱性。",
remediation="将特权决策迁移至服务端,客户端仅持有短期、加密的会话票据,每次解锁必须通过服务端签名验证。"
)
```
+188
View File
@@ -0,0 +1,188 @@
# MANUAL — 通用脱壳前置分析 SOP (Unpacking & Deobfuscation)
> 本文件适用于 G0/G1 阶段识别出加壳保护后的标准化处置流程。
> 覆盖:MPRESS / UPX / Themida / VMP 3.x / .NET Reactor / 自定义混淆。
> 原则:**能动态 dump 就不静态逆壳**,优先内存 Dump 重建,效率最高。
---
## 一、 壳类型速查与工具路由
| 壳特征(triage 输出信号)| 壳类型 | 首选脱壳路线 |
|---|---|---|
| `MPRESS1` / `MPRESS2` 节区节名 | MPRESS | 等待 OEP 后内存 dump(Scylla) |
| `UPX0` / `UPX1` 节名,入口在末节 | UPX | `upx -d target.exe` 直接命令行解压 |
| `themida`/`.Boot` 节名,大量无效导入 | Themida/WinLicense | OEP 断点 + Scylla IAT Rebuild |
| `.vmp0` / `.vmp1` 节名,VM bytecode | VMP 3.x | 内存 dump + 动态 trace 提取算法(见第四节)|
| 熵值 > 7.2(整个文件),无可读字符串 | 自定义壳 / 强加密 | rabin2 entropy + x64dbg 手动定位 OEP |
| `.text` 区段全 0、代码在 .rsrc 内 | 资源型 Loader | 提取资源区段 → 分析 Loader 落盘路径 |
| `_CorExeMain` 导入(.NET)| .NET 壳 | de4dot + dnSpy 优先,再看 VMP 字节码 |
---
## 二、 通用 OEP 定位 + 内存 Dump 流程(适用所有用户态壳)
### 步骤一:定位 OEP(原始入口点)
```text
策略 A(ESP 定律 — 通用于 MPRESS/UPX/简单 PE 壳):
1. 在 x64dbg 中运行程序,在首个用户态断点暂停
2. 在 ESP(栈顶指针)处设置内存写入断点(Hardware BP on Write)
3. 运行至断点触发,此时 EIP 即为解压后的 OEP
4. 验证:OEP 处通常为 "push ebp; mov ebp, esp" 或 .NET CRT init 代码
策略 B(节区执行监控 — 通用于 Themida/自定义壳):
1. 用 x64dbg 打开,在"内存布局"中对原始代码节区(.text)设置执行断点
2. 运行程序,壳在向 .text 写完解密代码后会 jmp 到 OEP,此时断点触发
3. 记录此时的 EIP 地址
策略 C(API 断点反向追踪 — 通用于所有壳):
1. 在 GetCommandLineA / GetModuleHandleA 等 CRT 初始化必调 API 上设断点
2. 程序调用此 API 时已完成壳初始化,此时在调用栈中回溯到 OEP
```
### 步骤二:内存 Dump 与 IAT 重建
使用 **Scylla**(x64dbg 内置插件):
```text
1. 在 OEP 处暂停后打开 Scylla(Plugins → Scylla)
2. OEP 自动填入,点击 "IAT Autosearch" 扫描导入表
3. 点击 "Get Imports" 获取完整 IAT
4. 对所有标记为无效(红色)的条目,点击 "Fix Dump" 尝试自动修复
5. 点击 "Dump" 导出内存映像
6. 点击 "Fix Dump" 将 IAT 写入 Dump 文件
7. 用 CFF Explorer 或 PE-bear 验证修复后的 PE 头完整性
```
---
## 三、 UPX 脱壳(最简单,一行命令)
```powershell
# 直接解压(破坏性操作,先备份!)
Copy-Item target.exe target_backup.exe
upx -d target.exe
# 验证解压成功
.\Tool\mcp\Tool\safe\radare2\bin\rabin2.exe -I target.exe | Select-String "class|bits|type"
```
若 UPX 头被抹除(修改魔数以对抗 `upx -d`):
```python
# 修复被抹除的 UPX 魔数
with open("target.exe", "r+b") as f:
data = f.read()
# 定位 UPX 压缩数据特征并还原魔数
idx = data.find(b"\x55\x58\x50\x00") # UPX! 魔数附近
if idx > 0:
f.seek(idx - 4)
f.write(b"UPX!")
print("Header patched, retry: upx -d target.exe")
```
---
## 四、 VMP 3.x 脱壳与算法提取(最复杂,动态 trace 优先)
> VMP 将原始 x86/x64 指令编译为自定义 VM 字节码,**不可能静态完整还原**。
> 正确策略:**不试图还原 VM 本身,而是在 VM 执行过程中 trace 算法结果**。
### 4.1 G0 阶段止损判定
```text
seep_auto_triage 检测到 .vmp0/.vmp1 节名后,Agent 应立即输出:
"⚠️ 目标存在 VMP 3.x 级虚拟机保护,超出全自动符号反编译范畴。
将切换为动态 trace 策略提取关键算法输出,而非还原完整伪代码。"
```
### 4.2 动态 Trace 策略(提取算法的输入/输出)
```javascript
// Frida 方案:监控 VMP 保护函数的入口参数与出口返回值
// 需先通过 auto_triage 确定被 VMP 保护的关键导出函数 RVA
const baseAddr = Process.enumerateModules()[0].base;
const vmProtectedFunc = baseAddr.add(0xDEAD00); // 替换为实际 RVA
Interceptor.attach(vmProtectedFunc, {
onEnter(args) {
console.log("[VMP] Func entered, args:");
for (let i = 0; i < 4; i++) {
try { console.log(` arg[${i}] = ${args[i]} = "${args[i].readUtf8String()}"`); }
catch(e) { console.log(` arg[${i}] = ${args[i]}`); }
}
},
onLeave(retval) {
console.log(`[VMP] Func returned: ${retval} (0x${retval.toString(16)})`);
}
});
```
### 4.3 Unidbg 模拟执行(无需真机,黑盒调用)
```bash
# 适用于 Android SO 中的 VMP 保护(Java_* 导出函数)
# 在 Unidbg 中直接调用目标 JNI 函数,忽略内部 VM 实现,只捕获结果
java -jar unidbg-android.jar \
--lib target.so \
--func Java_com_example_MainActivity_verifyLicense \
--args "test_user_id,TEST-SERIAL-1234"
```
---
## 五、 .NET 壳脱壳(.NET Reactor / ConfuserEx / Eazfuscator)
### 5.1 通用流程
```powershell
# 第一步:用 de4dot 自动识别并去混淆
.\de4dot.exe target.exe -o target_clean.exe
# 第二步:用 dnSpy 打开清理后的程序集检查
# 若 de4dot 无法识别,尝试:
.\de4dot.exe --dont-rename target.exe -o target_clean.exe
```
### 5.2 运行时 dump(对抗字符串/资源加密)
```javascript
// Frida 方案:在 .NET CLR System.Reflection.Assembly.Load 时捕获解密后的程序集
const Load = Module.getExportByName('clr.dll', '_CorExeMain');
// 等待 CLR 完成解密,dump AppDomain 中加载的所有程序集
Process.enumerateModules().forEach(m => {
if (m.name.endsWith('.dll') || m.name.endsWith('.exe')) {
console.log(`[CLR Module] ${m.name} @ ${m.base} size=${m.size}`);
// 用 Memory.readByteArray 导出并 PE-fix
}
});
```
---
## 六、 脱壳后完整性验证
```powershell
# 快速验证脱壳后 PE 是否可正常分析
$r2 = ".\Tool\mcp\Tool\safe\radare2\bin\rabin2.exe"
# 检查 PE 头完整性
& $r2 -I target_unpacked.exe | Select-String "class|type|bits|os|subsys"
# 检查导入表是否正常恢复
& $r2 -i target_unpacked.exe | head -20
# 检查节区属性(脱壳后 .text 应为 RX)
& $r2 -S target_unpacked.exe
```
---
## 七、 两击熔断纪律 —— 脱壳的止损规则
| 情形 | 触发 | 处置 |
|---|---|---|
| ESP 定律 2 次均找不到 OEP | 壳可能有 TLS 回调或多层包装 | 改用节区执行监控策略 |
| 节区执行监控 2 次失败 | 壳对内存断点有检测 | 改用 API 断点反向追踪 |
| 所有 Dump 策略失败 | 可能有内核级保护 | 声明"当前工具链无法完全脱壳",切换 Unidbg Emulation 提取算法关键结果 |
+143 -8
View File
@@ -277,47 +277,145 @@ def seep_r2_functions(binary_path: str, filter_name: Optional[str] = None, limit
return "\n".join(res) return "\n".join(res)
@server.tool() @server.tool()
def seep_r2_disasm(binary_path: str, target: str = "main", lines: int = 60) -> str: def seep_r2_disasm(
"""反汇编指定函数或地址,输出包含交叉引用 (XREF)、局部变量与源码行号提示的汇编代码。 binary_path: str,
target: str = "main",
lines: int = 60,
output_mode: str = "full",
limit_tokens: int = 0,
) -> str:
"""反汇编指定函数或地址,支持三种输出模式以控制上下文消耗(Context Budget Control)。
Args: Args:
binary_path: 目标二进制文件路径 binary_path: 目标二进制文件路径
target: 目标函数名、符号或虚拟地址(例如 'main', 'sym.imp.puts', '0x1013ef0') target: 目标函数名、符号或虚拟地址(例如 'main', 'sym.imp.puts', '0x1013ef0')
lines: 当以地址为目标时反汇编的最大指令行数,默认 60 lines: 当以地址为目标时反汇编的最大指令行数,默认 60
output_mode: 输出模式:
'full' = 完整原始反汇编(默认,适合精细分析)
'branch' = 仅保留条件跳转与调用指令(cmp/test/jcc/call/ret),聚焦控制流骨架
'summary'= 不输出指令,只统计:函数大小、块数、调用链、导入函数列表(最省 Token)
limit_tokens: 最大输出字符数(0=不限制),超出时尾部截断并附注 Truncated 标记
""" """
if not os.path.isfile(binary_path): if not os.path.isfile(binary_path):
return f"[ERROR]: Target file does not exist: {binary_path}" return f"[ERROR]: Target file does not exist: {binary_path}"
# 优先尝试函数级反汇编 pdf,若不是已知函数则按指令行数 pd 反汇编 if output_mode == "summary":
# 仅抽取函数级摘要统计,不输出原始指令
info_cmd = f"aaa; afi @ {target}"
xref_cmd = f"axt @ {target}"
call_cmd = f"axf @ {target}"
info_out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", info_cmd, os.path.abspath(binary_path)], timeout=60)
xref_out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", xref_cmd, os.path.abspath(binary_path)], timeout=60)
call_out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", call_cmd, os.path.abspath(binary_path)], timeout=60)
summary = (
f"[DISASM SUMMARY MODE — 上下文降噪 Token 节省模式]\n"
f"Target: {target}\n"
f"--- Function Info (afi) ---\n{_clean_r2_output(info_out)[:800]}\n"
f"--- Callers (axt / xrefs-to) ---\n{_clean_r2_output(xref_out)[:600]}\n"
f"--- Callees (axf / xrefs-from) ---\n{_clean_r2_output(call_out)[:600]}\n"
f"提示: 使用 output_mode='branch' 或 'full' 获取指令级详情。"
)
return summary
# 获取完整反汇编
cmd = f"aaa; pdf @ {target}" cmd = f"aaa; pdf @ {target}"
out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60) out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60)
cleaned = _clean_r2_output(out) cleaned = _clean_r2_output(out)
output_lines = [l for l in cleaned.splitlines() if not l.startswith("INFO:") and not l.startswith("WARN:")] output_lines = [l for l in cleaned.splitlines() if not l.startswith("INFO:") and not l.startswith("WARN:")]
if not output_lines or all(not l.strip() for l in output_lines): if not output_lines or all(not l.strip() for l in output_lines):
cmd = f"pd {lines} @ {target}" cmd = f"pd {lines} @ {target}"
out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60) out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60)
cleaned = _clean_r2_output(out) cleaned = _clean_r2_output(out)
output_lines = [l for l in cleaned.splitlines() if not l.startswith("INFO:") and not l.startswith("WARN:")] output_lines = [l for l in cleaned.splitlines() if not l.startswith("INFO:") and not l.startswith("WARN:")]
return "\n".join(output_lines) or "[Disassembly empty or symbol not found]" if output_mode == "branch":
# 过滤:仅保留含有控制流关键词的行(条件跳转、调用、返回)
branch_keywords = ("cmp ", "test ", "je ", "jne ", "jz ", "jnz ", "jb ", "jbe ",
"ja ", "jae ", "jl ", "jle ", "jg ", "jge ", "jmp ", "call ",
"ret", "jcc", "cbz ", "cbnz ", "bl ", "bne ", "beq ")
filtered = [l for l in output_lines if any(kw in l.lower() for kw in branch_keywords)]
total = len(output_lines)
kept = len(filtered)
header = (
f"[DISASM BRANCH MODE — 控制流骨架截面,已过滤 {total - kept}/{total} 行纯数据指令]\n"
f"Target: {target} | 保留行: {kept} | 完整输出请用 output_mode='full'\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
)
output_lines = filtered
result = header + "\n".join(output_lines)
else:
result = "\n".join(output_lines)
if limit_tokens > 0 and len(result) > limit_tokens:
result = result[:limit_tokens] + f"\n... [TRUNCATED at {limit_tokens} chars — use offset/target narrowing for full output]"
return result or "[Disassembly empty or symbol not found]"
@server.tool() @server.tool()
def seep_r2_decompile(binary_path: str, target: str = "main") -> str: def seep_r2_decompile(
"""将目标函数反编译为类 C 伪代码(使用 radare2 内置反编译引擎 pdc)。 binary_path: str,
target: str = "main",
output_mode: str = "full",
limit_tokens: int = 8000,
) -> str:
"""将目标函数反编译为类 C 伪代码,支持三档输出模式以控制 Token 消耗(Context Budget Control)。
Args: Args:
binary_path: 目标二进制文件路径 binary_path: 目标二进制文件路径
target: 目标函数名称或虚拟地址(例如 'main', 'sym.verify_key') target: 目标函数名称或虚拟地址(例如 'main', 'sym.verify_key')
output_mode: 输出控制模式(默认 full):
'full' = 完整伪代码(适合深度分析,默认)
'fold' = 折叠纯计算/赋值语句,仅保留 if/else/switch/return/API调用行(减少 ~60% Token)
'summary' = 不输出伪代码,仅返回:函数签名、入参、出参、直接调用的 API/函数名列表(最省 Token)
limit_tokens: 最大输出字符数(默认 8000),超出则截断附 Truncated 标记;设 0 表示不限制
""" """
if not os.path.isfile(binary_path): if not os.path.isfile(binary_path):
return f"[ERROR]: Target file does not exist: {binary_path}" return f"[ERROR]: Target file does not exist: {binary_path}"
if output_mode == "summary":
# 获取函数摘要信息(不输出函数体)
cmds = f"aaa; afi @ {target}; axf @ {target}"
out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmds, os.path.abspath(binary_path)], timeout=60)
clean = _clean_r2_output(out)
return (
f"[DECOMPILE SUMMARY MODE — 最省 Token 函数摘要]\n"
f"Target: {target}\n"
f"{clean[:1200]}\n"
f"提示: 使用 output_mode='fold' 或 'full' 获取伪代码详情。"
)
cmd = f"aaa; pdc @ {target}" cmd = f"aaa; pdc @ {target}"
out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60) out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60)
cleaned = _clean_r2_output(out) cleaned = _clean_r2_output(out)
output_lines = [l for l in cleaned.splitlines() if not l.startswith("INFO:") and not l.startswith("WARN:")] output_lines = [l for l in cleaned.splitlines() if not l.startswith("INFO:") and not l.startswith("WARN:")]
return "\n".join(output_lines) or "[Decompile output empty]"
if output_mode == "fold":
# 折叠模式:去除纯赋值/加减乘除/临时变量行,仅保留控制流与 API 调用
def _is_control_or_call(line: str) -> bool:
stripped = line.strip()
keywords = ("if ", "else", "switch", "case ", "return", "while ", "for ",
"do {", "goto ", "break", "continue",
"call ", "(", "->")
# 包含括号 = 可能是函数调用,包含关键词 = 控制流
return any(k in stripped for k in keywords) or stripped.startswith("//")
folded = [l for l in output_lines if _is_control_or_call(l)]
total = len(output_lines)
kept = len(folded)
header = (
f"[DECOMPILE FOLD MODE — 已折叠 {total - kept}/{total} 行纯赋值/计算语句,上下文降噪约 {int((total-kept)/max(total,1)*100)}%]\n"
f"Target: {target} | 保留行: {kept} | 完整输出请用 output_mode='full'\n"
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
)
result = header + "\n".join(folded)
else:
result = "\n".join(output_lines)
if limit_tokens > 0 and len(result) > limit_tokens:
result = result[:limit_tokens] + f"\n... [TRUNCATED at {limit_tokens} chars — 使用 target 缩小范围或 output_mode='fold'/'summary' 降低消耗]"
return result or "[Decompile output empty]"
@server.tool() @server.tool()
def seep_r2_diff(file_a: str, file_b: str, mode: str = "code") -> str: def seep_r2_diff(file_a: str, file_b: str, mode: str = "code") -> str:
@@ -366,6 +464,43 @@ def seep_r2_asm(code_or_hex: str, arch: str = "x86", bits: int = 64, disasm: boo
# 3. Android APK 逆向与动态 Hook 生成 # 3. Android APK 逆向与动态 Hook 生成
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@server.tool() @server.tool()
def seep_r2_xrefs(
binary_path: str,
target: str,
direction: str = "to",
limit: int = 10,
) -> str:
"""获取指定地址/函数的交叉引用关系,内置分页与统计摘要避免大量 xref 结果内爆上下文。
Args:
binary_path: 目标二进制文件路径
target: 目标函数名或地址(如 'sym.check_license', '0x401234')
direction: 方向— 'to'=谁引用了它(调用方), 'from'=它引用了谁(被调用方)
limit: 最多返回多少条引用(默认 10),超出部分附加统计摘要
"""
if not os.path.isfile(binary_path):
return f"[ERROR]: Target file does not exist: {binary_path}"
r2_cmd = f"aaa; {'axt' if direction == 'to' else 'axf'} @ {target}"
out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", r2_cmd, os.path.abspath(binary_path)], timeout=60)
cleaned = _clean_r2_output(out)
lines = [l for l in cleaned.splitlines() if l.strip() and not l.startswith("INFO:") and not l.startswith("WARN:")]
total = len(lines)
shown = lines[:limit]
result_lines = [
f"[XREFS {direction.upper()} — {target} | 共 {total} 条引用,显示前 {min(limit, total)} 条]",
f"注意: 若 total 远大于 limit,考虑缩小 limit 或先查 summary 模式确定目标函数。",
"━" * 60,
] + shown
if total > limit:
result_lines.append(f"... [已截断,剩余 {total - limit} 条未展示 | 增大 limit 或用 filter_name 缩小目标范围]"
f"\n模块来源分布小计: {dict(list({l.split()[2] if len(l.split())>2 else '?' for l in lines}))[:3]}"
)
return "\n".join(result_lines)
@server.tool()
def seep_apk_info(apk_path: str) -> str: def seep_apk_info(apk_path: str) -> str:
"""原生解析 APK 压缩包结构:统计 DEX 文件、Native .so 架构动态库、资源分布,并提取 AndroidManifest.xml 关键字符串(免 Java 环境)。 """原生解析 APK 压缩包结构:统计 DEX 文件、Native .so 架构动态库、资源分布,并提取 AndroidManifest.xml 关键字符串(免 Java 环境)。
+12
View File
@@ -241,6 +241,18 @@ metadata:
| `references/license-validation.md` | 393 | 许可 / 激活 / 卡密 / 订阅类目标(16 节全量手册) | | `references/license-validation.md` | 393 | 许可 / 激活 / 卡密 / 订阅类目标(16 节全量手册) |
| `references/tooling.md` | 42 | 找现成脚本 / 工程模板时 | | `references/tooling.md` | 42 | 找现成脚本 / 工程模板时 |
### MANUAL/ 战术手册索引(按需查阅)
> MANUAL/ 存放深度专项 SOP,由 Agent 在触碰对应信号时主动加载。
| 手册 | 触发条件 | 核心内容 |
| :--- | :--- | :--- |
| `MANUAL/ANTI-DEBUG.md` | G0/G1 检测到 `IsDebuggerPresent`、`NtQueryInformationProcess`、驱动级检测、时序反调试等信号 | 全量反调试绕过字典、代理 DLL 打桩框架、内核层 SOP 与两击熔断规则 |
| `MANUAL/UNPACKING.md` | G0/G1 检测到 `MPRESS/UPX/Themida/.vmp` 节名,或 `seep_auto_triage` 熵值 > 7.2 | 壳类型速查表、OEP 定位三策略、Scylla 内存 Dump 全流程、VMP 动态 trace 路线 |
| `MANUAL/POC-VALIDATION.md` | G4/G5 生成 PoC 后(Frida 脚本 / Smali 补丁 / 代理 DLL)必读 | 三阶段闭环验证模型、Frida 自愈映射表、Smali/DLL 本地加载器、断网铁证流程 |
| `MANUAL/IDA-PRO.md` | 用户询问 IDA Pro 接入或 ida MCP 无法启动时 | IDA 安装配置、install-ida.ps1、Radare2 降级路线 |
| `MANUAL/PREREQUISITES.md` | 首次部署或环境报错时 | Python/Node/JDK/ADB 版本要求与修复指引 |
### 快速对应 ### 快速对应
| 你要做的事 | 加载 | | 你要做的事 | 加载 |
+29 -2
View File
@@ -157,7 +157,7 @@ if (Test-Path $AgentDir) {
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 5. MCP 自动化服务层与底层工具 (MCP & Native Tools) # 5. MCP 自动化服务层与底层工具 (MCP & Native Tools)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
Write-Host "`n[5/6] 🔌 MCP 服务引擎与物理内置工具箱 (Native Tools)" -ForegroundColor White Write-Host "`n[5/7] 🔌 MCP 服务引擎与物理内置工具箱 (Native Tools)" -ForegroundColor White
Test-CheckItem "MCP" "核心服务端脚本 (seep_mcp_server.py 语法自洽)" { Test-CheckItem "MCP" "核心服务端脚本 (seep_mcp_server.py 语法自洽)" {
$sp = Join-Path $ToolDir 'mcp\seep_mcp_server.py' $sp = Join-Path $ToolDir 'mcp\seep_mcp_server.py'
@@ -206,7 +206,7 @@ Test-CheckItem "依赖" "Playwright 浏览器自动化依赖已解压 (playwrigh
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 6. 环境运行时与第三方依赖配置 # 6. 环境运行时与第三方依赖配置
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
Write-Host "`n[6/6] ⚙️ 外部运行时与商业授权协同 (Runtime & Commercial)" -ForegroundColor White Write-Host "`n[6/7] ⚙️ 外部运行时与商业授权协同 (Runtime & Commercial)" -ForegroundColor White
Test-CheckItem "运行时" "Python 解释器 (3.11+ 且可执行)" { Test-CheckItem "运行时" "Python 解释器 (3.11+ 且可执行)" {
$py = Get-Command python -ErrorAction SilentlyContinue $py = Get-Command python -ErrorAction SilentlyContinue
$py -ne $null $py -ne $null
@@ -235,6 +235,33 @@ Test-ManualItem "配置" "Claude Code 项目级 MCP 注册 (.mcp.json 在根目
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# 汇总仪表盘 # 汇总仪表盘
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# 7. MANUAL/ 战术手册完备性校验 (Tactical Manuals)
# -----------------------------------------------------------------------------
Write-Host "`n[7/7] 📚 MANUAL/ 战术手册完备性校验" -ForegroundColor White
$ManualDir = Join-Path $Root 'MANUAL'
Test-CheckItem "手册" "环境预要求指南 (MANUAL/PREREQUISITES.md)" {
Test-Path (Join-Path $ManualDir 'PREREQUISITES.md')
} "安装指南缺失,重新拉取仓库"
Test-CheckItem "手册" "IDA Pro 商业软件接入指南 (MANUAL/IDA-PRO.md)" {
Test-Path (Join-Path $ManualDir 'IDA-PRO.md')
} "IDA-PRO 指南缺失"
Test-CheckItem "手册" "反调试绕过战术手册 (MANUAL/ANTI-DEBUG.md)" {
Test-Path (Join-Path $ManualDir 'ANTI-DEBUG.md')
} "调试应对手册缺失,重新拉取仓库"
Test-CheckItem "手册" "通用脱壳前置分析 SOP (MANUAL/UNPACKING.md)" {
Test-Path (Join-Path $ManualDir 'UNPACKING.md')
} "脱壳手册缺失,重新拉取仓库"
Test-CheckItem "手册" "PoC 闭环自动化验证 SOP (MANUAL/POC-VALIDATION.md)" {
Test-Path (Join-Path $ManualDir 'POC-VALIDATION.md')
} "PoC 验证手册缺失,重新拉取仓库"
Write-Host "`n================================================================================" -ForegroundColor White Write-Host "`n================================================================================" -ForegroundColor White
Write-Host (" [体检报告] 核心检查通过: {0} 项" -f $script:OkCount) -ForegroundColor Green Write-Host (" [体检报告] 核心检查通过: {0} 项" -f $script:OkCount) -ForegroundColor Green