15、加壳与脱壳(上):什么是加壳、常见壳的分类
各位同学,今天我们来聊聊逆向工程里一个绕不开的话题——加壳与脱壳。说实话,我刚开始学逆向那会儿,最头疼的就是碰到加壳的程序。你辛辛苦苦分析半天,结果发现代码全是乱的,入口点也找不到,那种挫败感,嗯,我懂。
但别急,这恰恰是逆向的魅力所在。加壳就像给程序穿了一件“铠甲”,而我们要做的,就是把这件铠甲脱下来。今天这堂课,我们先从基础讲起,把“壳”这个东西彻底搞明白。
什么是加壳?
加壳,说白了就是给原始程序包上一层“外衣”。这层外衣负责在程序运行时,先把原始代码解压或解密到内存中,然后再跳转到真正的入口点去执行。
你想想看,一个正常的PE文件,它的入口点(OEP)是直接指向程序代码的。但加壳之后,入口点变成了壳代码的入口。壳代码先运行,完成解压或解密工作,最后才把控制权交给原始程序。
核心概念:加壳后的程序,入口点 ≠ 原始OEP。壳代码的入口叫“壳入口”,原始程序的入口叫“原始OEP”。脱壳的目标,就是找到这个原始OEP。
我在项目中遇到过不少恶意样本,它们特别喜欢用加壳来逃避杀软检测。有一次分析一个勒索软件,它用了三层壳,每一层都做了反调试。嗯,那确实是个难忘的夜晚。
常见壳的分类
壳的种类很多,但大致可以分为三类:压缩壳、加密壳、以及变态级的VMProtect。我们一个一个来看。
1. 压缩壳
压缩壳的目的很简单——减小程序体积。它就像给程序打了个zip包,运行时再解压。这类壳通常不加密,只是压缩,所以脱壳相对容易。
| 壳名称 | 特点 | 脱壳难度 |
|---|---|---|
| UPX | 最经典的压缩壳,开源,支持多种格式 | ★☆☆☆☆ |
| ASPack | Windows平台常见,压缩率高 | ★★☆☆☆ |
| MPRESS | 压缩比高,支持多平台 | ★★☆☆☆ |
我个人习惯,拿到一个未知样本,先用UPX的-d参数试试能不能直接脱。如果能,那省大事了。如果不能,再考虑其他方法。
2. 加密壳
加密壳比压缩壳狠多了。它不仅压缩,还会对原始代码进行加密,甚至修改PE结构,让分析工具无法正常识别。这类壳的主要目的是防止逆向分析。
| 壳名称 | 特点 | 脱壳难度 |
|---|---|---|
| ASProtect | 早期很流行,有反调试、反dump机制 | ★★★☆☆ |
| Armadillo | 支持多层保护,有CopyMem-II等高级特性 | ★★★★☆ |
| Enigma Protector | 现代加密壳,支持虚拟化、反调试 | ★★★★☆ |
为什么加密壳更难脱?因为它会在内存中动态解密代码,而且解密后的代码可能只存在一瞬间,你还没来得及dump,它又加密回去了。我曾经调试一个Armadillo加壳的程序,花了整整两天才找到正确的dump时机。
3. VMProtect
VMProtect是壳中的“天花板”。它不压缩也不加密,而是把原始代码翻译成一种虚拟机的字节码,然后用自己的虚拟机解释执行。你看到的代码全是VM指令,根本看不懂。
注意:VMProtect脱壳几乎不可能。通常的做法是分析VM虚拟机的行为,或者通过trace记录执行路径,还原出原始逻辑。这已经属于高级逆向的范畴了。
说实话,碰到VMProtect,我一般会先评估一下值不值得脱。如果只是分析一个功能点,可能用hook或者动态跟踪更划算。
查壳工具
在动手脱壳之前,第一步是查壳。你得知道对面穿的是什么铠甲,才能决定用什么武器。这里介绍两个我常用的工具。
PEiD
PEiD是老牌查壳工具,虽然已经很久不更新了,但对付经典壳依然好用。它通过特征码匹配来识别壳的类型。
使用方法:
1. 打开PEiD
2. 把目标程序拖进去
3. 看右下角显示的壳信息
示例输出:
"UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo"
"ASPack 2.12 -> Alexey Solodovnikov"
PEiD的缺点也很明显——对新壳基本无能为力。而且它容易被伪造的特征码欺骗。我记得有一次,一个样本故意在PE文件里插入了UPX的签名,PEiD直接报UPX,结果我脱了半天才发现根本不是。
Detect It Easy (DIE)
DIE是PEiD的现代替代品,目前还在活跃更新。它支持多种检测方式,包括特征码、熵值分析、结构分析等。
DIE的优势:
- 支持更多壳类型
- 可以显示熵值(高熵值通常表示加密或压缩)
- 支持插件扩展
- 界面更友好
使用技巧:
- 查看"Entropy"标签,如果熵值接近8.0,说明代码被加密或压缩过
- 查看"Versions"标签,可以看到可能的壳版本
我个人现在主要用DIE,偶尔用PEiD做交叉验证。两个工具配合使用,基本能覆盖90%以上的壳。
手动寻找OEP:ESP定律法
查完壳,接下来就是脱壳了。手动脱壳的第一步,就是找到原始OEP。这里介绍一个经典方法——ESP定律法。
ESP定律的原理很简单:壳代码在跳转到原始OEP之前,一定会把原始程序的上下文环境(寄存器状态)恢复好。而ESP寄存器(栈指针)在壳代码执行过程中,通常会被用来保存原始程序的栈环境。
具体操作步骤:
- 用调试器(如x64dbg)加载加壳程序
- 程序会在壳入口处断下(通常是pushad指令)
- 记录当前ESP的值
- 在内存窗口中,对ESP地址下硬件访问断点(Hardware Breakpoint)
- 运行程序,断点会在壳代码访问ESP时触发
- 继续运行,直到断点停在一条跳转指令附近
- 这条跳转指令的目标地址,就是原始OEP
小技巧:很多壳在跳转到OEP之前,会执行一条"popad"指令恢复寄存器。如果你看到popad,紧跟其后的跳转大概率就是去OEP的。
为什么ESP定律好用?因为壳代码无论如何伪装,它总得在某个时刻把栈环境恢复成原始程序的样子。这个“恢复”的动作,必然涉及对ESP的操作。我们只要抓住这个操作,就能找到OEP。
我曾经用ESP定律脱过一个加了三层壳的样本。第一层是UPX,第二层是ASPack,第三层是自定义的简单加密。每一层都用ESP定律找到了OEP,然后dump出来,再修复IAT。嗯,虽然过程繁琐,但思路清晰,最后成功还原了原始程序。
知识体系图
下面这张图总结了本章的核心知识结构,你可以对照着梳理一下思路。
小结
好了,今天的内容就到这里。我们讲了加壳的本质、常见壳的分类、查壳工具的使用,以及ESP定律法找OEP。这些都是脱壳的基础,也是你后续学习更高级脱壳技术的基石。
说实话,脱壳这件事,理论讲再多都不如动手练一次。我建议你找个UPX加壳的小程序,用ESP定律法亲手脱一次。第一次可能会有点懵,但多试几次,你就会发现其中的规律。
嗯,记住一点:脱壳不是目的,分析原始代码才是。别为了脱壳而脱壳,搞清楚程序到底在干什么,才是我们逆向工程师的本职工作。