吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9523|回复: 22
收起左侧

[第四题] 【吾爱破解2014CrackMe大赛】【第四组】

[复制链接]
currwin 发表于 2014-10-25 20:27
本帖最后由 Kido 于 2014-10-26 20:20 编辑

      如果这个cm我有能力分析出算法的话,我还是希望能够把算法弄出来的。不过本人能力还是不够。唉,nooby牛之所以会被称为大牛,大概就是因为这些吧。
      这个cm被编译为多个语言,既然如此,当然要选择最为简单的入手,去进行切入。这里我选择了APK的程序,当然,选择java的也是可以的。用解压软件解压APK后可以找到KeyGenMe.html文件,里面含有完整的keygen源代码。。。嘻嘻,开心了?且慢,先别那么高兴,既然大牛能够拿得出手,自然不会是那么简单的。
      简单的分析了一下,发现这个程序是使用数据流来进行驱动的。数据代码被保存在数据 var mam数组中:var mem = new Array(0, 0,...),然后下面的驱动操作就只有3种:
      具体如下
[JavaScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function vrun(input_buffer) {
        if (i == 0) {
                input_buffer = '';
        }
        input_buffer = input_buffer.split('');
        input_buffer.reverse();
        var output_buffer = '';
        while (i >= 0) {
                var a = mem[i];
                var b = mem[i + 1];
                var c = mem[i + 2];
                if (a < 0) {
                        if (input_buffer.length == 0) {
                                break;
                        }
                        mem[b] += input_buffer.pop().charCodeAt(0);
                        i += 3;
                } else if (b < 0) {
                        output_buffer += String.fromCharCode(mem[a]);
                        i += 3;
                } else {
                        if (mem[a] == undefined) {
                                mem[a] = 0;
                        }
                        if (mem[b] == undefined) {
                                mem[b] = 0;
                        }
                        mem[b] -= mem[a];
                        if (mem[b] < -2147483648) {
                                mem[b] += 4294967296;
                        } else if (mem[b] > 2147483647) {
                                mem[b] -= 4294967296;
                        }
                        if (mem[b] <= 0) {
                                i = c;
                        } else {
                                i += 3;
                        }
                } if (i < 0) {
                        output_buffer += '\n(press ENTER to reset)\n';
                        break;
                }
        }
        return output_buffer;
}

      分别是 a < 0 , b < 0, 以及两者都不是。核心的算法就是数据的运算:
     mem -= mem[a]  
     jmp c。
      利用这个运算+data数据,就代替了mov,retn,call,jmp等等的功能。很厉害,这里我看到了新的vm思路了,而且这样进行vm的话,连所谓的vm分派啊,vm_pop, vm_retn 等等都找不到了,因为这些都完全成为数据流驱动的代码了。于是,你会发现,你将会在vRun这个函数里面反复的走来走去,弄得一塌糊涂。
      弄不了了。。。弄不来了,难道就要这么放弃么?总不能就这样被大牛鄙视吧,就算要放弃也得努力一番,千万不能让大牛们看不起了。
      首先来猜一下作者的想法吧,首先,他做了一个简单的小cm,然后利用新的vm程序来对这段代码进行加密,转为为数据流,最后把这段数据流复制进程序去,并且添加数据的处理方法。这样就完了。
      这样简单的事情,我也可以做到,因此,我写了一个程序,来驱动他的数据,试图进行跟踪调试,果然的。。。调试起来变成了好几万步了。这样不行,不行的。
      再换一个想法,既然是数据流,用来解析的话又是按照一定的方法的,我是不是可以弄个小程序来对这段数据进行翻译?这下不,一下子代码就清晰起来了:
[Asm] 纯文本查看 复制代码
1
2
3
4
5
04098:   mem[ 4105 ] = 0       
04101:   mem[ 4105 ] -= mem[ 25954 ]
04104:   mem[ 0 ] -= mem[ 4107 ]
        if(mem[ 0 ] <= 0        jmp 13241
04108:   mem[ 25942 ] = 0

    上面我只列出了一部分数据,关键是,这样对数据进行分析以后,突破掉就瞬间找到了。首先,这些数据流是分成3个部分的:
0~3:            选择入口点。
3~259 :       为堆栈数据部分(?)
259 ~ 25715 为程序代码部分
25715 ~ End 为数据部分
     也就是说,就算转为数据了,程序的大体结构还是不变的。有堆栈数据,有代码数据,有数据。此时,翻看数据部分,可以看到这个字符串数据:
地址     内容
25720  开头信息 xxxxCrack Me by ......之类的
25858 Correct!
25868 Enter your code:
25886 Invalid length!
25904 Invalid number!
25921 Incorrect!

     以及存放这些字符串数据的指针:
地址     值                                  pStr                              调用地方    状态
25936: 25868                                //Enter your code        ->04568
25937: 25887                           //Invalid length!         ->04792        --> Patch
25938: 25904                           //Invalid number!      ->05421        --> Patch
25939: 25921                           //Incorrect!               ->06719        --> Patch
25940: 25858                           //Correct!                  ->06786
25941: 25720                           //开头信息                ->01404

      有了这些就容易进行分析了,这里调用这些字符串指针的地方我都找出来了,这里就直接跳到04792的地方去吧:
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
04792:   mem[ 25950 ] -= mem[ 25937 ]                                // Get pStr: Invalid Length
04795:   mem[ 0 ] -= mem[ 25950 ]                               // push Addr, 这里为mem[0]是因为地址需要动态计算,也就是说,这里的地址为堆栈地址
04798:   mem[ 25950 ] = 0       
04801:   mem[ 25954 ] -= mem[ 25951 ]                                //更新栈顶?
04804:   mem[ 4816 ] = 0       
04807:   mem[ 4816 ] -= mem[ 25954 ]                                //取堆栈地址?
04810:   mem[ 4817 ] = 0       
04813:   mem[ 4817 ] -= mem[ 25954 ]                                //...
04816:   mem[ 0 ] = 0       
04819:   mem[ 4826 ] = 0       
04822:   mem[ 4826 ] -= mem[ 25954 ]                       
04825:   mem[ 0 ] -= mem[ 4828 ]
        if(mem[ 0 ] <= 0        jmp 07078                     //call 子程序,7078,输出程序,用来设置outbuf的。

        大概意思应该就是这样,可以看到,这样v法还是挺有趣的,那些push,call都被mem -= mem[a] 来进行替代了,并且内部维护了一个堆栈,虽然还是遵循调用的规定,但是隐蔽性大大的提高了。看到这,我只能说一句服了。
      既然有call,那么就可以往上找,去找能够跳过这个call的跳转,幸好,就在附近:
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
04735:   mem[ 25943 ] = 0       
04738:   mem[ 25950 ] -= mem[ 25944 ]
04741:   mem[ 25943 ] -= mem[ 25950 ]
04744:   mem[ 25943 ] -= mem[ 25836 ]
        if(mem[ 25943 ] <= 0        jmp 04750
04747:   mem[ 25950 ] = 0        jmp 04765
04750:   mem[ 25950 ] = 0       
04753:   mem[ 25950 ] -= mem[ 25943 ]
        if(mem[ 25950 ] <= 0        jmp 04765
04756:   mem[ 25950 ] = 0       
04759:   mem[ 25943 ] = 0       
04762:   mem[ 25943 ] -= mem[ 25949 ]
04765:   mem[ 25943 ] -= mem[ 25950 ]
        if(mem[ 25943 ] <= 0        jmp 04835                        //jmp Next, 跳过Invalid Length的关键跳
04768:   mem[ 25954 ] -= mem[ 25951 ]                                     //判断长度不满20

     这里有很多jmp,但是看清楚了,只有最后一个jmp才是一个大跳,其他都是小型的跳转。我认为这一段都是由一个条件跳转变化过来的,根据没有。。。只是猜想,估计至少有一半是对的。有关键跳转,自然就应该让它跳过去,这里只需要把比较的2个数,mem[25943],mem[25950],修改为同样的两个数,就可以了,这里我选择了修改为 mem[25943] 与 mem[25943] 的比较,因为mem[25950]有其他重要的意义,哈哈。所以具体的操作就是,搜索 655E,6557,12E3 (25950,25943,04835)  改为 6557,6557,12E3
      其他两个:Invalid Number, Incorrect也是同样的修改了,就不一一指出了,最后给出一下修改点:
                                       搜索                                                                替换
1.:Patch Invalid Length: 25950,25943,04835  :655E,6557,12E3   -> 6557,6557,12E3
2.:Patch Invalid Number: 25950,25944,05464  :655E, 6558,1558   -> 6558,6558,1558
3.:Patch Incorrect :     25950,25943,06762  :655E, 6557,1A6A   -> 6557,6557,1A6A
          correct:jmp 6762 (1A6A):  


       扩展:有没有更加好的方法去进行分析呢?既然数据有了,反汇编出来的结果也有了,那么是不是稍作修改,就能在vs上面进行源码级别的调试呢?我估计是可以的,当然,其中一些细节的处理是不会那么简单的。哈哈。目标是直接调试着几千句代码,估计这样才能找出所谓的Handle。

        最后说一个很笨的方法吧,一开始因为是javascript的代码,手头上没有合适的调试工具,所以我就直接用Google的调试功能了。。。结果发现,有点厉害,简直就是发现了新大陆一样。哈哈,附上一个截图,来爽一下。
UZA[PS1X`6LH69WJS)%6(C8.jpg
      最后,还是等评委的点评吧,弄了这么久,也就只有这么一点的成果。估计大大们都已经keygen出来了,先膜拜一下写出cm来的nooby牛,然后膜拜写出keygen的大大。最后, 求分析。谢谢

PS: 附件为爆破后的程序以及分析的程序的源代码,求指点。


Crack4 and Disa.zip

172.02 KB, 下载次数: 61

免费评分

参与人数 1热心值 +1 收起 理由
a070458 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| currwin 发表于 2014-10-25 20:27
糟糕,忘记设置权限了。。。。求帮忙设置一下
a070458 发表于 2014-10-26 21:32
一开始我也在EXE里苦转, 然后找另一个JAVA过来看看  发现有源码, 但是找不到调试工具 所以没弄下去了~  恒心啊~
explorer126 发表于 2014-10-27 12:07
索马里的海贼 发表于 2014-10-29 14:00
谢谢楼主的分析学习到了..
joshdexipi 发表于 2016-3-19 17:24
谢谢楼主的指导
lamer 发表于 2016-3-23 21:06
支持楼主分享,好
hewap 发表于 2016-4-13 20:21
不错,学习了!
o6o7o5 发表于 2016-5-2 08:04
受教了!厉害!
szac29964 发表于 2016-5-27 13:04


新手搞不懂懂666666666
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-12 08:37

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表