《逆向工程核心原理》第8章 abex’ crackme #2 笔记

首先放上程序:https://pan.baidu.com/s/1J2_tpKkoAN-9oM_eLOCeVQ

第一步:先运行

这个程序具有典型的crackme形态,要求我们找出程序的序列号。从单独输入Name来看,生成Serial时才会用到Name字符串(依据经验推测)。输入合适的Name和Serial,按Check按钮。

先按照书上的示例输入:

弹出 “Wrong serial!” 消息框,即使多次尝试其他值也依然是这个结果。下面通过调试仔细分析其代码:


在这里先要了解 Visual Basic 文件的特征:

VB专用引擎:

VB文件使用名为 MSVBVM60.dll 的VB专用引擎。举个例子,显示消息框时,VB代码中要调用 MsgBox() 函数。其实,VB编辑器真正调用的是 MSVBVM60.dll 里的 rtcMsgBox() 函数,在该函数内部通过调用 user32.dll 里的 MessageBoxW() 函数(Win32API)来工作

本地代码和伪代码:

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

伪代码具有与 JAVA ( JAVA虚拟机)、Python(Python 专用引擎)类似的结构。使用伪代码的好处是非常方便代码移植

事件处理程序:

VB主要用来编写GUI程序。由于VB程序采用 Windows 操作系统的事件驱动方式工作,所以在 main() 或 WinMain() 中并不存在用户代码,用户代码存在于各个事件处理程序(event handler)之中。

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

为文档化的结构体:

VB中使用的各种信息( Dialog, Control, Form, Module, Function 等)以结构体形式保存在文件内部。并且微软并没有公开

开始调试:

为了方便看地址,我把“调试选项”中的“显示本地模块名称”和“显示符号地址”关了

第一条PUSH命令是将 RT_MainStruct结构体的地址(401E14)压入栈。然后 40123D 地址处的 CALL 00401232 命令调用 401232地址处的 JMP DWORD PTR [0x401E14] 指令。该 JMP 指令会跳转至 VB 引擎的主函数 ThunRTMain() (前面压入栈的 401E14 的值作为 ThunRTMain() 的参数)

以上3行代码是VB文件的全部启动代码。需要注意3点

1、间接调用

40123D 地址处的 CALL 401232 命令不是直接跳转到 MSCVVM60.dll 里的 ThunRTMain() 函数,而是通过中间 401232 地址处的 JMP 命令跳转

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

2、RT_MainStruct 结构体

这个结构体的成员是其它结构体的地址。也就是说,VB引擎通过参数传递过来的 RT_MainStruct 结构体获取程序运行需要的所有信息。

3、ThunRTMain() 函数

进入这个函数,发现内存地址完全不同了,这是 MSVBVM60.dll 模块的地址区域。换言之,这是VB引擎代码,现在还不用研究,回到 abex’ crackme #2

开始调试

如果输入的Name和Serial不正确,将会显示一个标题为“Wrong serial”的消息框,那么就从这里入手,在反汇编界面右键->查找->所有参考文本字串

然后再窗口中右键->查找文本

输入“Wrong serial!”,点击确定

然后可能是这个样子的:

这个并不是我们要找到的,所以右键->查找下一个:

看起来这个就是我们要找的,双击跟随:

这一串代码就是会生成那个提示错误消息框

所以这个代码之前一定有一个判断语句,来判断我们输入的Name和serial是不是正确的,如果不正确程序就会跳转到这一块。

那我们就往上找符合我们期待的这个条件跳转代码

第一个碰到的是这个:

这个命令直接跳过这些代码了,所以不是我们要找的,再往上找:

然后我们先看到了“Yep,….”,这些应该就是输入正确的时候执行的命令了。这个上面就有一个 je 命令,我们看一下它跳转到哪。

它刚好跳过了“Yep,…”然后跳到了“Wrong …”的前面,所以这个 je 命令各方面都符合我们的遐想

那么这个条件跳转命令前面的应该就是判断了,判断我们的输入和正确的值是否相同。

上面先是 test 命令,跟 je 命令结合起来就是 如果ax为0,那么就跳转

ax放着上面这个函数的返回值

那么我们就研究这个函数,它从栈中拿了两个数据之后把他们作为参数传进了函数,那我们就在这个地方设断点,然后重新运行这个程序

Ctrl+F2 后按 F9 运行至断点

程序会弹出来让我们输入,我们把之前输的数据输进去,来到我们之前设的断点。这个时候我们看一下设置的堆栈地址

选择下面的堆栈地址,右键->数据窗口中跟随地址

我们看一下栈中的数据。(这里先上一段书中的说明)

这是我的环境下的情况:

我们看到了字符串“B6C9DAC9”

把它输进程序,得到结果:

但是我遇到一个问题,如果我一开始输的Name不是“ReverseCore”,比如我按下图输入:

那么它在栈中的数据长这样:

然后就很奇怪,如果我继续这么输:

那么栈中数据长这样:

可以看到,程序采用了“以Name字符串为基础随时生成Serial”的算法,这就是书中之后所讲的“生成serial的算法”

过几天填坑XD

1
说点什么

avatar
1 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
1 Comment authors
danisjiang Recent comment authors

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

  Subscribe  
最新 最旧 得票最多
提醒