冰琥珀 发表于 2016-3-27 02:11:16

一个Crackme的逆向分析

===================================

          红客联盟&Milw0rm有奖活动

===================================

        看90办个活动不容易,发个帖子表示支持.
        这只是个简单的CrackMe,不加壳,属于那种只要会用OD和IDA就能分析的东西,但是既然把这个过程发出来,表示它还是有一点值得学习的东西在里面.
        废话就不多说了,先运行一遍软件,发现直接弹出个“注册失败”的对话框,然后用IDA加载,发现这个CM代码不多,而“注册失败”字符差串则是保存在Text中,选择Text,在键盘上按下“x”键,在弹出来的交叉参考对话框发现有一处代码修改了这个Text的值,如下图:

        于是想到这里可能是把“注册失败”改为“注册成功”。先做个测试,用OD加载软件,按下“ctrl”+“G”,输入0x00111045(这个地址要根据实际情况来确定,这里这个CrackMe的基地址是0x00111000),按下回车,来到下图所示的位置:

        说明:上图是IDA中的位置,在OD中是0x00111045
        这里调用CreateFile函数来打开一个文件,因为目前没有这个文件,所以这个函数注定会失败,所以那个jz就会跳转.于是就在OD中把(IDA中反汇编出来的是jz,OD中是je)的跳转地址改为0x001111fd,这样当这个函数调用失败后,程序就直来跳转到修改注册结果的地方,如下图所示:

        上图所示的是程序中“注册失败”改为“注册成功”的地方,在这里我们看到是用edx+eax+0x23e525dc,然后将这个结果写入到字符串中,我们先猜一下这个结果可能就是字符串“成功”的值,通过测试可知“成功”的值为0xa6b9c9b3,我们就先把ecx的值改为0xa6b9c9b3,把edx的值改为4(“注册”占4个字节),然后按F8,发现程序跑到这里就跑飞了,说明现在这个内存是不能改写的。不过既然要注册成功,那么这个内存就应该是要能改写的,这就说明前面有地方要修改这个内存地址的属性。
根据IDA中Text的交叉参考可知,除了这里和MessageBox用到了Text之外,还有一处地方用到这个地址,如下图:

        在这里把Text当成一个参数传进函数中,而这个函数的参数显然不是MessageBox,这时就想到这里把Text的属性改了,这样后面才能改写Text。而能修改内存属性的API,VirtualProtect就跳出来了,而且上面这个函数的参数和VirtualProtect一样,基本上能确定这里就是调用了VirtualProtect。只不过这里是一个函数指针,这个指针由上面得来:

        这里需要确定这个模块名和函数名的来源,继续向上看发现:


        在往上看发现这个buffer中的数据是从一个名为“CrackMeA.key”的文件中读取出来的:

        这样我们就可以知道,这个CM需要个注册文件,而这个注册文件中的第一个字符串是一个模块名,第二个字符串是一个函数名。然后继续分析接下来需要的一些数据。

        上图显示的是注册成功要用到的一些数据, 在这里把数据都取出来了之后,接下来就是校验了。先判断生成“成功”的值是否为空,通过上图可知,除了这两个值外,校验中还用到了另外两个值,所以在接下来的程序中,就先要判断这四个值是否为空,若是有一个为空则注册失败,代码如下:

        上面这里检测完了之后,栈中的数据如下图所示:

        接下来再进行一次压栈操作,代码如下:

        这时栈中的数据如下图:

        然后是注册验证,代码如下:
00E111AE|.D9CF          fxch st(7)                               ;交换st0与st7的值,这时st0为第二组值的长度
00E111B0|.DECD          fmulp st(5),st                           ;fLen1 * fLen2,结果保存在st4
00E111B2|.D9CB          fxch st(3)
00E111B4|.DECD          fmulp st(5),st                           ;fStr1 * fStr2,结果保存在st4,之前st4的值上移到st3
00E111B6|.D9CB          fxch st(3)
00E111B8|.DEC4          faddp st(4),st                           ;fStr1 * fStr2 + fLen1 * fLen2,结果保存在st3
00E111BA|.D9C4          fld st(4)                              ;压入fStr1
00E111BC|.DECD          fmulp st(5),st                           ;st5中的值也是fStr1,所以这里是fStr1 * fStr1,结果保存在st4
00E111BE|.D9C1          fld st(1)                              ;将fLen1压栈
00E111C0|.DECA          fmulp st(2),st                           ;st2也是fLen1,这里是fLen1 * fLen1,结果保存在st1
00E111C2|.D9CC          fxch st(4)
00E111C4|.DEC1          faddp st(1),st                           ;这里是fStr1 * fStr1 + fLen1 * fLen1
00E111C6|.D9C1          fld st(1)
00E111C8|.DECA          fmulp st(2),st                           ;这里是fStr2 * fStr2
00E111CA|.D9C3          fld st(3)
00E111CC|.DECC          fmulp st(4),st                           ;这里是fLen2 * fLen2
00E111CE|.D9C9          fxch st(1)
00E111D0|.DEC3          faddp st(3),st                           ;fStr2 * fStr2 + fLen2 * fLen2
00E111D2|.DECA          fmulp st(2),st                           ;(fStr1 * fStr1 + fLen1 * fLen1) * (fStr2 * fStr2 + fLen2 * fLen2)
00E111D4|.DCC8          fmul st,st                               ;(fStr1 * fStr2 + fLen1 * fLen2) * (fStr1 * fStr2 + fLen1 * fLen2)
00E111D6|.DEE9          fsubp st(1),st                           ;(fStr1 * fStr1 + fLen1 * fLen1) * (fStr2 * fStr2 + fLen2 * fLen2) - (fStr1 * fStr2 + fLen1 * fLen2) * (fStr1 * fStr2 + fLen1 * fLen2)
00E111D8|.DC25 C0C2E100 fsub qword ptr ds:               ;将上面得到的结果减去1,这里和接下去的这几行代码是判断上面的结果是否为1
00E111DE|.D95C24 10   fstp dword ptr ss:
00E111E2|.D94424 10   fld dword ptr ss:
00E111E6|.D9E1          fabs
00E111E8|.D95C24 10   fstp dword ptr ss:
00E111EC|.D94424 10   fld dword ptr ss:
00E111F0|.DC1D B8C2E100 fcomp qword ptr ds:
00E111F6|.DFE0          fstsw ax
00E111F8|.F6C4 05       test ah,5
00E111FB|.7A 20         jpe short crackmea.00E1121D            ;结果不为1则跳转
00E111FD|.8B55 00       mov edx,dword ptr ss:
00E11200|.8B07          mov eax,dword ptr ds:
00E11202|.8D8C02 DC25E5>lea ecx,dword ptr ds:;生成“成功”字符串
00E11209|.8B5424 20   mov edx,dword ptr ss:
00E1120D|.898A 9CC2E100 mov dword ptr ds:,ecx      ;用“成功”替换“失败”


        上面这段代码中,设第一个值为发fStr1,其长度为fLen1,第二个值为fStr2,其长度为fLen2。通过上面的计算,我们可以总结出这么一个方程:
(fStr1^2 + fLen1^2)*(fStr2^2 + fLen2^2) – (fStr1*fStr2 + fLen1*fLen2)^2 = 1……(1)
fStr1 + fStr2 = 0xa6b9c9b3 - 0x23e525dc……(2)
        到这里,第一个CrackMe的分析也就结束了,要想做注册机,则在注册算法中解出上面的方程组,然后把得到的数据填入CrackMeA.key文件中的响应位置即可.
        这个CrackMe破解的关键是能不能想到用VirtualProtect这个函数来修改内存属性,从而达到破解的目的.附件中有我逆出来的代码和编写好的注册机代码,以及注册文件和原文件,想练手的同学可以玩玩.
       

wuyan 发表于 2016-3-27 08:12:57

发威了,和我抢奖品啊

冰琥珀 发表于 2016-3-27 09:21:21

wuyan 发表于 2016-3-27 08:12
发威了,和我抢奖品啊

不能让你那么轻松的拿到

sladjfksld 发表于 2016-3-27 18:50:04

虽然我看不懂,但是为了呼应冰琥珀大牛,我决定冒个泡!

浮尘 发表于 2016-3-27 21:26:56

sladjfksld 发表于 2016-3-27 18:50
虽然我看不懂,但是为了呼应冰琥珀大牛,我决定冒个泡!

:lol我也看不懂

小圈圈 发表于 2016-3-28 10:16:57

冰琥珀 发表于 2016-3-27 09:21
不能让你那么轻松的拿到

抢得好!!!

小圈圈 发表于 2016-3-28 10:30:56

本帖最后由 小圈圈 于 2016-3-28 22:51 编辑

我想问问怎样才能防止VirtualProtect函数被修改或者删除
我现在有一个思路是母程序释放一个子保护程序互相监视VirtualProtect函数的内存,一个程序进程被结束后立即被另一个程序重启
即所谓的进程守护

//话说你这么破解让我等广大程序猿情何以堪( ⊙ o ⊙ )啊!

小圈圈 发表于 2016-3-28 10:33:56

还有说有些软件会搜索OD的进程防破解,是因为动态反汇编需要运行?
那搜索C32ASM这样的静态反汇编软件是不是没什么意义?

冰琥珀 发表于 2016-3-28 11:35:07

小圈圈 发表于 2016-3-28 10:30
我想问问怎样才能防止VirtualProtect函数被修改或者删除
我现在有一个思路是母程序释放一个子保护程序互相 ...

这样没用的,VirtualProtect在kernel.dll中,每个程序都会把它加载到自己的进程空间去,你监控的也只是你自己程序进程空间中的VirtualProtect,对其他的进程没影响
要实现监控,只能注入一个线程到目标进程中,然后在线程里对VirtualProtect进行监控

冰琥珀 发表于 2016-3-28 12:01:38

小圈圈 发表于 2016-3-28 10:33
还有说有些软件会搜索OD的进程防破解,是因为动态反汇编需要运行?
那搜索C32ASM这样的静态反汇编软件是不 ...

是的,OD就是动态调试,简单点的反调试,你可以对当前进程的父进程进行判断,如果父进程不是explorer.exe,就可以认为是被别的进程加载启动的
页: [1]
查看完整版本: 一个Crackme的逆向分析