Files

189 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 提取算法关键结果 |