- 第一章 - 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
第五十三章 - TPPpack 脱壳
上一章中最后留的那个小比赛最后的获胜者是 Ularteck 童鞋。下面我们就用 Ularteck 童鞋编写的第一个脚本来定位 OEP 以及修复 stolen bytes。脚本如下:
/* ############################################################################################################## CracksLatinoS - 2006 作者: Ulaterck. 描述: 该脚本的功能的定位 TPPpack 的 OEP 以及修复其 stolen bytes 目标程序: UnPackMe_TPPpack.exe 配置要求: ODBGScript 1.48 , HideDebugger 1.24 , HideOD, 停在入口点处,忽略 Kernel32 的异常,其他异常均不忽略. 因为我们这里要用到最后一次异常法,所以在执行该脚本之前首先要知道最后一次异常的地址,然后再执行该脚 本 以下关于该脚本的详细注释 ############################################################################################################## */ var dir_excep var Newoep var dir_JMP var dir_CALL var oep var StartScan var Opcodes var temp var temp2 var temp3 Data: mov Newoep, eip // 将入口点保存到变量 Newoep 中 ask "最后一次异常的地址是多少?" // 弹出一个对话框让用户输入最后一次异常的地址 cmp $RESULT,0 // 判断用户是否输入了地址 je warning // 如果用户没有输入地址则跳转到 warning 标签处 mov dir_excep, $RESULT // 将用户输入的地址保存到变量 dir_excep 中 jmp Initiation // 跳转到 Initiation 标签处 warning: msg "请重新执行该脚本,再次输入一个有效的地址!" jmp final Initiation: run // 运行起来 eoe check // 如果发生异常断了下来,就跳转到 check 标签处 check: cmp eip,dir_excep // 判断断下来的地方是不是最后一次异常处 je last // 断下来的地方刚好是最后一次异常处,则跳转到 last 标签处 esto // 忽略掉异常继续执行,相当于在 OD 中按 SHIFT+F9 jmp Initiation: // 跳转 Initiation 标签处继续定位最后一次异常处 last: findop eip,#FFE0# // 从最后一次异常处开始搜索 JMP EAX 指令,以便下面定位 stolen bytes mov dir_JMP,$RESULT // 将 JMP EAX 指令的地址保存到变量 dir_JMP 中 bp dir_JMP // 对 JMP EAX 指令设置断点 esto // 忽略掉异常继续执行,相当于在 OD 中按了 SHIFT+F9 bc dir_JMP // 删除掉 JMP EAX 指令处的断点 sti // 单步步入,相当于在 OD 中按 F7,单步以后就到了 stolen bytes 处 mov oep,eip // 将 stolen bytes 的起始地址保存到变量 oep 中 mov StartScan,eip // 将 stolen bytes 的起始地址保存到变量 StartScan 中 LookForCall: // 开始搜索 Stolen bytes 中需要修正偏移量的 CALL findop StartScan,#E8# // 搜索以机器码 E8 开头的 CALL 指令,即待修正偏移量的 CALL 指令 cmp $RESULT, 0 // 判断是否搜索到了待修正偏移量的 CALL 指令 je final // 没有搜索到的话,则跳转到 final 标签处 mov dir_CALL, $RESULT // 将待修正偏移量 CALL 的地址保存到变量 dir_CALL 中 mov StartScan, $RESULT // 将待修正偏移量的 CALL 指令的地址赋值给变量 StartScan add dir_CALL,1 // 指向偏移量 mov Opcodes, [dir_CALL] // 获取待修正的偏移量并保存到变量 Opcodes 中 add Opcodes,StartScan // 将偏移量加上 CALL 指令所在的地址 add Opcodes,5 // 加上 CALL 指令的长度 // 这样就得到了 CALL 指令的目标地址 //修正 CALL 指令的偏移量 mov temp, StartScan // 将 CALL 指令的地址保存到临时变量 temp 中 sub temp, oep // 计算 CALL 指令距离 stolen bytes 起始地址的长度,并保存到临时变量 temp 中 mov temp2, Newoep // 将入口点的值保存到临时变量 temp2 中 add temp,temp2 // 计算 CALL 指令新的地址,并保存到变量 temp 中 sub Opcodes, temp // 将目标地址减去 CALL 指令新的地址 sub Opcodes, 5 // 然后减去 5,就得到了 CALL 指令修正后的偏移量 edit: // 将 CALL 指令的偏移量修正 mov temp3, StartScan // 将 CALL 指令所在的地址保存到临时变量 temp3 中 add temp3,1 // 指向待修正的偏移量 mov [temp3], Opcodes // 修正偏移量 jmp LookForCall final: ret
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
附脚本的截图:
下面我来给大家详细讲解这个脚本的作用。
Part1:定位 OEP 并修复 stolen bytes(by Ularteck)。
详情:
首先我们需要配置一下反反调试插件。
OD 加载目标程序以后直接运行起来,可以看到完美运行。
下面我们利用最后一次异常法来定位 OEP,对于最后一次异常法大家应该很熟练了吧。我们经常会用到它。此法同样适用于 ASProtect 2.1 SKE,2.2 SKE,2.3SKE 以及带 VM 的版本。
这里我们先将所有忽略的异常选项都勾选上。
接着将程序运行起来,然后打开日志窗口,看看最后一次异常发生指令所在的地址是哪里。
我这里最后一次异常指令所在的地址为 0046D36B,下面我们就可以利用脚本来定位 OEP。
在使用脚本之前,我先演示一下如何手工定位 OEP。
我们重启 OD。
接着将忽略的异常选项的对勾都去掉。
接着直接按 F9 键运行起来。如果断在了不是 0046D36B 的异常处的话,就直接按 SHIFT+F9 忽略掉异常继续执行。
这里我们就断在了最后一次异常处。接下来按 ALT+M 打开区段列表窗口。
对代码段设置内存访问断点。
按 SHIFT+F9 忽略掉异常运行起来,断在了这里。
如果我们观察一下堆栈的话就会发现这里并不是真正的 OEP,明显存在 stolen bytes。
我们可以看到之前已经执行过 stolen bytes 了。返回地址为 8B0EA4,该地址属于起始地址为 8B0000 的区段。
好,下面我们重启 OD。
我们现在将脚本修改一下,让其自动定位到最后一次异常处。
将脚本修改成如下:
好,修改完毕以后。
我们重启 OD 以后,执行该脚本。
这里弹出了一个对话框要求我们输入最后一次异常指令所在的地址,这里我输入 46D36B。
单击 OK。
好了,我们可以看到脚本执行完毕了,我们可以看到刚好断在了最后一次异常处。
我们按 ALT+M 打开区段列表窗口。
接下来我们并不是跟刚才一样对代码段设置内存访问断点,这次我们对起始地址为 8B0000 的区段设置内存访问断点。
按 SHIFT+F9 忽略掉运行起来,断在了 stolen bytes 处。
如果我们按减号键可以看到回到了最后一次异常指令处。
以下脚本是根据 Martian 先生在他的教程中介绍的定位 stolen bytes 的思路编写的,定位 stolen bytes 的思路如下:首先定位到最后一次异常处,接着往下搜索机器码为 FFE0 的 JMP EAX 指令,搜到该指令以后,对其设置断点,接着运行起来,断到了 JMP EAX 处,然后按 F7 键单步一下,就可以到达 stolen bytes 处了。
我们在脚本中添加一个变量。
变量 dir_JMP 用于保存 JMP EAX 指令的地址。
接下来在 last 标签处添加以下内容:
我们执行该脚本看看效果。
我们可以看到成功定位到了 stolen bytes 处。下面我们要做的就是将 stolen bytes 拷贝到入口点处。
这里我们从 8B0E48 开始拷贝,一直到 8B0EA4 为止,注意是二进制复制。
粘贴到入口点处。
这里我们可以看到 46B067 处的这个 CALL 是一个间接 CALL。
这里原 stolen bytes 应该是 CALL 004293A0,目标地址是 004293A0。
但是由于这是一个间接 CALL,所以我们这里直接将其二进制复制到别的地方的话,目标地址就变了。
所以这个 CALL 被复制到别处的话,首先需要修正偏移量,我们来看看如何修正偏移量,首先我们在数据窗口中定位到该指令。
这里我们不用考虑前面的操作码,直接看后面的 4 个字节的偏移量。
FF B7 84 FC,为了下面列公式方便,这里我们将其命名为 OPCODES。
这里我们将 008B0E9F,即这个 CALL 指令所在的地址命名为 DIR_CALL。
我们来算一下目标地址 004293A0 是如何得到的:
目标地址 = OPCODES + DIR_CALL + 5
目标地址 = FFB784FC + 008B0E9F + 5 = 004293A0
好了,现在我们已经知道 004293A0 这个目标地址是如何得来的了。下面我们来计算新的 OPCODES。
目标地址- CALL 指令新的地址 - 5 = 新的 OPCODES
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
何谓 CALL 指令新的地址:即该 CALL 指令被拷贝到的新的地址。
例如:
如果我们将 Stolen bytes 拷贝到入口点处。
这里我们看到 46B067 这个地址。这个地址的计算公式如下:
原地址 - Stolen bytes 的起始地址 + 入口点
这里原地址为 008B0E9F。
stolen bytes 的起始地址 = 008B0E48
入口点为 0046B010。
8B0E9F - 8B0E48 + 46B010 = 0046B067
所以说新地址为 0046B067
好了,现在我们来计算新的 OPCODES。
目标地址 - 新地址- 5 = FFFBE334
004293A0 - 0046B067 - 5 = FFFBE334
这里新的 OPCODES 我们有了。
现在我们来手动编辑它。
我们定位到 stolen bytes 处。
我们在数据窗口中定位到这个 CALL:
这里我们跳过 E8 这个机器码,直接修改后面的 4 个字节的偏移量:
将其替换成新的 OPCODES.
我们可以看到该 CALL 的 OPCODE 已经改变了,现在我们将 stolen bytes 拷贝到入口点处。
我们可以看到 46B067 处的 CALL 的目标地址这次正确了。
下面我们给脚本添加一些内容让其自动完成上述操作。
然后
执行该脚本。
这里我们可以看到执行了该脚本后,下面 CALL 的偏移量都被修正了。
下面我们要做的就是将 stolen bytes 二进制复制到入口点处。
好,现在我们定位到了入口点处,我们将 EIP 修改到入口点处。
我们选择是,下面来进行 dump。
Martian 先生的教程中提到了,这个大小也得修改,不然单击 Dump 按钮,会报错。
修改为:
这里我不使用 OllyDump 来修复 IAT,所以我去掉了 Rebuild Import 的对勾。
然后按 dump 按钮进行 dump。
好了,这里我们就 dump 完成了,但是肯定是无法正常运行的,因为 IAT 还没有修复。
下面我们来修复 IAT。
如果我们直接用 OD 加载 dump 文件的话,直接就会报错。
我们尝试用 PE 编辑工具重建 PE 看看。
好,重建 PE 完毕了,我们再次用 OD 加载它。
这里提一句,HideOD 这款插件有时候会出错导致程序正常运行,所以最好将其用 HideDebugger 和 OllyAdvanced 代替。
好了,第一个脚本已经给大家介绍完了,接下来给大家介绍第二个脚本。
var base var dir_VirtualAlloc var dir_VirtualProtect var dir_mov Initiation: gpa "VirtualAlloc", "kernel32.dll" //获取 VirtualAlloc 这个 API 函数的地址 mov dir_VirtualAlloc, $RESULT log dir_VirtualAlloc gpa "VirtualProtect", "kernel32.dll" mov dir_VirtualProtect, $RESULT bp dir_VirtualAlloc run eob info info: mov base, eax log base bc dir_VirtualAlloc bp dir_VirtualProtect Area: eob Section run Section: cmp esi, 00460000 je Return jmp Area Return: bc dir_VirtualProtect mov Reg_esp, [esp] bp Reg_esp eob Area_1 run Area_1: bc Reg_esp find base, #897C24188B4424# mov dir_mov, $RESULT log dir_mov jmp Nop Nop: bp dir_mov eob Nop2 run Nop2: bc dir_mov fill dir_mov, 4, 90 final: msg "NOP 完毕,请按 F9 键运行." ret
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
附第二个脚本截图:
这是修复 IAT 其中一个比较经典的方法,Martian 先生在他的教程中详细介绍过。在到达 OEP 之前我们可以对 IAT 中重定向的项设置内存写入断点,断下来的地方就是写入重定向值的地方。但是要对 IAT 进行写入的话,首先得让 IAT 所在的内存单元具有写入权限,所以势必会调用 VirtualProtect 来修改 IAT 所在内存单元的内存访问属性,赋予其写入权限。所以我们可以先对 VirtualProtect 这个 API 函数设置一个断点,等执行完该函数赋予写入权限以后,我们再对 IAT 中重定向的项设置内存写入断点。
从 Martian 先生教程中这张截图我们可以看到断在了 VirtualProtect 这个 API 函数的入口处,其想修改 IAT 所在内存单元的访问属性。我们执行到返回,然后对重定向的 IAT 项设置内存写入断点,接着运行起来,断在了写入重定向值的地方。
这里就断在了写入重定向值的地方,仔细观察我们可以知道此时 EAX 中保存了重定向的值,而 EBP 指向的是对应的 IAT 项。
现在我们在前面几行处设置一个断点,跟踪一下,看看是什么情况。
我们可以看到前面几行会获取正确的 IAT 值,而接下来会将正确的 IAT 值覆盖为重定向的值。所以我们要做的就是将写入重定向值的语句 NOP 掉。
所以脚本要做的事情就是定位到写入重定向值的指令,并将其 NOP 掉。
首先脚本要做的第一件事情就是查找 VirtualAlloc 这个 API 函数的地址,首次断到 VirtualAlloc 这个 API 函数时,我们就可以获取需要 NOP 掉的指令所在内存单元的首地址了,因为在不同的机器上,待 NOP 掉的指令的地址是会变的,所以我们有必要动态获取它。
var base var dir_VirtualAlloc var dir_VirtualProtect var dir_mov Initiation: gpa "VirtualAlloc", "kernel32.dll" //获取 VirtualAlloc 这个 API 函数的地址 mov dir_VirtualAlloc, $RESULT log dir_VirtualAlloc
这里是获取 VirtualAlloc 这个 API 函数的地址,然后将其保存到变量 dir_VirtualAlloc 中,同理 VirtualProtect 也是一样。
gpa "VirtualProtect", "kernel32.dll" mov dir_VirtualProtect, $RESULT
获取 VirtualProtect 这个 API 函数的地址,然后将其保存到变量 dir_VirtualProtect 中。
bp dir_VirtualAlloc run eob info info: mov base, eax log base bc dir_VirtualAlloc bp dir_VirtualProtect
接着对 VirtualAlloc 设置断点,运行起来,如果断下来就跳转到 info 标签处,将该程序刚申请的内存单元的首地址保存到变量 base 中,下面我们需要 NOP 掉的指令将位于这块内存单元中。
接下来删除掉 VirtualAlloc 的断点,然后对 VirutalProtect 设置断点。
Area: eob Section run Section: cmp esi, 00460000 je Return jmp Area
断下来了的话,判断 ESI 的值是否等于 460000(IAT 的起始地址),因为该程序会调用 VirtualProtect 修改 IAT 所在内存单元的访问属性。如果 ESI 等于 460000 的话,就跳转到 return 标签处。
Return: bc dir_VirtualProtect mov Reg_esp, [esp] bp Reg_esp
删除掉 VirtualProtect 的断点,将 ESP 指向的内容(返回地址) 保存到变量 Reg_esp 中,接着对返回地址处设置断点。
Area_1: bc Reg_esp find base, #897C24188B4424# mov dir_mov, $RESULT log dir_mov jmp Nop
当 VirtualProtect 调用返回后,下面就可以在之前申请的内存单元中搜索需要 NOP 掉的指令了。这里我们利用特征码来搜索。
特征码为:897C24188B4424。
搜索到了的话就跳转到 Nop 标签处。
Nop: bp dir_mov eob Nop2 run Nop2: bc dir_mov fill dir_mov, 4, 90 final: msg "NOP 完毕,请按 F9 键运行." ret
好了,这里第二个脚本也介绍完了。感谢 Ularteck 童鞋提供的这两个脚本以及 Martian 先生提供的教程。我从 Martian 先生的教程中截了一张图片来解释第二个脚本。
本章,大家应该对于如何编写脚本更加了解了吧。
好,本章就到这里。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论