壳
壳是包裹在程序外的一层代码,通常先于程序执行,达到防止源程序不被非法修改或反编译的目的
壳的分类
壳分为压缩壳和加密壳
压缩壳
压缩壳主要是帮助减少PE文件(Windows下的文件格式)大小,隐藏PE文件内部的代码和资源
常见的压缩壳有:Upx、ASpack、PECompat
加密壳
加密壳应用有多种防止代码逆向分析的技术 ,用该壳的PE文件会比原文件大很多,有时恶意程序也用加密壳来降低杀毒软件的扫描
常见的加密壳有:ASProtector、Armadillo、EXECryptor、Themida、VMProtect
壳加载过程
1. 保存入口参数
壳初始化时会保存各寄存器的值,运行结束后由恢复这些值,最后返回原程序执行
通常用 pushad
/ popad
、pushfd
/ popfd
指令对来保存和恢复现场环境
2.获取所需函数API
先要大概知道什么是输入表,简单来说它是壳程序与DLL文件(动态链接库)交流的中枢
一般壳的输入表中只有 GetProcAddress
、GetModuleHandle
和 LoadLibrary
这几个 API 函数,若需要调用DLL文件中的其他API函数代码,需要通过
LoadLibraryA(W)
或LoadLibraryExA(W)
将 DLL 文件映射到内存中,就可以- 调用
GetModuleHandleA(W)
函数获得 DLL 模块句柄,一旦 DLL 模块被加载,就可以 - 调用
GetProcAddress
函数获取输入函数的地址
3.解密各区块数据
壳一般会按区块加密以保护源程序,源程序执行时又按区块解密,以让程序正常运行
4.跳转回原程序入口
壳一般会构造自己的输入表(IAT),所以跳回前会先回复之前PE文件的输入表,处理好重定向项(主要是 DLL 文件),之后会将控制权移交原程序执行。
脱壳方法
1、单步跟踪法
我们完整运行脱壳程序,跳过循环恢复代码的片段,边调试边观察,确保程序不会略过EOP,让软件实现自动脱壳
要点:
- 跟踪过程是不修改任何代码的
- 使用F7[单步步入]、F8[单步步过]、F4[直接执行到指定位置]等功能完整的进行自脱壳的过程
- 大循环用f4跳过,尽量实现向下jmp
- 函数载入不远处的call一般选择f7步入
- 一般如果jmp到很远的地址,那极有可能就是跳到原程序入口(EOP)
示例
程序2:easyre.exe 提取码:cscj
加壳软件:upx加壳工具
这里使用的是x32进行调试的,建议还是使用OD调试,否则程序入口代码前还会有很多其他的代码
进去x32调试要加载ntdll,一直f8后到达壳程序,pushad就是一个壳程序开始的标志
这个壳一直f8,遇到循环选择下一个地址f4直接跳过即可
但到下面这个循环(415861)时,不能直接选择415867进行f4,否则程序会直接显示输出input the flag的字符串,程序无法再向下调试。跳过415861的方法之一就是在如图两个地址上下断点,直接f9运行,就会直接停到415867的位置
我们就能继续往下运行,popad就是要脱壳完成的标志
看到有一个跳转到很远的地方,应该就是OEP了
跳转过去就是没加壳程序刚进入的程序入口
2、ESP定律法
原理是运用程序中的堆栈平衡来实现。
大多壳开始会使用pushad来保存寄存器,在pushad保存好寄存器后,对esp下硬件断点,之后直接f9运行,就会到达最后的popad,硬件断点就会触发。就我的理解是:也就是一开始(pushad)栈指针指向一个位置,但最后(popad)esp还是会回到一开始栈指针指向的地方,所以开始给esp的地址下断点,最后还是会恢复到这个地址,恢复到这个地址时,硬件断点就会触发。
硬件断点与软件断点的区别:
硬件断点由调试寄存器的值来控制,它是看地址来下断点和判断与触发断点的,而软件断点是看指令根据指令来下断点和判断断点来触发的
要点:
- 程序开始pushad/pushfd
- 在pushad/pushfd压栈后设置ESP硬件断点
- F9运行
- 删除断点后即可分析
示例
还是用上一个程序做演示
这里使用x32dbg作为调试器,使用吾爱破解的时候没有找到下ESP断点的地方
使用x32dbg时,程序刚载入是这样的
直接按F9,就会跳到pushad的地方,先F8一下执行pushad保存寄存器
然后在右边显示寄存器框里的黑色区域,右键——>设置硬件断点于ESP,然后F9运行
然后就运行到了popad的后面了,之后就单步几下就跳到原程序入口了
3、一步到达OEP法
找到离OEP或大jmp近的popad,下int3断点,然后直接运行到断点处实现脱壳
要点
- ctrl+f查找popad
- ctrl+l跳到下一匹配处,选择确认正确的popad位置,下断点运行到该处
- 只适用于极少数壳
示例
继续使用之前的程序例子
程序开始ctrl+f,查找,然后ctrl+l,会搜到多个popad,向下看是否有大jmp或OEP
正确的popad,后面有大jmp
4、 内存镜像法
暂没有找到合适的实例程序资源,但先记录一下,经检测这个方法一般用于ASPack壳加密
原理
在程序自解压或解密时会先访问程序资源段,自动脱壳后转回程序代码段,所以我们现在程序资源段下一个断点,运行完后又在代码段下一个断点运行
要点
- 菜单的选项—>调试选项—>异常—>勾选所有的忽略异常
- alt+m打开内存镜像,找到程序的第一个.rsrc,F2下断点,shift+f9运行到断点
- alt+m在第一个.rsrc上面的.text地址F2下断点,然后shift+f9
5、最后一次异常法
原理
程序自解压或自解密过程可能会触发很多异常,最后一次异常是很接近自动脱壳完成的地方,所以我们可以想办法给最后一个异常处下断点
要点
- Ollydbg 选项—>调试异常—>异常,将所有✔消掉,ctrl+f2重新加载程序
- 开始程序是一个跳转,按shift+f9,直到程序运行,要记录按shift+f9的次数m
- ctrl+f2重新加载程序,按m-1次shift+f9,OD右下角的框里会看到"SE句柄",ctrl+g,输入SE句柄前的地址
- 在地址上按f2下断点,shift+f9来到断点,f8跟踪
6、SFX法
Ollydbg自带的OEP寻找功能,找到后直接停在OEP处
要点
- OD异常选项卡都打上勾
- 同一框内的SFX选项卡选择 "字节模式跟踪实际入口 (速度非常慢)"
- 重新加载程序(如果跳出是否 "压缩代码?" 选择 "否", OD 直接到达 OEP)
Dump及IAT重建
在脱壳找到OEP后,我们需要将程序dump出来,并重建IAT
示例
还是用之前的easyre.exe程序
我们到达大jmp处后跳转到OEP地址0040DE05,然后右键->用OllyDump脱壳调试进程(LoadPE也可dump出来)
有一个框,查看入口点地址是否正确,取消勾选重建输入表
点击脱壳,保存为一个新程序,我们这里命名为dump1.exe
点击程序无法正常运行,如果OEP找对,放到IDA确实可以正常反编译,那么就是IAT出现了问题,需要重建IAT
这里使用ImportREC工具来修复输入表
打开 ImportREC 工具,选择一个正在运行的程序,即上面在调试中的 easyre.exe,所以Ollydbg dump后不要关闭这个进程
。因为这个工具需要入口点,0040DE05,基址为400000,那么在下方的OEP处输入0000DE05,然后点击自动搜索
点击确定后,点击获取导入表
然后点击修复转储,选择刚刚dump出来的dump1.exe程序,可能会报一个问题,但不用管,当前目录下会有一个dump_.exe程序,运行正常
手动查找IAT并使用ImportREC重建
示例
依然用easyre.exe
依然调试到OEP处,右键-查找-所有模块间的调用
随意双击一个程序的函数(非系统函数),来到函数调用处,右键,点击跟随,进入函数,再右键,数据窗口中跟随-内存地址(如果没有显示内存地址,那么可能程序函数选错了,重选一下就行),到左下方的框中,如果全是16进制就右键,长型-地址
在框中向上找IAT表的起始位置,选中所有,从IAT起始到IAT结束,这里是0040F000开始,下方显示块大小为0x14c
结束位置
打开 ImportREC ,选择进程,并在下面三个框中输入对应已知的的上面三个信息,分别是OEP、IAT起始位置以及IAT的块大小
然后点击获取导入表,在导入表函数框中右键,高级命令,选择代码区段
跳出一个框,点击完整转储,保存dump出的程序,这里命名dump.exe
之后点击修复转储,选择上面dump出来的dump.exe文件,就会显示保存了一个dump_.exe文件,运行正常
DLL文件脱壳
DLL 文件脱壳 - CTF Wiki (ctf-wiki.org)
参考链接: