Crack入门
本帖最后由 宇宙第一魔王 于 2022-3-26 01:11 编辑# Crack入门:abex'crackme
## 1.前期信息探查:
### 1.EXEinfo:
无壳,32位,用VB编写的程序:
### 2.运行一下:
要求我们输入name和序列号,随便输入一些东西,查看报错:
## 2.前置知识补充:
### 1.what is the visual basic?
- VB文件使用名为MSVBVM60.dll的VB专用引擎(Microsoft Visual Basic Virtual Machine 6.0)
- 根据使用的编译选项不同VB文件可以编译为本地代码(N code)与伪代码(P code),前者适用于调试器解析的IA-32指令;后者是一种解释器语言,它使用由VB引擎实现虚拟机并课自解析的指令(字节码)。因此,若想准确解析VB的伪代码就需要分析VB引擎并实现模拟器。
- 该语言适用于编写GUI图形界面,VB程序采用windows操作系统的事件驱动方式工作,所以在main( )或Winmain( )中并不存在用户代码(希望调试的代码),用户代码存在于各个事件处理程序之中。
## 3、动态调试:
执行程序后,在EP代码中首先要做的是调用VB引擎的主函数。EP的地址为401238
```assembly
401232 jmp dword ptr ds:;该jmp指令会跳转至VB引擎的主函数ThunRTMain()前面压入栈的地址 值作为ThunRTMain()的参数
401238 push 401e14 ;=>ep入口,push用来把RT_MainStruct结构体的地址压入栈
40123d call 401232
```
- 间接调用:40123D处的call跳转去的地方是一个jmp,并不是直接转到MSVBVM60.dll,而是通过中间地址4010A0跳转(4010A0处为IAT)
- RT_MainStruct结构体:ThunRTMain( )函数的参数RT_MainStruct结构体,其成员是其他结构体的地址,也就是VB引擎通过参数传递过来的RT_MainStruct结构体获取程序运行需要的信息
汇编指令回顾:
- ZF标志:判断结果是否为0,为零则值为一,反之则为零。
- JE:条件转移指令,若ZF为1,则跳转
- test:做and操作,但和and不同的是它不会赋值,单纯的修改指令寄存器
### 1.寻找核心判断:
现阶段首先考虑的是从字符串下手,之前出现了错误提示“Nope, this serial is wrong!”于是我们右击→查找→所有参考文本字串。找到对应字符串,双击进入汇编代码,有错误的就有正确,所以一定有判断语句,往上翻就能看到je(条件跳转),和上面的test(and操作)
注意到call指令调用的是__vbaVarTstEQ()函数,这个函数的作用就是字符串比较
为了验证可以在403332处下个断点,然后F9运行到这里将ZF的值改成0,这样F9就可以看到成功的提示了。
### 2.查看当前序列号:
既然已经知道字符串比较函数在403329处,那么上面的push很明显就是要比较的两个字符串,在403329处下断点。然后F9运行,还是随便输入点东西:
当程序停在403329处时,在寄存器处选中EAX,EDX然后Follow in dump 就可以看到真正的序列号了
```assembly
00403327 .52 push edx ; /var18 = 0019F1AC
00403328 .50 push eax ; |var28 = 0019F1BC
00403329 .FF15 58104000 call dword ptr ds:[<&MSVBVM60.__vbaVarTs>; \__vbaVarTstEq
```
现在可以去验证一下看序列号是否正确:
很显然两者对比发现序列号是根据name来随机生成的,所以继续分析Serial的算法
### 2.算法分析:
在代码分析之前先预测一下加密算法的代码,如果是win32 API程序,则会有如下特点:
- 读取name字符串(使用GetWindowText、GetDlgItemText等API)
- 开始循环,对字符串进行加密(XOR,ADD,SUB等)
在之前的程序分析时已经知道有一个关于序列号的条件判断语句(403332),下面就是往上分析代码,理解算法逻辑了。
tips:VB文件的函数之间存在着NOP指令
根据tips的特点我们直接往上翻,可以定位到402ED0处:
402ED0和402ED1可以看到一个典型的形成新的栈帧的操作,这里确定这是函数开始的部分。
然后这个part相当与初始化个变量
```assembly
00402F7E .50 push eax ;类对象模版地址
00402F7F .8D85 74FFFFFF lea eax,dword ptr ss:
00402F85 .50 push eax ;欲要设置引用对象指针给eax
00402F86 .FF15 38104000 call dword ptr ds:[<&MSVBVM60.__vbaObjSe>;msvbvm60.__vbaObjSet
```
接着就是获取name字符串,这里结合F9分析:
当执行到402F9E处时,ebp-0x88处就放入了“hahaha”
```assembly
00402F8C .8BF0 mov esi,eax
00402F8E .8D95 78FFFFFF lea edx,dword ptr ss: ;用于保存Name字符串的对象
00402F94 .52 push edx
00402F95 .56 push esi
00402F96 .8B0E mov ecx,dword ptr ds: ;abexcm2-.004042F0
00402F98 .FF91 A0000000 call dword ptr ds: ;获取Name
```
当然也可以直接点汇编窗口的402F8E处的ss:,右击数据窗口跟随。
Name字符串(以字符串的对象的形式)存储的地址为:
接受到字符串后,下一步是判断Name的长度是否小于4(这个部分的代码一直到4030F9,其实有一部分是小于4时的错误输出),判断依据为:
__vbaLenVar函数判断的是字符串长度
__vbavartstLt这里是上一个函数返回的长度,是否大于等于于4,这里注意是大于四时才跳转到4030F9处的地址继续执行,否则执行下方代码(也就是报错)
这里尝试了一下长度小于四时:
当输入的数值不小于4跳转到4030F9处后,下面代码进行的就是加密循环了,大概流程如下:
```assembly
00403102 .BB 04000000 mov ebx,0x4 ; EBX = 4(loop count)
···
0040318B .FF15 30104000 call dword ptr ds:[<&MSVBVM60.__vbaVarFo>; \__vbaVarForInit
00403191 .8B1D 4C104000 mov ebx,dword ptr ds:[<&MSVBVM60.#rtcMid>; msvbvm60.rtcMidCharVar
00403197 >85C0 test eax,eax ; loop start
00403199 .0F84 06010000 je abexcm2-.004032A5
···
0040329A .FF15 C0104000 call dword ptr ds:[<&MSVBVM60.__vbaVarFo>; \__vbaVarForNext
004032A0 .^ E9 F2FEFFFF jmp abexcm2-.00403197 ; loop end
004032A5 >8B45 08 mov eax,dword ptr ss:
```
先总体分析一下这个循环的框架,EBX作为循环的次数,而vbaVarForInit()函数和vbaVarForNext()函数是对字符串对象进行逐个字符引用。
下面我们主要对循环的加密方法进行分析:
在循环开始也就是403102处下个断点,Ctrl+F2重新调试,弹出对话框随便输入一些东西:
开始一路F8分析,直到注意到下图
__vbaStrVarVal 函数获取该字符的ASCII码的内存地址 (直接指向该字符而不是字符变量)
__rtcAnsiValueBstr 获取该字符的 ASCII码 的十六进制值并保存在 eax 中(eax 直接存储着这个码),
当指令执行到4031FD处时,就可以看到eax的值为68
汇编代码解释如下:
```assembly
004031F0 .FF15 80104000 call dword ptr ds:[<&MSVBVM60.__vbaStrVarVal>] ; \__vbaStrVarVal
004031F6 .50 push eax ; 从Name字符串中获取的一个字符(UNICODE)
; 这里获得的值为“h”
004031F7 .FF15 1C104000 call dword ptr ds:[<&MSVBVM60.#rtcAnsiValueBstr_5>; \rtcAnsiValueBstr()获取字符的十六进制ASCII码值
00403221 .8D55 AC lea edx,dword ptr ss:
00403224 .8D85 24FFFFFF lea eax,dword ptr ss:
0040322A .52 push edx ; EDX = 68
0040322B .8D8D 64FFFFFF lea ecx,dword ptr ss:
00403231 .50 push eax
00403232 .51 push ecx ; dest
00403233 .C785 2CFFFFFF>mov dword ptr ss:,0x64
0040323D .89BD 24FFFFFF mov dword ptr ss:,edi ; = EAX = 64
```
接着往下走call了一个__vbaVarAdd,这个函数的作用是两个变量值相加
看上面的两条指令,第一个变量的值已经确定为0x64,另一个的值跟踪数据地址可以发现就是用户名的第一个地址
也就是说这条指令的作用就是0x68+0x64=0xcc,这里的0x64可以理解成加密过程中的一个运算。
再看下一个函数:
__rtcHexVarFromVar 将计算结果转换为 UNICODE 形式:
指令F8到403261处,就可以看到此时值已经add后的结果已经变成了UNICODE形式:
循环的最后一部分是把生成的字符串连接出来:
```assembly
0040326C .8D4D BC lea ecx,dword ptr ss:
0040326F .8D55 AC lea edx,dword ptr ss:
00403272 .51 push ecx ;Old
00403273 .8D85 64FFFFFF lea eax,dword ptr ss:
00403279 .52 push edx ;NEW
0040327A .50 push eax ;Serial = strcat(Old, New)
0040327B .FF15 84104000 call dword ptr ds:[<&MSVBVM60.__vbaVarCat>] ;msvbvm60.__vbaVarCat
```
那具体是连接什么呢?
分别跟踪这三个值就会发现一处其实是0,这让我有点想不明白,直到我做第二次循环才发现这个加密的逻辑:
- 接收用户名字符串,序列号从前四个字符生成
- 将每个字符的ASCII码值加上0x64然后把结果转换成UNICODE形式
- 将所有UNICODE值拼接在一起
## 4.写注册机:
```python
Old = input("请输入您的用户名:")
i = 0
Serial = ""
while i<4 :
Serial += str(hex(ord(Old)+0x64))
i += 1
Serial = Serial.replace("0x", '').upper()
print(Serial)
```
写完试一下:
zpy2 发表于 2022-3-25 05:56
后F9运行到这里将ZF的值改成0,
ZF值如何改?
双击寄存器窗口中ZF标志后面的数字就可以了https://gitee.com/gitgit1/cloudimage/raw/master/img/sdfdfgdf1.png LogyJ 发表于 2022-3-30 10:58
感觉汇编语言好难学,大佬有啥好建议吗,就单纯看网上的课就好了嘛
我觉得,王爽的汇编语言是一定要看的,但我看完后其实分析程序的汇编也是一脸蒙,不过汇编语言给我打了个基础,我后面采取的办法是ida和od结合看c语言代码对应的汇编代码是怎么样的,然后看函数调用约定,被调用函数是如何访问调用函数的参数等等,就慢慢懂了,不过我目前还很菜,汇编代码分析起来也很费力 感谢楼主分享,学习一下 感谢分享,学习一下,谢谢! 学习,
感谢分享,深入学习下~ 学习了。感谢。 感谢分享 感谢分享 感谢分享! 后F9运行到这里将ZF的值改成0,
ZF值如何改? 向大神们致敬,我想我的脑子看这些不够用的,哎!