好久没有更新博客了,发个之前做的一个crackme
crackme过程
观察观察程序
打开程序
随便输入点看看
猜测输入name,根据程序中的加密算法得到serial。找到name对应的serial即可成功破解
丢exeinfo,无壳,32位
调试(VB文件的一些了解
1)执行程序后,在EP代码中首先要做的就是调用VB引擎的主函数(ThuRTMain()),EP的地址为 00401238。
2)此处的PUSH 00401E14命令用来把**RT_MainStruct结构体的地址(401E14)**压进栈中。
3)然后0040123D地址处的命令 CALL 00401232命令调用401232地址处的JMP DWORD PTR DS:[4010A0]指令。
4)该JMP指令会跳转至VB引擎的主函数ThunRTMain()(前面压进栈的401E14的值作为函数的参数)
以上三行代码是VB文件的全部启动代码
间接调用
0040123D地址处的CALL 00401232命令用于调用ThuRTMain()函数,这里使用了较为特殊的技法。不是直接跳转到MSVBVM60.dll里的ThuRTMain()函数,而是通过中间00401232地址处的JMP命令跳转。
这就是VC++,VB编译器中常用的间接调用法
4010A0地址是IAT(导入地址表)区域,包含则会MSVBVM60.ThuRTMain()函数的实际地址
ThunRTMain()函数
上图显示了ThuRTMain()代码的开始部分,可以注意到地址完全不同了。这是MSVBVM60.dll模板的地址区域。换言之,我们分析的不是程序代码,而是VB引擎代码。只是简单了解一下哈哈哈,这就马上进入正题
分析crackme
说实话刚丢进dbg的时候,面对一大堆的汇编我也不懂啊(bushi
刚开始肯定是难事,作为小白开始的时候就可以先利用这个小程序的错误消息框和字符串这个切入点
检索字符串
xdbg检索字符串:运行程序,右键→搜素→字符串
这个就像是ida检索字符串捏
往上找找
其实到这里就很好理解了
使用某种算法,通过比较我们输入的serial和根据name及加密算法得到的serial,代码分真代码和假代码(也就是通过条件判断,serial相同则继续向下执行真代码(即输出你成功的消息),不同就直接跳转到00403408处的假代码(说我们输入错误
这也就是为什么我们要选它的提示错误消息框作为切入点的原因,它会进行判断决定跳转哪一部分的代码
(调用00403329地址的__vbaVarTstEq()函数,比较(test命令),返回值(AX),由403332地址判断是真还是假)
调用__vbaVarTstEq()函数比较字符串后,执行TEST命令(TEST命令为逻辑比较,仅改变EFLAGS寄存器而不改变操作数的值)检测AX的值是否为0,再由JE指令决定执行哪一部分代码。
相关知识点
查找字符串地址
00403329地址处调用&__vbaBarTstEq()函数,其上面的两条push指令为比较函数的参数。
那么**SS:[ebp-0x44]**等等又说明什么了吗?
SS是栈段,EBP是基址指针寄存器,换而言之,SS:[ebp-0x44]是栈内的地址,它恰好又是函数中声明的局部对象的地址(局部对象存储在栈内)
那思路就变得简单明了,在执行比较函数之前,会先将根据我们输入的Name加密算的serial和我们自己输入的serial压进栈,那么我们只需观察栈的窗口即可。
EAX的值–>存储我们输入的serial的地址
EDX的值–>存储正确的serial的地址
得到正确的serial。
下面就详细分析一下加密算法
生成serial的算法
很显然条件判断代码属于某个函数。该函数可能就是check按钮的事件处理程序。原因在于选择Check按钮后,该函数会被调用执行,且含有用户代码来弹出成功/失败的消息框
查看函数开始的地方,即check按钮的事件处理程序,典型的栈帧,在这设置断点开始调试
读取Name字符串的代码
它的作用是将对象变量指向内存中的对象
可以看到name字符串(以字符串对象形式)存储到[ebp-88]地址
加密循环
上面循环的动作原理,就像是链表中使用next指针引用下一个元素一样,__vbaVarForInit(),__vbaVarForNext()可以让逆向分析人员在字符串对象中逐个引用字符。并且设置loop count(EBX)使其指定次数运转循环
(实测仅使用接收的Name字符串中的前4个字符。在代码内检查字符串的长度,若少于4个字符,就会弹出错误消息框)
加密方法
我输入的Name字符串为"naruku"
查看栈窗口
查看相关内存
EDX:
EAX:
ECX:
运行以下(蓝色背景)函数
将加密后的值存储到ECX寄存器所指的缓冲区
此时的栈:EDX→D2=210(ord(n)+ 100(16进制:64))
D2就是serial的前两个字符
下面代码将D2转换为D
调用函数后,查看EAX所指的缓冲区,生成了 'D2’字符串
查看实际字符串的地址(00552E3C)。数字D2变为Unicode字符串D2
下面的代码将生成的字符串连接起来:
最后不断循环,生成以下的序列号
serial=old(“D2C5D6”)+add(“C9”)=“D2C5D6D9”
加密方法整理如下:
1)从给定的Name字符串前端逐一读取字符(共四次)
2)将字符转换为数字(ascii代码)
3)向变换后的数字加上密钥64
4)再次将数字转换为字符
5)连接变换后的字符
若有错误,欢迎指出更正!!
参考书籍:逆向工程核心原理,加密与解密