第8章 abex'crackme#2

1. 运行abex’crackme#2

运行界面

输入合适的Name与Serial,按Check按钮,会弹出错误的消息框!
错误的消息框

2. Visual Basic文件的特征

2.1 VB专用引擎

举个VB引擎的例子,显示消息框时,VB代码中要调用MsgBox()函数.其实,VB编辑器真正调用到的MSVBVM60.dll里的rtcMsgBox()函数,在该函数内部通过调用user32.dll里的MessageBoxW()函数(Win32 API)来工作(也可以在VB代码中直接调用user32.dll里的MessageBoxM()).

2.2 本地代码和伪代码

VB文件可以编译为本地代码 (N code) 与伪代码 (P code) .本地代码一般使用易于调试器解析的IA-32指令;而伪代码是一种解析器 (Interpreter) 语言,它使用由VB引擎实现虚拟机并自解析的指令 (字节码) .因此,若想准确解析VB的伪代码,就需要分析VB引擎并实现模拟器.

2.3 事件处理程序

VB主要用来编写GUI程序,由于VB程序采用windows操作系统的事件驱动工作,所以在main()或winmain()中并不吃存在用户代码(希望调试的代码),用户代码存在于各个事件处理程序(event handler)之中.

就上述abex’crackme#2而言,用户代码在点击check按钮时触发的事件处理程序内.

2.4 未文档化的结构体

VB中使用的各种信息 (diglog,control,form,module,function等) 以结构体形式保存在文件内部.由于微软未作公开这种结构体信息,所以调试VB文件会难一些.

3. 开始调试

运行OllyDbg,查看abex’crackme#2文件的反汇编代码
EP

执行程序后,在EP代码中首先要做的是调用VB引擎的主函数 (ThunRTMain()).
ThunRTMain()

EP的地址为401238.401238地址处的PUSH 401E14命令用来把RT_MainStruct结构体的地址(401E14)压入栈.然后40123D地址处的CALL 00401232命令调用401232地址处的的JMP DOWRD PTR DS:[4010A0]指令.该JMP指令会跳转至VB引擎的主函数ThunRTMain()(前面压入栈的401E14的值作为ThunRTMain()的参数.)

3.1 间接调用

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

这就是VC++,VB编译器中常用的间接调用法 (Indirect Call).

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

3.2 RT_MainStruct结构体

要注意的是ThunRTMain()函数的参数RT_MainStruct结构体.这里,RT_MainStruct结构体存在于401E14地址处.VB引擎通过参数传递过来的RT_MainStruct结构体获取到程序运行需要的所有信息.

3.3 ThunRTMain()函数

ThunRTMain()代码开始

4. 分析crackme

目前找RT_MainSturct结构体非容易之事,我们借助思路查找错误消息框和字符串.

4.1 检索字符串

All  referenced  text  strings

双击相应字符串,转到其他地址处.
403458地址处

消息框的标题(“Wrong serial!”),内容(“Nope,this serial is wrong!”),还有实际调用信息框函数的代码(4034A6)都显示出来了.

从编程的观点来看,使用某种算法生成序列号,通过比较用户输入的序列号与字符串,代码分为TRUE(序列号相同)与FALSE(序列号不同)两大部分.通俗点说,就是前后存在的字符串比较代码.且序列号正确时程序代码会调用消息框输出成功信息.

往上拖动滚动条,在前面看到了条件转移语句的代码.
条件转移指令

调用403329地址的_vbaVarTstEq()函数,比较(Test命令)返回值(AX)后,由403332地址的条件转移指令(JE指令)决定执行”真”代码还是”假”代码.

上述代码使用的汇编指令说明

  • Test : 逻辑比较 (Logical Compare)
    与bit-wise logical”AND”一样(仅改变EFLAGS寄存器而不改变操作数的值)若两个操作数中一个为0,则AND运算结果被置为0->ZF=1.
  • JE : 条件跳转指令 (Jump if equal), 若ZF=1,则跳转.

4.2 查找字符串地址

403329地址处的_vbaVarTstEq()函数为字符串比较函数,起上方的2个PUSH指令为比较函数的参数,即比较字符串.

调试至403329

403329地址处
00403321地址处的SS:[EBP-44]表达的是 : SS是栈段,EBP是基址指针寄存器.换言之,SS:[EBP-44]指的是栈内地址,它恰好又是函数中声明的局部对象的地址(局部对象存储在栈内).在此状态下查看栈.(栈地址会随调试的环境不同而改变)

跟随堆栈地址

EDX表示实际的地址,EAX表示用户输入的地址.

我们选择数据窗口,长型查看实际的字符串.
Serial字符串

我们重新运行程序,输入Name=”kevin”,Serial=”CFC9DACD”并提示破解成功!
成功

但是Name和Serial之间什么关系呢?为了测试我们输入Name的另外一个值,Serial保存不变.程序显示错误!这就证明了最初的判断 : “以name字符串为基础随时生成Serial”的算法.

4.3 生成Serial的算法

查找函数开始部分

很显然条件转移代码属于某个函数,该函数可能就是Check按钮的事件来处理程序.原因在于选择Check按钮后,该函数会被调用执行.且含有用户代码来弹出成功/失败消息框.

我们向上查找函数开始部分,仔细查看00402ED0地址处的命令.

00402ED0地址处

上述代码是栈帧代码,开始执行函数就会形成栈帧.由此得知该位置就是函数开始部分,即是Check按钮事件的处理程序.

汇编指令
VB文件的函数之间存在着NOP指令.NOP指令表示不执行任何动作的指令.

为了准确分析代码,在402ED0位置处下断点后调试.

按钮事件的处理程序

4.4 预测代码

  • 若是Win32 API程序,则有如下特点.
    • 读取Name字符串 (使用GetWindowText / GetDlgItemText 等API).
    • 启动循环,对字符加密 (XOR / ADD / SUB等).

上述代码使用VB引擎函数编写而成,若预测正确,在事件处理程序开始代码开始调试.查找读取Name字符串的部分后,接着就是出现加密循环.

4.5 读取Name字符串的代码

读取Name字符串的代码

查看00402F8E地址处的代码可以看到,函数的局部对象ss:[ebp-0x88]地址传递给(PUSH)函数的参数.查看该地址.先在00402FA0处下一个断点然后选中跟随数据
ss:[ebp-0x88]

Name字符串(以字符串对象形式)存储到[EBP-88]地址处.

4.6 加密循环

继续调试,遇到如下循环,即一系列循环语句.

循环语句

简单的动作原理为 : 链表中使用next指针引用下一个元素,并且设置EBX,4 使其按指定的次数运转循环.

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

4.7 加密方法

  • 从该给定的Name字符串前端逐一读取字符(共4次).
  • 将字符转换为数字(ASCII代码).
  • 向变换后的数字加64.
  • 再次将数字转换为字符.
  • 连接变换后的字符.

5. 总结

通过”Wrong serial”字符串作为目标,然后查找该字符串的代码.从查找的代码开始,向上拖动滑动条.找到生成栈帧(函数开始)的部分.