《逆向工程核心原理》第8章——abex‘crackme #2


运行abex’crackme #2

下载地址:https://pan.baidu.com/s/1qXhyt8C

运行程序:
在这里插入图片描述
输入符合要求的name与serial,点击check:
在这里插入图片描述
弹出wrong serial消息框。


Visual Basic文件的特征

abes’ crackme #2文件由Visual Basic编写而成。调试前首先了解Visual Basic文件特征。

VB专用引擎

VB文件使用名为MSVBVM60.dll的VB专用引擎,比如:显示消息框时,VB代码中要调用MsgBox()函数。VB编辑器真正调用的时MSVBVM60.dll中的rtcBox函数,在该函数内部通过调用user32.dll里的MessageBoxW()函数(Win32 API)来工作。

本地代码与伪代码

根据使用编译选项的不同,VB文件可以编译为本地代码(N code)和伪代码(P code)。本地代码一般使用易于调试解析的IA-32指令;伪代码是一种解释器语言,若要准确解析,就必须分析VB引擎并实现模拟器。

事件处理程序

VB主要用来编写GUI程序。VB采用Windows操作系统的事件驱动方式工作,在main()或Winmain()中不存在用户代码,用户代码存在于各个事件程序中。
如本程序中,用户代码在点击Check按钮时触发的事件处理程序内

未文档化的结构体
VB中使用的各种信息(Dialog,Control,Form,Modul,Function)以结构体的形式保存在文件内部。微软未公开,故调试VB文件会困难一些。


开始调试

运行OD查看程序反汇编代码

在这里插入图片描述上示3行代码即为VB文件的全部启动代码。

push 401E14将RT_MainStruct结构体的地址入栈。
call 401232调用401232地址处的jmp 4010A0指令,跳转到VB引擎主函数ThunRTMain(),起前面入栈的401E14作为函数的参数。

间接调用

40123D地址处的call 401232命令用于调用ThunRTMain()函数,这里并未直接跳转到MSVBVM60.dll里的ThunRTMain()函数,而是通过中间401232地址处的jmp命令跳转。

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

RT_MainStruct结构体

ThunRTMain()函数的参数时RT_MainStruct结构体,它存放在401E14处。查看:
在这里插入图片描述
RT_MainStruct结构体的成员是其他结构体的地址。也就是说,VB引擎通过参数传递过来的RT_MainStruct结构体获取程序运行需要的所有信息。

ThunRTMain()函数

该函数属于VB引擎代码。


分析crackme(1)——分析程序执行流程

检索字符串

all referenced text strings检索字符串:
在这里插入图片描述
双击跳转到该字符串处:
可以看到消息框的标题、内容、实际调用消息框函数的代码(4034A6)均显示出来了。
在这里插入图片描述按照编程逻辑,用户序列号与字符串后,代码分为TRUE和FALSE两部分。换言之,上述代码前后存在字符串比较代码与条件转移语句:
在这里插入图片描述
可以看到调用403329地址的_vbaVarTstEq()函数,TEST比较返回值AX后,由403332地的条件转移指令决定执行TRUE代码还是FALSE代码。

查找字符串地址

403329地址的_vbaVarTstEq()函数为字符串比较函数,其上方的2个PUSH指令为比较函数的参数,即比较的字符串。
在这里插入图片描述
调试至403329地址处,输入如下,输入序列号为1234:
在这里插入图片描述
可以看到入栈的地址分别为12F458与12F468。
在这里插入图片描述
查看栈中存放数据:
在这里插入图片描述
很容易看到12F468中存放的是用户输入的serial值。12F458中存放的是实际的serial值。

进入字符串所在地址查看实际字符串:
在这里插入图片描述在这里插入图片描述

运行程序,用户名不变,序列号输入为D6C9DAC9:
在这里插入图片描述
可以看到找到了正确的序列号并破解成功。

但如果改变name值,序列号不变:
在这里插入图片描述显示错误信息,这说明程序使用了“以name字符串为基础随时生成Serial”的算法。


分析crackme(2)——分析生成Serial的算法

前面提到的条件转移代码显然属于某个函数,该函数应该就是check按钮的事件处理程序。因为选择check按钮后,该函数会被调用执行,且含有用户代码来弹出成功/失败消息框。向上查找
在这里插入图片描述
可以看到
push ebp
move ebp,esp
这是典型的栈帧代码,开始执行函数时就会形成栈帧,由此可知该位置是函数开始部分,即check按钮的事件处理程序。

预测代码

根据编程习惯,预测生成序列号的方法。若为Win32 API程序:
(1)读取name字符串,使用GetWindowText、GetDlagItem Text等API
(2)启动循环,对字符加密,XOR、ADD、SUB等

VB文件原理类似。若预测正确,从前面的事件处理程序其实代码开始调试,查找到Name字符串后,紧接着就会出现加密循环。

注意:调试前先预测代码的实现是个好习惯。预测正确可以节约大量调试时间。

读取name字符串的代码

接下来以call指令为主进行调试。开始调试后遇到的第四条call指令:
在这里插入图片描述
lea edx,dword ptr ss:[ebp-88]返回间接操作数的偏移地址,EDX=12F414
push edx 地址12F414被传递给函数作为参数,要重点关注该地址
push esi esi=01ACFFF4
mov ecx,dword ptr ds:[esi]ecx=01AC3628,即地址ds:[esi]处存放内容
call dword ptr ds:[ecx+A0]调用函数获取name

执行完call命令后,查看12F414:
在这里插入图片描述
可以看到217AAC处存放着name字符串,即name字符串以字符串对象的形式存储到[EBP-88]地址。
在这里插入图片描述
加密循环
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
调试发现循环从403197处test eax,eax开始,直到4032A0处jmp 403197结束,循环4次。
这两条语句等同于:if(AX==0) goto 4043A5

显然这里的vbaVarForInit与vbaForNext均类似于指针,作用是获取字符串下一字符。loop count使其按指定次数循环。

这里仅接收name字符串中的前4个字符。在代码内检查字符串的长度,少于4个字符弹出错误消息框。

加密方法

在这里插入图片描述
push eax eax=228B84,查看该地址处可以看到存放的是获取的name的一个字符。
在这里插入图片描述
继续执行call dword ptr ds:[40101C]将UNICODE变为ASCII。
该函数返回值为r的ASCII码值72h。存放在EAX中。
在这里插入图片描述
继续调试:
在这里插入图片描述
调试到到此处看一下栈:
在这里插入图片描述
查看各内存地址:
在这里插入图片描述
运行函数:
在这里插入图片描述
可以看到函数执行后ECX指向的缓冲区中0012F408变为D6(计算结果D6=72+64)。是前面正确序列号的前两个字字符。
在这里插入图片描述
继续调试,下面代码将数字D6 转换为字符串D6(UNICODE):
在这里插入图片描述
执行函数后,查看EAX所指缓冲区0012F400,可以看到生成了D6字符串:
在这里插入图片描述
查看实际字符串的地址。可以看到UNICODE字符串D6:
在这里插入图片描述
下面代码实现了字符串的拼接:_vbaVarCat()函数,这一部分在下一次循环时再关注:
在这里插入图片描述
第二次循环同上:
可以看到C9(C9=65(‘e’)+64)以字符串形式存储起来了。
在这里插入图片描述

字符串的拼接部分

_vbaVarCat()函数执行前:

查看栈:
在这里插入图片描述
ECX:
在这里插入图片描述
EDX:
在这里插入图片描述
执行后:
查看栈:
在这里插入图片描述
EAX:
在这里插入图片描述
可以看到EAX指向区域存放的时拼接后的结果,ECX指向区域处存放的时拼接前的字符串,EDX指向区域存放的是新读取字符加密后的字符串。

执行至最后一次循环:

执行前:

EDX存放新的字符串:
在这里插入图片描述
ECX存放拼接前的字符串:在这里插入图片描述
执行后:

EAX存放拼接后的结果:
在这里插入图片描述
循环结束后跳转至4032A5处继续执行,继续执行跳转至4032E9,继续执行,就可以看到前面提到的403332分支指令处,分支跳转至403408,即可看到弹出错误序列号窗口的代码,如下:
在这里插入图片描述
输入正确的系列号,调试到403329处查看:
在这里插入图片描述

可以看到不发生跳转,顺序执行直接弹出正确的窗口:
在这里插入图片描述


总结

函数加密方法的整理:

  1. 从给定的Name字符串中逐个读取字符(4次)
  2. 将字符转换为数字(ASCII)
  3. 向变换后的数字加64
  4. 再次将数字转换为字符
  5. 连接变换后的字符

整个函数执行流程:

  1. 输入Name与序列号,点击check,触发事件处理程序
  2. 根据输入Name加密生成正确的序列号
  3. 输入序列号与正确序列号比较,判断是否正确
  4. 条件转移,若错误弹出错误窗口,正确弹出正确窗口。

总结前文调试的过程:

  • 通过字符串检索查找到弹出错误消息框的代码位置。
  • 向上查找定位到条件转移语句的位置:该处进行序列号的比较,此处可以在内存中定位到两个序列号的存储位置。
  • 继续向上查找,找到栈帧代码push ebp;mov ebp,esp;开始执行函数就会形成栈帧,由此定位函数开始部分,即check按钮的事件处理程序
  • 预测代码接下来将要执行的是:读取name字符串、启动循环对字符加密。
  • 向下查找call函数,多次调试定位到向后第4个call函数读取name字符串。同时定位到字符串存储位置。
  • 继续调试,遇到一系列循环语句,发现一共循环四次。进一步分析循环部分,即加密部分执行的代码,找到加密方法。
  • 调试发现加密部分执行的操作是:依此读取name字符串字符,将其转化为ASCII,与密钥64相加,结果再次转换为UNICODE,并将其与之前的加密结果拼接。**调试过程中发现栈被分为三个区域:ECX是用于保存结果的缓冲区,EAX存储密钥,EDX存放Name字母的ASCII码。**后续拼接时,原本的ECX值赋给EAX,新的EAX存放着最终拼接的结果。EDX存放着每次加密生成的新的字符,后面紧跟着以前拼接的结果。
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值