您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 企业财务 > 使用OllyDbg从零开始Cracking第二十九章-P-CODE
第二十九章-P-CODE-Part1(本章的CrackMe需要支持库MSVBVM50.DLL)前面章节我们已经介绍了VisualBasic破解相关的基础知识,如果大家还想更加深入的研究VB应用程序破解的话,可以继续学习高级篇中的关于VB破解的相关内容。(PS:就是我打包上传到百度网盘的西班牙破解文集系列)COCO写的VB破解教程就比较好,其中可能会涉及到比较复杂的处理技巧,拿来练习做好不过了。还有一些其他优秀教程可供我们更加深入的研究VBCracking。接着下来我们将讨论下一个话题-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是我朋友JBDUC写的,用来介绍P-CODE相关的内容正好合适,我们需要找到该CrackMe的正确序列号。很多人调试P-CODE可能喜欢用WKT,如果大家想了解WKT怎么调试P-CODE的话,可以看JBDUC关于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这个函数。好了,现在我们来看看字符串列表中有没有什么有价值的字符串。貌似没看到什么有用的字符串。好,那我们直接给JMPMethCallEngine这一行下一个断点吧。接下来我们选中JMPMethCallEngine这一行,单击鼠标右键选择-Follow,转入MethCallEngine内部,接着在MethCallEngine入口点处设置一个断点。运行起来。弹出了注册窗口,等待我们输入序列号。对于Native的VB程序,我们可以断API函数,但是P-CODE就搞不定了,但4C法对P-CODE程序依然有用。现在我们随便输入一个错误的序列号。我们单击Registrar(西班牙语:注册)按钮。断在了JMPMethCallEngine处,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),就只有几行代码,也没实现什么很神奇的操作,嘿嘿。还可以看到XOREAX,EAX,然后就是读取后面操作码。这里首先读取紧跟在04后面两个字节的参数。通过MOVSX指令将FF74(这是个负数,前面汇编章节介绍过)保存到EAX中,我们继续跟踪。EAX的值为-8C(十六进制),我们双击EAX值的话可以看到:我们可以看到FF74对应的十进制是-140也就是十六进制的-8C。我们可以看到ExDec中显示的是8C。接下来一行,操作码的参数值被加上EBP寄存器的值。接下来一行使用PUSH指令将刚刚运算的结果压入堆栈。这里相当于PUSHEBP-8C(EBP-8C:标识着堆栈中的局部变量),我的机器上,EBP的值为12F4E0,减去8C就得到了12F454。即当前EAX中的值。也就是使用PUSH指令压入堆栈中的值。好,我们继续往下跟。我们可以看到通过XOREAX,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,我们姑且将这张表称之为DescriptionItemTable。这里由该表的起始地址偏移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。这下面并不是我们之前看到的XOREAX,EAX结束,而是OREAX,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一下这个操作码0dVCallHresult。表示获取文本框中输入的文本。这里将读取我们输入的错误序列号,我们继续跟,看看是不是这样。我们可以看到该操作码跟之前一样还是以XOREAX,EAX结束。首先读取EBP-4C的内容(指向数据项的指针)保存到EAX中。接下来将这个值压入堆栈。接着读取操作码的参数。这里将参数值保存到EDI中。然后读取EAX指向的内存单元的内容,这是另一个表的起始内容。我们看到该表的00A0偏移处。这里依然是间接CALL表中内容,我们不跟进这个CALL,直接按F8键单步步过这个CALL,然后看堆栈的结果。我们按F8键执行这个CALL。接着EBP-44的内容保存到EDX中。这里判断某个值,接着读取下一个操作码。这里你可能会问读取的是什么,是我们输入的错误序列号吗?我们看看ExDec先。我们可以看到该操作码的参数是8C,也就是EBP-8C,我们看看EBP-8C的值是多少。这里我们可以看到是12F454。其保存的是15D3BC是我们输入的错误序列号的指针。嘿嘿,终于找到了我们输入的错
本文标题:使用OllyDbg从零开始Cracking第二十九章-P-CODE
链接地址:https://www.777doc.com/doc-2687072 .html