- 第一章 - 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
第四十九章 - PeSpin V1.3.04 脱壳-Part2
本章我们来修改 PeSpin 的 IAT。
当我们到达伪造的 OEP 处时,我们随便定位一个 API 函数调用处,接着定位到 IAT,通过观察很容易得知 IAT 的起始地址为 460818,结束位置为 460F28。但是我们会发现一个问题:有些 CALL 并没有调用 IAT 中的项。我们一起来看个例子。
这里整个 IAT 我都用浅绿色高亮标注出来了,但是我们会发现 IAT 中的有些项找不到参考引用的地方(比如说这些 0006 开头的值),这些值并不指向任何 API 函数,而且也不在任意一个 DLL 的地址空间范围之内,那么也就是说这些值不是重定向过的就是垃圾数据了。
现在我们来查看所有的 API 函数调用处,在反汇编窗口中单击鼠标右键选择-Search for-All intermodular calls。
我们会发现很多 CALL 读取的都不是 IAT 中的值,倒像是另一张表中的值(PS:这里为了方便下文的描述,我们姑且称这张表为 IAT2 )。
下面我们就在数据窗口中来定位到 IAT2,我们随便选中一个 CALL,单击鼠标右键选择 Follow in Disassembler,在反汇编窗口中定位到该 CALL。
我们来到了这里。
下面我们在数据窗口中定位到该 IAT2 中的项。
这里我们可以看出一些规律,我用浅绿色高亮标注出了连续排列的 3 项,项与项之间是用零隔开的,这里大概可以知道,IAT2 中的元素是重定向到各个 API 函数的。
我们单击鼠标右键选择 Follow,看看其会最终会重定向到哪个 API 函数。
经过几次跳转之后来到了这里。
又经过几次跳转以后我们来到了这里:
大家细心观察的话,会发现这里我用浅绿色标注出来的这三条指令在前面壳创建的区段中已经执行过了(不记得的话可以按减号键往回翻看),这样做的目的很可能是防止破解者通过在 API 的入口点处设置断点以此来判断是不是被重定向到了该函数。这里如果我们想知道该 API 函数是什么的话,直接在选中 MOV EDX,DWORD PTR SS:[ESP + 4]这条指令,单击鼠标右键选择 New origin here,将 EIP 指针指向该指令。(PS:其实不需要这样做,我们可以直接将反汇编窗口中内存地址与机器码中间的那一条线往右边拖动,就可以看到 API 函数的名称为 RtlLeaveCriticalSection 了)
这里我们可以看到 EIP 寄存器的右边显示出了该 API 函数的名称为 RtlLeaveCriticalSection。
好了,这里我们还是将 EIP 的值恢复吧,以免出错。
好了,这里我们就知道 IAT2 中的项的确是重定向的 IAT 项。也就是说该壳不会去调用 IAT 中的那些 0006 开头的值(这些值是该壳可以构造的,也可以说是混淆视听吧),那么 0006 开头的值占据的 IAT 项怎么办呢?对于这些项该壳会调用 IAT2 中相应的项,这么做的目的无疑是为了增加我们修复 IAT 的难度。我们该如何来修复 IAT 呢?
首先还是来看到 42891E 地址处的这个 CALL(PS:这里该地址大家可能各不相同,以自己机器上的为准)。
我们已经知道了该 CALL 实际上调用的 API 函数是 RtlLeaveCriticalSection。我们可以利用 RtlLeaveCriticalSection 这个函数入口地址来做做文章,首先第一步,我们需要定位到 46F525 这一项的值 00A205EC 是哪里写入的。
我们定位到了 46F525 这一项写入指令处以后,直接将写入指令中的 00A205EC 替换成 7C9110ED(RtlLeaveCriticalSection 的地址) 即可。
这里我们需要想个办法让其直接调用正确的 API 函数,也就说我们需要将该 IAT 项中的内容由 00A205EC 替换成 7C9110ED。
首先我们要知道该项是由哪里写入的话,我们需要给该项设置一个硬件写入断点。
下面我们就来看看 00A205EC 这个值是哪里写入到 46F525 中的,现在我们重启 OD。
断在了入口点处,我们运行起来。
中间会由于写入 46F525 断几次,但是因为写入的值并不是 00A205EC,所以我们继续运行,运行几次以后,我们发现这里写入的是 00A205EC,由于硬件断点是断在下一条指令处,所以我们来看看前一条指令是什么。
这里的代码被混淆了,看不出个所以然来。如果想清楚的看到前一条指令到底是什么的话,我们可以选中前面的一个 JMP 指令,然后单击鼠标右键选择 Follow。
这样就可以清楚的看到前一条指令是什么了,重定向到 IAT2 中值来至于 EAX 寄存器,我们来看看 EAX 此时的值。
我们可以看到的确是 00A205EC(PS:每个人机器上可能该值不同,以自己机器的为准),好了,我们已经定位到了关键点了。
接下来我们给 MOV DWORD PTR DS:[EDI],EAX 这一条指令设置一个硬件执行断点。
我们运行起来,再次断了下来,会发现继续在写入 46F52E 后面的 IAT2 中的项。我们观察一下寄存器的情况,会发现 EAX 中正好保存的是另一个重定向的值。
我们继续运行,会继续写入下一个重定向的值。
这里我们注意观察 EDI 值的变化,我们会发现 EDI 的值是递增的,指向的都是 IAT2 表中的每一个元素,再看看 EDX 的值,EDX 指向的正好是 IAT 中的项,好,现在我们重启 OD,一直运行,直到断到写入 46F52E 的前一项为止。
好了,这里由于写入 46F529 所在内存单元的时候断了下来,即 46F52E 的前一项,这里我们知道下一项要写入的是 RtlLeaveCriticalSection 这个 API 函数重定向过的值,这里我们不继续按 F9 运行了,我们利用 OD 自带的跟踪功能来协助我们分析,下一项的正确的 IAT 项值应该 7C9110ED(RtlLeaveCriticalSection 这个 API 函数的首地址)(PS:以自己机器上的地址为准),这里我们来看看哪个寄存器中会出现 7C9110ED 这个值,我们利用 OD 的自动跟踪功能来定位这个值。
我们来设置一个自动跟踪的条件:
EAX == 7C9110ED || EBX == 7C9110ED || ECX == 7C9110ED || EDX == 7C9110ED || ESI == 7C9110ED || EDI == 7C9110ED
||表示任意一个条件成立即为真。
也就是说当以上寄存器组中任意一个的值等于 7C9110ED 的时候就会停下来。
这里我们被忘了勾选上 Debugging options-Trace 菜单项中的 Always trace over system DLLs,Always trace over string commands(这个选项的意思就是遇到诸如 REPS 这样的字符串循环操作指令的时候直接跳过) 这两项,因为如果让 OD 自动跟踪系统 DLL 或者字符串操作指令的话耗费的时间可能会非常长,接下来我们选中 Debug-Trace into 菜单项开始自动跟踪。
稍等片刻就会停在这里。
我们可以看到这是一个 CALL 的返回地址,我们来看看跟踪的日志信息。
我们双击日志中的这一项。
(PS:这里我的日志信息中并没有记录 CALL NEAR DWORD PTR SS:[EBP + 404F95]这一项,这里由于我的日志跟踪信息中没有记录这条指令的缘故,所以我这里也显示不出 GetProcAddress 这个 API 函数名称,但是影响并不大)
这里明显可以看出壳在获取 API 函数的地址,接着会重定向到自己创建的区段中。
接下来我们来定位哪里会写入 00A205EC 这个值。
我们将跟踪的条件修改为:
EAX == 00A205EC || EBX == 00A205EC || ECX == 00A205EC || EDX == 00A205EC || ESI == 00A205EC || EDI == 00A205EC
我们再次选择 Debug-trace into 菜单项进行自动跟踪。
停在了这里,此时 EAX 指向的是正确的 API 函数地址,而 ESI 指向了重定向过的值,嘿嘿。这个点非常完美。我们删除之前创建的硬件断点。接着给 MOV DWORD PTR SS:[ESP+1C],ESI 这一条指令处设置硬件执行断点。
我们重启 OD,然后直接运行起来,可以看到第一次就断在了这里。
我们可以看到此时 EAX 同样指向了一个正确的 API 函数-GetSystemTime,而 ESI 寄存器也同样指向了一个重定向过的值,所以我们来 Patch 这条指令以此来修复 IAT2。
这里我们将 MOV DWORD PTR SS:[ESP+1C],ESI 这条指令修改为 MOV DWORD PTR SS:[ESP+1C],EAX,这样就用正确的 API 函数地址替代了重定向过的值,接着我们删除之前设置的硬件断点,运行起来。
我们来看看 IAT2 发生了什么变化。
好,我们可以看到现在 IAT2 中保存的都是正确的 API 函数地址了。
我们看下 46F52E 调用指令的情况试试:
这里我们可以看到调用的确是正确的 API 函数了,不再是重定向的值了。但是这里仍然是是 IAT2 中的值被修复了,IAT 中哪些 0006 开头的无效的项仍然没有被修复,所以下一步我们要将 IAT 中无效的值修复。
现在我们有两个切入点,一个就是是 46BBC4 这一条指令,我们需要将 ESI 修改为 EAX,这样就可以确保正确的 API 函数地址被填充到 IAT2 中。
另一个切入点是 46C010 这一条指令,我们分别给该这处指令设置硬件执行断点,接着重启 OD。
断在了这里,我们将 ESI 修改为 EAX。
我们运行起来。
断在了这里,这里是将正确的 API 函数地址填充到 IAT2 中,此时 EDX 指向了对应的 IAT 项,而 EDI 指向了 IAT2 中对应的项,现在
是我们修复 IAT 的绝佳时机,我们做如下操作即可:
我们知道上面这条 JMP 指令是直接跳转到 MOV DWORD PTR DS:[EDI],EAX 这条指令的,这里压根我们就不需要其无条件跳转,这条指令相当于是费的,我们将其 NOP 掉。
如下:
这里我们在上面添加一条指令 MOV DWORD PTR DS:[EDX],EAX,这样可以将正确 API 函数地址就被写入到了 IAT 中了。
这样 IAT 中和 IAT2 两张表中都将保存的是正确的 API 函数地址,现在我们删除掉之前设置的硬件断点。
给主程序代码段设置内存访问断点,运行起来,不一会儿就能断到伪造的 OEP 处。
这里我们就断到了伪造的 OEP 处,我们随便在下面找一个 CALL, 分别看看 IAT 和 IAT2 的情况如何。
这里我们可以看到 IAT2 中保存到都是正确 API 函数地址了。那么 IAT 中呢?
这里我们可以看到 IAT 中之前那些 0006 开头的值也已经被修复了。好了,下面我们要做的就是将代码段中的这些间接 CALL IAT2 中的项转化为 CALL IAT 中的项,下面我们就通过一个简单的脚本来完成。
我会给大家逐一介绍每一条语句是干什么用的,但是在解释脚本之前我们先来修复一下 stolen bytes。
这里脚本我已经写好了,可能不是最优的,但是已经足够用了,名称叫做 Spin.txt。
首先:
var var_call var var_table var var_api var var_iat var var_program var var_end
这里是声明该脚本中需要用到的一些变量。下面我会给大家介绍每个变量的功能。
mov var_program,401000 mov var_iat,460818
这里是初始化变量,将主模块代码段的起始地址赋值给变量 var_program,因为我们要从主模块的代码段起始地址处开始定位 CALL。接着将 IAT 的起始地址赋值给变量 var_iat。
TheStart: findop var_program,#FF15??# log $RESULT
这里才是我们脚本真正开始执行的地方。TheStart 是一个标签,顾名思义:开始。接下来是通过 findop 命令从 401000 地址处开始查找以 FF15 开头的间接 CALL,??表示通配符。如果查找成功,地址将会保存到保留变量$RESULT 中,否则$RESULT 将等于 0。
接下来通过 log 命令将$RESULT 的记录到 OD 的日志窗口中,这个命令不是必须的,只是为了方便我们的查看。
mov var_call,$RESULT cmp var_call,0 je TheFinal cmp var_call,44904B jae TheFinal
这里首先将查找到结果$RESULT 赋值给变量 var_call,接着判断查找的结果是否为 0,如果为 0,表示没有找到以 FF15 开头的间接 CALL,那么就直接跳转到 TheFinal 标签处结束该脚本的执行,如果找到了,判断间接 CALL 指令的地址是否高于代码段中最后一项间接 CALL 指令所在的地址,如果高于,同样跳转到 TheFinal 标签处结束脚本的执行。
TheFollow: add var_call,2 log var_call mov var_table,[var_call] log var_table mov var_api,[table] log var_api cmp var_api,50000000 jb TheJmp
这里将间接 CALL 指令的地址+2,然后读取 4 字节的内容保存到变量 var_table 中,也就是 IAT2 中的表项,接着读取表项的值也就是读取 API 函数的地址,判断其是否大于 50000000,如果大于说明是正常的 API 函数地址,如果小于就说明不是正确的 API 函数地址,我们跳转至 TheJmp 标签处继续定位下一个间接 CALL。
TheLoop: cmp var_api,[var_iat] je TheSolve add var_iat,4 cmp var_iat,460F28 jae TheJmp jmp TheLoop
我们现在有 IAT2 中正确的 API 函数的地址了,所以接着我们就需要查询 IAT 中跟其一致的 API 函数地址,如果是 API 函数地址相等的话,就可以将该间接 CALL 中的 IAT2 中的项替换成 IAT 中的项了,但是如果查遍整个 IAT 都没有查找到与之相等 API 函数地址的话,就跳转到 TheJmp 标签处进行定位下一个间接 CALL,如果在 IAT 中查找到了相等的 API 函数地址,就跳转到 TheSolve 标签处进行替换工作。
TheSolve: log var_iat log var_call mov [var_call],var_iat
这里将 IAT2 的项值替换成对应的 IAT 的项值。
sti mov eip,4271F7
这两行其实其实不起作用,但是由于 OllyScript 插件要求程序必须执行一些东西,不能单单的进行搜索或者更改值,或者挂起什么也不做,所以我们不得不添加一个 STL 命令,让其单步执行,相当于 F7,然后再将 eip 指向伪造的 OEP 处。
这里 OD 有一点不好的地方,就是当有些脚本 Patch 过的字节超过 1000 的时候就弹出一个消息框进行提示,我们点击几次 OK 以后脚本就执行完毕了,IAT 也就修复成功了。
好了,现在我们来 dump,然后用 IMP REC 修复 IAT。
这里我们发现存在一个无效的项,我们直接将其剪切掉。接着我们修复刚刚 dump 出来的文件,我们会发现无法运行,好像还缺少点什么。
这里存在 AntiDump,我们会发现入口点下面存在一些 CALL,这些 CALL 的地址属于 PE 头,而不是代码段。
我们单击鼠标右键选择 Search for-All intermodular calls。查看所有的 API 函数的调用处。
我们定位到无效的 CALL。
我们可以看到这些是需要我们修复的。
但愿不需要我们编写第二个脚本来进行修复。
现在我们需要在刚刚 dump 并修复了 IAT 的文件中定位 1000(十六进制) 个字节的空间用于存储 PE 头中有用的信息。
我们用 OD 加载刚刚 dump 并修复了 IAT 的那个文件,随便定位一个 1000(十六进制) 字节的区域,这里我找的区域起始地址为 45CAB0。
这里我们需要验证一下这块虚拟地址空间是否在文件中存在,我们单击鼠标右键选择 View-Executable file:
我们可以看到这块虚拟地址空间在文件中的确存在,现在我们需要从之前那个断在 OEP 处的 OD 中拷贝 PE 头中的 1000(16 进制) 个字节出来。
选中整个区段。
二进制复制并粘贴到 dump 文件所在 OD 的 45CAB0 为起始地址,长度为 1000(16 进制) 字节的区域中。并且将修改保存到文件。
现在 PE 头的数据我们是拷贝过去了,下面我需要在程序开始运行起来,起始地址为 45CAB0,长度为 1000 这部分数据覆盖到 PE 头中以此来修复 AntiDump。但是现在有个问题呀,PE 头具有可写权限吗?PE 头并一定具有可写的权限,那么我就需要给其赋予可写权限,我们通常会调用 VirtualProtect 这个 API 函数来修改内存的访问权限。但是该程序的 IAT 中并没有 VirtualProtect 这个 API 函数对应的项。
怎么办呢?通常为了兼容性考虑会使用 LoadLibrary 和 GetProcAddress 这两个 API 函数来配合获取 VirtualProtect 的地址。但是这里我会告诉你一个更加快捷的方式。
假设 LoadLibraryA 是 IAT 中的一项,这里 LoadLibraryA 的实现代码我已经用绿色高亮显示了。其实几乎所有的 XP 系统上 LoadLibraryA 与 VirtualProtect 的入口地址的差是一个定值。我们只需要通过 LoadLibraryA 的首地址减去这个定值就可以定位到 VirtualProtect 的首地址。
这里我们定位到 IAT 中的 LoadLibrary 这一项。
这里首先将 LoadLibraryA 的地址存放到 EAX 中。下面我们来计算一下 LoadLibraryA 与 VirtualProtect 这两个函数入口地址之间的差值。
这里我们可以看到 LoadLibraryA 与 VirtualProtect 这两个函数入口地址之间的差值为 2A7。
获取到 VirtualProtect 的入口地址以后,我们就可以对 PE 头赋予写入权限了。
这里是 MSDN 中对于 VirtualProtect 这个函数的说明。
我们首先需要一个 PUSHAD 指令保存寄存器环境,接着将 LoadLibrary 函数的入口地址保存到 EAX 中,接着将其减去 2A7 就得到了 VirtualProtect 这个函数的入口地址。接着调用 VirtualProtect 给 PE 头赋予写入权限。该函数的参数如下:
首先是要修改的访问权限的起始地址为 400000,长度为 1000,新的访问权限为可读可写可执行,旧的访问权限保存到 45CA50 所指向的内存单元中。接着将起始地址为 45CAB0,长度为 1000 的数据拷贝到起始地址为 400000,长度为 1000 的内存单元(也就是 PE 头) 中。接着调用 POPAD 指令还原寄存器环境,然后跳往 OEP 处。
现在要做的事情就是将入口点修改为 45CA10 了。
这里我们将 AddressOfEntryPoint 修改为 5CA10 即可(RVA)。
保存修改到文件,直接运行起来。
嘿嘿,搞定。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论