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
401232 jmp dword ptr ds:[0x4010A0] ;该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 就可以看到真正的序列号了
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相当与初始化个变量
00402F7E . 50 push eax ;类对象模版地址
00402F7F . 8D85 74FFFFFF lea eax,dword ptr ss:[ebp-0x8C]
00402F85 . 50 push eax ;欲要设置引用对象指针给eax
00402F86 . FF15 38104000 call dword ptr ds:[<&MSVBVM60.__vbaObjSe>; msvbvm60.__vbaObjSet
接着就是获取name字符串,这里结合F9分析:
当执行到402F9E处时,ebp-0x88处就放入了“hahaha”
00402F8C . 8BF0 mov esi,eax
00402F8E . 8D95 78FFFFFF lea edx,dword ptr ss:[ebp-0x88] ;用于保存Name字符串的对象
00402F94 . 52 push edx
00402F95 . 56 push esi
00402F96 . 8B0E mov ecx,dword ptr ds:[esi] ; abexcm2-.004042F0
00402F98 . FF91 A0000000 call dword ptr ds:[ecx+0xA0] ;获取Name
当然也可以直接点汇编窗口的402F8E处的ss:[ebp-0x88],右击数据窗口跟随。
Name字符串(以字符串的对象的形式)存储的地址为:[ebp-0x88]
接受到字符串后,下一步是判断Name的长度是否小于4(这个部分的代码一直到4030F9,其实有一部分是小于4时的错误输出),判断依据为:
__vbaLenVar函数判断的是字符串长度
__vbavartstLt这里是上一个函数返回的长度,是否大于等于于4,这里注意是大于四时才跳转到4030F9处的地址继续执行,否则执行下方代码(也就是报错)
这里尝试了一下长度小于四时:
当输入的数值不小于4跳转到4030F9处后,下面代码进行的就是加密循环了,大概流程如下:
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:[ebp+0x8]
先总体分析一下这个循环的框架,EBX作为循环的次数,而vbaVarForInit()函数和vbaVarForNext()函数是对字符串对象进行逐个字符引用。
下面我们主要对循环的加密方法进行分析:
在循环开始也就是403102处下个断点,Ctrl+F2重新调试,弹出对话框随便输入一些东西:
开始一路F8分析,直到注意到下图
__vbaStrVarVal 函数获取该字符的ASCII码的内存地址 (直接指向该字符而不是字符变量)
__rtcAnsiValueBstr 获取该字符的 ASCII码 的十六进制值并保存在 eax 中(eax 直接存储着这个码),
当指令执行到4031FD处时,就可以看到eax的值为68
汇编代码解释如下:
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:[ebp-0x54]
00403224 . 8D85 24FFFFFF lea eax,dword ptr ss:[ebp-0xDC]
0040322A . 52 push edx ; EDX = 68
0040322B . 8D8D 64FFFFFF lea ecx,dword ptr ss:[ebp-0x9C]
00403231 . 50 push eax
00403232 . 51 push ecx ; dest
00403233 . C785 2CFFFFFF>mov dword ptr ss:[ebp-0xD4],0x64
0040323D . 89BD 24FFFFFF mov dword ptr ss:[ebp-0xDC],edi ; [EBP-DC] = EAX = 64
接着往下走call了一个__vbaVarAdd,这个函数的作用是两个变量值相加
看上面的两条指令,第一个变量的值已经确定为0x64,另一个的值跟踪数据地址可以发现就是用户名的第一个地址
也就是说这条指令的作用就是0x68+0x64=0xcc,这里的0x64可以理解成加密过程中的一个运算。
再看下一个函数:
__rtcHexVarFromVar 将计算结果转换为 UNICODE 形式:
指令F8到403261处,就可以看到此时值已经add后的结果已经变成了UNICODE形式:
循环的最后一部分是把生成的字符串连接出来:
0040326C . 8D4D BC lea ecx,dword ptr ss:[ebp-0x44]
0040326F . 8D55 AC lea edx,dword ptr ss:[ebp-0x54]
00403272 . 51 push ecx ;Old
00403273 . 8D85 64FFFFFF lea eax,dword ptr ss:[ebp-0x9C]
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.写注册机:
Old = input("请输入您的用户名:")
i = 0
Serial = ""
while i<4 :
Serial += str(hex(ord(Old[i])+0x64))
i += 1
Serial = Serial.replace("0x", '').upper()
print(Serial)
写完试一下: