0x00 前言
对于加壳程序第一件事就是要找到OEP(oringinal Entry point),由于加壳的缘故,当PE文件载入OD或者其他调试软件时进入的的往往是壳程序的入口地址。所以要进行逆向分析第一步就必须找到PE程序的原始入口点。
0x01 壳的加载过程
壳和病毒在某些方面比较类似,都需要比原程序更早获得控制权。壳修改了原程序的的执行文件的组织结构,从而比原程序更早获得控制权,并且并不会影响原程序的正常运行。了解的壳的加载过程对于脱壳加壳异常重要。壳的加载过程如下:
1)保存入口参数
加壳程序初始化时保存各个寄存器的值,外壳执行完毕,再恢复各个寄存器内容,最后再跳到源程序的入口处执行。通常,用pushad/popad,pushfd/popfd指令来保存的和恢复现场环境。
2)获取壳自己所需的API函数
一般的壳输入表只有GetProAddress,GetMoudleHanle,LoadLibrary这几个API函数,甚至只有Kernel32.DLL以及GetProAddress。如果需要其他的API函数,可以通过LoadLibraryA(W)或者LoadLibraryExa(W)将DLL文件映像映射到调用进程的地址空间,函数返回的HINSTANCE值用于标识文件映像到虚拟内存地址。
LoadLibrary函数的原型如下:
HINSTANCE LoadLibrary{
LPCTSTR lpLibFileName //DLL文件名地址
}
返回值:成功时返回模块句柄,失败返回NULL。
当DLL文件已经被映射到调用进程的地址空间里,可以调用GetModuleHanleA(W)函数获得DLL模块的句柄,函数的地址原型如下:
HMODULE GetModuleHandle{
LPCTSTR lpModuleName //DLL文件地址
}
一旦模块被加载,线程就可以调用GetProcAddress函数获取输入函数地址。函数的原型如下:
FARPROC GetProcAddress{
HMODULE hModlue //DLL模块句柄
LPCSTR lpProName //函数名
}
这三个函数异常重要,对于程序加壳帮助很大。后面几篇将会详细介绍用法,这里暂且罗列出来。
3)解密原程序的各个区块的数据
壳出于保护原程序代码和数据的目的,一般都会加密原程序文件的各个区块,在程序时外壳将会对这些数据解密,以让程序能够正常运行。壳一般都是按区块加密的,那么在解密时也是按区块解密的,并且把解密的区块数据按照区块的定义放在合适的内存位置。
4)ITA的初始化
ITA填写,本来应该由PE加载器实现。但是由于加壳时,自己构造了一个输入表,并且让PE文件头输入表指针指向了自建的输入表。所以PE装载器就对自建的输入表进行填写。那么原来PE输入表只能由外壳程序来填写了。外壳所要做的就是将这个新输入表结构从头到尾扫描一遍,对每一个DLL引入的所有函数重新获取地址,并填写在ITA表中。
5)重定位处理
文件执行时将被映像到指定的内存地址中,这个初始地址称为基址。对于EXE文件,windows系统会尽量使用EXE问价所指定的内存地址,比如某EXE问价的基址为40000h,而运行时Windows系统提供给程序使用的基地址也是40000h。这种情况就不需要重定位了。对于DLL文件,windows没办法每一次提供DLL运行时提供相同的基址。对于这种情况,重定位是必须的。此时壳程序也需要提供PE文件的重定位功能。所以加壳DLL文件比加壳EXE文件多一个重定位表。
6)HOOK-API
程序文件中输入表的作用是让windows系统在程序运行时提供API的实际地址给程序使用。在程序的第一行代码执行前,windows系统就完成了这项工作。
壳程序一般都修改了原程序的输入表,然后自己模仿windows系统的工作来填充输入表的中相关数据。在填充过程中,外壳程序可填充HOOK-API的代码地址,这样就可间接的获取程序的控制权。
7)跳转到程序入口点(OEP)
经历过以上步骤后,外壳程序的功能就完成了,随后他会把控制权交给原程序,一般的壳这里会有一个明显的“分界线”。当然现在越来越多的加密壳将OEP一段代码搬到外壳的地址空间里,然后将这段代码清除掉。这种方式称为StolenBytes。这样,OEP与外壳就没有明显的分界线了,这增加了脱壳的难度。
0x02 利用两次内存断点法手动找到OEP
两次内存法的原理就是利用了壳加载过程第三步时需要对各个区段进行解密并将解密后的区段写入各个区段,完毕之后会跳转至原程序的OEP处。当然,如果我们能判断出壳何时跳转至OEP处最好,但是一般这并不容易。但是我们可以先对.data区块下断后再运行程序(因为区段.code比.data先解压,运行到这个断点时.code以及解密完成),随后再对.code(有的编译器是.text)段下断在运行,这样程序就会停在OEP处(因为解密完成后壳程序一定再次返回到OEP处,将控制权交给原程序)。这个方法就是两次内存法。
0x03 实例介绍两次断点法找OEP过程
1)将文件拖入od,alt+m进入内存模板,随后对.data区块按F2下断,如下图:
2)点击F9运行,此时程序停在了下来,如下图:
这里其实马上就要对.data区块进行解密读写操作了,此时再alt+m进入内存模块,对.text(这个就是.code区块,由于编译器不同,有的显示.text区块)区块下断。
3)点击F9运行,此时程序停止,如下图:
其实这里就是原程序的OEP地址了,由于od对PE文件进行了分析,所以显示如上图,我们可以右键,删除模块分析即可得到下图:
标红框的地方就是OEP地址了。
0x04 总结
两次内存断点法虽然简单,但是我们还是要弄清楚其中的原理。它其实就是利用壳加载过程需要对区段进行解密然后返回原程序OEP这一特性。(PS:这种方法对于那种去区段信息改的面目全非的基本无效,我昨天就碰到一个加壳程序,区段信息只留下几个自己写,连code,data这些常用区段都没有,所以使用之前请用lordPE等工具查看一下区段信息)