Windows逆向之脱壳-定位OEP
Aking9 发表于 河南 技术文章 1734浏览 · 2024-07-18 11:05

前置知识

OEP(Original Entry Point)-原始入口点

  • OEP通常用于描述一个可执行文件在没有被壳(加壳软件或者加密软件)保护的情况下,代码的正常执行起点。
  • 当一个软件被加壳保护之后,其执行起点会被改为壳的代码段,真正的程序代码(也就是我们说的OEP)会被加密或者压缩存放在其他位置,只有在适当的时机下才会被解密和加载出来执行。
  • OEP就是反向工程师在分析加壳软件的时候,需要找到的一个非常重要的位置,只有脱掉壳,找到OEP,才能进一步分析和理解程序原始的执行逻辑与功能。

定位OEP

单步追踪法

原理

单步追踪法是脱壳技术中最基础的方法

大致流程:通过单步步过-F8、单步步入-F7和运行到指定代码F4来完整跟踪程序的自脱壳过程。这样可以在软件自动脱壳模块运行完毕后,到达OEP,并dump程序。

小Tips:

  • 对于循环/回跳操作,我们可以直接在该循环的下一指令F4

  • 对于Call指令,我们先F8,若发现程序运行,则记录地址,F7跟踪

  • 当对这个程序的壳运行熟悉后,我们还可以时使用F2F9来更加快速定位。

    在追踪时,我们可能会因为一些意外是的OllyDbg中断,此时F9效率就很快

    注意:有些程序可能会存在CC断点检测,我们需要注意。

案例

UPX加壳程序

  1. 程序导入OllyDbg,我们开始单步跟踪程序

  2. 循环跳过案例:

    当我们F8运行至0x00409CA1初,我们发现程序会回跳至0x00409C90

    分析这段代码,发现0x00409C98处还有一处跳转

    因此对于这种情况,我们就可以直接在0x00409CA3F4

  3. 当F8指定到0x00409D45JMP 00409C96指令时,我们则可以点击其下一条指令,我们可以看到有跳转语句会跳转至0x00409D4A

    我们继续F4使得程序执行到0x00409D4A处的POP ESI

  4. 我们继续分析,分析看到POPADJMP 00401000,我们已经解压成功

  5. F8执行,到达OEP

ASPack加壳程序

  1. 程序导入OllyDbg,开始追踪程序

  2. 注意0x00408002处的Call指令CALL 0040800AF8运行会直接是的程序运行。

    F7步入程序,我们发现与前面

    对于0x00440800ECALL 00408014指令,我们也要F7跟进

    这里原因是因为,程序处于未解压状态,因此OllyDbg分析的指令会有差错

  3. 循环回跳

    当我们F8跟进程序时,我们发现在0x00408140处有回跳,我们在0x00408142处F4执行

    继续向下执行,当遇到0x0040815D处,为我们一样F4跳过

  4. 这里把所有的回跳给列出来

    • 0x00408140-JMP SHORT 0040812D
    • 0x0040815D-JMP SHORT 0040812D
    • 0x004081A3-JNZ 004080C7
    • 0x0040837F-JMP 004082B6
    • 0x00408395-JMP 00408285JMP 00408285
  1. 我们一致追踪,当执行到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定律法

堆栈平衡概念

我们知道在执行函数时会涉及到堆栈

对于汇编程序:在函数执行时一般要包含和恢复被执行过程修改的寄存器。

  • 在开始时将寄存器入栈
  • 在返回前将寄存器出栈

函数执行过程:

  1. 函数参数入栈

    函数的参数按照特定的顺序(通常是从右向左的顺序)入栈,

  2. 函数调用指令执行

    将返回地址(也就是跳回调用者的地址)压入栈中

  3. 将EIP跳转至函数段首

函数执行结束:

  1. 计算结果(如果有的话)在寄存器或是某个特定的内存位置中。

  2. 函数参数出栈

    函数调用而压入栈的参数将会从栈中弹出。这个过程有时被称为“清理堆栈”。

  3. 返回地址出栈并跳转,回到调用者的代码中。

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需要忽略所有的异常

步骤如下:

  1. 程序导入OllyDbg后,对程序的模块中.rsrc(可能名字会更改,这里需要尝试)设置断点
  2. Shift + F9/F9运行程序,程序中断时对.rsrc区域上方的.text/.code段设置断点
  3. Shift + F9/F9运行此程序,运行到第二次断点处
  4. 剩余使用单步追踪法跟踪OEP

案例

UPX加壳程序

  1. 程序导入OllyDbg后,我们在Memory Map窗口,对.rsrc设置断点

  2. F9运行程序,程序中断至0x00409D9A

  3. 我们对.code设置断点,这里对UPX0设置断点

  4. F9运行程序,程序中断至0x00409DA1

  5. 我们对剩余指令使用单步追踪法

ASPack加壳程序

  1. 程序导入OllyDbg,对.rsrc设置断点

  2. F9运行程序,程序中断与0x0040875F

  3. 此时,我们对.code设置断点、

  4. F9运行程序,程序中断于0x00401000

内存镜像法-特殊OD

特殊之处

内存镜像法实际上就是二次断点法,这里分开写是因为所下断点不同,以及OllyDbg不同。

特殊之处:如果你设置一个内存访问断点,正常情况下应该是内存读取(ON READ)或者执行(ON EXECUTE)都断下来。但是该OllyDbg只会在执行(ON EXECUTE)的时候断下来。

案例

UPX加壳程序

  1. 程序导入特殊OllyDbg,在UPX0设置内存访问断点

  2. F9运行程序,需要等待一段时间,程序中断与OEP

ASPack加壳程序

  1. 程序导入特殊OllyDbg,

  2. F9运行程序,程序中断与0x00401000,但是此时不是OEP内容

  3. 我们再次F9,成功达到OEP

特殊方法定位OEP

这些特殊方法适用的范围有明显特征

一步直达法

原理

对于某些壳会以JMPCALL作为自解指令的末尾,因此此可以搜索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

  1. 程序导入OllyDbg,我们可以看到第一条指令为PUSHAD

  2. 我们Ctrl + B,输入要查找的指令机器码

  3. 我们可以看到第一个搜到的不是POPAD

    我们多次Ctrl + L,查找下一个,最终找到POPAD

JMP

  1. 我们同样步骤搜索JMP

  2. 第一个搜到的不是我们要找到的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-异常,所有异常都不忽略

具体流程如下

  1. 将异常都不忽略,将ExCounter.dll放在OllyDbg的Plugin目录

  2. 导航栏 -->插件 -->异常处理器 -->第一步:开始计数。OllyDbg会重新导入OD

  3. 导航栏 -->插件 -->异常处理器 -->第二步:带我到OEP。OllyDbg会重新加载程序值OEP处

    有些情况可能会未定位在OEP,此时我们可以在Memory Map窗口中,在CODE段设置断点。

  4. 导航栏 -->插件 -->异常处理器 --> 全部重置

手工定位

详细步骤如下:

  1. 点击选项->调试选项—>异常, 把里面的√全部去掉! 按下CTRL+F2重载下程序
  2. 我们按SHIFT+F9, 直到程序运行, 记下从开始按SHIFT+F9到程序运行的次数m
  3. CTRL+F2重载程序, 按SHIFT+F9(这次按的次数为程序运行的次数m-1次)。
  4. 然后我们对程序的code段设置断点
  5. F9运行,程序中断在OEP

PS:其中第1、2步可以个别更改为:

  1. 程序导入OllyDbg,对异常设置忽略
  2. F9运行程序,在Log窗口查看异常次数

这里使用bitarts_evaluation.c.exe来进行演示

  1. 程序导入OllyDbg,对异常设置忽略

  2. F9运行程序,在Log窗口查看异常次数,16次

  3. 我们将异常都不忽略

  4. F9运行程序,当触发异常中断时,Shift + F9忽略异常,记录次数,

    当第15次时停止,最后一次异常为INT 3,程序停留在0x0046E890

  5. 此时我们对code设置断点,

  6. Shift + F9,程序中断在OEP

异常插件定位

大概流程如下:

  1. 将异常都不忽略,将ExCounter.dll放在OllyDbg的Plugin目录

  2. 导航栏 -->插件 -->异常处理器 -->第一步:开始计数。OllyDbg会重新导入OD

  3. 导航栏 -->插件 -->异常处理器 -->第二步:带我到OEP。OllyDbg会重新加载程序值OEP处

    有些情况可能会未定位在OEP,此时我们可以在Memory Map窗口中,在CODE段设置断点。

  4. 导航栏 -->插件 -->异常处理器 --> 全部重置

实际操作

  1. 将异常都不忽略

  2. 首先使用插件计数

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

  4. 同手工定位,在.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还需重置。

附件:
0 条评论
某人
表情
可输入 255