08.abex'crackme-2

运行这个程序
Pasted image 20250312125103
要求我们输入正确的账户与序列号

1. Visual Basic文件的特征

Visual Basic(VB)文件现在已经不太常见了,尤其是在现代开发环境中。微软早在 2008 年就停止了对 VB6(经典 Visual Basic)的官方支持,而 VB.NET 作为 .NET 生态的一部分,虽然仍然在维护,但在开发者社区中的使用率已经大幅下降

此crackme是由 Visual Basic编写而成的。所以我们可以先了解一下其特征

1.1. VB专用引擎

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

1.2. 本地代码与伪代码

根据使用的编译选项的不同,VB文件可以编译为本地代码(Ncode)与伪代码(Pcode)。
本地代码一般使用易于调试器解析的IA-32指令
伪代码是一种解释器(Interpreter)语言,它使用由VB引擎实现虚拟机并可自解析的指令(字节码)。因此,若想准确解析VB的伪代码,就需要分析VB引擎并实现模拟器。

1.3. 事件处理程序

VB主要用来编写GUI程序,IDE用户界面本身也最适合于GUI编程。由于VB程序采用Windows操作系统的事件驱动方式工作,所以在mainWinMain中并不存在用户代码(希望调试的代码),用户代码存在于各个事件处理程序(eventhandler)之中。
就上述abex'crackme ,用户代码在点击Check按钮时触发的事件处理程序内。

1.4. 未文档化的结构体

VB中使用的各种信息(Dialog、Control、Form、Module、Function等)以结构体形式保存在文件内部。由于微软未正式公开这种结构体信息,所以调试VB文件会难一些。

2. 开始调试

首先在EP代码中找到调用VB引擎的主函数 ThunRTMain()
Pasted image 20250312130223

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 0x401E14RT_MainStruct 结构体的地址 401E14 压入栈。
然后 call 00401232 调用 401232 处的指令。 即会跳转到VB引擎的主函数 ThunRTMain() (前面压入栈的401E14 的值作为 ThunRTMain() 的参数)

这就是VB文件的全部启动代码,非常简单

2.1. 间接调用

40123D 地址处的 CALL 401232 命令用于调用 ThunRTMain 函数,这里使用了较为特别的技法。不是直接转到 MSVBVM60.dll 里的 ThunRTMain 函数,而是通过中间 401232 地址处的 JMP 命令跳转

这是VC++ 、VB编译器中常用的间接调用法

4010A0 地址是IAT(Import Address Table,导入地址表)区域,包含着 MSVBVM60.ThunRTMain 函数的实际地址Pasted image 20250312131429

2.2. RT_MainStruct结构体

要注意的是 ThunRTMain 函数的参数 RT_MainStruct 结构体。这里,RT_MainStruct 结构体存在于 401E14 地址处,如图所示。
Pasted image 20250312131808
微软未公开 RT_MainStruct,但是有国外的逆向分析高手已经完成了对 RTMainStruct 结构体的分析,并公布在网络上。
RT_MainStruct 结构体的成员是其他结构体的地址。也就是说,VB引擎通过参数传递过来的 RT_MainStruct 结构体获取程序运行需要的所有信息。此处省略对RT_MainStruct结构体的详细说明。

2.3. ThunRTMain()函数

图中显示了 ThunRTMain 代码的开始部分,可以看到内存地址完全不同了。这是 MSVBVM60.dII 模块的地址区域。换言之,我们分析的不是程序代码,而是VB引擎代码(现在还不需要分析如此庞大的代码)
Pasted image 20250312132133

3. 分析crackme

当前我们很难直接去分析 RT_MainStruct 结构体。 需要找一个更简单的方法
我们可以通过程序提示的字符串入手。因为程序会提示错误后的信息
Pasted image 20250312132335

3.1. 检索字符串

查找->所有参考字符串
Pasted image 20250312132920
双击跳转
Pasted image 20250312133052
我们这里找到了消息提示的调用函数
根据逻辑判断。看到是会根据我们的 name 生成一个序列号。然后判断是否正确,然后回显信息
这里我们往上翻 去找一下那个判断语句,而判断语句一般就是一个跳转命令
Pasted image 20250312135505
通过上面 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,则跳转。

3.2. 查找字符串地址

我们现在已经找到了比较函数 __vbaVarTstEq 的地址,那么上面的两个push 就是传入的函数参数
Pasted image 20250312135935
我们先调试到比较函数地址 403329
我们观察上面的两处指令
Pasted image 20250312144711
这里 SS:[EBP-44] 指的是栈内地址。它恰好就是函数声明中的局部对象的地址(局部对象存储再栈区)
下面信息显示了栈内地址为 19f288

我们可以通过 选中地址,然后在数据框中跟随地址 即可查看对应的内存地址
Pasted image 20250312145117
查看存储在栈中的内存地址
Pasted image 20250312145220
这里与C++的string类一样,VB 字符串使用可变长度的字符串类型。 如图,直接显示的不是字符串,而是16字节大小的数据(这就是VB中使用的字符串对象)
不同的值被这样统一起来,仅方框显示的值是不同的,看上去就像内存地址一样(可变长度字符串类型内部持有实际动态分配的字符串缓存地址)

我们右键选择ASCII数据类型 即可查看到我们实际输入的值进行对比的值
Pasted image 20250312145934
切回来选择这个
Pasted image 20250312150014

Pasted image 20250312150408
如图
EAX 19f298 是我们输入的值 123456 字符串所在的地址是 66145c
EDX 19f288 是实际的序列号 C795D8D6 字符串所在地址是 6615c4
我们可以查看一下
Pasted image 20250312150812

VB默认使用基于Unicode的可变长度字符串对象。可变长度字符串对象会根据需要在内部随时动态分配/释放内存。因此,每次运行时字符串的地址会有所不同。此外,调试时无法一眼看全实际字符串,这也是调试的困难之一。

下面我们运行程序 输入对应的名字与序列号即可弹出正确的信息
Pasted image 20250312151122

3.3. 生成Serial的算法

这里我们探讨一下生成序列号的算法。 我们只知道了 c1trus 对应的序列号是 C795D8D6
那我们想批量生成序列号,肯定需要知道其算法

查找函数开始部分
我们上面找到了一个条件转移的代码。 那个应该就是check后进行的操作。当我们点击check后,这个计算序列号的函数就会被调用执行。然后就会进行对比,并弹出消息框

所以我们应该往上一点点找函数开始的部分。
而且结合 07.栈帧中的知识。我们知道在执行函数之前会生成栈帧
通常命令是 PUSH EBP 然后 MOV EBP,ESP
那我们就着重看这个命令,也可以直接查找。
很容易就找到了生成栈帧的代码
Pasted image 20250312152114
可以发现上面有大量的 nop命令

VB文件的函数之间存在着NOP指令(图8-14的402ECC~402ECF地址区)。NOP:NoOperation,不执行任何动作的指令(只消耗CPU时钟)。

为了方便分析代码。我们先在此处下断点

3.4. 预测代码

如果你有经验,就可以预测出生成序列号的方法,若是win32API程序
则一般有一下特点

  • 读取Name字符串(使用 GetWindowsTextGetDlgItemText 等API)
  • 启动循环,对字符进行加密(XOR ADD SUB 等)
    VB引擎编写的文件也有类似的规律,如果正确的化,我们从断点处开始调试,查找到读取 name 字符串的那部分后,紧接着就是加密循环

3.5. 读取name字符串的代码

开始调试后我们会遇到好几个 call
直到调试到第四个 call
Pasted image 20250312153525
上面的 lea edx,dword ptr ss:[ebp-0x88] 指令把函数的局部对象 ss:[ebp-0x88] 地址传递给了函数的参数。 我们可以看一下这个地址
Pasted image 20250312160419

因为要查找的是Name字符串,在VB中,字符串使用字符串对象(这与C语言使用char数组不同)我们很难认出实际的字符串,所以需要调整一下地址视图的模式

然后继续调试到 call 函数之后
Pasted image 20250312160732
这里我们可以可以看到 name 字符串的值了。 其地址就是 19F244 就是 EBP-88

3.6. 加密循环

继续调试就会遇到以下循环。一系列的循环语句
Pasted image 20250312161137
Pasted image 20250312161215
Pasted image 20250312161305

简单讲解上述循环的动作原理,就像在链表中使用next指针引用下一个元素一样,
__vbaVarForInit__vbaVarForNext 可以使逆向分析人员在字符串对象中逐个引I用字符。并且设置loopcount(EBX)使其按指定次数运转循环。

实测仅使用接收的Name字符串中的前4个字符。在代码内检查字符串的长度,若少于4个字符,就会弹出错误消息框。

3.7. 加密方法

这里太难整了。我先跳过了。等以后再来分析