脱壳实践之寻找OEP——两次内存断点法

  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这一特性。

原文地址:https://www.cnblogs.com/2f28/p/9855433.html

时间: 2024-07-29 18:53:16

脱壳实践之寻找OEP——两次内存断点法的相关文章

破解之寻找OEP[手动脱壳](1)

OEP:(Original Entry Point),程序的入口点,软件加壳就是隐藏了OEP(或者用了假的OEP), 只要我们找到程序真正的OEP,就可以立刻脱壳. PUSHAD (压栈) 代表程序的入口点 POPAD (出栈) 代表程序的出口点,与PUSHAD相对应,一般找到这个OEP就在附近啦! 常见寻找OEP脱壳的方法: 方法一: 1.用OD载入,不分析代码! 2.单步向下跟踪F8,是向下跳的让它实现 3.遇到程序往回跳的(包括循环),我们在下一句代码处按F4(或者右健单击代码,选择断点—

破解之寻找OEP[手动脱壳](2)

1.使用ESP定律 OD载入后,F8一次,在寄存器窗口的ESP的内容上(如0012FFA4)右键:“在数据窗口中跟随”,到内存数据窗口,将内存数据窗口以HEX 数据形式显示,在刚才的地址起始位置上(如0012FFA4)上右键:“断点”->“硬件访问”->“字”,F9直接运行,再F8一次或二 次,一般会到push ebp这句代码,这句代码所在的地址,就是OEP. 2.二次断点法 OD载入后,点击“M”按钮进入内存映射页面,先在数据(data).资源(rsrc).rdata.idata等区段下访问

寻找OEP

1.使用ESP定律 OD载入后,F8一次,在寄存器窗口的ESP的内容上(如0012FFA4)右键:“在数据窗口中跟随”,到内存数据窗口,将内存数据窗口以HEX数据形式显示,在刚才的地址起始位置上(如0012FFA4)上右键:“断点”->“硬件访问”->“字”,F9直接运行,再F8一次或二次,一般会到push ebp这句代码,这句代码所在的地址,就是OEP. 2.二次断点法 OD载入后,点击“M”按钮进入内存映射页面,先在数据(data).资源(rsrc).rdata.idata等区段下访问断点

JVM高级特性与实践(一):Java内存区域 与 内存溢出异常

对于从事C.C++的开发人员而言,在内存管理领域,他们具有绝对的“权利”——拥有每个对象的控制权,并担负着每个对象生命周期的维护责任.而对于Java开发人员而言,在虚拟机自动内存管理机制的帮助下,无需为每一个创建new操作去配对 delete/free 代码,减少内存泄漏和内存溢出的问题,这些都交给了Java虚拟机去进行内存控制,但是正因如此,当出现相关问题时,若不了解JVM使用内存规则,就难以排查错误.接下来以此篇文章记录学习Java虚拟机内存各个区域概念.作用.服务对象以及可能产生的问题.

C++中两块内存重叠的string的copy方法

如果两段内存重叠,用memcpy函数可能会导致行为未定义. 而memmove函数能够避免这种问题,下面是一种实现方式: 1 #include <iostream> 2 using namespace std; 3 void* memmove(void* str1,const void* str2,size_t n) 4 { 5 char* pStr1= (char*) str1; 6 const char* pStr2=(const char*)str2; 7 if (pStr1< pS

如果两段内存重叠,用memcpy函数可能会导致行为未定义

如果两段内存重叠,用memcpy函数可能会导致行为未定义,改进: void* memmove(void* str1,const void* str2,size_t n) { char* pStr1= (char*) str1; const char* pStr2=(const char*)str2; if (pStr1 < pStr2 ) { for(size_t i=0;i!=n;++i) { *(pStr1++)=*(pStr2++); } } else { pStr1+=n-1; pStr

学习:脱壳之内存执行寻找OEP

前言:类似排除法,如果一开始直到OEP的地址话,那么成功脱壳的几率应该百分之百,如果不知道,那自己尝试的时候,还是需要检查检查 1.直接加载入OD,打开内存窗口观察,发现是UPX的壳 内存映射, 条目 22 地址=00401000 大小=00008000 (32768.) 属主=CRACKME_ 00400000 区段=UPX0 包含=输出表 类型=Imag 01001002 访问=R 初始访问=RWE 内存映射, 条目 23 地址=00409000 大小=00001000 (4096.) 属主

学习:脱壳之VB程序快速寻找OEP

VB程序的OEP的特征,一般都是push一个数据到堆栈,然后进行CALL,而这个CALL调用的系统的dll文件,这个是重点要考的 那么就会有一个思路:当加壳完,最后来到OEP处,程序运行会调用系统DLL,那么我们是不是只要给系统的DLL下内存断点,当访问的时候就是不是会让OD捕获异常,使其断下点呢? 1.把一个UPX壳的VB加载OD中,EP为如下,这里选择用特殊OD来调试VB,原因是这个OD只有当访问断点处才会被断点下来,其他就算内存写入就会被断点,这种OD对VB好进行脱壳 2.发现找到系统的D

学习:脱壳之异常法寻找OEP

一个练手unpackme bitarts 1.直接载入OD,这里记录一种脱壳方法,F9运行程序,查看记录窗口,发现有许多异常处 2.这里需要注意的是红标处,这是最后一个异常,其实这次也可以直接在内存窗口中的代码段(或者是利用排除法)直接下内存断点,然后F9运行,等待对程序的代码段(或者其他)的进行访问/写入的时候下断,这里我们使用的是shark恒老师教的异常法来进行学习.... 3.然后将OD的调式选项进行关闭,然后进行手动运行,手动忽视异常,来到上面图中所标出的最有一个int3的地址0046E