欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[翻译]使用OllyDbg从零开始Cracking 第三十章-P-CODE-Part2

[翻译]使用OllyDbg从零开始Cracking 第三十章-P-CODE-Part2

前面已翻译章节列表:

[COLOR="Red"][FONT="Arial Black"][SIZE="6"]使用OllyDbg从零开始Cracking[/SIZE][/FONT][/COLOR]

本章内容:
(本章CrackMe支持库MSVBVM50.DLL)
本章我们继续讨论P-CODE。
以下是我从JBDUC的教程里收集的一些操作码:
6c → ILdRf   将指定操作数压入堆栈

1b → LitStr5  将字符串压入堆栈

fb → Lead0 

30 → EqStr 比较两个字符串(与Lead0配合使用)

2f → FFree1Str 释放内存空间

1a → FFree1Ad 释放内存空间

0f → VCallAd 通过虚拟机运行操作码

1c → BranchF 条件跳转指令,如果栈顶的值为false则跳转(相当于汇编指令JNE/JNZ)

1d → BranchT 条件跳转指令,如果栈顶的值为true则跳转(相当于汇编指令JE/JZ)

1e → Branch 无条件跳转(嘿嘿,相当于汇编指令JMP)

fc → Lead1 

c8 → End 终止程序(与Lead1配合使用)

f3 → LitI2 将立即数压入堆栈

f4 → LitI2_Byte 将指定数据转化为字节整型并压入堆栈

70 → FStrI2 将栈顶的WORD型元素保存到内存单元中,然后执行出栈操作

6b → FLdI2 将WORD型参数压入堆栈

a9 → AddI2 栈顶两个WORD型元素相加,相加的结果置于栈顶

ad → SubI2 栈顶两个WORD型元素相减,相减的结果置于栈顶

b1 → MulI2 栈顶两个WORD型元素相乘,相乘的结果置于栈顶

好了,以上列出了一些操作码以及相应的含义,这里还有一份<<P-Code_OPCODES>>文档,这份文档是关于VB P-CODE虚拟机的说明文档,其阐述了操作码的解析原理(但是并不全,嘿嘿)。如果大家遇到了不熟悉的操作码的话,可以参考一下该文档,可能有帮助。
好了,这里我们首先来讲解clave2这个CrackMe,将其加载到ExDec看看都显示些什么。

这里我们可以看到开始于401CC0处,这里不能完全依然于ExDec,因为有时候它的分析不怎么准确,所以我们还是像上一章节一样手工来定位第一个操作码吧。

我们定位到入口点上面的API函数MethCallEngine。

这里我们给JMP MethCallEngine这一行设置一个断点,为了防止还有其他地方调用MethCallEngine,我们在JMP MethCallEngine这条指令上面单击鼠标右键选择-Follow定位到MethCallEngine的入口点,在入口点处也设置一个断点。

我们运行起来看看会不会触发刚刚设置的断点。

我们可以看到弹出了注册窗口,但是并没有触发我们设置的断点,说明在执行P-CODE之前注册窗口就产生了,可能有的程序执行PCODE在窗口产生之前,而我们这里刚好相反,其实这无关紧要,现在我们随便输入一个错误的用户名和序列号。
接着我们单击Registrar(注册)按钮,就会断在JMP MethCallEngine这一行,好,现在单击工具栏中的M按钮打开区段列表窗口定位到代码段(这里我们使用原版的OD,不用那个Patch过的OD,那个Patch过的OD对P-CODE应用程序并不奏效),对代码段设置内存访问断点。

接下来我们多运行几次,直到断在读取第一个操作码的指令处为止。

这里我们可以看到ESI指向了第一个操作码,并且该操作码将被保存到AL中。
和ExDec中显示的第一个操作码是位于401CC0处的04一致。

我们应该还记得上一章介绍过的04这个操作码是将后面紧跟的参数压入堆栈,这里该参数是EBP - 8C。
下面我们来看看P-CODE的说明文档中操作码是如何解析的,如下图:

04   567B   0B8E   2   1    2 就是将一个参数压入堆栈, 0B8E指的是对应参数的RVA(相对虚拟地址), 第一个2指的是所有参数所占的总字节数,接下来的一个1指的是参数的个数,最后的一个2指的是单个参数所占的字节数,由于这个例子只有一个参数,所以最后只有一个2,如果具有多个参数的话,后面会依次显示各个参数所占的字节数。
我们这里的第一个操作码所执行的操作即PUSH EBP - 8C,继续看下面的操作码,但是本章我们不跟上一章那样从头到尾跟踪每个操作码,这里我们只跟踪关键的操作码。

我们看到这两处VCALLHresult,都是读取文本框中用户输入的信息,第一个有可能是读取用户输入的用户名,第二个可能是读取用户输入的序列号,我们直接给401D4C地址处的操作码设置内存访问断点。

接着我们运行起来。

断了下来,继续往下跟踪直到读取下一个操作码的指令为止。

这里我们跟到了读取下一个操作码的指令处。

可以看到局部变量local_008C即EBP - 8C,我这里EBP - 8C等于12F454,一起来看看该地址处保存了什么。

在堆栈窗口中看到12F454地址处保存了我们输入的用户名。

至此我们就定位到输入的用户名,接下来就是要定位输入的序列号,同理,给下图中的操作码设置内存访问断点。


运行起来,马上就断在读取该操作码的指令。

跟前面一样,跟踪到读取下一个操作码的指令处为止。

我们来看看输入的序列号是不是也被保存到了局部变量中。

我们会发现跟之前一样输入的序列号也被保存到了EBP - 8C中。

至此我们又定位到了输入的序列号,我们不必跟踪每个操作码,只需要对关键的操作码设置内存访问断点,上一章,我们跟踪了每个操作码的执行过程,让大家可以更好的理解P-CODE的运行机制,本章的话,我们就没有像上一章那样赘述了,下面的跟踪步骤还是像刚刚那样定位关键点即可。

这里貌似在进行比较,接着释放局部变量的内存空间,然后根据刚刚比较的结果来决定是跳转到401E59处调用rtcMsgBox弹出正确序列号提示框还是直接往下执行弹出错误序列号的提示框。

根据ExDec显示的内容来看这里很可能是进行序列号的比较,然后根据比较的结果来决定是条件跳转到提示序列号正确的消息框处还是提示序列号错误的消息框,所以我们可以给下图中的两个操作码设置内存访问断点,看看会发生什么。


运行起来。

断在了读取第一个操作码的指令处,继续往下跟踪直到读取第二个操作码的指令处为止,然后看看执行些什么操作。

第二个操作码是EF。
接着查看一下操作码列表中关于FB EF的说明(见附件中的OPCODES.TXT)。

操作码列表中并没有对FB EF进行相应的解释,但是根据ExDec中显示的内容ConcatVar字面意思可以理解为拼接变量值,一起来看一看ExDec中的描述。

可以看到两个局部变量将进行拼接,其中一个是EBP - 9C即字符串“CRK”,另一个是EBP - 018C,接下来我们分别定位到这两个变量。

第一个变量是EBP - 9C,在我机器上为12F444。(PS:这里需要说明一下,这里的变量类型属于variant型,也是就传说中的变体类型,相信学习过COM组件的童鞋一定不会陌生,VB中的variant类型属于一种结构体,该结构体的前两个字节表示类型,后面有3个WORD是保留的,接下来才是其真正的值。关于variant的类型定义这里大家可以看jjnet大哥在以前的帖子中给出的一段定义,见附录),前两个字节为8,表示类型8(b_str),也就是字符串类型,其实际指向的字符串首地址为12F44C。

接下来我们看下一个变量EBP - 18C,也就是12F354,属于类型3(I4),即占4字节的整型数,其真实值为2EA。


我们跟进这个操作码中。

这里是读取参数并保存到EDI中。

我们可以看到保存到EDI中参数值为12F434。
接着我们到了__vbaVarCat这个API函数的调用处,堆栈中显示该函数有三个参数。

我们来看看每个参数的具体情况。

这是第一个参数。

这是第二个参数,表示一个整型数,数值为02EA(十六进制)。

接着是第三个参数,表示一个字符串,其实际指向的字符串首地址为401748,即CRK。

好了,现在我们已经弄清楚了这几个参数的情况,接下来我们跟进到__vbaVarCat这个API函数内部,看到其内部调用的一些其他的API函数,就会明白拼接过程是如何实现的了。

好,这里我们跟到了__vbaStrCat的调用处,从堆栈中可以看出将要进行拼接的两个字符串分别是CRK和746,而之前的那个数值02EA呢?

02EA(十六进制)对应的十进制数值如下:

正好是764,所以__vbaVarCat这个函数首先会将数字型变量转化为了字符串(PS:十六进制数值转化为十进制数值),我们跟踪到该函数的RET处。

我们可以看到两个变量被拼接到了一起。

和前面一样,我们继续往下跟直到下一个操作码读取第一个参数为止。

这里我们可以看到第一个参数的前两个字节值为8,表示类型8(b_str),也就是字符串类型,其指向的字符串首地址为15D88C,这里我们可以看到就是刚刚拼接的字符串。

好了,接下来马上要进行序列号的比较了,我们往下跟直到读取下一个操作码为止。


这里是一个双操作码操作,继续往下跟直到读取FB40的第二个操作码为止。

这里操作码列表中也没有FB 40解释,我们继续跟踪看看该操作码都干了些什么,嘿嘿。

这里我们可以看到该操作码快结束的地方有一个CALL指令,我们来看看这个CALL的参数。

第一个参数为零,第二个参数为12F434,在数据窗口中定位到12F434。

前两个字节值为8,表示类型8(b_str),即字符串类型,其实际指向的字符串首地址为15D88C。

接下来我们在数据窗口中定位到另一个参数。

前两个字节为8008,表示组合类型8000 | 8(VT_RESERVED | VT_BSTR),即字符串型和保留类型的组合,实际指向的字符串首地址为15CA94,也就是输入的序列号的首地址。

这个CALL,OD中没有解析其函数名称,我猜测可能是比较以上两个字符串。

我们在这个CALL这里设置一个断点,接着按F8单步执行这个CALL。
可以看到EAX的结果为FFFFFFFF(PS:这里作者描述有误,该函数的结果是保存在EAX中,而并非堆栈中),很可能说明刚刚比较的两个字符串不相等,这里我们直接输入正确的序列号,接着按注册按钮看看会不会断在刚刚设置的这个断点处。

单击Registrar按钮。

断了下来,我们直接按F8键执行这个CALL。

我们可以看到比较的结果为零(PS:这里这个函数的返回值是保存在EAX中,并非栈顶,作者描述有误,现已更正),说明两个字符串相等。

正如大家所看到的,我们没有必要从头到尾把整个程序跟一遍,我们只需要观察一下有没有什么关键的操作码,直接跟关键的操作码就能够轻松的找到正确的序列号。
下面是该CrackMe的详细的操作码注释清单:
401CC0: 04 FLdRfVar                local_008C

401CC3: 21 FLdPrThis               ;[SR] = [stack2]

401CC4: 0f VCallAd                 text    ;获取窗口句柄

401CC7: 19 FStAdFunc               local_0088

401CCA: 08 FLdPr                   local_0088

401CCD: 0d VCallHresult             get__ipropTEXTEDIT   ;读取文本框中的内容

401CD2: 6c ILdRf                   local_008C            ;读取到的文本

401CD5: 1b LitStr:                   &                   ;将字符串压入堆栈

401CD8: Lead0/30 EqStr                                   ;比较两个字符串

401CDA: 2f FFree1Str               local_008C             

401CDD: 1a FFree1Ad                local_0088

401CE0: 1c BranchF:                401CE6                ;如果不相等则跳转

401CE3: 1e Branch:                 401e8c                 ;无条件跳转

401CE6: 04 FLdRfVar                local_008C

401CE9: 21 FLdPrThis              

401CEA: 0f VCallAd                 text                   ;获取窗口句柄

401CED: 19 FStAdFunc               local_0088

401CF0: 08 FLdPr                   local_0088

401CF3: 0d VCallHresult            get__ipropTEXTEDIT     ;读取文本框中的内容

401CF8: 6c ILdRf                   local_008C             ;读取到的文本

401CFB: 4a FnLenStr               

401CFC: f5 LitI4:                  0x6  6  (....)            ;将占4个字节的立即数压入堆栈

401D01: d1 LtI4                                          ;是否小于(?)

401D02: 2f FFree1Str               local_008C

401D05: 1a FFree1Ad                local_0088

401D08: 1c BranchF:                401D3F                ;如果不成立则跳转(>=6)

401D0B: 27 LitVar_Missing         

401D0E: 27 LitVar_Missing         

401D11: 3a LitVarStr:              ( local_00BC ) P-Code

401D16: 4e FStVarCopyObj           local_00CC

401D19: 04 FLdRfVar                local_00CC

401D1C: f5 LitI4:                  0x40  64  (...@)

401D21: 3a LitVarStr:              ( local_009C ) Minimum 6 characters

401D26: 4e FStVarCopyObj           local_00AC

401D29: 04 FLdRfVar                local_00AC

401D2C: 0a ImpAdCallFPR4:          _rtcMsgBox

401D31: 36 FFreeVar         local_00AC local_00CC local_00EC local_010C 

401D3C: 1e Branch:                 401e8c      ;如果小于6个字符则跳转

401D3F: 04 FLdRfVar                local_008C

401D42: 21 FLdPrThis              

401D43: 0f VCallAd                 text

401D46: 19 FStAdFunc               local_0088

401D49: 08 FLdPr                   local_0088

401D4C: 0d VCallHresult            get__ipropTEXTEDIT  ;读取文本框中的内容

401D51: 3e FLdZeroAd               local_008C          ;读取到的文本

401D54: 46 CVarStr                 local_00AC

401D57: 04 FLdRfVar                local_00CC

401D5A: 0a ImpAdCallFPR4:          _rtcLowerCaseVar    ;转化为小写字母

401D5F: 04 FLdRfVar                local_00CC

401D62: 04 FLdRfVar                local_00EC

401D65: 0a ImpAdCallFPR4:          _rtcTrimVar          

401D6A: 04 FLdRfVar                local_00EC

401D6D: Lead1/f6 FStVar            local_011C

401D71: 1a FFree1Ad                local_0088

401D74: 36 FFreeVar         local_00AC local_00CC 

401D7B: 04 FLdRfVar                local_011C

401D7E: Lead0/eb FnLenVar         

401D82: Lead1/f6 FStVar            local_012C

401D86: 28 LitVarI2:               ( local_00BC ) 0x1  (1)

401D8B: 04 FLdRfVar                local_013C

401D8E: 04 FLdRfVar                local_012C

401D91: Lead3/68 ForVar:           (when done) 401DE0        ; FOR i=1 to m 开始循环

401D97: 28 LitVarI2:               ( local_00AC ) 0x1  (1)

401D9C: 04 FLdRfVar                local_013C

401D9F: Lead1/22 CI4Var           

401DA1: 04 FLdRfVar                local_011C

401DA4: 04 FLdRfVar                local_00CC

401DA7: 0a ImpAdCallFPR4:          _rtcMidCharVar    ;截取字符串的部分字符   

401DAC: 04 FLdRfVar                local_00CC       ;...用户名

401DAF: Lead2/fe CStrVarVal        local_008C

401DB3: 0b ImpAdCallI2             _rtcAnsiValueBstr   ;取字符的十六进制值

401DB8: 44 CVarI2                  local_00BC

401DBB: Lead1/f6 FStVar            local_016C

401DBF: 2f FFree1Str               local_008C

401DC2: 36 FFreeVar         local_00AC local_00CC 

401DC9: 04 FLdRfVar                local_017C

401DCC: 04 FLdRfVar                local_016C

401DCF: Lead0/94 AddVar            local_00AC

401DD3: Lead1/f6 FStVar            local_017C

401DD7: 04 FLdRfVar                local_013C       ;设置循环变量

401DDA: Lead3/7e NextStepVar:      (continue) 401D97   ;循环

401DE0: 04 FLdRfVar                local_017C

401DE3: 04 FLdRfVar                local_012C

401DE6: Lead0/94 AddVar            local_00AC

401DEA: Lead1/f6 FStVar            local_018C

401DEE: 04 FLdRfVar                local_008C

401DF1: 21 FLdPrThis              

401DF2: 0f VCallAd                 text

401DF5: 19 FStAdFunc               local_0088

401DF8: 08 FLdPr                   local_0088

401DFB: 0d VCallHresult            get__ipropTEXTEDIT   ;读取文本框中的内容

401E00: 3e FLdZeroAd               local_008C           ;序列号

401E03: 46 CVarStr                 local_00CC

401E06: 5d HardType               

401E07: 3a LitVarStr:              ( local_009C ) CRK

401E0C: 04 FLdRfVar                local_018C

401E0F: Lead0/ef ConcatVar        

401E13: Lead0/40 NeVarBool        

401E15: 1a FFree1Ad                local_0088

401E18: 36 FFreeVar         local_00CC local_00AC 

401E1F: 1c BranchF:                401E59

401E22: 27 LitVar_Missing         

401E25: 27 LitVar_Missing         

401E28: 3a LitVarStr:              ( local_00BC ) P-Code

401E2D: 4e FStVarCopyObj           local_00CC

401E30: 04 FLdRfVar                local_00CC

401E33: f5 LitI4:                  0x10  16  (....)

401E38: 3a LitVarStr:              ( local_009C ) Key nonValid!

401E3D: 4e FStVarCopyObj           local_00AC

401E40: 04 FLdRfVar                local_00AC

401E43: 0a ImpAdCallFPR4:          _rtcMsgBox

401E48: 36 FFreeVar         local_00AC local_00CC local_00EC local_010C 

401E53: 1e Branch:                 401e8c

401E56: 1e Branch:                 401e8c

401E59: 27 LitVar_Missing         

401E5C: 27 LitVar_Missing         

401E5F: 3a LitVarStr:              ( local_00BC ) P-Code

401E64: 4e FStVarCopyObj           local_00CC

401E67: 04 FLdRfVar                local_00CC

401E6A: f5 LitI4:                  0x30  48  (...0)

401E6F: 3a LitVarStr:              ( local_009C ) Key Correct!

401E74: 4e FStVarCopyObj           local_00AC

401E77: 04 FLdRfVar                local_00AC

401E7A: 0a ImpAdCallFPR4:          _rtcMsgBox

401E7F: 36 FFreeVar         local_00AC local_00CC local_00EC local_010C 

401E8A: Lead1/c8 End              

401E8C: 13 ExitProcHresult 

好了,那么这个CrackMe我们就搞定了...
现在大家应该知道了如何用OllyDbg来调试P-CODE应用程序了吧,不像以前,WKT可能调试P-CODE程序很方便,但是现在很多P-CODE程序会检测WKT,ExDec等工具,所以我们就可以用OD配置好反反调试插件来分析。
现在我们再来看一个名字叫做nags1的CrackMe,我们需要剔除掉其NAG窗口,首先我们将其加载到ExDec看看显示了些什么。

这里我们可以看到NAG窗口实际上仅仅是调用了rtcMsgBox弹出的一个消息框。但是P-CODE代码我们不能像常规的汇编指令那样直接NOP掉。

我们用OllyDbg加载该CrackMe,然后给rtcMsgBox的操作码设置一个内存访问断点。


运行起来,马上就会断在了读取rtcMsgBox操作码的指令处。

当前栈顶指针指向的是12F9E8,我们继续往下跟踪直到读取下一个操作码的指令处为止,但是还没到读取下一个操作码的指令处,NAG窗口就弹出来了,我们单击Aceptar(OK)按钮。

现在我们终于跟到读取下一个操作码的指令处,刚刚跟踪的过程中,如果你足够留心的话,你就会发现是执行了上面的CALL EAX指令弹出的NAG窗口,这个CALL其实就是调用rtcMsgBox这个API函数,当前栈顶指针指向的是12F9FC。

这里为了不让该程序调用rtcMsgBox弹出NAG窗口,我们可以用别的操作码来代替这个0A操作码,这里我就用F5这个操作码来替换。

F5表示PUSH imm#4,说明只有一个参数且该参数占4个字节,刚好可以与0A这个操作码的参数所占的大小匹配上。

我们可以看到0A操作码有两个参数,每个参数占两个字节,所以两个参数总共占4个字节。
这里我们就将0A替换成了F5,然后将参数值设置为零,即将零压入堆栈。

根据ExDec显示的信息我们知道下一个操作码为36,我们替换操作码的时候必须保证新的操作码与原操作码参数所占大小相等,如果参数大小不匹配的话,那么替换以后,程序很可能会出错,(PS:你可能联想一下我们以前常讲到的的堆栈平衡,当然这里跟堆栈平衡是两码事,我只是打个比喻而已,嘿嘿),接下来我们将刚刚所做的修改保存到文件。



我们直接运行起来,可以看到直接弹出了主窗口,并没有出现NAG窗口,说明我们修改成功了,这里你也可以使用其他的操作码来替换,给大家留一个练习的小程序nag2,大家可以自行尝试剔除掉NAG窗口。
至此,我们关于P-CODE的内容就介绍完了,下一章我们开始介绍脱壳。
附录:
Variant结构体的定义,变体的类型

---------------------------------传说中的分割线----------------------------------

---------------------------------传说中的分割线----------------------------------

上传的附件 1.png
2.png
3.png
4.png
5.png
6.png
7.png
8.png
9.png
10.png
11.png
12.png
13.png
14.png
15.png
16.png
17.png
18.png
19.png
20.png
21.png
22.png
23.png
24.png
25.png
26.png
27.png
28.png
29.png
30.png
31.png
32.png
33.png
34.png
35.png
36.png
37.png
38.png
39.png
40.png
41.png
42.png
43.png
44.png
45.png
46.png
47.png
48.png
49.png
50.png
51.png
52.png
53.png
54.png
55.png
56.png
57.png
58.png
59.png
60.png
61.png
62.png
63.png
64.png
65.png
66.png
67.png
68.png
69.png
70.png
71.png
72.png
73.png
74.png
75.png
76.png
77.png
clave2.7z
exdec.7z
MSVBVM50.7z
nags1.7z
nags2.7z
olly_parcheado_para_vb.7z
P-Code_OPCODES.7z
使用OllyDbg从零开始Cracking 第三十章-P-CODE-Part2.doc

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功