*文章原创作者:mintancy,本文 未经许可禁止转载
萌新第一次学习逆向,所以在这里也算总结和记录,如有错误之处还请指出。
分析文件是exe格式,附带一个dll链接文件。要求得到解密后的三个key,exe主程序负责key1的解密,dll文件负责key2和key3的解密。文件可以在链接处下载(链接: https://pan.baidu.com/s/1eS22yH0 密码:wtne)。
ida(V6.8,吾爱破解网站下载)静态分析+ollydbg(V1.10,官网下载)动态分析。
本人是mac系统,然后安装了虚拟机:vmware+windows xp sp3。
那么,我们进入正题吧。
将程序crackme.exe在ida中打开,可以看到对应的汇编语言。
然后看到左侧列表有个main函数,直接双击,找到汇编程序的main入口处,f2加断点高亮显示:
此时,可以直接在高亮处按F5反汇编得到对应的C代码:
代码前面部分就是各种初始化,变量声明之类的,没什么特别。继续往下看,看到”&unk_40FA58″看起来是调用了数据段的某个地址的内容,于是双击看看是什么:
哟,很明显的字符串,会不会是key1呢?于是输入key1,显示错误。然后继续看看程序中使用这串字符串的位置:
可以看到,在程序找到字符串之后,接下来就是具体的解密部分,其实可以看到很明显的一句汇编语言:
.text:00401128 dec edi
寄存器edi中存储的是加密字符,执行的计算是减一,也可以理解是向前移位的计算。
到这一步,再打开ollydbg进行动态分析,验证自己的猜想。其实直接分析汇编语言也可以得到答案,只不过觉得执行程序得到结果更形象。
在之前还是简单地介绍一下od的界面和在之后用到的界面(其实也就是常用的几个界面):
汇编代码窗口和寄存器窗口是两个常用窗口,一个确定程序运行到了哪一步,一个确定需要的内容在内存的存储位置。
对照在ida找到的main函数,直接右键选择“Go To”跳到main函数位置设置断点(在没有ida静态分析不知道主函数位置的网上有各种方法):
然后反复调试程序设置合适的断点,可以确定汇编代码的功能并得到计算后的字符串:
这一段代码很短,可以一步一步调试(F7),然后注意oa右边的寄存器窗口,截图中解释的地址就是根据相应寄存器存储内容确定的。
解密算法执行结束之后到内存地址窗口找到存储解密字符串的位置[00393F68],可以看到一串熟悉的字符串亲切地躺在这里:
此时不太确定是不是key1,然后到程序上输入:
YEAH!!第一个key1找到啦!萌新特别开心!
然后一鼓作气,继续解决key2和key3。这个时候有点陷入汇编代码的茫茫大海中,直接在od中使用f7动态调试,没有再对照ida中的代码。本意是想分析一下后面的执行顺序,看看内存的变化。
f7太慢,然后又按f8。结果执行又太快,还没注意到具体解密算法,就看到右下角栈区域出现了一个疑似key的字符串。找到对应的内存位置,上下滚动一下发现有两个字符串:
然后拿到程序中试试:
踏破铁鞋无觅处,得来全不费工夫啊!
但是,本着认真学习的态度,我决定再回过头看看解密这两个字符串使用了什么算法。
od使用 alt+f2 重新载入程序。
记住key2在内存中的写入位置:003B3D78,然后重新载入程序,设置内存写入断点,看看是什么时候在经历了什么步骤之后写入的。结果发现并没有这个位置,有可能是载入动态链接库之后才赋予的地址:
继续分析c代码,发现忽略了这两个地址引用:
就按照上一个key的方法再次找到汇编语言中使用这两字符串的地方:
同理在od中定位到[0040123D]然后继续在od中F8动态调试,找到合适的断点:
进入key2解密函数,此时已载入动态链接库。再查找内存地址003B3D78,出现了该地址内容。可以看到该内存地址还没有被初始化,在此位置设置内存写入断点(关于设置内存写入断点的细节可以参见链接:http://bbs.pediy.com/thread-187118.htm):
然后F9执行之后在一行代码停下,可以看到此时key已经开始写入:
此时再使用f7可以单步执行溯源找到解密算法:
可得:key2的解密是让字符串与77进行亦或(xor)运算。
同理得到key3的解密算法,key3的内存写入地址是003B3DC0:
key3的解密是让字符串和0F做与运算然后左移4位。
等等!你以为这样就结束了吗?!别走,快回来!
并没有,让我们再回过头来看看主程序的代码:
看到这个sub_401000()木有?程序调用了动态库的decode函数对字符串解密得到了key2和key3,也就是v21和v23,v24、v26分别是用户输入的key2和key3。然后它们分别经过sub_401000()函数的洗礼(加密),来看看这个函数到底在干什么:
没办法,萌新看到这一串C语言就觉得头疼,还是使用oa调试看看吧。这段代码可以当作是再次加密,同时将加密后字符串存入[003929A0]开头的地址中:
这段代码中,需要理解的就是imul操作符及后面的代码对字符进行的操作。在这里使用了IMUL单操作数的使用方法:
imul AL;
意思就是:
AL=AL*AL;
而后面的IMUL CL意思是:
AL=AL*CL;
综上对单个字符的操作描述为:取字符的十六进制数(寄存器EAX低八位即AL),平方后取低八位结果(存入AL中),用这结果再加上循环次数(CL)的平方得到加密字符存入BL中,再写入内存。
用python语言表示为:
#在寄存器中字符用16进制表示,在这里假设字符a也用16进制表示;
#计数君ECX低八位CL用t表示:
a = int(‘a’, 16); //计算的时候将a转化为10进制数
a = a * a; //自乘得到结果
a = hex(a – (a/255*255)); //取乘法结果的低八位再转化为16进制数
a = a +hex( ‘t*t’); //然后再与循环次数的平方相加得到最后结果
所以最后的结论就是,将存储在程序中的key2和key3解密并加密得到key22和key33,然后用户输入usr_key2和usr_key3用同样的加密算法加密得到usr_key22和usr_key33。最后比较key22和usr_key22,key33和usr_key33是否相等。
用户输入前程序做了什么:
1. key1向前移一位解密得key11。
程序输入key之后程序做了什么:
1. 输入usr_key1正确之后生成中间dll文件,程序载入两个dll文件;
2. key2与77做XOR运算再加密得key22;
3. key3与0F做AND运算然后左移4位再加密得key33;
4. 用户输入usr_key2,usr_key3;
5. usr_key2加密得usr_key22,usr_key3加密得usr_key33
6. if (key22 == usr_key22 && key33 == usr_key33),you win!
分析结束,撒花撒花~~
*文章原创作者:mintancy,本文 未经许可禁止转载