目录脱壳寻找原始程序 OEPOEP Original Entry Point首先要运行到壳的节区位置为什么要先运行到壳的节区位置编辑如何确认程序是加壳的定位壳的程序入口点EP程序运行起来以后代码就吐出来了拿到原始程序的 OEP程序入口点寻找真实 OEP 地址的方法论下硬件访问断点具体操作步骤找到 OEP 之后——脱壳的后半场常见坑与进阶点脱壳流程速查图脱壳寻找原始程序 OEPOEP Original Entry Point首先要运行到壳的节区位置为什么要先运行到壳的节区位置加壳后磁盘上 PE 文件的原始代码节区.text / UPX0里存放的是加密/压缩后的数据不是可读指令。只有壳的 stub 代码位于壳节区是明文指令可以执行加壳PE的节区磁盘视角 ┌──────────────────────────────┐ │ 节区表 │ ├──────────────────────────────┤ │ 壳节区UPX1 / .aspack... │ ← 明文指令壳stub可执行 ├──────────────────────────────┤ │ 原始代码节区.text / UPX0 │ ← 加密/压缩数据静态不可读 └──────────────────────────────┘如果程序还没运行到壳的节区stub 未执行原始代码节区仍然处于加密/混淆状态——此时看到的全是乱码数据不是程序原本的指令。所以脱壳第一步永远是让壳代码先跑起来运行到壳的节区位置让壳自己把原始代码吐出来。程序被加载后OS 会跳转到入口点EP当前指向壳节区从这里开始运行的就是壳的 stub 代码。如何确认程序是加壳的用 DIEDetect It Easy识别壳的类型UPX / ASPack / Themida…观察节区名UPX0/UPX1、.aspack、.petite 等明显不是正常编译器的节区名看入口点正常程序 EP 指向 .text 节区加壳程序 EP 指向壳节区如 UPX1定位壳的程序入口点EP壳的入口点 加壳后 PE 的入口点OS 加载后执行的第一条指令。动态定位x64dbg / OllyDbg 载入程序后默认就停在 EP模块入口。静态定位解析 PE 头读取AddressOfEntryPoint字段或用 IDA 打开看入口处指令特征辅助判断壳类型。壳存在的标志性特征——入口处保存现场pushad ; 保存 8 个通用寄存器现场保护 pushfd ; 保存标志位寄存器 FLAGS call xxxxxxxx ; 调用壳的核心代码 ... ; 壳核心工作解密、解压、重定位、重建IAT popfd ; 恢复标志位 popad ; 恢复通用寄存器 jmp OEP ; 跳回原始入口点 ← 最终目标看到 pushad/pushfd 就说明程序被加壳了——正常编译的代码开头是push ebp; mov ebp,esp函数序言不会用 pushad/pushfd 保存全部寄存器。执行策略运行程序执行壳代码。遇到call壳的核心函数必须用F7步入进入执行——壳的核心工作解密、重建 IAT都在这个 call 里F8 跳过的话壳没执行原始代码不会还原。注意pushad/pushfd 是 32 位指令。64 位程序没有 pushad/pushfd改为逐条 push 寄存器ESP 定律不能直接用替代方案见文末补充。程序运行起来以后代码就吐出来了启动程序后壳 stub 完成解压/解密原始代码在内存中还原像从压缩包里解出来。此时进程内存 还原的原始代码 壳重建好的 IAT——已经是可以分析的形态。但不能直接 dump必须先把入口点改到原始程序的 OEP 再 dump。在壳入口 dump 的话文件运行时又会执行壳代码甚至再次解压出错等于没脱。dump 的正确时机让程序停在 OEP而不是继续往下跑。此时内存状态最干净——原始代码已解密、IAT 已重建、原始程序自身的初始化还没执行无副作用。拿到原始程序的 OEP程序入口点在壳的状态下通过分析找到原始程序入口点 OEP 的地址。把入口点修改到原始 OEP 位置然后 dump 出内存镜像→ 脱壳成功。脱壳本质让壳自己完成解压在它跳向 OEP 的那一刻截停进程把运行中的内存镜像保存成文件。寻找真实 OEP 地址的方法论调试加壳 PE 的完整思路用 x64dbg 加载加壳 PE停在壳 EP。F7 单步步入执行壳代码。此时看到的是壳的解密循环不是原始代码——不用分析它继续执行。运行完壳的代码解压结束原始代码吐出。寻找 OEP观察壳收尾的地方恢复现场 → 跳转把入口点定位到原始位置。壳 stub 的完整生命周期关键模型pushad ; ① 保存八大寄存器 pushfd ; ② 保存标志位寄存器 call 壳核心函数 ; ③ 执行壳代码解密、重定位、重建IAT——壳的正事 ... ; 中间过程不用逐条分析 popfd / popad ; ④ 恢复现场 ← 壳寿终正寝的信号 jmp OEP ; ⑤ 跳转原始入口点 ← 要抓的就是这条跳转方法论核心ESP 定律的变体前两行pushad / pushfd 保存环境。执行壳代码忽略中间过程。重点监控 popad/popfd对 pushad/pushfd 压栈的地址下一个访问类型的硬件断点观察它什么时候被 pop 出来。断点命中pop 发生→ 壳已运行完毕 →pop 指令的下一条或附近就是跳向 OEP 的跳转跳转目标就是 OEP 的真实位置。原理栈上压入的寄存器数据只有 popad 时才会被再次访问。硬件访问断点 在栈上放陷阱壳一收尾必然踩中。下硬件访问断点具体操作步骤操作流程x64dbg 为例1. 停在壳入口 EPF8 单步执行完 pushad让 ESP 指向压栈后的栈顶 ※ 必须在 pushad 执行完之后再对 ESP 下断断点地址才正确 2. 右键当前 ESP 值寄存器窗口→ 断点 → 硬件访问断点Access ※ 断点类型选访问而非执行——要等 pop 读栈时命中 3. F9 运行 4. 断点命中 → 停在 popad/popfd 附近 → 壳运行完毕 → 找到程序入口点0019FF50 46 02 00 00 34 A0 47 00 34 A0 47 00 80 FF 19 00 F...4.G.4.G...命中后的操作断下后我们就找到了popadpop 的位置。下一行就能看到跳转指令jmp 地址或jmp eax这类间接跳转——这就是壳跳回原始代码的那一跳。用F7 跟进这条 jumpjmp eax是间接跳转eax 由壳动态算出F7 跟进后才会显示真实地址。最终到达原始程序的入口点 OEP。OEP 的验证特征怎么确认真的找到了跳转目标之后是函数序言push ebp; mov ebp,esp32位或sub rsp, xx64位代码呈线性结构正常函数调用不再有解密循环后续 dump 修复后DIE 能识别出原始编译器特征找到 OEP 之后——脱壳的后半场找到 OEP ≠ 脱壳完成。dump 之后必须修复 IAT否则脱壳文件无法运行。1. 停在 OEP不要 F9 继续运行原始代码 2. 打开 Scylla 插件x64dbg 自带/ ImportREC 3. 填入 OEP 的 RVAOEP地址 − 模块基址 4. IAT Autosearch → Get Imports确认导入函数列表正确 5. Dump保存内存镜像为文件 6. Fix Dump修复 IAT / 重定位表→ 得到可运行的脱壳 PE为什么必须修复壳在运行时才通过 LoadLibrary / GetProcAddress 动态重建 IATdump 出来的是运行时状态不修复的话文件脱离调试器重新加载时没有这些导入信息直接崩。验证脱壳成功脱壳文件独立运行行为与原文件一致 DIE 不再显示壳名、能识别编译器特征。常见坑与进阶点pushad 只存在于 32 位x64 没有 pushad/pushfdESP 定律不能直接用。64 位脱壳改用对原始代码节区下内存访问断点或监控ret跳到 .text 区的那一跳。硬件断点数量有限x86/x64 只有 4 个调试寄存器DR0~DR3最多同时 4 个硬件断点。反调试壳壳会在解压前检测调试器IsDebuggerPresent / 时间差检测被检测到会走假流程解压垃圾数据。对策ScyllaHide 插件一键绕过常见反调试。F7 与 F8 的选择call壳核心函数必须 F7进入执行壳的解密循环用 F7 逐条走太慢可配合 F4运行到光标处或条件断点跳过循环。花指令 / 假跳转强壳会在跳 OEP 前插入垃圾指令junk看到多个连续跳转时跟着实际执行的走最终以目标处是函数序言为判定标准。间接跳转壳常用jmp eax / jmp [esp]动态跳转必须先 F7 跟进才能看到真正的 OEP 地址。自动化框架对应你笔记里的写壳脱壳框架寻找OEP可用 x64dbg 脚本scriptdll、x64dbgpyPython API或 Scylla 的自动化接口。框架流程 定位 EP → 执行 pushad 后对 ESP 下硬件断 → 运行断下 → 识别 popad 后第一条跳转 → 解析目标地址 → dump。壳的变体越多识别规则就要越灵活。脱壳流程速查图加载加壳PE停在壳EP │ ▼ F8 执行 pushad → 对 ESP 下硬件访问断点 │ ▼ F9 运行 → 断点命中popad/popfd处→ 壳运行完毕 │ ▼ 向下找跳转指令jmp / jmp eax→ F7 跟进 │ ▼ 到达 OEP函数序言特征→ 记下地址 │ ▼ Scylla填 OEP RVA → 搜索 IAT → Dump → Fix Dump │ ▼ 脱壳完成 → DIE 验证壳名消失编译器特征出现