executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
re-unpack-simple压缩壳脱壳:UPX/ASPack/FSG。触发词:脱壳、unpack、UPX、esp定律、OEP
Independent GitHub snapshot · Automated classification · Original documentation governs
pushad/pusha 特征;需要干净样本继续静态/动态分析apt install upx-ucl / dnf install upx / pacman -S upxbrew install upxupx/upx 下载 win64 zip(或 choco install upx)upx --versionapt install gdb / dnf install gdb / pacman -S gdb,验证 gdb --version。Wine 直读:wine sample.exe 运行后 gdb -p <pid> attach 直接操作 Wine 进程(见 [[re-analyze/platform-tips]] Linux 分支)NtQuery/Scylla release按顺序执行,每步记录结果(证据路径 + sha256,见 [[re-triage]]);动手前先按 [[re-triage]] 记录脱壳基线五件套(SHA256 / PE timestamp / EP RVA / section hash / import table),脱壳后逐项对照再判定成败。
优先官方 / 自动解包(upx -d):
upx -d sample.exe -o unpacked.exe
file unpacked.exe # 应显示正常 PE 结构;仍报 UPX / 报错 → 失败
sha256sum unpacked.exe
upx -d 失败常见原因:文件被修改过(自校验 / 二次加壳)、UPX 版本不兼容。失败则走步骤 2 手动流程。
ESP 定律找 OEP(设断在 pushad 后):
pushad 保存寄存器 → 壳解密完毕后 popad → jmp OEP。对 pushad 之后的 ESP 所指地址下硬件访问断点,运行后在 popad 后的第一条指令附近找 OEP。pushad 压栈是壳"保存现场"的必然行为,ESP 定律利用它定位"恢复现场"点——popad 恢复后即解密完成、控制流将跳真实代码。它只在"入口压栈保护现场、尾部恢复后跳转"这一实现下成立,并非对一切壳都有效:TLS 回调壳(回调先于 EP 执行,EP 处无压栈现场)、多线程壳(多线程各自压栈、断点命中的栈地址错位)、VMProtect/Themida 等虚拟化壳(入口直接进 VM dispatcher,无 pushad/popad 周期)都不适用。先确认 EP 入口确实是 pushad/popad 周期再用,否则直接走步骤 3 或转 [[re-unpack-advanced]]。pushad → 再单步执行 pushad → 记录 pushad 执行后的 ESP 值 → 右键 ESP 寄存器 → Hardware Breakpoint(on access)→ 运行 → 断在 popad 后第一条指令 → 下面几行 jmp 的目标即 OEP。记下 OEP 地址。(gdb) starti # 停在 EP
(gdb) si # 单步执行 pushad(x86 pushad 压 8 个寄存器)
(gdb) awatch *(int*)$esp # 对 pushad 后的 ESP 所指地址下硬件观察点(访问即断)
(gdb) continue # 运行,popad 访问该地址时触发
push ebp; mov ebp,esp),其后紧跟大量正常 API 引用。内存断点法(VirtualAlloc 断点):
push ebp; mov ebp,esp + 后续密集正常 API 引用),多阶段壳重复下断直到控制流稳定离开壳代码,再进入步骤 4。bp VirtualAlloc → 每次返回后看分配区域是否被写入可执行内容(壳的解压目标节)→ 对该区域下内存访问断点(View > Memory Map > 目标节 > Set breakpoint on access)→ 运行到壳完成解密处。也可直接对壳第二节(UPX1/.aspack)下内存断点。break *VirtualAlloc,返回后查看分配区,再对分配地址下 watch。转储 OEP(时机:解密完成后,见 [[re-analyze/platform-tips]] 关键经验;默认转储优先):
gcore -o out <pid>(完整流程见 [[re-memdump]];转储前按 maps 过滤 vsyscall/vdso)。Plugins > Scylla > Attach to process → 填 OEP 地址(步骤 2 记录值)→ Dump。先记镜像基址 + OEP 偏移。IAT 修复(Scylla/ImpREC):
Plugins > Scylla > Attach to process → OEP 填步骤 2 的地址 → IAT Autosearch → Get Imports(核对 API 解析正确)→ Fix Dump 输出 unpacked_fixed.exe。file unpacked_fixed.exe → 导入 [[re-ghidra]] / [[re-ida]] 反编译确认导入表可解析 → 沙箱内复跑([[re-sandbox]])确认行为与脱壳前一致、无自校验触发。file 仍报 UPX、反编译只见压缩数据;原因——壳未运行到 OEP 解密完成就转储;对策——解密完成后再 dump([[re-analyze/platform-tips]] 关键经验),默认转储优先GetProcAddress 动态调用、导入表解析失败;原因——导入表由壳在运行时重建,转储未修复;对策——步骤 5 Scylla/ImpREC 修复,修复后重新反编译验证gdb -p attach Wine 进程读内存、下断点、gcore 转储upx -d 解一层后 file 仍报壳特征;原因——壳套壳(如 ASPack v2.12 第一层解完还有一层在解密 IAT);对策——第一层落地后继续观察:出现又一次 pushad/popad 周期、或 VirtualFree 释放 IAT 解密堆时才是最后一步,跟到最终 jmp 并验证目标是真实代码序言(push ebp; mov ebp,esp + 密集正常 API 引用)再 dump,别在过渡 jmp 前早一跳FF 25(间接 jmp)找 IAT 起址,找不到或找到错位置;原因——不是所有程序都经间接跳转调 API(直接 CALL [addr] 很常见);对策——用调试器 Find > All intermodular calls 定位一个真实程序调用点,顺 CALL/JMP [addr] 跟到跳转表顶端计算 IAT 起址与块大小,再填 ImportREC/ScyllaSizeOfImage/NumberOfSections/SizeOfHeaders,或节表各节的 RVA/raw 范围与 SectionAlignment/FileAlignment 不匹配,或 Win7+ 上 LordPE/ImportREC 因 ASLR 失败;对策——Fix Dump 后仍异常就用 pefile 逐项核对修正这些字段,必要时关闭随机基址后重新转储。CheckSum 不属此类——普通用户态 EXE/DLL 加载期不校验,仅驱动、boot 期加载的 DLL 与载入关键系统进程的 DLL 需重算WINEDEBUG=+loaddll 看加载失败日志、断 GetVersionEx/注册表读取看是否命中环境检测;确认是 Wine 环境限制后果断转 Windows VM([[re-sandbox]])完成脱壳,不在 Wine 里反复折腾