banner
lca

lca

真正的不自由,是在自己的心中设下牢笼。

《从零开始学IDA逆向》学习笔记-14(程序脱壳简介)

image

什么是加壳?#

本章演示了对 upx 加壳程序进行脱壳。

加壳是指通过一种压缩或者加密的手段将程序的可执行代码隐藏起来,避免被轻易的逆向。加壳会在程序中加入额外的区段(STUB,存根),在程序开始运行后,将加密的文件进行解密并保存到内存中其他区段,或者创建原程序中的区段,然后跳转到解密后的代码执行。

大部分通过破坏 IAT(import table)也就是导入函数表,以及文件头(HEADER)来保护文件。它们会加入反调试代码来避免被脱壳出原始文件。

通过 die 查看是否加壳。

image

上图可以知道是 upx 3.91 版本加壳,程序是 32 位,i386 架构。

加载加壳文件#

image

加载加壳文件时,取消勾选创建输入段,勾选手动加载

点击 ok 后,弹出如下窗口,点击确定。

image.png

加壳后程序的入口

原始程序入口

加壳后程序的入口,地址是 0x409BE0,而原始文件的地址是 0x401000。

文件和内存占用#

对比这 2 个文件的区段,在加壳文件的文件头下面有个 upx0 的区段,占用的内存比原始文件中的其他区段要大。

image

原始文件

image.png

加壳文件

加壳文件的 upx0 区块结束于0x409000,而在原始文件 header 以下区段从0x4010000x408200,当一个程序执行时,它在硬盘上可能只占用 1k,但在内存中可能占用 20k 或者更大。

image

如上图,在原始文件 CODE 区段的起始地址是0x401000,区段文件的大小 (Section size in file) 为 0x600 字节,而内存大小 (Virtual size) 占用 0x1000 字节。

image.png

转到加壳文件中,如上图,upx0 区段的起点是0x401000,upx0 区段再硬盘的大小为 0,而内存占用却是 0x8000 字节,程序在这里占用了足够大的空间来保存原始的程序代码,然后跳转过来执行。

image.png

加壳文件 0x401000 处的跳转

0x401000 前置的 dword_表示数据类型为 DWORD,"?" 号表示只占用了内存位置而未保存任何内容,dup 表示 0xc00 个 dword,也就是 0x3000 字节。0x404000 同样也占用了 0x1400 字节。

image.png

那么总共就是 0x8000 个字节用于存储原始代码的内容。

image.png

如下图,在 0x401000 处,按 x 键,可以看到此处有两个引用(稍后回来看这部分内容)。

image.png

可执行代码的引用

upx1 区段文件占用是 0xe00,内存占用是 0x1000。

image.png

upx1 区段的文件及内存占用

可能程序使用了一些简单的加密隐藏了原始代码,对于这个区段的起点 0x409000 有几处引用。

image.png

0x409000 的引用

有一处引用来自(下方,down)可执行部分,点击跳转到该处。

image.png
程序入口

stub 和 oep#

上图程序入口之后的 stub 中,ESI 寄存器传入 0x409000 这个地址,如下图所示,可执行代码在原始文件已加壳代码的下方,同属于 upx1 区块,所以在 upx1 区块中,存在原始文件加密后保存的内容以及 0x409be0 之后的 stub 代码。

image.png

跟踪到的可执行代码

下图中,可以发现,程序从 0x409000 处开始读取内容,通过某种运算之后再保存到0x401000(EDI=ESI-0x8000)。程序读取 ESI 指向的内容作为来源,然后执行操作后,再存储到 EDI 指向的内容,恢复成原始代码。

image.png

回到 upx0 区段,在 upx0 区段处,有一处引用

image.png

0x401000 处的引用

下图中有一处无条件跳转到 0x401000 处,也就是上图中的 0x401000 处的引用。

image.png

jmp near是直接向之后的地址数跳转的指令,那么这里执行完 stub 并且生成原始代码后,程序会跳转到 0x401000 处 (OEP,original entry point),也就是原始程序的入口(程序最开始执行的地方),相应的存根入口 (stub entry point) 是 0x409be0。

之后原始程序入口直接叫做ORIGINAL ENTRY POINTOEP。如果是一个加壳之后的程序,无法知道它的具体位置,而在此程序中,程序实有 OEP 的,OEP 就是 0x401000。

寻找 OEP#

大部分情况下,是无法获取原程序的,所以无法直接获得加壳程序的 OEP 地址。那么,接下来介绍如何寻找 OEP。

当 STUB 完成解密操作并且生成原始代码之后,会跳转执行程序。一般来说,在写入原始代码的区块执行的第一条指令就是 OEP。

在进入 OEP 之前设置断点,查看执行到这里之前是否已经生成了原始程序。

image.png

在跳转处设置断点

选择 local windows debugger 调试,然后开始调试,运行到断点处。

image.png

image.png

运行到断点处

程序运行到断点处跳转,按 F8 单步运行。

image.png

弹出警告信息 upx0 区段原先被解析为数据,点击是,将它解析为代码。

image.png

运行到 OEP

现在程序已经解密出原始代码并跳转执行,这里的代码和原始程序中 0x401000 处的代码非常类似。但是由于没有定义为函数 (loc_401000),所以无法切换到图形视图。不过这也可以自动实现。

image.png

在 IDA 界面左下角有一个隐藏菜单,右键选择 “重新分析程序”,现在回到 0x401000 处,显示 sub_401000 表示这是一个函数,按空格则可以切换回图形视图。

image.png

重新分析后的代码

image.png

sub_401000

通过执行断点寻找 OEP#

另一种寻找 OEP 的方法,找到第一个区段第一条执行的指令,这种方法有时候会成功。

在调试器中启动加壳后的程序并且在入口处暂停。

image.png

Stub entry point

转到一个区段的起点,也就是 0x401000

image.png

按 F2 设置断点,并且设置执行时中断,而不是读取或写入的时候,否则会在解密时读取密码或者写入原始代码时中断,由于不知道具体的位置,作者设置了覆盖整个区段的断点(0x8000 字节)。

image.png

设置完成后所有的指令都变红了。

image.png

对整个区段设置断点

然后打开菜单栏-调试器-断点-断点列表,禁用其他断点。

image.png

然后运行程序。

image.png

触发断点

当这种方法奏效时,发现 OEP 就是 0x401000,禁用这个断点。

image.png

OEP

重新分析之后,0x401000 识别为函数。

目前为止,介绍了 2 种寻找 OEP 的方法,创建解密代码的内存快照。接下来要做的就是 DUMP(转存)以及重建 IAT 来获得没有加壳并且能够运行的程序。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。