壳
1. 什么是壳 (Packer)?
壳是在原始的可执行文件(如 EXE, ELF)上加的一层特殊代码。
-
物理层面: 它通过压缩、加密等手段改变原程序的代码段和数据段。
-
逻辑层面: 程序运行时,操作系统首先加载的是“壳”,由壳负责在内存中将真正的程序还原(解压/解密),然后再跳转到原程序的入口点(OEP)执行。
核心作用:
-
压缩: 减小文件体积,方便传输。
-
保护 (防逆向): 隐藏程序真实的逻辑,防止被静态分析工具(如 IDA)直接读取,对抗调试器。
2. 壳的分类
根据加壳的目的,通常分为两大类:
A. 压缩壳 (Packer)
主要目的是减小体积,通常不具备很强的对抗性。
-
UPX: 最经典的开源壳,支持多种平台(Windows, Linux 等)。
-
ASPack: Windows 平台上经典的压缩壳。
B. 加密壳/保护壳 (Protector)
主要目的是对抗反汇编、对抗调试和虚拟机检测。
-
VMProtect (VMP): 目前顶级的壳,将代码转换为自定义的虚拟机指令,极难逆向。
-
Themida: 著名的强保护壳,拥有极强的反调试和内核级保护。
-
Enigma Protector: 常用于商业软件保护。
3. 重要概念:OEP 与 Dump
-
OEP (Original Entry Point): 原程序入口点。这是脱壳的目标——找到程序在没加壳时的第一个执行地址。
-
Dump (内存转储): 当壳把程序在内存中还原后,我们将这段内存数据抓取并保存为文件的过程。
4. 如何脱壳 (Unpacking)
脱壳通常分为 自动脱壳 和 手动脱壳 (ESP 定律)。
步骤一:查壳
在开始之前,先确定是什么壳。
- 工具: Die (Detect It Easy), PEID, 或 Linux 下的
file命令。
步骤二:脱壳操作
1. 自动脱壳 (针对简单壳)
如果是 UPX 等常见压缩壳,直接使用自带工具即可:
2. 手动脱壳 (针对复杂或魔改壳)
这是 CTF 逆向的基本功,通常遵循以下流程:
-
寻找 OEP: 使用调试器(x64dbg 或 GDB)跟踪,直到找到壳跳转回原程序的那个“大跳转”。
-
Dump 内存: 在 OEP 处停下,将内存镜像导出。
-
修复 IAT (导入地址表): 加壳程序往往会破坏导入表,需要使用工具(如 Scylla)重新关联 DLL 函数。
3. ESP 定律
这是手动脱壳最常用的技巧,适用于大多数压缩壳。
-
原理: 壳在运行开始时通常会执行
PUSHAD(将所有寄存器压栈)以保存环境,运行结束准备跳回原程序时会执行POPAD(出栈)。 -
操作:
-
在程序刚启动后的第一个
PUSH动作后,观察 ESP 寄存器。 -
对该 ESP 指向的地址下 硬件访问断点 (Hardware Breakpoint)。
-
当程序运行触发该断点时,通常就在壳代码的末尾,附近必然有一个跳转通往 OEP。
-
强壳(如 ASProtect, Armadillo)会利用 SEH 来对抗单步追踪。
-
核心逻辑: 壳作者故意在代码里写一些会引发崩溃的指令(比如访问非法内存、抛出特定中断),以此来阻断调试器的单步跟踪。同时,壳会自己偷偷修改 SEH 链,把异常处理函数(Handler)指向它自己写的下一段解密代码。
-
运行轨迹: 触发异常 -> 调试器或系统接管 -> 移交给壳注册的 SEH Handler -> 壳在 Handler 中继续解密 -> 再次触发异常...
-
如何利用:
-
在调试器中,取消对所有异常的拦截(Shift+F9,让异常直接抛给程序自己处理)。
-
观察程序一共会抛出多少次异常才跑飞(或运行起来)。假设是 5 次。
-
重新加载程序,按 Shift+F9 忽略前 4 次异常。
-
在第 5 次(最后一次)异常发生时停下。此时壳已经接近完成解压。
-
查看当前的 SEH 链(在栈中或者调试器的 SEH 窗口),找到当前接管异常的 Handler 地址。
-
在这个 Handler 地址处下断点,跟进去单步走。因为这是最后一次异常,处理完之后,程序通常就会恢复寄存器并
JMP到 OEP。
-
5. 练习
-
从 UPX 开始: 自己写个 C 语言的 Hello World,用
upx加壳,然后尝试用 x64dbg 手动找到 OEP,不要只依赖命令脱壳。 -
掌握工具链:
-
Windows: x64dbg, Scylla, Die.
-
Linux: GDB (配合插件 pwndbg 或 gef).
-
-
识别特征码: 多看各种壳的开头汇编代码,久了一眼就能看出这是什么壳。