- 第一章 - 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
第二十九章 - P-CODE-Part1
(本章的 CrackMe 需要支持库 MSVBVM50.DLL)
前面章节我们已经介绍了 Visual Basic 破解相关的基础知识,如果大家还想更加深入的研究 VB 应用程序破解的话,可以继续学习高级篇中的关于 VB 破解的相关内容。(PS:就是我打包上传到百度网盘的西班牙破解文集系列)
COCO 写的 VB 破解教程就比较好,其中可能会涉及到比较复杂的处理技巧,拿来练习做好不过了。还有一些其他优秀教程可供我们更加深入的研究 VB Cracking。接着下来我们将讨论下一个话题-P-CODE。
我们知道 VB 编写的应用程序有两种编译方式:一种是 Native 方式,我们前面章节已经讨论过了,两一种就是 P-CODE 方式-我们接下来将讨论的话题。
Native 编译的代码和 P-CODE 的主要区别在于:Native 是直接执行代码段中的代码的。而对于 P-CODE,如果我们使用之前那个 Patch 过的 OD 加载它,并且给代码段设置内存访问断点的话(实际上是内存执行断点),除非是直接调用 API 函数,否则不会断下来。
说明,该区段没有直接可供 CPU 执行的代码。
P-code,实际上是一组自定义的指令集,必须通过基于堆栈的虚拟机翻译为 80X86 上的指令集才能执行,即通过 msvbvm50.dll 和 msvbvm60.dll 这两个动态库来解释执行。也可以理解为通过 P-CODE 告诉虚拟机将要进行什么操作。例如:
1e 表示 执行无条件跳转(JMP)
1e 意味着执行无条件跳转,该无条件跳转是通过虚拟机(msvbvm50.dll,msvbvm60.dll) 来执行的,也就是说 P-CODE 程序会读取代码段中的值,然后由这些值来告诉虚拟机需要执行什么操作。这也就是为什么我们说 P-CODE 程序的代码段中没有可执行代码的缘故。
现在就让我们一起拿起“手术刀”来跟踪,剖析一个 CrackMe 的 P-CODE 的奥秘吧。
我们实验的这个 CrackMe 名字叫做 clave1,这个 CrackMe 是我朋友 JB DUC 写的,用来介绍 P-CODE 相关的内容正好合适,我们需要找到该 CrackMe 的正确序列号。
很多人调试 P-CODE 可能喜欢用 WKT,如果大家想了解 WKT 怎么调试 P-CODE 的话,可以看 JB DUC 关于 P-CODE 的破解教程,我们这里还是用 OllyDbg 来调试了,由于微软官方并没有提供操作码的清单,所以还会用到另外一个工具 EXDEC-这个工具可以识别操作码的名称。
我们使用原版的 OD,配置好反反调试插件,加载该 CrackMe。可以看到停在了入口点处。
说明一点,之前介绍的用于剔除 Native 程序的 NAG 窗口的 4C 法同样适用于 P-CODE。
现在我们来看看跟 Native 的不同的地方:
我们从入口点往下看,会发现没有几行代码。
我们只看到了大片的字节码,这里不要试图去分析这些字节码(毫无意义),我们应该还记得对于 Native 方式编译的 VB 应用程序,入口点往下还会有大量的代码吧。
也有相似的字节码,我们继续往下。
这里我们可以看到大段的代码,而 P-CODE 的程序的话 OllyDbg 可能也会显示处少量的代码,OD 识别成了指令,但其实这些地方也是纯字节码。
我们继续来看刚刚那个 P-CODE 的 CrackMe。
另外一个特征就是 MethCallEngine 这个 API 函数,该函数我们在 P-CODE 方式编译的 VB 应用程序中都能看到,所以我们甄别一个 VB 应用程序是不是以 P-CODE 方式编译的,一般有两步:1:看代码段中入口点以下是不是大片的字节码 2:看有没有 MethCallEngine 这个函数。
好了,现在我们来看看字符串列表中有没有什么有价值的字符串。
貌似没看到什么有用的字符串。
好,那我们直接给 JMP MethCallEngine 这一行下一个断点吧。
接下来我们选中 JMP MethCallEngine 这一行,单击鼠标右键选择-Follow,转入 MethCallEngine 内部,接着在 MethCallEngine 入口点处设置一个断点。
运行起来。
弹出了注册窗口,等待我们输入序列号。对于 Native 的 VB 程序,我们可以断 API 函数,但是 P-CODE 就搞不定了,但 4C 法对 P-CODE 程序依然有用。
现在我们随便输入一个错误的序列号。
我们单击 Registrar(西班牙语:注册) 按钮。
断在了 JMP MethCallEngine 处,MethCallEngine 函数对 P-CODE 进行初始化。
我们继续运行,断在了 MethCallEngine 的入口处,我们来看看它做了些什么。
我们可以看到跳转到了 7413D243 处。
现在我们用 ExDec 打开该 CrackMe。ExDec 是一款专门针对 P-CODE 的反编译器。我们来看看它显示了些什么。
我们可以看到将被读取的第一个字节是 04,位于 401BD0 地址处,应该在第一次读取代码段指令的附近,我们可以给该字节设置一个内存访问断点。
我们单击 Registrar 按钮后就会断在了 MethCallEngine 处,我们给代码段设置内存访问断点。运行起来的话,将断在了读取代码段的指令处,读取 401BD0 内存单元中 04 的指令应该就在附近,所以我们接着给该字节设置内存访问断点,继续运行,就能马上定位到。
我们按 F9 运行起来。
断了下来,这个时候我们给刚刚那个 04 字节设置内存访问断点,运行起来,又断了下来,我们可以看到 ESI 指向的就是 401BD0 内存单元。(PS:这里下断点的顺序我换了次序,一次就可以定位到,作者 10 次才定位到)
这里读取[ESI]的 04 字节值保存到 AL 中,这里是读取到的第一个 P-CODE 操作码。
接着我们来看看 ExDec 中显示的其他操作码。
我们将 ExDec 跟 OD 的数据窗口显示的内容对应起来看,会发现这些操作码并不连续,这是因为中间夹杂着操作码需要的参数。
正如你所看到的,这里正在读取第一个字节。
我们可以看到当前 ESI 指向了 401BD0,下一行,ESI 值递增 1,以便读取操作码的参数。
接着我们就到了间接跳转 JMP 指令这里,这一行将去执行这个操作码(我们在 ExDec 中看到的 04)。
我们可以看到一个陌生的操作码。
这里我们可以看到将执行操作码 04(即 FLdRfVar),就只有几行代码,也没实现什么很神奇的操作,嘿嘿。还可以看到 XOR EAX,EAX,然后就是读取后面操作码。
这里首先读取紧跟在 04 后面两个字节的参数。
通过 MOVSX 指令将 FF74(这是个负数,前面汇编章节介绍过) 保存到 EAX 中,我们继续跟踪。
EAX 的值为-8C(十六进制),我们双击 EAX 值的话可以看到:
我们可以看到 FF74 对应的十进制是-140 也就是十六进制的-8C。我们可以看到 ExDec 中显示的是 8C。
接下来一行,操作码的参数值被加上 EBP 寄存器的值。
接下来一行使用 PUSH 指令将刚刚运算的结果压入堆栈。
这里相当于 PUSH EBP - 8C (EBP - 8C:标识着堆栈中的局部变量),我的机器上,EBP 的值为 12F4E0,减去 8C 就得到了 12F454。即当前 EAX 中的值。也就是使用 PUSH 指令压入堆栈中的值。
好,我们继续往下跟。
我们可以看到通过 XOR EAX,EAX 指令将 EAX 清零了,这就意味着操作码被清零了,该操作完成了,重置寄存器的值,然后接下来一行就可以读取下一个操作码了。
第二个操作码是 21,在接下来的一行读取它。
操作码跟之前一样依然被是保存在 AL。
现在 ESI 被加上 3,指向当前操作码的参数,接着通过间接跳转 JMP 去执行操作码 21。
我们来 Google 一下它的含义。
好,这里我们可以看到有些前辈做了注释,虽然我们不知道它具体是干什么用的,但是根据字面的意思来理解就是加载一个指针,并且指向一个数据项。
我们继续往下跟。
这里是将 EBP + 8 指向内存单元的内容读取出来并保存到 EBP - 4C 指向的内存单元中。
这个值在我的机器上是 15B000,我们在数据窗口中定位到这个地址。
该地址中保存的是 4022E8,我们继续在数据窗口中定位到 4022E8。
这里我们可以推断出 15B000 其实是一个指针。该指针指向了一张表,虽然对我们的破解起不到什么实质性的帮助,但起码我们还是看出一点门道了。
还有一点就是可以看出该操作码没有参数。
我们继续跟。
这里 EAX 又被清零了,下一行读取第三个操作码。
从 ExDec 中我们可以看出该操作码是 0F。
VcallAd
以上是 0F 这个操作码具体的解释,我们可以看到它有一个占两个字节的参数。
该参数我这里显示的 0300,其表示句柄表中数据元素的偏移。接下来是一个间接跳转 JMP,我们跟进去看看。
又是读取 EBP - 4C 的内容,保存到 EBX 中。
这里我们可以看到是 15B000,并使用 PUSH 指令压入到堆栈中。
接着是将参数值 300 保存到 EAX 中。
这里 EBX 的值为 15B000(我们已经知道了它指向了一张表),该表起始地址为 4022E8,我们姑且将这张表称之为 Description Item Table。
这里由该表的起始地址偏移 300。
表的起始地址偏移 300 就得到了 4025E8,保存到 EAX 中。
该值指向了表的这里。
这里操作码 0F 就是根据参数值指明的偏移量来定位前一个操作码获取到的表中的数值。
这里是使用 CALL 指令间接调用 EAX 内存单元中保存的值处。
该值为 7414C348,这里我们不跟进去,结果会被保存在堆栈中的。
我们直接按 F8 键单步步过这个 CALL。
我们可以看到堆栈中保存了结果,我们需要弄明白它表示什么意思。
接下来的操作码是 19,参数值是 88,代表一个局部变量。
这里我们直接跟进。
我们看到这里。
通过 MOVSX 指令读取出占两个字节的参数值,将其保存到 EAX 中。FF 开头表明该参数值是一个负数。
该值对应的十六进制为-88,跟 ExDec 中显示的刚好对应起来了。
十进制的-139 正好等于十六进制的-88。
接着 ESI 加 2,然后刚刚计算出的-88 加上 EBP 的值,即将 EBP - 88 保存到 EAX 中。
这里我们可以看到到达了一个 CALL 处,根据堆栈的来看其有三个参数。
第一个参数是前一个操作码执行的结果,第二个参数我这里是 12F458,即 EBP - 88-表示一个局部变量。第三个参数是-1。这里我们不跟进这个 CALL,直接按 F8 键单步步过这个 CALL,看看会发生什么。
我们会发现堆栈发生了变化,ECX 被清零了。
EBP-88 内存单元保存了前一个操作码执行的结果。
接下来一个操作码是 08,它也将局部变量 EBP - 88 作为参数。
我们跟进这个 JMP。
这下面并不是我们之前看到的 XOR EAX,EAX 结束,而是 OR EAX,EAX,接着使用条件跳转判断 EAX 是否为零。我们来看看它具体干了些什么。
首先将操作码的参数 FF78 保存到 EAX 中,注意这里使用的是 MOVSX,FF 开头表示是负数,十六进制值为-88。
这一行是将 EAX + EBP 指向内存单元的值保存到 EAX 中,即 EBP - 88 这个局部变量的值。
这里判断 EAX 是否为零,如果为零就跳转到 74145A15 地址处。如果不为零就继续往下执行。
这里将 EAX 的值保存到 EBP - 4C 中。
我们应该还记得之前读取 EBP + 8 的内容,接着将其保存到 EBP - 4C 中。所以说 EBP - 4C 的值不为零。
所以我们将 EBP - 4C 称为指针数据元素。
接下来是下一个操作码。
这里我们来 Google 一下这个操作码 0d VCallHresult。
表示获取文本框中输入的文本。
这里将读取我们输入的错误序列号,我们继续跟,看看是不是这样。
我们可以看到该操作码跟之前一样还是以 XOR EAX,EAX 结束。
首先读取 EBP - 4C 的内容(指向数据项的指针) 保存到 EAX 中。
接下来将这个值压入堆栈。
接着读取操作码的参数。
这里将参数值保存到 EDI 中。
然后读取 EAX 指向的内存单元的内容,这是另一个表的起始内容。
我们看到该表的 00A0 偏移处。
这里依然是间接 CALL 表中内容,我们不跟进这个 CALL,直接按 F8 键单步步过这个 CALL,然后看堆栈的结果。
我们按 F8 键执行这个 CALL。
接着 EBP - 44 的内容保存到 EDX 中。
这里判断某个值,接着读取下一个操作码。这里你可能会问读取的是什么,是我们输入的错误序列号吗?我们看看 ExDec 先。
我们可以看到该操作码的参数是 8C,也就是 EBP - 8C,我们看看 EBP - 8C 的值是多少。
这里我们可以看到是 12F454。
其保存的是 15D3BC 是我们输入的错误序列号的指针。
嘿嘿,终于找到了我们输入的错误序列号。接下来一个操作码是 6C ILdRf。
这里的解释是该操作码加载一个引用的值。我们跟进这个操作码看看。
这里。
这里通过 MOVSX 指令读取参数的值,是个负数。
参数是 FF74,所以保存到 EAX 中是:
对应的十六进制是-8C。
这里将 EAX + EBP 指向的内容压入堆栈,实际上是将 EBP - 8C 的内容压入堆栈。
这里我们可以看到 EBP - 8C 指向了我们输入的错误序列号,堆栈中也保存了这个指针。
我们在数据窗口中清楚地看到指向了我们输入的错误序列号。
下一个操作码是:
1b LitStr,根据字面上的意思来理解是”字符串”。
我们来看看它会干些什么。
我们跟进该操作码。
可以看到参数为 0001,将被保存到 EAX 中,是个正数。
接下来一行我们可以看到 4017E4 被保存到了 EDX 中,这个值是什么,我们暂时还无从知晓。
压入这个 4016F8 是干嘛的呢?
ExDec 中只显示了两个单引号,表明将一个空字符串压入堆栈。
通过数据窗口我们也能看出是一个空字符串,即当我们单击注册按钮时,下一个操作码会检查我们输入的是否为空。
ExDec 中显示如下:
这里 Lead0 是第一个操作,30 EqStr 是第二个操作。我们来 Google 一下它的意思。
Lead0/30 EqStr - 比较两个字符串。
也就是说这里将比较两个字符串。这是一个双操作码的操作。第一个操作码的操作数是 FB,我们跟进这个操作码。
这里直接以 XOR EAX,EAX 结束,什么也没做,接着读取第二个操作码。
第二个操作码是 30,接着读取参数。操作执行完后以 XOR EAX,EAX 结束。我们跟进这个操作码。
这第二个操作码 PUSH 0。
接下来是一个 CALL,有三个参数。我们按 F8 键单步步过这个 CALL,看看会发生什么。
堆栈移动了,里面值没有变,只是堆栈被抬高了。
下一行 CMP AL,0,这里 AL 保存的是上一个 CALL 的结果,我这里的值是:
AL = 01
表示两个字符串不相等。
比较完以后首先将 EAX 置零。如果刚刚比较的结果不为零,就将零压入堆栈,如果比较的结果相等就将 FFFFFFFF 压入堆栈。说明在做检查,嘿嘿。
我们接着看下一个操作码。
我们 Google 一下它的意思会发现跟 SysFreeString 类似,就是释放字符串所占的内存空间。我们可以看到这里要释放的内存空间是 EBP - 8C。
这里首先将 EDX 赋值为 1,接着通过 MOVSX 将参数值保存到 EBX 中,接着将 EBX - FF74(十六进制的-8C) 压入堆栈。
这里 EBX + EBP 即 EBP - 8C,所以压入堆栈的是错误的序列号。
这里我们可以看到下面的 CALL 里面会调用一个 API 函数 SysFreeString,然后返回。
这个时候我们输入的错误序列号被清空了。
这里我们输入的错误系列号其实还在 15D3BC 这个内存单元中,只不过它的指针 EBP - 8C 被清空了而已。
接着看下一个操作码。
这里将清除局部变量 EBP - 88 的内容。
EBP - 88 的值是多少?
这不是表中的数据项吗,将被清除,我们接着往下看。
这里是获取操作码的参数。
FF78 对应十六进制的-88。
这里又是将 EBP - 88 的内容保存到 EAX 中,接着判断它是否为零。这里不为零,我们继续。
这里调用这个 CALL 释放 EBP - 88 局部变量的内存空间。
这里 EAX 被清零了。
下一个操作码是:
该操作码是一个条件跳转,所有的 Branch 开头的都是跳转操作:
指令 操作码 跳转条件
Branch 1e 无条件跳转
BranchF 1c 栈顶数据为 false 则跳转
BranchT 1d 栈顶数据为 True 则跳转
这是一个条件跳转操作,如果栈顶数据为假就跳转,这里检查文本框中的序列号是否正确。
如果跳转了的话,下个操作码将是:
这里的条件跳转直接越过了 401BF3 处的无条件跳转。
这个操作就结束了,我们继续看下一个。
这里到达了 401BF6 这个分支,条件跳转成立了,越过了 401BF3 处的无条件跳转,嘿嘿。
Lead3/c1 LitVarl4
这是个双操作码的操作,我们来看一看。
这里第一个操作码结束了,继续读取第二个操作码。
我们继续跟踪。
这里读取操作码的参数。
FF54 以 FF 开头,所以是一个负数。
这次参数占 4 个字节,被保存到 EAX 中。
这是一个局部变量。
EBP + FFFFFF54 + 8 即第一个参数加上 8,结果是 12F43C,参数的值被保存到 12F43C 中。
接着我们跳转到了这里。
这里将 12F434 压入堆栈。
12F434 是一个结构体的指针。而这个结构体里面又保存了其他 3 个结构体。
我们跟到了这里,嘿嘿。
如果该 CrackMe 采用的是硬编码的话,我们可以切换到小数形式,看看是不是正确的序列号。我们直接再打开一个这个 CrackMe。
我们在 OD 中看看十进制值为多少。
我们可以看到十进制值为 246810,我们在 CrackMe 中输入它。
嘿嘿,提示输入的序列号正确。我们接着看比较的过程。
下一个操作是双操作码。
第一个操作码是 FC,我们跟进这个操作码,直接就结束了,接着读取第二个操作码。
该操作码是 F6,我们跟进去,ExDec 中显示的是 EBP - 9C,即 local_009c。
这里读取参数,是个负数。
对应十六进制的-9C。
这里 EBP + EBX,即 EBP - 9C,值为 12F444,这里面是空的
这里判断[EBX]是否小于 8,如果小于 8 则跳转。
接着又跳转,这里我们就不深究了,直接看到该操作码的最后一行。
这里将 3 保存到 EBP - 9C 中。
执行后。
下一行拷贝这里的内容到 EBP - 9C 中。
EBP - 9C 结构里保存了正确的序列号,还有这个数字 3。
接着看下一个操作码。
这里跟前面的介绍基本上是一样的了。
相同的地方我们直接略过,直接看到我们还没有跟过的操作码。
这里我们跳过前面的,直接给 401C17 这个操作码设置一个内存访问断点,当读取到这个操作码的时候就会断下来。
断在了这里,这里读取操作码 0A,我们看看 ExDec 中显示的:
ImpAdCallFPR4,表示调用一个 API 函数。
例如:
4017F5: 0a ImpAdCallFPR4: _rtcMsgBox
这个例子是调用__rtcMsgBox 这个 API 函数。
再来看
401C17: 0a ImpAdCallFPR4: _rtcR8ValFromBstr
这里读取该操作码的参数。
参数被保存到 ECX 中。
EAX 被赋值为 401000,接着判断 EAX 是否为零。
接着读取第二个参数。
我们到了 CALL EAX 这里,此时 EAX 值为 401000,我们看看这个地址是哪个 API 函数。
传递给该函数的参数在堆栈中:
可以看到是我们输入的错误序列号。
这里该操作序列号被装载到了浮点寄存器 ST0 中去了,浮点寄存器我们还没有介绍过,下面还有一些寄存器。如果你看不到浮点寄存器的话,你可以在寄存器窗口中单击鼠标右键选择-View FPU registers。
加载我们错误序列号的位置在这:
我们看到下一个操作码。
401C1C:Lead2/6b CVarR8
我们跟进这个操作码。
这是一个双操作码的操作,先读取第一个操作码,接着读取第二个。
接下来,是一些浮点指令,我们还没有介绍过。
但是我们可看到读取的参数。
这里:
EBP + EAX
这里 FSTP 指令将 ST0 中的内容保存 EAX+8 指向的 1 内存单元中,即 12F43C 中。然后执行一次出栈操作。我们后面章节再详细讨论。
执行以后:
这里有可能是我们输入的错误序列号,我们将其转化为 64 位双精度小数看看,单击鼠标右键。
可以看到正好是我们输入的错误序列号,但是这里占的是 8 个字节,即 64 位。从逻辑上来讲,一个占 4 个字节,32 位,一个占 8 个字节,64 位,看起来不一样。
切换为正常模式显示。
该指令将浮点寄存器的状态字保存到 AX 中,我们执行这一行。
这里该操作就结束了。
这个操作码我们不知道是干什么用的,我们还是跟一跟吧。
这一行是将 ESP 指向的内容保存到 EAX 中。
这个内存单元位于转换后的错误序列号的上面。
下一个操作码
这里将 EBP - 9C 压入堆栈:
接下来又是一个双操作码的操作。
这里读取第二个操作码。
这里我们到了一个 CALL 处,堆栈中参数如下:
其中一个指向了正确的序列号,一个指向了错误的序列号,将它们进行比较吗?
这里我们下一个断点。
我们可以看到这个 CALL 返回的结果 EAX 值为 1。
这里 FFFFFFFF 被压入堆栈,为了看到正确的序列号是多少,我们在比较这里设置一个断点。
03C41A 对应的十进制值为 246810,我们在文本框中输入这个值。
我们按下 Registrar 按钮,断了下来,我们看到比较处。
246810 对应的十六进制,可能会以不同格式存储,在不同的 CALL 中会被转化然后进行比较,我们看看结果是什么。
我们可以看到 EAX = 0。
栈顶元素也被置零了。
接下来的 BranchF 操作码就会根据栈顶元素值来决定显示什么提示框了。
你可能会说怎么这么长啊,因为这里是初次介绍 P-CODE,所以我们给大家逐一介绍了每个操作码,后面章节我们就不会这么赘述了。我们可以根据 ExDec 反编译器得知每个操作码的名称,然后用 OllyDbg 来定位调试。
下一章节,我们将介绍 clave2 这个例子,大家可以先试试。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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