- 第一章 - 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
第三十九章 - 神马是 stolen bytes
本章与下一章节将介绍 stolen bytes(PS:壳偷代码) 以及 OD 脚本编写方面的内容。我们拿 UnPackMe_PELock1.06 来讲解。这款壳拿来介绍 stolen bytes 正合适,很多经典的教程都拿它作为范例来讲解。
用 OD 加载它,停在了入口点处。
直接按 F9 键运行起来,会发现该程序会异常报错弹个错误框出来,直接 shift + F9 忽略异常继续运行,弹出主程序对话框,这里可以通过日志窗口中最后异常发生处来定位 OEP,大家可以自行尝试,我就不再赘述了,下面我们换种方法来定位最后一次异常发生处。
这里我们给 Ring3 异常分发函数 KiUserExceptionDispatcher 设置一个断点,所有的异常都会经过这一个点,我们在 OD 中定位到这个函数。
我们来分析一下这个函数。
最上面的红色箭头标注的是 NTDLL.DLL 中的 KiUserExceptionDispatcher 的起始地址,紧接着下面有一个 CALL,这里是调用 SEH 链中的异常处理函数,执行完毕以后返回,根据返回的结果来决定是否继续执行程序。
这里我们就没法继续往里面跟了,因为 OD 不能跟进 RING0,这里我们只能对异常处理函数下断点或者对触发异常的区段设置内存访问断点。这样 RING0 的部分执行完了以后,OD 就断在异常产生处所在区段。
好,现在我们运行起来,第一次异常断了下来。
我们在日志窗口中可以看到(在我的机器上) 异常发生在 3A5C74 处,好,接下来把忽略异常的调试选项都勾选上,我们给该异常发生处设置一个断点,接着我们看看堆栈窗口。
我们可以看到红色箭头标注处,也存放了异常发生的地址,确切的说是[ESP + 14]。
因此,这里我们用条件记录断点代替 INT 3 断点来记录[ESP + 14]的值,即记录异常触发的地址,嘿嘿。
这里我们在 KiUserExceptionDispatcher 的起始地址处单击鼠标右键选择-Breakpoint Conditional log。
这里我们将 Expression 设置为[ESP + 14](异常发生的地址),Pause program 设置为 Never,Log value of expression 设置为 Always,运行起来。
我们可以看到程序运行起来了,奇怪,这个壳居然没有检测条件断点,也没有对 KiUserExceptionDispatcher 下断进行检测。
好,下面我们来看看日志窗口中记录的结果。
我们可以看到记录了很多异常触发的地址,我们往下看。
我们可以看到最后一个异常发生在 3A6744 处,是非法访问异常,这里我们不能对该处设置 INT 3 断点,因为该壳会有检测导致程序无法运行。
这里我们可以将 Memory access violation 这一项对勾去掉,当产生内存访问异常的时候就会断下来,虽然这种方法可以奏效,都是只要是内存访问异常就会断下来,会断很多次,其实我们还有更快速定位的方法,这里我们还是将 Memory access violation 这一项勾选上。
我们还是来设置条件断点,我们打开断点列表,在断点上单击鼠标右键选择-Edit condition。
这里根据日志信息中显示的最后一次异常发生在 3A7644 地址处,我们只需添加一个条件就能让其断在最后一次异常处,我们设置条件为[ESP + 14] == 3A7644,这样当最后异常触发时就会断下来。
接着将 Pause program 这一项设置为 On condition,这样当条件满足时就会断下来,我们运行起来。
这里,就断到了最后一次异常处,我们没有必要一个异常一个异常的去定位。
现在如果我们对第一个区段设置内存访问断点就能定位到 OEP(真的是这样吗?嘿嘿,我们一起来看看)
这里我们可以看到断在了第一个区段中,正常来说,这里应该就是 OEP 了呀,但是看这些代码怎么不像 OEP 呀,这是怎么回事呢?这里就要给大家介绍 stolen bytes 了。stolen bytes:即某些壳在处理 OEP 代码的时候,会把 OEP 处固定的代码 NOP 掉,然后把这些代码放到壳代码的空间中去(而且常伴随着花指令)!使原程序的起始代码从壳空间开始执行,然后再 JMP 会原程序空间。如果我们脱掉壳,这一部分代码就会遗失,也就达到了反脱壳的目的,这就是 stolen code(或者 stolen bytes) 技术,或者更确切的说是 stolen OEP code 技术。
stolen bytes 了以后会怎么样呢?很简单,如果我们 dump 以后,修复 IAT 的时候,这里 OEP 就会被指定为 4271D6,但是这样的话程序就无法运行了,因为前面几行代码还没有执行,这几行代码在壳空间中,所以是不会被转储的,因此也得不到执行。
那我们该怎么办呢?
一种方式就是尝试跟踪壳的代码,当所有异常都触发以后我们就会到达错误 OEP 处,我们将跟踪过程中执行过的代码都保存到一个 txt 中,便于我们的分析,这里我们在 JMP 到错误的 OEP 4271D6 之前都执行些什么呢?
下面我们重启程序,看看哪些可能是 stolen bytes。
我们看下栈顶:
我的机器,当前指针为 12FFC4,大家有可能是其他的值,当到达真正的 OEP 处时,一般来说,栈顶指针应该也会指向 12FFC4 或者附近的地址,但是如果是假的 OEP 呢?
我们再次定位到假的 OEP 处。
如果是到达真正的 OEP 处时,栈顶应该是 12FFC4 或者附近的地方,但是该壳把 OEP 处前几行的代码清空掉了,并填入了垃圾数据,并且把这前几行代码放到壳代码的空间中去!接着在壳空间执行原程序的前几条指令,然后再 JMP 到原程序的假 OEP 处。大家应该还记得该 crackme 的 OEP 是 4271B0,我们来看看该地址处是什么。
这里我们可以看到,壳将 OEP 处的原始字节都填充为了垃圾指令,我们对该区段设置内存访问断点会断在 4271D6 处,不会断在前面。
有很多方法可能定位 stolen bytes,但最为经典,最为常用的方法还是在最后一次异常产生后单步跟踪,我们现在还是跟到最后一次异常处。
我们知道最后一次异常是在 3A6744 地址处产生的,这里我们对异常处理程序并不感兴趣,我们直接在其下面 7C91EB03 地址处下断。
运行起来,接下来程序将返回到哪里呢?我们要对哪里下断?一起来看看堆栈。
这里第一个参数是 CONTEXT 结构体的指针,我们在数据窗口中定位到该地址。
这里如果大家熟悉 CONTEXT 结构的话,就会知道其中有个字段标识的是 EIP 寄存器,其他一些字段标识的是其他一些寄存器,当异常处理完毕以后会根据 CONTEXT 结构中的 EIP 值来决定返回到哪里,后面章节我们会详细这个 CONTEXT 结构,现在我们只需要知道程序返回到哪里,EIP 字段位于偏移 B8 处(PS:可以参考 VC 中 CONTEXT 结构体的定位)。
因此,程序将返回到 3A6746 地址处。我们给该地址所在区段设置一个内存访问断点。
运行起来,我们可以看到断在了这里。
接下来我们来设置自动跟踪的条件,有好几个选项可供设置,比如说我们可以定位恢复寄存器环境指令,当恢复寄存器环境后,接下来就要执行 OEP 处的代码了,当前还可以设置其他条件,比如说,我们设置如下两项。
EIP is Range 这一项我们设置为 401000~475000,即原程序的所有区段,这样我们就能在 Run Trace 窗口中看到跟踪过程中执行了哪些指令,下面 Condition is TRUE 这一项我们设置 ESP = 12FFC4,即跟踪到栈顶指针跟假的 OEP 一致时,停止跟踪。
注意,这里调试选项中的 Trace 选项卡中的这两项我们也可以尝试勾选上,看看自动跟踪的效果如何,如果效果不好,我们再来去掉这两个对勾。
跟踪完毕以后,我们可以单击鼠标右键选择-Log to file(将日志信息保存到文件),再来进一步分析。
我们选择主菜单中的 Debug-Trace into,断了下来,但是并没有到达假的 OEP 处,我们继续 Trace into 多次以后才会到达假 OEP 处,Run Trace 窗口中记录了自动跟踪所执行的指令,我们依然看出来哪里是真正的 OEP 所在。
既然不奏效,那我们来换个条件试试,我们设置 Command is one of 这一项,当自动跟踪过程中遇到设置的命令序列中的任意一个就会停止跟踪,这里,我们填入两条命令,POPAD 和 PUSH EBP,即当遇到恢复寄存器环境或者 OEP 处常见的第一条指令时停止自动跟踪,我们来看看能不能奏效,单击主菜单中的 Debug-Trace into。
这里有可能是真正的 OEP,我们往下跟。
这里很明显是垃圾指令,紧接着下面是一个无条件跳转,没有实际作用,相当于花指令。继续往下跟。
这里应该是缺失的第二条指令。
我们继续往下跟,这里应该是一条正常的指令。
接下来我们就会到达假的 OEP 4271D6 处,因为这里是 PUSH 4271D6,接着通过 RET 就可以返回到假的 OEP 处。
接下来我们将可以将 stolen bytes 拷贝出来,我们首先在数据窗口中看看假 OEP 之前的内存单元的情况。
我们需要将 stolen bytes 填充到假 OEP 前面的内存单元中,好,现在我们按减号键返回到刚刚真正 OEP 的第一条指令处。
我们依次将缺失的指令所对应的字节码拷贝到记事本中,最后一个 PUSH 指令和 RET 指令不需要拷贝,这两条指令时用来跳转到假 OEP 处的,并不是 stolen bytes。
55 8B EC 6A FF 68 60 0E 45 00 68 C8 92 42 00 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 C4 A8 53 56 57 89 65 E8
以上是 38 个 stolen bytes,也即是 16 进制的 26。
所以应该把假 OEP 往下抬高 26(十六进制) 个字节。
即如果壳没有抽取 OEP 处的代码的话,真正的 OEP 应该是 4271B0,我们需要将被抽取的代码填充到 4271B0 处。
全选记事本中的字节拷贝出来,并在 4271B0 为起始地址,长度为 26(16 进制) 的区域上面单击鼠标右键选择 Binary paste(二进制粘贴)。
我们来看看拷贝的指令。
好了,stolen bytes 被找回来了,下面我们进行 dump 的时候需要将 OEP 修改为 4271B0,这样我们就解决了 stolen bytes 的问题。
下一章节我们来讨论如何编写脚本修改该程序的 IAT。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论