宇宙第一魔王 发表于 2022-3-24 19:53

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)

```

写完试一下:



宇宙第一魔王 发表于 2022-3-25 08:35

zpy2 发表于 2022-3-25 05:56
后F9运行到这里将ZF的值改成0,

ZF值如何改?

双击寄存器窗口中ZF标志后面的数字就可以了https://gitee.com/gitgit1/cloudimage/raw/master/img/sdfdfgdf1.png

宇宙第一魔王 发表于 2022-3-31 10:09

LogyJ 发表于 2022-3-30 10:58
感觉汇编语言好难学,大佬有啥好建议吗,就单纯看网上的课就好了嘛

我觉得,王爽的汇编语言是一定要看的,但我看完后其实分析程序的汇编也是一脸蒙,不过汇编语言给我打了个基础,我后面采取的办法是ida和od结合看c语言代码对应的汇编代码是怎么样的,然后看函数调用约定,被调用函数是如何访问调用函数的参数等等,就慢慢懂了,不过我目前还很菜,汇编代码分析起来也很费力

ytlk0535 发表于 2022-3-24 20:43

感谢楼主分享,学习一下

LukeMouse 发表于 2022-3-24 21:15

感谢分享,学习一下,谢谢!

ManaCola 发表于 2022-3-24 23:24

学习,
感谢分享,深入学习下~

keylinjay 发表于 2022-3-24 23:48

学习了。感谢。

graffee 发表于 2022-3-25 00:00

感谢分享

CXC303 发表于 2022-3-25 00:07

感谢分享

williamxia 发表于 2022-3-25 01:06

感谢分享!

zpy2 发表于 2022-3-25 05:56

后F9运行到这里将ZF的值改成0,

ZF值如何改?

zhpsxq 发表于 2022-3-25 07:52

向大神们致敬,我想我的脑子看这些不够用的,哎!
页: [1] 2 3 4 5
查看完整版本: Crack入门