Windows逆向之脱壳-定位OEP
前置知识 OEP(Original Entry Point)-原始入口点 OEP通常用于描述一个可执行文件在没有被壳(加壳软件或者加密软件)保护的情况下,代码的正常执行起点。 当一个软件被加壳保护之后,其执行起点会被改为壳的代码段,真正的程序代码(也就是我们说的OEP)会被加密或者压缩存放在其他位置,只有在适当的时机下才会被解密和加载出来执行。 OEP就是反向工程师在分析加壳软件的时候,需要找到
前置知识
OEP(Original Entry Point)-原始入口点
- OEP通常用于描述一个可执行文件在没有被壳(加壳软件或者加密软件)保护的情况下,代码的正常执行起点。
- 当一个软件被加壳保护之后,其执行起点会被改为壳的代码段,真正的程序代码(也就是我们说的OEP)会被加密或者压缩存放在其他位置,只有在适当的时机下才会被解密和加载出来执行。
- OEP就是反向工程师在分析加壳软件的时候,需要找到的一个非常重要的位置,只有脱掉壳,找到OEP,才能进一步分析和理解程序原始的执行逻辑与功能。
定位OEP
单步追踪法
原理
单步追踪法是脱壳技术中最基础的方法
大致流程:通过单步步过-F8、单步步入-F7和运行到指定代码F4来完整跟踪程序的自脱壳过程。这样可以在软件自动脱壳模块运行完毕后,到达OEP,并dump程序。
小Tips:
对于循环/回跳操作,我们可以直接在该循环的下一指令
F4对于
Call指令,我们先F8,若发现程序运行,则记录地址,F7跟踪当对这个程序的壳运行熟悉后,我们还可以时使用
F2和F9来更加快速定位。在追踪时,我们可能会因为一些意外是的OllyDbg中断,此时F9效率就很快
注意:有些程序可能会存在
CC断点检测,我们需要注意。
案例
UPX加壳程序
程序导入OllyDbg,我们开始单步跟踪程序

循环跳过案例:
当我们F8运行至
0x00409CA1初,我们发现程序会回跳至0x00409C90分析这段代码,发现
0x00409C98处还有一处跳转
因此对于这种情况,我们就可以直接在
0x00409CA3处F4当F8指定到
0x00409D45处JMP 00409C96指令时,我们则可以点击其下一条指令,我们可以看到有跳转语句会跳转至0x00409D4A
我们继续F4使得程序执行到
0x00409D4A处的POP ESI我们继续分析,分析看到
POPAD和JMP 00401000,我们已经解压成功
F8执行,到达OEP

ASPack加壳程序
程序导入OllyDbg,开始追踪程序

注意
0x00408002处的Call指令CALL 0040800AF8运行会直接是的程序运行。F7步入程序,我们发现与前面

对于
0x00440800E处CALL 00408014指令,我们也要F7跟进这里原因是因为,程序处于未解压状态,因此OllyDbg分析的指令会有差错
循环回跳
当我们F8跟进程序时,我们发现在
0x00408140处有回跳,我们在0x00408142处F4执行
继续向下执行,当遇到
0x0040815D处,为我们一样F4跳过这里把所有的回跳给列出来
0x00408140-JMP SHORT 0040812D0x0040815D-JMP SHORT 0040812D0x004081A3-JNZ 004080C70x0040837F-JMP 004082B60x00408395-JMP 00408285JMP 00408285
我们一致追踪,当执行到
0x004083BA时,我们执行到关键指令
004083AF 61 POPAD 004083B0 75 08 JNZ SHORT CRACKME-.004083BA 004083B2 B8 01000000 MOV EAX,1 004083B7 C2 0C00 RETN 0C 004083BA 68 00104000 PUSH CRACKME-.00401000 004083BF C3 RETN我们执行
POPAD,这与开头的PUSHAD想照应,当执行到0x004083B0会跳转至0x004083BA
我们执行这两条指令,成功找到OEP

ESP定律法
堆栈平衡概念
我们知道在执行函数时会涉及到堆栈
对于汇编程序:在函数执行时一般要包含和恢复被执行过程修改的寄存器。
- 在开始时将寄存器入栈
- 在返回前将寄存器出栈
函数执行过程:
函数参数入栈函数的参数按照特定的顺序(通常是从右向左的顺序)入栈,
函数调用指令执行
将返回地址(也就是跳回调用者的地址)压入栈中
将EIP跳转至函数段首
函数执行结束:
计算结果(如果有的话)在寄存器或是某个特定的内存位置中。
函数参数出栈
函数调用而压入栈的参数将会从栈中弹出。这个过程有时被称为“清理堆栈”。
返回地址出栈并跳转,回到调用者的代码中。
ESP定律原理
ESP定律的原理在于程序中堆栈平衡的合理利用。
压缩/加密壳:
- 将当前寄存器内容压栈,如使用
PUSHAD - 在解压结束后,会将之前的寄存器值出栈,如使用
POPAD,
因此我们可以在这些地址设置硬件访问断点,F9运行,当程序中断时,则距离OEP就很近了
记录系统加载时的ESP值,对其设置硬件断点
- OllyDbg硬件断点快捷命令
HW 内存地址 - 数据窗口区 -->转到内存地址 -->选取 -->右键 -->断点 -->硬件访问-->双字。
特殊规律
OEP的ESP值:
在WinXP中多数EXE在运行到OEP的时
ESP == 0x0012FFC4我们对
0x0012FFC0设置硬件访问断点在Windows10中多数EXE运行到OEP时
ESP == 0x0019FF74我们对
0x0019FF70设置硬件访问断点
注意:操作系统不一样其ESP值不一样,这只是在我的虚拟机两个版本得到的结果。
案例
UPX加壳程序

ASPack加壳程序

二次断点法
原理
二次断点法主要是对.rsrc区域和.text/.code区域设置断点
前提:OllyDbg需要忽略所有的异常
步骤如下:
- 程序导入OllyDbg后,对程序的模块中
.rsrc(可能名字会更改,这里需要尝试)设置断点 Shift + F9/F9运行程序,程序中断时对.rsrc区域上方的.text/.code段设置断点Shift + F9/F9运行此程序,运行到第二次断点处- 剩余使用单步追踪法跟踪OEP
案例
UPX加壳程序
程序导入OllyDbg后,我们在
Memory Map窗口,对.rsrc设置断点
F9运行程序,程序中断至
0x00409D9A
我们对
.code设置断点,这里对UPX0设置断点
F9运行程序,程序中断至
0x00409DA1
我们对剩余指令使用单步追踪法

ASPack加壳程序
程序导入OllyDbg,对
.rsrc设置断点
F9运行程序,程序中断与
0x0040875F
此时,我们对
.code设置断点、
F9运行程序,程序中断于
0x00401000
内存镜像法-特殊OD
特殊之处
内存镜像法实际上就是二次断点法,这里分开写是因为所下断点不同,以及OllyDbg不同。
特殊之处:如果你设置一个内存访问断点,正常情况下应该是内存读取(ON READ)或者执行(ON EXECUTE)都断下来。但是该OllyDbg只会在执行(ON EXECUTE)的时候断下来。
案例
UPX加壳程序
程序导入特殊OllyDbg,在
UPX0设置内存访问断点
F9运行程序,需要等待一段时间,程序中断与OEP

ASPack加壳程序
程序导入特殊OllyDbg,

F9运行程序,程序中断与
0x00401000,但是此时不是OEP内容
我们再次F9,成功达到OEP

特殊方法定位OEP
这些特殊方法适用的范围有明显特征
一步直达法
原理
对于某些壳会以JMP或CALL作为自解指令的末尾,因此此可以搜索JMP或者CALL指令的机器码,(只适用于少数壳:UPX、ASPACK).
对于有些壳我们还可以搜搜POPAD,因为有的壳第一条指令是PUSHAD,与PUSHAD对应的是POPAD 0x61
关键指令:
- JMP-无条件跳转指令
E9:长跳转-JMP 00401000EB:短跳转-JMP SHORT 004010F1FF25:间接寻址-JMP DWORD PTR DS:[4031AC]
- CALL-函数调用指令
E8:直接调用-CALL 40143AFF96:间接调用-CALL DWORD PTR DS:[ESI+9560]
- POPAD-PUSHAD对于指令:
61
查找方式
Ctrl + B:查找,反汇编窗口 --> 查找 --> 二进制字符串在HEX +00 这个输入框输入指令的机器码。
Ctrl + L:查找下一个或者右键 -->查找 -->所有命令-->输入要查找的指令
或者直接右键 -->查找 -->所有命令
案例
这里以UPX为案例
POPAD
程序导入OllyDbg,我们可以看到第一条指令为
PUSHAD
我们
Ctrl + B,输入要查找的指令机器码
我们可以看到第一个搜到的不是
POPAD
我们多次
Ctrl + L,查找下一个,最终找到POPAD
JMP
我们同样步骤搜索
JMP
第一个搜到的不是我们要找到的
JMP指令,我们要继续Ctrl +L
所有命令查找
右键 -->查找 -->所有命令,输入POPAD

我们在新窗口可以看到多条POPAD

我们根据地址,我们选择0x00409DF5的POPAD指令

SFX法-OllyDbg
原理
SFX-Self Extractting Files-自解压文件:文件可以自己解压并执行内部文件。
SFX设置
忽略异常:导航栏 -->
Options-选项 -->Debugging options-调试设置 --Execptions-异常所有异常都勾选,添加忽略范围
00000000-FFFFFFFFSFX选项:导航栏 -->
Options-选项 -->Debugging options-调试设置 --SFXTrace real entry blockwise-块方式跟踪真正入口处(速度快、不一定准确)。Trace real entry byttewise-字节方式跟踪真正入口处(速度慢、定位更精确)。
注意:SFX使用的范围是程序入口点EP必须是代码段(BaseOfCode)之外才可以用
案例
这里使用ASPack加壳的程序
当未设置
SFX时,SFX默认设置如下
块方式跟踪真正入口处,发现程序直接运行,寻找失败

字节方式跟踪真正入口处,成功定位到OEP

末次异常法
如果我们在脱壳的过程中发现目标程序产生大量异常的话,就可以使用最后一次异常法,
原理
程序在自解压或自解密过程中,可能会触发无数次的异常。如果能定位到最后一次程序异常的位置,可能就会很接近自动脱壳完成位置。
异常设置:忽略异常:导航栏 -->Options-选项 -->Debugging options-调试设置 --Execptions-异常,所有异常都不忽略
具体流程如下
将异常都不忽略,将
ExCounter.dll放在OllyDbg的Plugin目录导航栏 -->插件 -->异常处理器 -->第一步:开始计数。OllyDbg会重新导入OD
导航栏 -->插件 -->异常处理器 -->第二步:带我到OEP。OllyDbg会重新加载程序值OEP处
有些情况可能会未定位在OEP,此时我们可以在Memory Map窗口中,在CODE段设置断点。
导航栏 -->插件 -->异常处理器 --> 全部重置
手工定位
详细步骤如下:
- 点击
选项->调试选项—>异常, 把里面的√全部去掉! 按下CTRL+F2重载下程序 - 我们按
SHIFT+F9, 直到程序运行, 记下从开始按SHIFT+F9到程序运行的次数m。 CTRL+F2重载程序, 按SHIFT+F9(这次按的次数为程序运行的次数m-1次)。- 然后我们对程序的code段设置断点
- F9运行,程序中断在OEP
PS:其中第1、2步可以个别更改为:
- 程序导入OllyDbg,对异常设置忽略
- F9运行程序,在
Log窗口查看异常次数
这里使用bitarts_evaluation.c.exe来进行演示
程序导入OllyDbg,对异常设置忽略

F9运行程序,在
Log窗口查看异常次数,16次
我们将异常都不忽略

F9运行程序,当触发异常中断时,
Shift + F9忽略异常,记录次数,当第15次时停止,最后一次异常为
INT 3,程序停留在0x0046E890
此时我们对
code设置断点,
Shift + F9,程序中断在OEP
异常插件定位
大概流程如下:
将异常都不忽略,将
ExCounter.dll放在OllyDbg的Plugin目录导航栏 -->插件 -->异常处理器 -->第一步:开始计数。OllyDbg会重新导入OD
导航栏 -->插件 -->异常处理器 -->第二步:带我到OEP。OllyDbg会重新加载程序值OEP处
有些情况可能会未定位在OEP,此时我们可以在Memory Map窗口中,在CODE段设置断点。
导航栏 -->插件 -->异常处理器 --> 全部重置
实际操作
将异常都不忽略

首先使用插件计数

然后点击到达最后一次异常处

同手工定位,在
.code段设置断点,Shift + F9运行,到达OEP
VB特征脱壳
原理
Visual Basic程序 OEP
68 PUSH 指针地址
E8 F0FFFFFF CALL ThunRTMain地址
这两条指令的目的是调用ThunRTMain函数初始化各种变量。
Visual Basic 程序的开始就是一条push命令,这里实际上压入的是一个指针,指向的是VBHeader结构体,
然后就是call指令调用
MSVBVM60.dll/MSVBVM50.dll中的ThunRTMain。
案例
我们导入未加壳程序

UPX加壳程序
程序导入OllyDbg,对MSVBVM60.dll的.text设置断点

F9运行程序,程序过一段时间会中断

我们在堆栈区得到0x004019AA,我们在反汇编窗口转到该地址

ASPack加壳程序
同样的步骤,我们设置内存断点

F9运行,过一段程序中断

转到0x004019AA

VMProtect2.12.3加壳程序

导入OllyDbg,设置内存断点

F9运行程序,程序运行一段时间会中断

转到0x004019AA,找到OEP,但是程序还未IAT还需重置。
