- 第一章 - OD 的各个窗口介绍
- 第二章 - 数值系统
- 第三章 - 寄存器
- 第四章 - 汇编指令
- 第五章 - 数学指令
- 第六章 - 比较和条件跳转
- 第七章 - call ret
- 第八章 - 循环 字符串指令和寻址方式
- 第九章 - 基本概念
- 第十章 - 断点
- 第十一章:硬件断点与条件断点
- 第十二章 - 消息断点
- 第十三章 - 硬编码序列号寻踪-Part1
- 第十四章 - 硬编码序列号寻踪-Part2
- 第十五章 - 硬编码序列号寻踪-Part3
- 第十六章 - 序列号生成算法分析-Part1
- 第十七章 - 序列号生成算法分析-Part2
- 第十八章 - 序列号生成算法分析-Part3
- 第十九章 - OllyDbg 反调试之 IsDebuggerPresent
- 第二十章 - OllyDbg 反调试之检测 OD 进程名
- 第二十一章 - OllyDbg 反调试之检测 OD 进程名,窗口类名,窗口标题名
- 第二十二章 - OllyDbg 反调试之 UnhandledExceptionFilter,ZwQueryInformationProcess
- 第二十三章 - OllyDbg 反调试之 ProcessHeap,NTGlobalFlag,OutputDebugStringA
- 第二十四章 - OllyDbg 反调试之综合 CrackMe
- 第二十五章 - 异常处理
- 第二十六章 - Visual Basic 程序的破解-Part1
- 第二十七章 - Visual Basic 程序的破解-Part2
- 第二十八章 - Visual Basic 程序的破解-Part3
- 第二十九章 - P-CODE-Part1
- 第三十章 - P-CODE-Part2
- 第三十一章 - 脱壳简介
- 第三十二章 - OEP 寻踪
- 第三十三章 - 神马是 IAT 如何修复
- 第三十四章 - 手脱 UPX,修复 IAT
- 第三十五章 - 手脱 ASPack V2.12
- 第三十六章 - IAT 重定向
- 第三十七章 - 论 IAT 重定向之修复
- 第三十八章 - 手脱 Yoda's Protector v1.3(Yoda's Crypter)
- 第三十九章 - 神马是 stolen bytes
- 第四十章 - OllyDbg 脚本的编写
- 第四十一章 - 神马是 AntiDump
- 第四十二章 - ACProtect V1.09 脱壳(寻找 OEP 绕过硬件断点的检测 修复 Stolen code)
- 第四十三章 - ACProtect V1.09 脱壳(编写脚本修复 IAT)
- 第四十四章 - ACProtect V1.09 脱壳(修复 AntiDump)
- 第四十五章补充章节-ReCrypt v0.80 脱壳(续)
- 第四十六章 - Patrick 的 CrackMe-Part1
- 第四十七章 - Patrick 的 CrackMe-Part2
- 第四十八章 - PeSpin V1.3.04 脱壳-Part1
- 第四十九章 - PeSpin V1.3.04 脱壳-Part2
- 第五十章 - 再谈 ReCrypt v.0.80 脱壳(调戏 OutputDebugString)
- 第五十一章 - ASProtect v2.3.04.26 脱壳-Part1
- 第五十二章 - ASProtect v2.3.04.26 脱壳-Part2
- 第五十三章 - TPPpack 脱壳
- 第五十四章 - EXECryptor v2.2.50.a 脱壳-Part1
- 第五十五章 - ExeCryptor v2.2.50.a-Part2
- 第五十六章 - EXECryptor v2.2.50.b 脱壳
- 第五十七章 - ExeCryptor v2.2.50.c/d/e/f/g 脱壳
- 第五十八章 - ExeCryptor v2.2.50
第五十四章 - EXECryptor v2.2.50.a 脱壳-Part1
当我们遇到一款之前没有分析过的壳的时候,不要盲目的下手,我们最好先在网上搜一下该壳相关的 UnPackMe。如果有相关的 UnPackMe 的话,我们可以将 UnPackMe 与目标程序对照着来分析。如果实在找不到相关的 UnPackMe 的话,我们可以去下载一个该壳的加壳器,然后我们找一个比较简单的小程序(PS:或者自己编写一个小程序也可以,有源代码最好不过了) 来作为加壳的对象。接着我们使用该壳的加壳器对我们的小程序进行加壳,我们逐一选择不同的加密强度,从最低保护强度到最高保护强度。接下来逐一分析该壳不同的加密选项都有什么不同,等我们把该壳的各个保护手段都研究透彻了以后,再来分析我们最初的目标程序,就会容易很多。就算以后该壳发布了新的版本,我们有了对其之前版本的深入理解,再来分析其最新版本应该也不会太困难,因为通常来说,新版本的改动不会很大。
我已经为大家准备好了 EXECryptror 的一系列 UnPackMe,虽然不是 EXECryptror 最新的版本,但是对于研究 EXECryptror 的保护机制已经足够了。(PS:EXECryptror 在当年来说算的上一款猛壳)
这里我们可以看到不同等级的 UnPackMe,难度逐一递增。
本章我们的目标程序是上图中的这个 UnPackMe_ExeCryptor2.2.50.a.exe。我们直接运行该程序,在弹出的对话框中可以看到保护措施。
我们可以看到这个等级的加密强度几乎为零,只是简单的压缩代码/数据/资源,跟 UPX 壳的做法很比较相似。这里如果我们将 UPX 的 UnPackMe 与该程序对照着来分析的话,就可以很容易的得知其 OEP 以及 IAT 的起始地址,大小。
我们用 OD 加载 UPX 的 UnPackMe,断在了入口点处。
这个入口我们应该很熟悉了吧,可以说几乎本系列教程的每一个章节都可以看到。由于该 UnPackMe_ExeCryptor2.2.50.a.exe 与 UnPackMe_UPX1.91.a.exe 的原程序都是一样的,所以加壳以后,它们的 OEP,以及 IAT 也应该是一样的,它们调用的第一个 API 函数都是 GetVersion。我们可以得到这些基本的信息。
好,下面我们来分析 UnPackMe_ExeCryptor2.2.50.a.exe。
首先配置好 OllyAdvanced 这个反反调试插件。
这里我用 Patched 4 这款 OD(我一般都是用这个版本),OllyAdvanced 这款插件里面有很多选项可供我们选择,其他的选项勾不勾选无所谓,但是 Break on TLS Callback 这个选项这里我们一定要记得勾选。
如果大家用 OD 加载 UnPackMe_ExeCryptor2.2.50.a.exe 的话,会发现还没有达到入口点程序就退出了。这是因为 ExeCryptor 利用了 TLS CALLBACK 这一特性在入口点之前执行代码-检测是否正在被调试,如果是,则退出进程。TLS CALLBACK 可以在入口点之前执行代码这个特性最初是由一个病毒作者发现的,后来被 ExeCryptor 的作者利用来进行反调试。
这里我们将首次中断的地方切换为 System breakpoint 处。
我们一运行起来就会断在 TLS CALLBACK 处。
我们可以看到 OD 状态栏上的提示。
这是 OllyAdvanced 这个插件帮我们定位到的 TLS CALLBACK 回调函数的入口地址。下面我们来看看如何手工定位 TLS CALLBACK 回调函数的入口地址。
我们通过在数据窗口中按 CTRL+G 输入 400000 定位到 PE 头,然后单击鼠标右键选择-Special-PE header 将数据窗口的显示模式切换为 PE 解析模式,往下拉。
这里我们可以看到 TLS Table address 为 93110(RVA),加上映像基址 400000 就得到了 493110,即 TLS TABLE 的起始地址。我们在数据窗口中定位到该地址,我们往下面看就可以找到 TLS 回调函数的入口地址。
这里我们就定位到了 ExeCryptor 在到达入口点之前要执行代码的起始地址了,使用 OllyAdvanced 插件的话,它可以帮助我们直接定位到这个地址。
接下来我们来看看如何使用 PE 文件编辑器来定位 TLS 回调函数的入口地址。
这里我们单击 directory 按钮查看数据目录。
我们可以看到 TLS Table 起始地址的 RVA 为 93110,大小为 18。这右边还有个 TLS 按钮,单击该按钮我们就可以精确的查看 TLS 的回调函数入口地址的指针等信息。
我们可以看到 TLS 回调函数的入口地址存放在 49312C 中,我们在数据窗口中定位到该地址。
我们可以看到的确是 TLS 回调函数的入口地址,嘿嘿。好了,现在我们就知道如何用 OD,PE 文件编辑器以及 OllyAdvanced 插件来
定位 TLS 回调函数的入口地址了。使用 OllyAdvanced 插件的话,我们直接就可以断在 TLS 回调函数的入口地址处,如果是手工的话,我们首先要断在系统断点处,然后在 TLS 回调函数的入口处设置一个断点,然后运行起来,就可以断在 TLS 回调函数的入口处了。
我们运行起来。
这里我们可以看到壳已经检测到自己正在被调试,退出了进程。OllyAdvanced 插件里面的选项我们都勾选上了还被检测到,那我们用 AntiDetectOlly 这个工具 Patch 一下 OD 试试看,会发现还是会被检测到。好,那我们再次回到 TLS 回调函数入口,看看为什么会被检测到。
这里我们打开断点窗口查看一下,尽管我们之前并没有设置断点,我们可以看到这里有一个一次性断点。
而此时壳在 TLS 回调函数中的检测代码还没有执行,当检测代码执行的时候,就会发现内存中有指令被替换成 CC,也就说明正在被调试,所以这里我们删除掉这个断点,然后运行起来,看看还会不会被检测到。
这里我们可以看到程序正常运行起来了,并没有退出。也就是说的确是这个断点被壳检测到了,才导致退出的。我们直接手动将该断点删除即可。如果遇到有的情况,删除了这个断点,还是退出的话,那么就是说出除了 OllyAdvanced 插件里面的反反调试选项以外,我们还要添加其他的反反调试选项。
好了,现在问题我们已经解决了,我们重启 OD。
现在我们打开区段列表窗口,假设 OEP 位于代码段的话,那么我们选中起始地址为 401000 的区段(代码段),单击鼠标右键选择 Set break-on-execute。
因为我们刚刚重启了 OD,所以我们还要再次删除断点列表窗口中的断点。
运行起来。
(PS:这里利用 OllyBone 这个插件的 Set break-on-execute 选项,我依然是怎么断也断不下来,无语球了,哈哈哈。等明年有时间我自己写个 break-on-execute 的插件吧!这里的话我就给大家介绍一下我断 OEP 的方法吧,嘿嘿。首先最后一次异常法大家就不要想了,因为压根就没有异常,哈哈。我呢,是用 OD 自带的 Set break-on-access 这个选项来定位 OEP 的,由于是访问断点,所以读取,写入,执行的时候都会断下来,所以肯定是没有 OllyBone 的 break-on-execute 快的,但是 OllyBone 不好用,我也没有办法。我们重启 OD,断在了 TLS CALLBACK 回调函数的入口处。
老规矩,删除掉断点列表窗口中的一次性断点。
接着给代码段设置 break-on-access 断点。
这里的大家要记住,break-on-access 是一次性断点,断下来了就没了,下次要用的话,还要设置一次。
这里我们就设置完毕 break-on-access 断点了,我们可以看到该区段的起始地址被标注为红色了。
我们运行起来。
断在了这里,从 OD 状态栏中的提示信息,我们可以知道这是由于写入导致的中断。
我们要的是执行导致的中断,而不是读取或者写入导致的中断。
这里大家不要盲目的再次设置 break-on-access 断点,然后按 F9 键直接运行起来。
如果基础好的童鞋的话,一眼就可以看出这里是一个循环,大家看出来没有?我的天!有童鞋说木有看出来!
我们用鼠标选中接下来的 4DB304 这个地址处的跳转指令。
看到没,出现了向上指的红色箭头,这款代码不是循环操作是什么?嘿嘿。那么怎么样跳过循环呢?很简单,直接对下一条语句即 4DB306 处设置一个断点,然后运行起来,就可以跳过这个循环了。但是大家不要慌,将代码往下拉,看看这是不是一个嵌套循环。
我们会发现 4DB364 处也是一个向上的跳转,说明这块代码是一个双层嵌套循环。
好,那么我们直接对 4DB364 这个地址的下一条语句处即 4DB336 地址处设置一个断点。
运行起来。
我们可以看到断在了 4DB366 地址处,也就是我们跳过了这个双层嵌套循环。
接下来我们删除掉 4DB366 地址处的断点,然后依然是对代码段设置 break-on-access 断点。
运行起来。
这样我们就断在了 OEP 处,注意到 OD 状态栏中的提示信息没有?该中断是由执行导致的。
以上就是我定位 OEP 的方法。
不知道有木有童鞋的 OllyBone 插件能够断下来,如果你们中有人能断下来,请分享一下经验,谢谢,反正我是一次都没有断下来。
~~~~(>_<)~~~~
)
这里我们可以看到断在了我们熟悉的 OEP-4271B0 处了,这个等级的保护并不存在 stolen bytes,我们直接就可以定位到 OEP。
我们对比着 UPX 的 UnPackMe 来看,它到达 OEP 后以后,下面的 GetVersion 的调用处并没有被重定向,而我们这里被重定向了,下面我们来修复 IAT。
首先我们来定位 IAT 的起始地址和结束位置。
这里我们可以看到 IAT 的起始地址为 460818,跟 UPX 的 UnPackMe 一样。我们继续往下定位 IAT 的结束位置。
这里我们可以看到 IAT 的结束地址为 460F28。我们来计算一下 IAT 的长度。
计算得出 IAT 的长度为 710。
OEP(RVA):271B0
IAT 起始地址(RVA):60818
IAT 的大小:710
这里 IMP REC 重建 IAT 所需要的数据我们都有了。
下面我们的任务就是来修复 IAT,ExeCryptor 并不存在我们前面章节介绍过的关键跳,它是怎么做的呢?它会在特定的时候将正确的 API 函数地址填充到对应的 IAT 项中,所以这里我们给 GetVersion 所在的 IAT 项设置内存写入断点。
这里我们首先要选择 Remove break-on-execute 将 break-on-execute 断点删除掉,以免出错。
这里我们给将要调用的第一个 API 函数所在的 IAT 项设置了内存写入断点,运行起来,看看会发生什么。
断在了这里,我们可以看到这一条指令是将正确的 API 函数地址保存到对应的 IAT 项中。
下面几行,我们会发现其会将 479030 处的代码修改为 C3,即 RET 指令,然后利用 RET 指令返回到某地址处再去调用实际的要调用的 API 函数,下面我们来详细跟踪一下这个流程。
我们可以看到在正确的 API 地址被保存到对应的 IAT 项中后,下面会向 479030 地址处写入一个 C3,我们看看 479030 地址处之前是什么内容。
479030 之前是这样的:
写入 C3 后变成了:
这里我们可以看到被修改为了 RET 指令,相当于该壳在修复 IAT 项以后再进行自修改(修改自身代码)。但是该壳的代码并不位于第一个区段,所以这里我们重启 OD,达到 OEP 以后对 479030 地址处的指令设置内存访问断点。
首先我们到达第一个 API 函数调用处。
下面我们给返回地址 4271DC 处设置一个断点。
现在我们运行起来。
我们可以看到断在了条件跳转处,当 API 函数地址被填充到对应 IAT 项中以后,这里就会被自修改为 RET 指令,壳的自修改是我们要重点关注的,也就是说我们在到达 OEP 处以后,可以对壳修复 IAT 项代码所在的区段设置内存写入断点,当断下来时,就到了自修改的地方,
现在我们重启 OD,到达 OEP 处。
这里我们再次到达第一个 API 函数调用处,这里是 CALL 重定向后的地址 492493,当 479030 处被修改为了 RET 指令,重定向的 IAT 项已经被恢复为正常的 API 函数地址了,所以这里我们给壳所在的区段设置内存写入断点,接着运行起来,看看会发生什么。
接着我们还是对返回地址 4271DC 处设置一个断点,运行起来,看看会发生什么。
继续:
下一个:
这是实际上是在填充一个字符串,我们就不一个字母一个字母的看了,我们直接在数据窗口中查看填充完毕后的整个字符串是什么。
我们可以看到实际上是一个 DLL 的名称字符串,可能下面会被用来获取 API 函数的地址,我们继续。
这里是添加字符串结束符’\0’。
这里又是重复上面的步骤,但是这次是填充字符’K’的小端存储方式,4B 反过来就是 B4,这么做的目的可能是为了隐藏字符串。
接着到了这里,我们可以看到是保存 Kernel32.dll 的基地址,我这里是 7C800000。
接着再次到了这里,填充 C3,此时对应 IAT 项中的值已经被修复为正常的 API 函数地址了。
我们继续运行就断在了返回地址处,我们按 F7 单步继续往下跟踪到下一个 API 函数调用处。
给返回地址处设置一个断点,运行起来。
这里是将 47A0BC 处的首字节修改为 RET 指令。
我们单步往下跟踪到 47A0BC 的 RET 指令处,可以看到对应 IAT 项中的值已经被修改为正确的 API 函数地址了,该 API 函数是 Kernel32.dll 导出的。
我们继续看接下来要调用的这个 API 函数。
到了这里,我们对比着 UPX 的 UnPackMe 来看。
我们可以看到这里实际要调用的 API 函数是 VirtualAlloc,我们还是在返回地址处设置一个断点,运行起来。
这里又是填充另一个 RET,我们来看看修改之前的代码是什么:
被修改为 RET 之前是 POP EDX,修改为 RET 以后,就会返回到 480D4A 处去调用实际要调用的 API 函数,即 VirtualAlloc。
也就是说调用 API 函数的过程是,首先将对应 IAT 项中重定向的值修改为正确的 API 函数地址,然后通过自修改得到 RET 指令,
接着通过该 RET 指令返回到相应的地址处去调用实际要调用的 API 函数。
我们继续往下单步跟踪来验证一下:
可以看到这里 480D5F 处的 POP EDX 被修改为了 RET,此时我们可以注意到 4609A8 这个 IAT 项中的值已经被修复为正常的 API 函数地址了,接着执行 RET 指令,就会返回到 480D45 处,调用实际要调用的 API 函数。
好了,下面我来给大家总结一下整个过程:首先获取相应模块的基地址(可能是调用的 LoadLibraryA),然后是获取对应的 API 函数地址(可能是调用 GetProcAddress),接着将 IAT 项中重定向的值修改为正确的 API 函数地址,然后修改自身区段的代码来达到调用实际要调用 API 函数的目的。
好了,本章就到这里,下一章我们来尝试编写脚本修改 ExeCryptor 的 IAT。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论