前置知识
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 0040800A
F8运行会直接是的程序运行。F7步入程序,我们发现与前面
对于
0x00440800E
处CALL 00408014
指令,我们也要F7跟进这里原因是因为,程序处于未解压状态,因此OllyDbg分析的指令会有差错
-
循环回跳
当我们F8跟进程序时,我们发现在
0x00408140
处有回跳,我们在0x00408142
处F4执行继续向下执行,当遇到
0x0040815D
处,为我们一样F4跳过 -
这里把所有的回跳给列出来
-
0x00408140
-JMP SHORT 0040812D
-
0x0040815D
-JMP SHORT 0040812D
-
0x004081A3
-JNZ 004080C7
-
0x0040837F
-JMP 004082B6
-
0x00408395
-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 00401000
-
EB
:短跳转-JMP SHORT 004010F1
-
FF25
:间接寻址-JMP DWORD PTR DS:[4031AC]
-
- CALL-函数调用指令
-
E8
:直接调用-CALL 40143A
-
FF96
:间接调用-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-FFFFFFFF
-
SFX选项:导航栏 -->
Options
-选项 -->Debugging options
-调试设置 --SFX
Trace 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还需重置。
- OEP.zip 下载