吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7281|回复: 7
收起左侧

[原创] 160个Crackme038之P-Code初窥门径

[复制链接]
鬼手56 发表于 2019-5-2 11:05

【软件名称】:CyberBlade.2.exe

【软件大小】:61.0 KB

【下载地址】:https://github.com/TonyChen56/160-Crackme

【加壳方式】:未加壳

【保护方式】:Name/Serial

【编译语言】:VB P-Code

【调试环境】:W10 x64

【使用工具】 OD,VBExplorer,VB Decompiler

【破解日期】:2019-5-1

【破解目的】:学习分析P-Code类型的程序,理解P-Code虚拟机的解释过程

【目标程序】:

1556708849213.png

这一次的目标程序的这个Crackme,是160个Crackme里面的第38个。运行时需要 Visual Basic 5.0 运行库支持。这个Crackme的分析,用到了三个工具,每个工具都有各自的用途:

  • OD:用于跟踪P-Code伪指令的具体细节及在静态分析过程中无法查看的数据
  • VBExplorer 用于查看P-Code伪指令及注释
  • VB Decompiler 用于静态查看反汇编的伪代码,减少OD跟踪伪指令的工作量

VB的变量类型

想要分析这个Crackme,首先需要了解VB变量类型在内存中的存储方式。

VB中的variant类型属于一种结构体,该结构体的前两个字节表示变量的类型,后面有3个WORD是保留的,接下来才是其真正的值,如下图:

1556709733923.png

也就是说VB变量中真正的数据是存储在首地址+8的位置处,下图显示了VB的所有的变量类型及含义

1556710030041.png

实战分析

首先用 VB Decompiler反编译目标程序,找到Check按钮的点击事件,分析整个点击事件的校验过程

1556710105908.png

这个程序的校验过程分为五个部分,下面讲解每一个部分的校验过程

第一部分 基础校验

1556710231368.png

首先根据静态分析的结果可以看到,该程序首先会校验用户名和序列号是否为空,然后判断序列号长度是否小于5个字节,否则提示错误。即使看不懂反汇编后的VB代码,也可以通过字符串知道整个过程。

第二部分 根据用户名计算结果

1556710408523.png

第二部分的校验过程看的就不那么清晰了,需要利用OD动态跟踪每一个伪指令的具体操作流程。

1556710535210.png

用VBExplorer对目标程序进行反编译,一直往下拉,根据字符串直接忽略第一部分的基础校验,来到0040E380的位置

1556710696535.png

1556710785498.png

我们可以看到第一个被执行的伪指令是0040E380处的0D,接下来将程序载入OD,数据窗口跟随->0040E380

1556710983201.png

然后给第一个字节0D下内存访问断点,F9运行

1556711082940.png

然后随便输入一个用户名和序列号,点击Check

1556711172208.png

程序首先会读取一个字节的操作码到AL,

1556711238195.png

1556711278805.png

来看下VB Explorer中显示的操作码,后面的注释提示这是在调用一个函数,0D后面的是操作码的参数,接着esi自增1,指向操作码的参数

1556711497850.png

接着通过一个jmp跳转去执行操作码,0x741BED94是地址跳转表的首地址,eax保存下一条指令的操作码,由于每一个跳转地址是一个DWORD,所以用eax乘以4的值加上跳转表的基地址来索引下一条伪指令的解释单元,我们跟随这个jmp

1556711686479.png

首先把[ebp-0x4C]赋值给eax,然后将eax压栈。我们需要知道eax的含义。数据窗口跟随之后,发现是一个指针,再次选中前四个字节,数据窗口跟随DWORD,然后将数据显示方式切换为长型->地址

1556711857266.png

这其实是一个函数的跳转表,再接着把esi的内容A0赋值给edi,而esi始终执行的是操作码

1556711904345.png

1556711973157.png

可以看到这一步实际上是在取操作码的参数A0了

1556712068872.png

接着取出eax的内容,然后将eax加上edi,eax实际是跳转表的首地址,那么参数A0,就是跳转表的偏移

1556712146823.png

接着call eax,我们不需要跟进这个函数,只需要关注栈中的第二个参数0x19F134即可。直接步过这个函数

1556712193680.png

可以看到栈中的参数显示出了我们刚才输入的用户名,再接着单步到xor eax,eax的地址

1556712253436.png

这里把eax的值清零了。也就是说第一条伪指令已经执行完成了。

这个就是P-Code虚拟机的解释过程,其中esi始终指向要解释的伪指令,eax保存的是将要解释的伪指令。

1556712401376.png

接着看下一条伪指令,6C是操作码,64FF是参数。后面的注释告诉我们这条伪指令是在将某个DWORD值入栈。继续跟踪

1556712507784.png

首先取出操作码6C,然后esi+5执向伪指令参数,接着跳转执行这条伪指令,直接跟进jmp

1556712588929.png

首先取出参数FF64放到eax中,FF64是一个负数,
1556712705001.png

也就是十进制的9C,这也是为什么VBExplorer里会显示LOCAL_009C的原因,这个9C代表[ebp-9C],是个局部变量
1556712739397.png

1556712821823.png

接着将[eax+ebp]入栈,压栈的是刚刚输入的用户名[eax]的值是-98,这里其实是将[ebp-98]局部变量压入栈,然后eax清零,表示这条伪指令结束。

继续看下一条,伪代码解释的求长度

1556712914426.png

首先取出伪指令,然后直接跟进jmp

1556712952453.png

1556712982348.png

这里调用vbaLenBster,参数是之前输入的用户名,接着将用户名长度入栈后,eax清零,接着看下一条FD6934EF

1556713040846.png

这条伪指令后面并没有给出解释,但是没有关系,我们可以根据伪指令的执行过程猜测指令含义,这里其实是一个双操作码的指令,

1556713164758.png

首先取出操作码FD,直接跟进jmp,什么都没有做,直接将eax清零。之后再次取出操作码69,继续跟进jmp

1556713252449.png

这里将bx赋值为0x3,然后跳转,继续跟进

1556713328624.png

接着取出参数FF34,FF34也是个负数,然后再将FF34加上ebp,代表这是一个局部变量

1556713377379.png

接下来将ecx赋值给[eax+0x8]的地址处,我们数据窗口跟随eax,然后将bx赋值给eax,赋值完成后eax值如下:

1556713486438.png

还记得VB的变量类型吗?前两个字节是变量类型,03代表是Long,中间是6个字节的保留位,首地址+8的位置才是真正的数值。

这个07是之前通过vbaLenBstr获取到的用户名长度,在这里转成了变量,并将变量首地址压栈。

1556713716840.png

现在我们就能通过实际的跟踪结果来得出这条指令的含义了。就是将int值转成变量类型。由于整个跟踪过程实在是复杂,我这里只贴出算法的关键部分

1556714180680.png

1556714156536.png

在40E3C1处截取用户名的第一个字符串

1556714306609.png

接着在40E3CD处将截取的用户名每一位转成ASCII值

1556714417224.png

1556714357646.png

1556714437018.png

接着将用户名每一位的ASCII值转为十进制后进行字符串拼接

1556714497096.png

整个过程循环,循环次数为用户名的长度,可以直接在这个地方下断点看到最后的结果

1556714601134.png

即拼接用户名的ASCII十进制字符串,第二部分的算法就完成

1556714703448.png

在VB伪代码中,var_94就是最后拼接的结果

第三部分 除以圆周率

接下来是第三部分,直接来看VB Decompiler中的伪代码

1556715059139.png

这一部分的逻辑也很清晰,如果用户名拼接的字符串长度大于9的话,就将这个结果转为浮点数除以圆周率,一直除到结果的长度小于9。这个部分我也用OD详细跟过每一条伪指令,确实和静态反汇编的逻辑是一样的

但是在loc_40E449的位置,将var_94和一个值进行了异或,并且还减去了另外一个值。这两个数值我们无从得知,只能跟踪OD

1556715464545.png

根据伪指令的助记符XorVar和SubVar快速定位到这两个地址,下内存访问断点,很快就能找到这两个值

1556715213219.png

可以看到这里实际上是将0x30F85678和用户名的结果进行异或

1556715542588.png

然后减去0xD8B3,找到了这两个数,第三部分也就结束了

第四部分 干扰代码

1556715641011.png

这个是最有意思的,你会发现代码初始化了10次循环,循环将一个变量和Key值进行比较,但问题在于Then分支没有任何代码。这也就是说不管这个循环中的比较成立与否 都对我们没有任何影响

第五部分 关键比较

1556715762143.png

最后一部分,比较序列号减去var_94是否等于用户名长度,也就是说用户名计算的结果再加上用户名的长度就是真正的序列号。最后对这个程序的校验过程做一个总结

总结:

  1. 检测用户名和序列号是否为空,序列号长度是否小于5
  2. 将用户名各个字符的ASCII码的十进制转换成字符串连接起来得到一个数result
  3. 判断result长度是否大于9 如果大于则将result转为浮点数之后除以圆周率 直到resultI的长度小于或等于9
  4. 将result和0x30F85678进行异或并减去0xD8B3,再加上用户名长度就是正确的序列号

写出注册机

接着我们根据已经分析的算法写出这个程序的注册机,这个注册机用C++写还是太费劲了 直接用python快

Name = "GuiShou"
s = int(''.join([str(ord(i)) for i in Name]))

while len(str(s))>9:
    s = int(s // 3.141592654)

Serial = (s ^ 0x30F85678) - 55475 + len(Name)
print(Serial)

1556763300812.png

1556763346706.png

总结

P-Code的程序并非如传言一样不可战胜,只要你有足够的耐心,配合VB Decompiler+VBExplorer+OD的黄金组合,剩下的就是纯体力活了。

P-Code类的程序用OD跟踪伪指令虽然能看到每一处实现细节,但是毕竟还是太费力了。这个时候如果能总结出一套相对比较完整的P-Code的伪指令及每个参数的具体含义再配合WKTVBDebugger,调试P-Code就显得游刃有余了。如果有大佬总结出来了还请发我一份 哈哈。

附上分析过程

最后附上分析过程和相关文件

:0040E380  0DA0000300                      VCallHresult               ;Call ptr_0040342C                   获取输入的用户名
:0040E385  6C64FF                          ILdRf                      ;Push DWORD [LOCAL_009C]   将用户名压入堆栈
:0040E388  4A                              FnLenStr                   ;vbaLenBstr                                   求用户名长度
:0040E389  FD6934FF                        CVarI4                     ;                                                   将用户名长度转为变量
:0040E38D  2F64FF                          FFree1Str                  ;SysFreeString [LOCAL_009C]; [LOCAL_009C]=0      释放用户名内存
:0040E390  1A68FF                          FFree1Ad                   ;Push [LOCAL_0098]; Call [[[LOCAL_0098]]+8]; [[LOCAL_0098]]=0         释放局部变量
:0040E393  FE68B4FE8901                    ForVar                     ;                                            初始化循环次数 开始循环 
:0040E399  0464FF                          FLdRfVar                   ;Push LOCAL_009C            将局部变量0压入堆栈      
:0040E39C  21                              FLdPrThis                  ;[SR]=[stack2]
:0040E39D  0F0003                          VCallAd                    ;Return the control index 02
:0040E3A0  1968FF                          FStAdFunc                  ;
:0040E3A3  0868FF                          FLdPr                      ;[SR]=[LOCAL_0098]
***********Reference To:[propget]TextBox.Text
                              |
:0040E3A6  0DA0000300                      VCallHresult               ;Call ptr_0040342C          获取输入的用户名
:0040E3AB  046CFF                          FLdRfVar                   ;Push LOCAL_0094            将局部变量0压入堆栈 
:0040E3AE  2824FF0100                      LitVarI2                   ;PushVarInteger 0001        将局部变量1压入堆栈 
:0040E3B3  04D4FE                          FLdRfVar                   ;Push LOCAL_012C            将局部变量0压入堆栈 
:0040E3B6  FC22                            CI4Var                     ;vbaI4Var                              将变量1转为数字      
:0040E3B8  3E64FF                          FLdZeroAd                  ;Push DWORD [LOCAL_009C]; [LOCAL_009C]=0          将用户名压入堆栈
:0040E3BB  4644FF                          CVarStr                    ;                                                                                        将用户名字符串转为变量
:0040E3BE  0404FF                          FLdRfVar                   ;Push LOCAL_00FC                                                        将局部变量0压入堆栈
**********Reference To->msvbvm50.rtcMidCharVar                                        
                               |
:0040E3C1  0A0A001000                      ImpAdCallFPR4              ;Call ptr_00401006; check stack 0010; Push EAX   截取用户名的第一个字符
:0040E3C6  0404FF                          FLdRfVar                   ;Push LOCAL_00FC                                                将用户名的第一个字符压入堆栈
:0040E3C9  FDFEB0FE                        CStrVarVal                 ;                                                                                将用户名的第一个字符从变量转为字符串                                                        
**********Reference To->msvbvm50.rtcAnsiValueBstr
                               |
:0040E3CD  0B0B000400                      ImpAdCallI2                ;Call ptr_0040100C; check stack 0004; Push EAX  将用户名的第一个字符转为ASCII值
:0040E3D2  4434FF                          CVarI2                     ;                                        将用户名的第一个字符的ASCII值转为变量
:0040E3D5  FBEFE4FE                        ConcatVar                  ;                                        将ASCII值的十进制进行字符串拼接
:0040E3D9  FCF66CFF                        FStVar                     ;                                    将拼接的字符串转为变量
:0040E3DD  2FB0FE                          FFree1Str                  ;SysFreeString [LOCAL_0150]; [LOCAL_0150]=0           释放字符串 
:0040E3E0  1A68FF                          FFree1Ad                   ;Push [LOCAL_0098]; Call [[[LOCAL_0098]]+8]; [[LOCAL_0098]]=0    
:0040E3E3  36060044FF24FF04                FFreeVar                   ;Free 0006/2 variants                                                        释放变量
:0040E3EC  04D4FE                          FLdRfVar                   ;Push LOCAL_012C                                                             
:0040E3EF  FE7EB4FE2D01                    NextStepVar                ;                                                                            开始下一轮循环
:0040E3F5  046CFF                          FLdRfVar                   ;Push LOCAL_0094                                                      将用户名拼接的字符串压入堆栈——7111710583104111117(0x13)                 
:0040E3F8  FBEB44FF                        FnLenVar                   ;vbaLenVar                                                                        求用户名拼接的字符串长度  
:0040E3FC  2854FF0900                      LitVarI2                   ;PushVarInteger 0009                                                  将整形变量9压入堆栈
:0040E401  5D                              HardType                   ;                                                                                        修改变量9的类型
:0040E402  FB74                            GtVarBool                  ;Push (Pop1 >= Pop2)                                                比较长度是否大于9
:0040E404  1CB901                          BranchF                    ;If Pop=0 then ESI=0040E425                                        如果不大于9则跳转到0040E425
:0040E407  046CFF                          FLdRfVar                   ;Push LOCAL_0094                                                        将用户名拼接的字符串压入堆栈
:0040E40A  FEC454FF50455254                LitVarR8                   ;                                                                                        将参数一(圆周率)的类型修改为浮点数
:0040E416  FBBC44FF                        DivVar                     ;                                                                                    将用户名拼接的字符串除以圆周率
:0040E41A  FBE124FF                        FnFixVar                   ;                                                                                        相当于字符串拷贝
:0040E41E  FCF66CFF                        FStVar                     ;                                                                                        将用户名拼接的字符串转为浮点数                                                                                
:0040E422  1E8901                          Branch                     ;ESI=0040E3F5                                                                如果长度大于9则跳转至0040E3F5        
:0040E425  046CFF                          FLdRfVar                   ;Push LOCAL_0094                                                        将浮点数结果压入堆栈
:0040E428  FEC154FF7856F830                LitVarI4                   ;                                                                                        将浮点数转为整形
:0040E430  FB1744FF                        XorVar                     ;                                                                                        将结果和30F85678进行异或
:0040E434  FCF66CFF                        FStVar                     ;                                                                                        保存结果
:0040E438  046CFF                          FLdRfVar                   ;Push LOCAL_0094                                                        将异或后的结果压栈
:0040E43B  080800                          FLdPr                      ;[SR]=[STACK_0008]                                
:0040E43E  8A4C00                          MemLdStr                   ;Push DWORD [[SR]+004C]                                                将0xDBD3压栈
:0040E441  FD6954FF                        CVarI4                     ;                                                                                        将0xDBD3转为变量
:0040E445  FB9C44FF                        SubVar                     ;                                                                                        用异或后的结果减去0xDBD3
:0040E449  FCF66CFF                        FStVar                     ;                                                                                        保存结果

需要分析记录和相关文件可以到我的Github下载:https://github.com/TonyChen56/160-Crackme



免费评分

参与人数 2吾爱币 +4 热心值 +2 收起 理由
solly + 1 + 1 我很赞同!
朱朱你堕落了 + 3 + 1 我要是有师傅这水平就好了!哎!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

woaijiajia 发表于 2019-5-2 20:06
谢谢分享
solly 发表于 2019-5-2 23:58
本帖最后由 solly 于 2019-5-3 00:01 编辑

CVarI4,不是“将int值转成变量类型”,是将 Long类型转换成可变类型,CVarI2 才是integer转换成Variant类型,还有 CVarR8 ,CR8I4,CI4UI1,CR8I2, CUI1I4, CUI1I2, CI2UI1,CI4UI1, CStrUI1, CVarStr,CR8Str,CStrR8 。。。。。。等等p-code码都是类型转换指令,其中 C 表示类型转化,U是无符号,I是整数,R是浮点数,Var是Variant类型,Str是字符串,然后数字1,2,4,8表示字节数,如R8是8字节的Double类型, I1是Byte, I2是Integer,I4是Long,UI2是Word,UI4就是 DWORD 整数。。。

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
鬼手56 + 1 + 1 学习了 学习了 感谢指正

查看全部评分

火星啊 发表于 2019-5-4 11:49
92013 发表于 2019-5-8 13:10
这个很赞,非常感谢经验分享,学习了
13432503812 发表于 2019-6-10 19:38
谢谢分享
安尼大大 发表于 2019-7-26 11:27
值得提升学习了
飘飘Casey 发表于 2020-3-25 21:51
亲,我有个P-code 文件,有偿求高人破解,请联系rainrui15@gmail.com
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 14:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表