banner
lca

lca

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

《从零开始学IDA逆向》学习笔记-16(Aspack脱壳)

puahad 和 popad#

加壳程序:unpackme.aspack.2.2,可从互联网上搜索。

image.png

ida 打开,手动加载程序,取消勾选创建输入段。

image.png

加壳程序入口

上图中,第一条指令是pushadpushad会把所有通用寄存器的值传到栈上。

pushad按如下顺序将所有寄存器的值保存到栈上。

image.png

pushad 传值顺序

popad是和pushad相反的操作,它将栈中的值传出,按如下顺序保存到寄存器。

image.png

在大部分简单的壳中,使用了pushad指令将寄存器的初始状态保存,然后再跳转到OEP执行内存中的原始代码之前使用POPAD恢复寄存器的初始状态。
根据这条规律,可以通过pushad-popad法来轻松找到OEP

使用 idapython 进行调试#

什么是pushad-popad法呢?

使用调试器来运行程序,运行调试器有两种方式,第一种通过菜单栏-调试器-选择调试器,选择local windows debugger,第二种方式是使用 python 来运行调试器,这次选用第二种方式运行调试器。

1、通过import idc导入 idc 模块,输入idc.load然后按 tab 键补全,找到idc.load_debugger,参数为idc.load_debugger("win32",0) //1为远程调试,返回 True,代表执行成功。

image.png

载入 load_debugger 并设置参数

此时已加载了local windows debugger

image.png

在 pusha 之后设置断点

image.png

pusha 之后设置断点

pushad-popad法是在执行 pushad 后一条指令之前,找到栈上保存寄存器值的位置,然后在该位置设置一个断点,在程序解密出原始代码之后跳转到 OEP 执行之前会通过 popad 恢复寄存器的初始值,然后触发该断点暂停执行,从而确定 OEP 的位置。

按 F2 键在 pusha 的下一条指令上设置断点,那么执行 pusha 后调试会暂停下来。(pusha 类似于 pushad)

如果使用 python 来设置断点,可以使用如下语句:

idaapi.add_bpt(0x46b002,0,idc.BPT_SOFT)
idaapi.add_bpt(0x46b002,0,0)
  • 第一个参数是断点地址
  • 第二个参数是断点的长度
  • 第三个参数是断点的类型(软件断点 BPT_SOFT 或 0)

image.png

选择了调试器并且设置了第一处断点后,现在需要启动调试器运行程序然后暂停在该断点上,按 F9 键或者使用 python 语句运行程序。

idc.StartDebugger("","","");

在高版本(ida 7.7)中,上述命令报错,需要使用下述命令。

idc.start_process("","","")

执行上述语句后,调试器会运行到之前设置的断点处,也就是0x46b002处。

image.png

运行到断点0x46b002

下图中,栈视图中的值就是 pushad 保存的寄存器的值,之后会通过 popad 指令读取,所以可以在第一行上设置一处断点。

image.png

在这次调试中,断点的位置是 0x19ff54,就是 esp 执行栈中的位置。

点击 ESP 右侧的小箭头,会跳转到对应窗口的该地址(此处是汇编窗口)。

image.png

按 F2 在此处可设置断点,修改成读取和写入时触发,而不是执行时触发。

image.png

如果窗口未出现,可以打开菜单调试器 - 断点 - 断点列表,然后右键选择编辑设置。

如果用 python 可以安装如下方式设置断点:

idaapi.add_bpt(0x19ff54,1,3)

image.png

断点列表如下

image.png

断点列表

上述代码中,第一个参数表示断点地址,第二个参数表示断点的大小,第三个参数表示类型,3 代表 read-write access(读取和写入),执行这语句和手动设置断点一样。

BPT_EXEC = 0,
BPT_WRITE = 1,
BPT_RDWR = 3,
BPT_SOFT = 4,

设置断点类型的参数

在断点列表中,右键单击第一次设置的断点,选择 DISABLE 禁用,或者使用如下 python 语句。

image.png

idaapi.enable_bpt(0x46b002,0)

image.png

  • 第一个参数为断点地址
  • 第二个参数如果为 1 则启用断点,0 为禁用断点

0x46b002 标记为绿色代表禁用,栈上的断口是红色为启用。

image.png

按 F9 键继续调试或者输入如下的 python 语句

idaapi.continue_process()

下图中,调试在 popad 指令取回寄存器初始值之后中断,然后程序将跳转到 OEP 也就是 0x4271b0,因为 push ret 就和 jmp 类似。

image.png

popa 指令

继续单步执行执行到 oep。

image.png

既然找到了 oep 入口,那么就可以重新分析可执行文件,识别函数。

image.png

重新分析程序

image.png

使用 idapython 进行 dump#

找到了 oep 后,下一步就是进行转存,需要文件的基址以及可执行文件最后一个区段的最高地址。

image.png

上图可以知道,基址是 0x40000,最高地址是 0x46e000。

转存脚本如下:

import idaapi
import idc
import struct

start_ea = 0x400000
end_ea = 0x46e000
step = 4  # 每个地址处的数据占用4个字节

file_path = "dump.bin"
with open(file_path, "wb") as f:
    for ea in range(start_ea, end_ea, step):
        # 读取指定地址处的4个字节数据并进行小端字节序转换
        bin_data = struct.pack("<L", idaapi.get_32bit(ea))
        f.write(bin_data)

通过文件 - 脚本文件加载上面的 py 脚本,执行后,在当前目录生成一个 dump.bin 文件,修改其扩展名为 exe。

通过 peeditor 打开,打开区段视图,对所有的区段右键单击,选择 dumpfixer。

image.png

此时,图标已修复。

image.png

修复图标后,使用 Scylla 0.98 附加进程到被调试的加壳文件,目前执行到 OEP 入口。

image.png

加载此进程。

image.png

输入 oep 为 004271B0,单击 IAT Autosearch 和 Get Imports。

image.png

点击 show invalid,会发现有一处未能识别的 API,尝试自动修复失败,需要手动修复。

image.png

0x460818 处的 API

上图中,0x460818 是第一个有效区段的 API 函数,它上方更多的是未能识别的 API 地址。

检查第一个未能识别的 0x46080c 处是什么内容,按 D 键改变数据类型重组字节,如下图:

image.png

这里的内容不指向任何有效地址,如果按 CTRL+X,也没有任何引用。

image.png

而对于真正的 API 函数,应该有引用信息表明它在何处被调用。

所以,由于这些不是 API 函数,所以可以删除。

image.png

单击 clear 键,然后点击 IAT Autosearch,弹出的窗口选择否。

image.png

现在的 IAT 起点位与 0x460810,然后点击 Get Import,就可以找到所有的 API。

image.png

点击 Fix Dump 并转存文件,选择之前导出的文件。

image.png

最后脱壳程序正常运行。

image.png

到此处,整个程序的脱壳就算是结束了。

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.