第十六章:加壳与脱壳(下)——使用工具脱壳与手动脱壳实战
好,咱们接着上一章聊。上回我们把加壳的原理和常见壳的特征讲透了,这一章直接上硬菜——怎么把壳脱掉。说实话,脱壳这件事,在逆向工程里属于「会者不难,难者不会」的典型。我刚开始学的时候,对着一个UPX加壳的程序折腾了一下午,最后发现一条命令就搞定了……嗯,那种感觉,你们懂的。
16.1 工具脱壳:UPX与ASPack
先讲最简单的——用工具脱壳。说白了,有些壳自带脱壳功能,或者有专门的脱壳工具。你不需要懂汇编,不需要会调试,点几下鼠标就完事。但我要提醒一句:工具脱壳只适用于标准壳,遇到变种或自定义壳,工具就废了。
16.1.1 UPX脱壳
UPX是最常见的壳,没有之一。它本身就是一个压缩工具,加壳和脱壳用的是同一个程序。命令极其简单:
upx -d 目标程序.exe
就这一行,完事。我曾经在分析一个恶意样本时,发现它用了UPX加壳,但加了自定义的魔数(Magic Number),导致UPX识别不了。这时候怎么办?手动改一下文件头,把魔数改回标准的"UPX0""UPX1",再执行脱壳命令,就搞定了。说白了,UPX的脱壳逻辑是固定的,它只认文件头里的标记。
16.1.2 ASPack脱壳
ASPack是另一个常见的壳,多见于一些老旧的Windows程序。它不像UPX那样自带脱壳功能,需要借助第三方工具。我个人习惯用 UnAspack 或 Quick Unpack。
操作步骤很简单:
- 打开UnAspack
- 选择目标文件
- 点击"Unpack"按钮
但要注意,ASPack有多个版本,2.x和2.4x的脱壳方式略有不同。我记得有一次,一个ASPack 2.4加壳的程序,用UnAspack死活脱不下来。后来发现是程序有自校验,脱壳后文件校验和不一致,程序直接崩溃。解决办法是:先脱壳,再用十六进制编辑器把自校验的跳转改掉。
16.2 手动脱壳实战:单步跟踪法
工具搞不定的,就得手动来。手动脱壳的核心思路就一句话:找到原始入口点(OEP)。壳执行完它的解密逻辑后,最终会跳转到原始程序的入口。我们只要抓住那个跳转,就能dump出原始代码。
单步跟踪法,说白了就是一步一步跟着壳走,直到它跳到OEP。听起来简单,但实际操作时,壳会设置各种反调试陷阱,比如SEH异常、花指令、垃圾代码等。
16.2.1 操作步骤
- 用OD(OllyDbg)加载加壳程序
- 停在壳的入口点(通常是pushad)
- 开始单步跟踪(F8),遇到call就F7进去
- 注意观察堆栈和寄存器变化
- 当看到类似
jmp eax或call eax时,很可能就是跳向OEP
我给你们一个经验值:UPX壳通常在单步跟踪100-200步后就能看到OEP。ASPack稍微复杂点,可能需要300步左右。但如果你走了500步还没看到OEP,八成是走岔路了。
push ebp
mov ebp, esp
push -1
push 00401000 ; 典型的VC++入口
如果你看到这样的代码,恭喜,找到OEP了。
16.2.2 避坑指南
我曾经在跟踪一个加了VMP(VMProtect)壳的程序时,单步跟踪了整整两个小时,最后发现它根本就不是线性执行的——VMP会把代码虚拟化,单步跟踪根本找不到OEP。所以,单步跟踪法只适用于压缩壳(UPX、ASPack、MPRESS等),不适用于保护壳(VMP、Themida、Enigma等)。
16.3 手动脱壳实战:内存镜像法
内存镜像法,也叫"断点法"。它的原理是:壳在解密原始代码后,会把原始代码加载到内存中。我们只需要在内存中搜索原始代码的特征,就能找到OEP。
16.3.1 操作步骤
- 用OD加载程序,停在壳入口
- 按Alt+M打开内存映射窗口
- 找到程序的代码段(通常是.text或CODE段)
- 在代码段设置内存访问断点(F2)
- 按F9运行,程序会在访问代码段时断下
- 断下后,再单步几次,就能看到OEP
为什么这个方法有效?因为壳在解密完成后,必须跳转到原始代码段去执行。你想想看,它不跳过去,程序怎么跑?所以,在代码段设断点,就是守株待兔。
16.4 IAT修复与重建
脱壳后,最头疼的问题就是IAT(导入地址表)损坏。壳为了保护原始导入表,通常会把它加密或压缩。脱壳后,IAT是乱的,程序一运行就崩溃。
这时候就需要用 ImportREC 这个神器了。它的工作原理是:从内存中扫描出所有被调用的API函数,然后重建一个干净的IAT。
16.4.1 使用ImportREC修复IAT
- 打开ImportREC,选择脱壳后的进程
- 填入OEP的RVA(相对虚拟地址)
- 点击"自动搜索IAT"
- 如果自动搜索失败,手动指定IAT的起始地址和大小
- 点击"获取导入表"
- 检查导入表是否完整(有没有无效的API)
- 点击"修复转储文件",选择之前dump出来的文件
我遇到过最坑的情况是:自动搜索IAT时,ImportREC把一些数据段误认为是IAT,结果修复出来的导入表全是乱码。这时候怎么办?手动分析。用OD打开脱壳后的程序,找到那些call指令,看看它们跳转到哪里。如果跳转地址在某个DLL的范围内,那就是一个有效的导入函数。
- 每个导入函数必须指向正确的DLL和函数名
- IAT表中的地址必须是连续的
- 如果某个API显示为"无效",检查是不是被重定向了
16.5 知识体系总览
下面这张图,是我自己总结的脱壳知识体系。你把它理解了,脱壳这件事就通了八成。
16.6 实战总结
脱壳这件事,说白了就是一场「猫鼠游戏」。壳在不断地进化,脱壳技术也在不断地更新。我给你们一个建议:先学会工具脱壳,再学手动脱壳。工具能解决的,别浪费时间手动搞。但工具搞不定的,手动技术就是你的底牌。
最后,分享一个我自己的习惯:每次脱壳成功后,我都会用 PEiD 或 Detect It Easy 再查一下,确认壳确实被脱干净了。有时候壳会残留一些垃圾代码,虽然不影响运行,但会影响后续分析。
公众号:蓝海资料掘金营,微信 deep3321