逆向分析abex' crackme #2

这里接着逆向分析abex' crackme #2。

运行程序,要求填入Name和Serial:


随意填写后点击Check:


可以看到,显示这个序列号错误。

由此可知,该程序是通过获取用户输入的Name字符串,再通过加密的方式加密Name字符生成相应的Serial值。这里可以明确一下目标,即逆向分析该加密算法具体是如何实现的。


OllyDbg打开该exe文件:


MSVBVM60.dll为VB专用引擎。

EP代码前面的代码为IAT区域,是一些调用VB引擎的函数,如ThunRTMain()。接着PUSH指令将RT_MainStruct结构体的地址作为ThunRTMain()函数的参数压入栈,再执行CALL指令即调用ThunRTMain()函数。这里用到的是间接调用的方法调用ThunRTMain()函数,是VC++和VB编译器中常用的间接调用方法。

这里查看一下RT_MainStruct结构体的内容:


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

接着进入ThunRTMain()函数继续调试:


这里是MSVBVM60.dll模块的地址区域,是VB引擎代码而不是程序代码,因而不用对其进行分析。

回想一下,可以利用字符串检索法进行查找:


找到相关字符串点击进去查看,找到条件分支语句:


调用__vbaVarTstEq()函数比较字符串后,执行TEST命令(TEST命令为逻辑比较,仅改变EFLAGS寄存器而不改变操作数的值)检测AX的值是否为0,再由JE指令决定执行那部分代码。

由此,可以查找__vbaVarTstEq()函数的参数,即需要比较的两个字符串:


设置断点运行到该push指令处,查看EAX和EDX两个寄存器保存的内容:


这里,VB字符串使用可变长度的字符串类型,因而显示16字节大小的数据而非直接显示字符串。其中EAX保存的值为用户输入的Serial值“123456”,而EDX保存的值为用户输入的Name值经过加密后的实际的Serial值、此处为“B7AFAD95”。将该值作为Name为“SKI12”的Serial值输入:


由此可知,当输入Name为“SKI12”,Serial为“B7AFAD95”时即可显示正确的信息。也就是说,程序是根据输入的Name来生成特定的Serial值。由此便需要查看生成Serial的算法。

先查找程序开始的位置,往上翻滚,找到栈帧出现的位置即可:


在栈帧处设置断点便于调试。其中,VB文件的函数之间存在着NOP指令。

由之前的结果显示可知,程序是先读取输入的Name字符串,再对字符进行加密以生成Serial序列号。


接着找到读取Name字符串的代码。

读取输入的字符串,必然有调用的CALL指令,从断点开始逐行调试,当调试执行完00402FB6地址的命令时,可以看到寄存器EAX的值为输入的Name字符串,即该地址的指令用于将输入的字符串地址保存到EAX寄存器中:



由此可以推测出,[EBP-88]变量即为字符串变量,其上的00402F98地址的CALL指令即为获取Name字符串的调用指令。

下面重新调试验证一下,在栈窗口右键>Address>Relative to EBP,然后一直观察[EBP-88]的值,当执行了00402F98地址的CALL指令后,该值变为输入的Name字符串“SKI12”:


至此,确定了获取输入Name字符串的指令地址为00402F98。


接着调试下去即为加密循环。

加密循环主要查找关键的循环语句,这里为__vbaVarForInit()和__vbaVarForNext()函数:



接着调试加密方法。

从对字符串进行提取处理的过程出发,在上述循环中寻找如__vbaStrVarVal()等的字符串相关处理函数:


在调用该函数的CALL指令处设置断点,运行完该指令后,查看EAX寄存器保存的地址所保存的内容:


可以看到,其为输入的Name字符串的第一个字符“S”。

接着rtcAnsiValueBstr()函数将“S”字符进行Unicode>ASCII的变换。已知’S’=53。

调试时需要注意相关PUSH指令将寄存器的值存入栈。调试至push edx指令时可以查看得到EDX寄存器保存的地址所保存的内容确实是“S”的ASCII值53:


接着调试完0040323D地址的指令,查看栈,按程序执行的一样依次将EDX、ECX、EAX寄存器的值压入栈,其中EDX的值所指的地址保存着“S”的ASCII值53:


转到Dump窗口,右键>Long>Address with ASCII dump:


可以看到EAX寄存器保存的“d”实际为密钥(64),而ECX寄存器保存内容的区域是用于保存结果的缓冲区。

接下来的CALL指令即是调用__vbaVarAdd()函数将加密后的值保存到ECX寄存器所指的缓冲区中,加密后的值为53+64=B7,执行完该CALL指令后再次查看ECX寄存器所指区域是否存在“B7”:



接着下面的代码将ASCII数值转换为Unicode字符:


rtcHexVarFromVar()函数将相应ASCII数值转换为Unicode字符。上述代码将数字B7转换为字符“B7”(Unicode)。执行完该CALL指令后,查看EAX寄存器所指的缓冲区:


可以看到ASCII数值转换为字符“B7”。到该字符串实际的地址查看:



接着,下面的代码将生成的字符串连接起来,注意到__vbaVarCat()函数,其作用是将字符串进行连接:


调试至该CALL指令处再查看几个寄存器保存的内容:


再F9执行一次循环查看:


可以看到EAX和EDX寄存器保存了第二个字符,而ECX寄存器保存了上一次的字符。

依次运行至结尾:



可以推测出,加密后的字符只取了前面前四个加密字符相连接作为Serial序列号。

至此,可以得出上述加密方法小结:

1、从输入的Name字符串前端逐一读取字符(共4次);

2、将字符转换为数字(Unicode>ASCII);

3、向变换后的数字加64;

4、将数字转换为字符(ASCII>Unicode);

5、连接变换后的字符即为Serial序列号。


分析至此,加密算法的具体实现已了解清楚,为了简便地实现Serial值的计算,可以编写简易的脚本对任意输入的Name字符串进行Serial序列号的生成:

# coding=utf-8
# abex' crackme #2

text = raw_input("[*]Please input the Name:")
text_new = ""
count = 1
for s in text:
	count += 1
	if count > 5:
		break
	b = ord(s)
	d = int(0x64)
	num = b + d
	hex_num = hex(num).upper()
	text_new += hex_num.split('X')[1]

print "\n[*]The Serial of the input name is:", text_new
print "\n"




猜你喜欢

转载自blog.csdn.net/ski_12/article/details/80546988