首先了解一下壳的种类:压缩壳,加密壳,虚拟机壳
加壳的原理:加壳的原理通常是加密,压缩源程序的各个区段,并给源程序添加一个或者多个区段作为源程序的引导代码,然后将源程序的入口点修改为外壳程序的入口点。
(注意:通常而言,一些简单的壳,壳代码在原程序之前运行,当然,现在很多强壳已经达到了肉中有壳,壳中有肉的局面)
壳的解密例程负责解密,然后跳转至OEP处开始执行。
研究壳有一段时间了,打算发一些脱壳系列的文章,总结一下脱壳技巧,本篇为脱壳第一篇,后续将会发一些进阶系列的文章。
一般而言,脱壳的基本步骤如下:
1:寻找OEP(注意:OEP指的是程序原始入口点,不是壳的入口点,英文全称是(original entry point)
2:转储,也就是dump
3: 修复IAT(导入地址表,英文全称:import address table )
4:检查目标程序是否存在AntiDump等阻止程序被转储的保护措施,并修复
注:遇到一些外文名词,可以尝试找到其英文全称,这样更加有助于理解
常用的脱壳方法(此处介绍一些常见脱壳方法,若要提高自己的能力,务必动手多练)
1 一步直达法,搜索JMP(0E9)或者CALL(0E8)指令的机器码
对于一些简单的壳有效,例如UPX,ASPACK,原理:一般情况下,壳在解密完区段后会通过一个长跳转或者CALL跳转到OEP处。
下面举一个简单的例子:
一步直达法脱UPX壳:
首先,OD加载,断在了程序的入口点
ctrl+b
多按几次ctrl+l
直到出现如下界面,跳往第一区段
除了搜索jmp call 的机器码之外,还可以搜索call eax,jmp eax等指令的机器码,很多时候壳会将OEP的值存放在寄存器中,然后通过CALL/JMP 寄存器 跳到OEP.可以通过右键,search for-> all commands来搜索.
2 堆栈平衡法,也即ESP定律法
类似于函数的开头会保存寄存器,函数结束会释放.(因为函数过程会用到一些寄存器),某些壳会使用pushad指令保存寄存器环境,解密各个区段完毕后,跳往OEP之前,会使用popad指令恢复寄存器环境.
这里还是用上一个例子(见上图),开头是pushad
注意:有些情况下,壳的第一条指令可能不是pushad,但是,有时候会在开头附近,还有时候,不用pushad,而是一个一个的push各个寄存器,例如: push eax,push ebx等等,这些都不是重点,关键在于壳在跳往OEP之前会恢复寄存器环境.
如上图所示,各个寄存器的值被压入了堆栈中,对这些值设置内存断点或者硬件断点(有些壳会检测硬件断点,初学者暂且不谈),当解密例程读取这些值得时候就会中断下来,此时OEP应该就在附近了
在esp上选则follow indump
选中四字节右键->breakpoint->hardwareonaccess->dword
我们断在了popad得下一行
这里要说明一下,现在的壳有很多保护机制,检测机制,有些壳会检测到这种方法,所以大家在脱壳得时候要尝试多种不同的方法
3 使用OD自带得SFX定位OEP
如下,打开debug option
一个是inaccurate,(这个是快的)一个是very slow(慢).
注意:只有当ollydbg发现入口点位于代码段之外时才会起作用,程序得入口点位于代码段中时该选项就不起作用了,壳得入口点位于代码段中得情况还是比较少见得.
这里我选的是inaccurate然后重启OD(重启才有作用),这里我选的是ASpack壳
然后断在了OEP,如下
4最后一次异常法
这个方法比较实用
首先,将exception菜单项中的忽略异常选项都选上,然后运行起来,然后打开日志窗口,如下
注意红线上面的部分,我们可以看到好多异常,但是都不是位于第一区段的,这说明了什么问题那?(此处思考两秒钟)
这说明产生的这些异常不是在源程序运行期间产生的,而是在壳的解密例程执行期间产生的异常,最后一次异常是在红线处,
好了,现在重启OD,将exception中的忽略异常的选项都去掉
然后运行,产生异常断了下来,直接shift+F9忽略定位到最后一次异常,(这里指的是壳的解密例程的最后一次异常,接着就跳转到OEP了)如图
接着对代码段设置内存访问断点(OD上方的M按钮打开内存映射)
右键,set breakpoint on access
定位到OEP如下
5 内存映像法
对OD来讲,正常的内存访问断点读取,写入,执行的时候都会断下来,这里用一个打过补丁的OD,内存访问断点仅当执行的时候才会断下来
首先看下区段列表
本文开头已经提到,壳的解密例程会解密源程序的各个区段并写回原处,这里,只有当执行的时候才会断下来,所以,当其断下来的时候(第一个区段设置内存执行断点)才会断下来,所以,当它断下来时候,基本是有很大概率就是OEP了.
6 利用壳最常用的API来定位OEP
大部分的壳都会用到GetProcAddress,LoadLibrary这两个API,因为可以自己编程实现函数的加载,
断了下来,因为我们想要知道哪些地方调用了该函数,设置条件记录断点.记录的表达式设置为esp,也就是返回地址,快捷键shift+,
运行,查看日志
因为428C2B位于第一区段,即第一区段调用的,我们应该关注428C2B上面这一处.
好了,下面我们重新设置条件,运行起来.
断了下来,这里要注意,条件断点断下来是粉红色的,这是和普通断点的区别.
好了我们现在已经在OEP附近了.接着给代码段设置内存访问断点,很快就定位到OEP了.
此外,除了GetProcAddress之外,我们还可以尝试其他API函数,比如LoadLibrary,ExitThread等等.
7 利用应用程序调用的第一个API函数来定位OEP
这种方法和上面的方法很类似,就是给应用程序的第一个API设置断点,比如GetModuleHandleA,GetDesktopWindow,MessageBox,GetVersion等等.
这里我们用第一个例子的程序,很快就定位到了OEP
好了,最后要提醒一下,只看不练一点用都没有,脱壳是一门艺术,没有固定的方法,其实还有其他的方法,个人感觉脱壳最重要的还是手感,不练哪来的手感,对程序的逆向分析也是,都需要感觉
后续会讲一下IAT重定向以及antidump等进阶的东西.
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
最后于 2020-11-9 11:20
被Golden_Boy编辑
,原因: