返回介绍

第三十四章 - 手脱 UPX,修复 IAT

发布于 2025-01-31 21:06:59 字数 13490 浏览 0 评论 0 收藏 0

我们在上一章中给大家介绍了 IT(导入表),IAT(输入函数地址表) 的相关概念以及原理。有的人可能会认为,我们只是想修复 IAT 呀,并不需要知道 IAT 的具体原理以及它是如何被填充的吧?不是有现成的 IAT 自动修复工具吗?可以很明确的告诉大家,了解操作系统是如何填充 IAT 的过程非常有必要的。因为很多壳会检测这些常用的 IAT 修复工具,致其不能正常运行,在这种情况下,我们就需要自己进行相应的手工修复。

本章我们还是用最简单的 CRACKME UPX 作为例子,我们将对其进行脱壳以及修复 IAT,让其能正常运行。

首先第一步我们来定位 OEP,我们用 OD 加载 CRACKME UPX。

这里我们用 ESP 定律来定位 OEP,现在我们停在了入口点处,单击 F7 键执行 PUSHAD。

在 ESP 寄存器值上面单击鼠标右键选择-Follow in Dump。

就可以在数据窗口中定位到刚刚 PUSHAD 指令保存到堆栈中的寄存器环境了,我们选中前 4 个字节,我们通过单击鼠标右键选择 Breakpoint-Hardware,on access-Dword 给这 4 个字节设置硬件访问断点。

运行起来,马上就断在了 JMP OEP 指令处。

我们直接按 F7 键单步到 OEP 处。

好了,现在我们处于 OEP 处,原程序区段已经解密完毕,我们现在可以进行 dump 了。

前面已经提到过,有很多 dump 的工具,OD 有一个款插件 OllyDump 的 dump 效果也不错。上一章中我们使用 LordPE 来进行 dump 的 ,这里我们来使用另外一个工具 PE TOOLS 来进行 dump。

我们用 PE-TOOLS 定位到 CRACKME UPX 所在的进程。

这里这个 crackme.exe 就是,因为我忘了把那个重命名的 CRACKME UPX 放到哪里去了,所以我又重新弄了一个新的,忘了改名字,直接命名为原来的名字 crackme.exe 了,当前这个 crackme.exe 进程停在了 OEP 处。

单击鼠标右键选择-Dump Full。

这里,我们就 dump 完毕了,接下来我们来修复 IAT,关闭 PE TOOLS,将 PE TOOLS 目录下的 dumped.exe 拷贝一份到 CRACKME UPX 所在的目录中。

我们知道没有修复 IAT,是不能运行的,我们双击 Dumped.exe 看看会发生什么。

提示无效的 win32 程序,我们需要修复 IAT,我们需要用到一个工具,名字叫做 Import REConstructor,不要关闭 OD,将让其断在 OEP 处,Import REConstructor 需要用到它。

运行 Import REConstructor,定位到 CRACKME UPX 所在的进程。

这里很多初学者可能会有疑问-如何定位 IAT 的起始位置和结束位置呢?我们知道当前该 CRACKME UPX 进程停在了 OEP 处,此时壳已经将导入表破坏了,我们知道 IID 项的第四个字段为动态库名称字符串的指针,第五个字段为其对应 IAT 项第一个元素的地址,这些已经被壳破坏了,我们需要通过其他方式来定位。

我们知道 API 函数的调用通常是通过间接跳转或者间接 CALL 来实现的。

JMP [XXXXXXX] or CALL [XXXXXX]

上一章我们已经介绍过了,这样是直接调用 IAT 中保存的 API 函数地址。我们来看看 CRACKME UPX。

这里我们看到第二行的 CALL 指令,OD 提示调用的是 Kernel32.dll 中的 GetModuleHandleA,是个间接跳转,我们选中这一行,单击鼠标右键选择-Follow。

这里我们定位到获取 IAT 中函数地址的跳转表,这里就是该程序将要调用到的一些 API 函数,我们可以看到这些跳转指令的都是以机器码 FF 25 开头的,有些教程里面说直接搜索二进制 FF 25 就可以快速的定位该跳表。

其实,并不是所有的程序都通过这种间接跳转来调用 API 函数,所以直接搜索 FF 25 这种方法有时候会失败,而通过定位某个 API 函数的调用处,然后 Follow 到跳表是这种方式才是一直有效的。

这里我们看到 JMP [403238]:

403238 是 IAT 的其中一个元素,里面保存的是 GetModuleHandleA 这个 API 函数的入口地址,我们 dump 出来的程序的 IAT 部分跟这里是一样的,我们来看看 IAT 的起始位置和结束位置分别在哪儿。

其实,大家可以通过跳转表中最小的地址和最大的地址算出 IAT 的起始位置和结束位置,但是比较慢,比较好的做法是,直接将数据窗口往上滚动,我们知道 IAT 的每个元素中都保存了一个 API 函数的入口地址,比如这里的 7C80B529,在数据窗口中显示的形式为 29 B5 80 7C (小端存储),我们将显示列数调整为两列,这样看起来更方便一些。

这里我们看到的就是整个 IAT 了,我们直接下拉到 IAT 的尾部,我们知道属于同一个动态库的 API 函数地址都是连续存放的,不同的动态库函数地址列表是用零隔开的。

有一些壳会将这部分全部填零,使得我们的 IAT 重建工作变得异常困难,这里由于原程序还需要调用这些 API 函数,所以该壳没有将这部分填零,我们现在来看看其中一个动态库的 IAT 项,如下:

这里显示了该 DLL 中的三个 API 函数,入口地址都是 7C XXXXXX 的形式,然后紧接着是一个零。

我们单击工具栏中的 M 按钮看看 7C 开头的地址是属于哪个 DLL 的。

我们可以看到这几个地址处于 kernel32.dll 的代码段范围内。

当然在你们的机器上 kernel32.dll 可能在别的地址处,但在我的机器上,这几个函数地址是属于 kernel32.dll 的。

属于 kernel32.dll 中的函数地址这里我用粉红色标注出来了,我们再来看看 77DXXXXX 这类地址是属于哪个 DLL 的。

这里我们可以看到 77DXXXXX 这类地址是属于 user32.dll 的,我也用粉红色标注出来了。

我们可以看到 user32.dll 的这些函数地址项上面是全零的,表示 IAT 的起始地址为 403184,403184 中存放的了 IAT 中的第一个元素。

这里我们用红线标注出来了,403184 是 IAT 的起始地址。有些强壳可能会将 IAT 前后都填充上垃圾数据,让我们定位 IAT 的起始位置和结束位置更加困难。但是我们知道 IAT 中的数值都是属于某个动态库代码段范围内的,如果我们发现某数值不属于任何一个动态库的代码段的话,就说明该数值是垃圾数据。

现在我们知道了 IAT 开始于 403184,我们现在来看一看 IAT 的结束位置在哪里。

后面我们会遇到有些壳会将 IAT 重定向到壳的例程中去,然后再跳转到 API 函数的入口处,这样的话,上面这样定位 IAT 的起始和结束位置的做法就行不通了。该如何应对这样情况,我们后面再来介绍。

这里我们可以看到 IAT 的最后一个元素的地址形式为 76XXXXXX,我们来看看它是属于哪个 DLL 的。

这里我们可以看到其是属于 COMDLG32.DLL 的,后面全是零了,所以 40328C 是 IAT 的结束位置。

好了,现在我们知道了 IAT 的起始位置和结束位置。

begin:403184
end:40328c

Import REConstructor 重建 IAT 需要三项指标:

1)IAT 的起始地址,这里是 403184,减去映像基址 400000 就得到了 3184(RVA:相对虚拟地址)。

2)IAT 的大小

IAT 的大小 = 40328C - 403184 = 108(十六进制)

3)OEP = 401000(虚拟地址)- 映像基址 400000 = 1000(OEP 的 RVA)。

我们将这三条数据输入到 Import REConstructor 中。

我们单击 Get Imports。

我们看到 Import REConstructor 找到了 IAT 中的每项元素,并且 valid 显示的都是 YES,表示这些项都是有效的,那么无效的是什么情况呢,有些壳会将这些值进行重定向,并不是直接调用 API 函数,就会导致 Import REConstructor 定位出来的项都是无效的,好了现在这些项都是有效的,我们就可以进行 dump 了。

在 dump 之前我们先来看看 User32.dll 这个项中内容是什么,选中该项,单击左边的+号就可以展开。

好了,我们可以看到每个 API 函数名称字符串的指针都显示出来了,该程序调用的第一个 API 函数 GetModuleHandleA 这一项在哪里呢?

大家应该还记得,IAT 的中的第一项值为 403238,减去映像基址 400000 就等于 3238,位于 kernel32.dll 中。

我们单击 kernel32.dll 这一项左边的+号。

可以看到 3238 对应的正好是 GetModuleHandleA。好了,现在我们就可以对之前 dump 出来的程序的 IAT 进行修复了,我们单击 Fix Dump 按钮。

选中之前 dump 出来的文件。

我们可以看到修复完毕了,修复过的文件被重命名为了 dumped_.exe。我们来看一看。

我们双击它,看看能不能正常运行。

我们可以看到还是提示无效的 win32 程序,嘿嘿,为什么呢?别担心,通常修复了 IAT 以后,都会出现这种状况。我们现在来打开 PE TOOLS。

我们选择菜单项中的 Rebuild PE(重建 PE),我们找到刚刚的 dumped_.exe,重建之,运行,发现可以正常运行了,嘿嘿。

大家将这个程序拿到其它机器上运行也是没有问题的。

我们将 dumped_.exe 加载到 OD 中。

这里 OD 提示入口点位于代码段之外,因为 UPX 壳将代码段指定到了第三个区段。这个问题可以修复。我们下面来修复它。

我们单击 OK,停在了入口点处,我们在数据窗口中定位到 400000 地址处。

将数据窗口的显示模式切换为 PE 头模式。

往下拉:

可以看到 PE 头的相对虚拟地址(RVA) 为 80,即虚拟地址(VA) 为 400080。

这里我们看到 Base of Code = 9000,表示代码段的相对虚拟地址为 9000,我们需要将代码段的相对虚拟地址修改为 1000。

我们选中 BaseOfCode 这一行,单击鼠标右键选择-Modify integer。

修改完毕以后我们将所做的修改保存到文件。

我们再次加载修复过的程序,可以看到 OD 没有弹出警告窗口了。我们单击工具栏中 M 按钮看看各个区段的描述信息。

我们可以看到这里多出了一个区段,名字叫做 mackt,这是 Import REConstructor 给该程序添加的一个新的区段,专门用来存放新的导入表。我们在数据窗口中看一看导入表的情况(PE 头显示模式)。

我们可以看到导入表的相对虚拟地址为 B000,即虚拟地址为 40B000,刚好就是 Import REConstructor 添加的那个区段。

我们将数据窗口的显示模式切换为正常模式。

我们在数据窗口中定位到导入表 40B000 地址处。

这里我们可以看到第一个 IID,第四个字段为 DLL 名称字符串的指针,这里是 B078(RVA),即 40B078(VA)。

指向的是 user32.dll。

第五个字段为该 DLL IAT 项的起始地址的 RVA,这里是 3184,即 IAT 中的第一个元素的地址为 403184。

这里我们可以看到 IAT 中保存的各个 API 函数的入口地址。我们定位到可执行文件中对应 IAT 的偏移处,看看各个 API 函数名称字符串指针的情况如何。

我们可以看到 IAT 项中的值为 B084,即 40B084,指向的刚好是 KillTimer 这个 API 函数名称字符串。

这样我们就通过 Import REConstructor 完成 IAT 的重建工作,操作系统在程序启动的时候就可以根据 IAT 中 API 函数名称字符串通过调用 GetProcAddress 获取到相应 API 函数地址,然后重新填充到 IAT 中。

本章我们就完成了第一个 IAT 的修复工作,虽然不是很难,但是也算是对我们前面章节知识点的一个汇总吧。大家要好好理解本章的内容。

本章我们没有介绍 AntiDump,等我们后面遇到了再介绍。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文