吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9392|回复: 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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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, 2024-11-22 05:53

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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