From 8f3c49c84bbfd7d3e9b9541e49beb36cdf1afa7a Mon Sep 17 00:00:00 2001 From: angusdevgo Date: Thu, 24 Sep 2026 14:48:05 +0800 Subject: [PATCH] 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 --- MANUAL/ANTI-DEBUG.md | 190 +++++++++++++++++++++++++++++++++++ MANUAL/POC-VALIDATION.md | 171 +++++++++++++++++++++++++++++++ MANUAL/UNPACKING.md | 188 ++++++++++++++++++++++++++++++++++ Tool/mcp/seep_mcp_server.py | 151 ++++++++++++++++++++++++++-- Tool/skill/softseep/SKILL.md | 12 +++ setup/verify.ps1 | 31 +++++- 6 files changed, 733 insertions(+), 10 deletions(-) create mode 100644 MANUAL/ANTI-DEBUG.md create mode 100644 MANUAL/POC-VALIDATION.md create mode 100644 MANUAL/UNPACKING.md diff --git a/MANUAL/ANTI-DEBUG.md b/MANUAL/ANTI-DEBUG.md new file mode 100644 index 0000000..f0584c1 --- /dev/null +++ b/MANUAL/ANTI-DEBUG.md @@ -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 + +// 实际 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 行为无法覆盖 | 无法还原算法 | 输出"该目标超出当前工具链全自动范畴",转人工分析 | diff --git a/MANUAL/POC-VALIDATION.md b/MANUAL/POC-VALIDATION.md new file mode 100644 index 0000000..51f579f --- /dev/null +++ b/MANUAL/POC-VALIDATION.md @@ -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\ + +# 重打包 +.\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//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="将特权决策迁移至服务端,客户端仅持有短期、加密的会话票据,每次解锁必须通过服务端签名验证。" +) +``` diff --git a/MANUAL/UNPACKING.md b/MANUAL/UNPACKING.md new file mode 100644 index 0000000..98758d6 --- /dev/null +++ b/MANUAL/UNPACKING.md @@ -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 提取算法关键结果 | diff --git a/Tool/mcp/seep_mcp_server.py b/Tool/mcp/seep_mcp_server.py index 5401d4e..624c46e 100644 --- a/Tool/mcp/seep_mcp_server.py +++ b/Tool/mcp/seep_mcp_server.py @@ -277,47 +277,145 @@ def seep_r2_functions(binary_path: str, filter_name: Optional[str] = None, limit return "\n".join(res) @server.tool() -def seep_r2_disasm(binary_path: str, target: str = "main", lines: int = 60) -> str: - """反汇编指定函数或地址,输出包含交叉引用 (XREF)、局部变量与源码行号提示的汇编代码。 +def seep_r2_disasm( + binary_path: str, + target: str = "main", + lines: int = 60, + output_mode: str = "full", + limit_tokens: int = 0, +) -> str: + """反汇编指定函数或地址,支持三种输出模式以控制上下文消耗(Context Budget Control)。 Args: binary_path: 目标二进制文件路径 target: 目标函数名、符号或虚拟地址(例如 'main', 'sym.imp.puts', '0x1013ef0') lines: 当以地址为目标时反汇编的最大指令行数,默认 60 + output_mode: 输出模式: + 'full' = 完整原始反汇编(默认,适合精细分析) + 'branch' = 仅保留条件跳转与调用指令(cmp/test/jcc/call/ret),聚焦控制流骨架 + 'summary'= 不输出指令,只统计:函数大小、块数、调用链、导入函数列表(最省 Token) + limit_tokens: 最大输出字符数(0=不限制),超出时尾部截断并附注 Truncated 标记 """ if not os.path.isfile(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}" out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60) cleaned = _clean_r2_output(out) 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): cmd = f"pd {lines} @ {target}" out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60) cleaned = _clean_r2_output(out) 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() -def seep_r2_decompile(binary_path: str, target: str = "main") -> str: - """将目标函数反编译为类 C 伪代码(使用 radare2 内置反编译引擎 pdc)。 +def seep_r2_decompile( + binary_path: str, + target: str = "main", + output_mode: str = "full", + limit_tokens: int = 8000, +) -> str: + """将目标函数反编译为类 C 伪代码,支持三档输出模式以控制 Token 消耗(Context Budget Control)。 Args: binary_path: 目标二进制文件路径 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): 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}" out = _run_cmd([R2_EXE, "-q", "-e", "scr.color=0", "-c", cmd, os.path.abspath(binary_path)], timeout=60) cleaned = _clean_r2_output(out) 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() 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 生成 # --------------------------------------------------------------------------- @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: """原生解析 APK 压缩包结构:统计 DEX 文件、Native .so 架构动态库、资源分布,并提取 AndroidManifest.xml 关键字符串(免 Java 环境)。 diff --git a/Tool/skill/softseep/SKILL.md b/Tool/skill/softseep/SKILL.md index 0795179..52c2dd4 100644 --- a/Tool/skill/softseep/SKILL.md +++ b/Tool/skill/softseep/SKILL.md @@ -241,6 +241,18 @@ metadata: | `references/license-validation.md` | 393 | 许可 / 激活 / 卡密 / 订阅类目标(16 节全量手册) | | `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 版本要求与修复指引 | + ### 快速对应 | 你要做的事 | 加载 | diff --git a/setup/verify.ps1 b/setup/verify.ps1 index 24133f5..e7e443f 100644 --- a/setup/verify.ps1 +++ b/setup/verify.ps1 @@ -157,7 +157,7 @@ if (Test-Path $AgentDir) { # ----------------------------------------------------------------------------- # 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 语法自洽)" { $sp = Join-Path $ToolDir 'mcp\seep_mcp_server.py' @@ -206,7 +206,7 @@ Test-CheckItem "依赖" "Playwright 浏览器自动化依赖已解压 (playwrigh # ----------------------------------------------------------------------------- # 6. 环境运行时与第三方依赖配置 # ----------------------------------------------------------------------------- -Write-Host "`n[6/6] ⚙️ 外部运行时与商业授权协同 (Runtime & Commercial)" -ForegroundColor White +Write-Host "`n[6/7] ⚙️ 外部运行时与商业授权协同 (Runtime & Commercial)" -ForegroundColor White Test-CheckItem "运行时" "Python 解释器 (3.11+ 且可执行)" { $py = Get-Command python -ErrorAction SilentlyContinue $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 (" [体检报告] 核心检查通过: {0} 项" -f $script:OkCount) -ForegroundColor Green