运行这个程序
要求我们输入正确的账户与序列号
Visual Basic(VB)文件现在已经不太常见了,尤其是在现代开发环境中。微软早在 2008 年就停止了对 VB6(经典 Visual Basic)的官方支持,而 VB.NET 作为 .NET 生态的一部分,虽然仍然在维护,但在开发者社区中的使用率已经大幅下降
此crackme是由 Visual Basic编写而成的。所以我们可以先了解一下其特征
VB文件使用名为MSVBVM60.dll
(MicrosoftVisual BasicVirtual Machine6.0)的VB专用引擎(也称为TheThunder Runtime Engine)。
举个使用VB引擎的例子,显示消息框时,VB代码中要调用 MsgBox
函数。其实,VB编辑器真正调用的是 MSVBVM60.dll
里的 rtcMsgBox
函数,在该函数内部通过调用 user32.dll
里的 MessageBoxW
函数(Win32API)来工作(也可以在VB代码中直接调用 user32.dll
里的 MessageBoxW
。
根据使用的编译选项的不同,VB文件可以编译为本地代码(Ncode)与伪代码(Pcode)。
本地代码一般使用易于调试器解析的IA-32指令
伪代码是一种解释器(Interpreter)语言,它使用由VB引擎实现虚拟机并可自解析的指令(字节码)。因此,若想准确解析VB的伪代码,就需要分析VB引擎并实现模拟器。
VB主要用来编写GUI程序,IDE用户界面本身也最适合于GUI编程。由于VB程序采用Windows操作系统的事件驱动方式工作,所以在main
或WinMain
中并不存在用户代码(希望调试的代码),用户代码存在于各个事件处理程序(eventhandler)之中。
就上述abex'crackme ,用户代码在点击Check按钮时触发的事件处理程序内。
VB中使用的各种信息(Dialog、Control、Form、Module、Function等)以结构体形式保存在文件内部。由于微软未正式公开这种结构体信息,所以调试VB文件会难一些。
首先在EP代码中找到调用VB引擎的主函数 ThunRTMain()
00401232 $- FF25 A0104000 jmp dword ptr ds:[0x4010A0] ; msvbvm60.ThunRTMain
00401238 > $ 68 141E4000 push 0x401E14
0040123D . E8 F0FFFFFF call 00401232 ; <jmp.&MSVBVM60.#100>
分析一下几条命令
00401238
是EP的地址、命令 push 0x401E14
把 RT_MainStruct
结构体的地址 401E14
压入栈。
然后 call 00401232
调用 401232
处的指令。 即会跳转到VB引擎的主函数 ThunRTMain()
(前面压入栈的401E14 的值作为 ThunRTMain()
的参数)
这就是VB文件的全部启动代码,非常简单
40123D
地址处的 CALL 401232
命令用于调用 ThunRTMain
函数,这里使用了较为特别的技法。不是直接转到 MSVBVM60.dll
里的 ThunRTMain
函数,而是通过中间 401232
地址处的 JMP
命令跳转。
这是VC++ 、VB编译器中常用的间接调用法
4010A0
地址是IAT(Import Address Table,导入地址表)区域,包含着MSVBVM60.ThunRTMain
函数的实际地址
要注意的是 ThunRTMain
函数的参数 RT_MainStruct
结构体。这里,RT_MainStruct
结构体存在于 401E14
地址处,如图所示。
微软未公开 RT_MainStruct
,但是有国外的逆向分析高手已经完成了对 RTMainStruct
结构体的分析,并公布在网络上。
RT_MainStruct
结构体的成员是其他结构体的地址。也就是说,VB引擎通过参数传递过来的 RT_MainStruct
结构体获取程序运行需要的所有信息。此处省略对RT_MainStruct结构体的详细说明。
图中显示了 ThunRTMain
代码的开始部分,可以看到内存地址完全不同了。这是 MSVBVM60.dII
模块的地址区域。换言之,我们分析的不是程序代码,而是VB引擎代码(现在还不需要分析如此庞大的代码)
当前我们很难直接去分析 RT_MainStruct
结构体。 需要找一个更简单的方法
我们可以通过程序提示的字符串入手。因为程序会提示错误后的信息
查找->所有参考字符串
双击跳转
我们这里找到了消息提示的调用函数
根据逻辑判断。看到是会根据我们的 name
生成一个序列号。然后判断是否正确,然后回显信息
这里我们往上翻 去找一下那个判断语句,而判断语句一般就是一个跳转命令
通过上面 call dword ptr ds:[0x401058]
命令调用 __vbaVarTstEq
函数比较(TEST命令)返回值(AX)后,由403332
地址的条件转移指令(JE指令)决定执行“真”代码还是“假”代码。
TEST:逻辑比较(Logical Compare)
与bit-wiselogical“AND'一样(仅改变EFLAGS寄存器而不改变操作数的值)若2个操作数中一个为0,则AND运算结果被置为0->ZF=1。
JE:条件转移指令(Jumpif equal)
若ZF=1,则跳转。
我们现在已经找到了比较函数 __vbaVarTstEq
的地址,那么上面的两个push 就是传入的函数参数
我们先调试到比较函数地址 403329
处
我们观察上面的两处指令
这里 SS:[EBP-44]
指的是栈内地址。它恰好就是函数声明中的局部对象的地址(局部对象存储再栈区)
下面信息显示了栈内地址为 19f288
我们可以通过 选中地址,然后在数据框中跟随地址 即可查看对应的内存地址
查看存储在栈中的内存地址
这里与C++的string类一样,VB 字符串使用可变长度的字符串类型。 如图,直接显示的不是字符串,而是16字节大小的数据(这就是VB中使用的字符串对象)
不同的值被这样统一起来,仅方框显示的值是不同的,看上去就像内存地址一样(可变长度字符串类型内部持有实际动态分配的字符串缓存地址)
我们右键选择ASCII数据类型 即可查看到我们实际输入的值与 进行对比的值
切回来选择这个
如图
EAX 19f298
是我们输入的值 123456 字符串所在的地址是 66145c
EDX 19f288
是实际的序列号 C795D8D6 字符串所在地址是 6615c4
我们可以查看一下
VB默认使用基于Unicode的可变长度字符串对象。可变长度字符串对象会根据需要在内部随时动态分配/释放内存。因此,每次运行时字符串的地址会有所不同。此外,调试时无法一眼看全实际字符串,这也是调试的困难之一。
下面我们运行程序 输入对应的名字与序列号即可弹出正确的信息
这里我们探讨一下生成序列号的算法。 我们只知道了 c1trus
对应的序列号是 C795D8D6
那我们想批量生成序列号,肯定需要知道其算法
查找函数开始部分
我们上面找到了一个条件转移的代码。 那个应该就是check后进行的操作。当我们点击check后,这个计算序列号的函数就会被调用执行。然后就会进行对比,并弹出消息框
所以我们应该往上一点点找函数开始的部分。
而且结合 07.栈帧中的知识。我们知道在执行函数之前会生成栈帧
通常命令是 PUSH EBP
然后 MOV EBP,ESP
那我们就着重看这个命令,也可以直接查找。
很容易就找到了生成栈帧的代码
可以发现上面有大量的 nop命令
VB文件的函数之间存在着NOP指令(图8-14的402ECC~402ECF地址区)。NOP:NoOperation,不执行任何动作的指令(只消耗CPU时钟)。
为了方便分析代码。我们先在此处下断点
如果你有经验,就可以预测出生成序列号的方法,若是win32API程序
则一般有一下特点
GetWindowsText
、GetDlgItemText
等API)XOR
ADD
SUB
等)name
字符串的那部分后,紧接着就是加密循环开始调试后我们会遇到好几个 call
直到调试到第四个 call
上面的 lea edx,dword ptr ss:[ebp-0x88]
指令把函数的局部对象 ss:[ebp-0x88]
地址传递给了函数的参数。 我们可以看一下这个地址
因为要查找的是Name字符串,在VB中,字符串使用字符串对象(这与C语言使用char数组不同)我们很难认出实际的字符串,所以需要调整一下地址视图的模式
然后继续调试到 call
函数之后
这里我们可以可以看到 name
字符串的值了。 其地址就是 19F244
就是 EBP-88
继续调试就会遇到以下循环。一系列的循环语句
简单讲解上述循环的动作原理,就像在链表中使用next指针引用下一个元素一样,
__vbaVarForInit
、__vbaVarForNext
可以使逆向分析人员在字符串对象中逐个引I用字符。并且设置loopcount(EBX)使其按指定次数运转循环。
实测仅使用接收的Name字符串中的前4个字符。在代码内检查字符串的长度,若少于4个字符,就会弹出错误消息框。
这里太难整了。我先跳过了。等以后再来分析