currwin 发表于 2014-10-25 20:27

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

本帖最后由 Kido 于 2014-10-26 20:20 编辑

      如果这个cm我有能力分析出算法的话,我还是希望能够把算法弄出来的。不过本人能力还是不够。唉,nooby牛之所以会被称为大牛,大概就是因为这些吧。
      这个cm被编译为多个语言,既然如此,当然要选择最为简单的入手,去进行切入。这里我选择了APK的程序,当然,选择java的也是可以的。用解压软件解压APK后可以找到KeyGenMe.html文件,里面含有完整的keygen源代码。。。嘻嘻,开心了?且慢,先别那么高兴,既然大牛能够拿得出手,自然不会是那么简单的。
      简单的分析了一下,发现这个程序是使用数据流来进行驱动的。数据代码被保存在数据 var mam数组中:var mem = new Array(0, 0,...),然后下面的驱动操作就只有3种:
      具体如下
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;
                var b = mem;
                var c = mem;
                if (a < 0) {
                        if (input_buffer.length == 0) {
                              break;
                        }
                        mem += input_buffer.pop().charCodeAt(0);
                        i += 3;
                } else if (b < 0) {
                        output_buffer += String.fromCharCode(mem);
                        i += 3;
                } else {
                        if (mem == undefined) {
                              mem = 0;
                        }
                        if (mem == undefined) {
                              mem = 0;
                        }
                        mem -= mem;
                        if (mem < -2147483648) {
                              mem += 4294967296;
                        } else if (mem > 2147483647) {
                              mem -= 4294967296;
                        }
                        if (mem <= 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
   jmp c。
      利用这个运算+data数据,就代替了mov,retn,call,jmp等等的功能。很厉害,这里我看到了新的vm思路了,而且这样进行vm的话,连所谓的vm分派啊,vm_pop, vm_retn 等等都找不到了,因为这些都完全成为数据流驱动的代码了。于是,你会发现,你将会在vRun这个函数里面反复的走来走去,弄得一塌糊涂。
      弄不了了。。。弄不来了,难道就要这么放弃么?总不能就这样被大牛鄙视吧,就算要放弃也得努力一番,千万不能让大牛们看不起了。
      首先来猜一下作者的想法吧,首先,他做了一个简单的小cm,然后利用新的vm程序来对这段代码进行加密,转为为数据流,最后把这段数据流复制进程序去,并且添加数据的处理方法。这样就完了。
      这样简单的事情,我也可以做到,因此,我写了一个程序,来驱动他的数据,试图进行跟踪调试,果然的。。。调试起来变成了好几万步了。这样不行,不行的。
      再换一个想法,既然是数据流,用来解析的话又是按照一定的方法的,我是不是可以弄个小程序来对这段数据进行翻译?这下不,一下子代码就清晰起来了:
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的地方去吧:
04792:   mem[ 25950 ] -= mem[ 25937 ]                              // Get pStr: Invalid Length
04795:   mem[ 0 ] -= mem[ 25950 ]                               // push Addr, 这里为mem是因为地址需要动态计算,也就是说,这里的地址为堆栈地址
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 来进行替代了,并且内部维护了一个堆栈,虽然还是遵循调用的规定,但是隐蔽性大大的提高了。看到这,我只能说一句服了。
      既然有call,那么就可以往上找,去找能够跳过这个call的跳转,幸好,就在附近:
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,mem,修改为同样的两个数,就可以了,这里我选择了修改为 mem 与 mem 的比较,因为mem有其他重要的意义,哈哈。所以具体的操作就是,搜索 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的调试功能了。。。结果发现,有点厉害,简直就是发现了新大陆一样。哈哈,附上一个截图,来爽一下。

      最后,还是等评委的点评吧,弄了这么久,也就只有这么一点的成果。估计大大们都已经keygen出来了,先膜拜一下写出cm来的nooby牛,然后膜拜写出keygen的大大。最后, 求分析。谢谢{:1_918:}

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


currwin 发表于 2014-10-25 20:27

糟糕,忘记设置权限了。。。。求帮忙设置一下

a070458 发表于 2014-10-26 21:32

{:1_906:}一开始我也在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
页: [1] 2 3
查看完整版本: 【吾爱破解2014CrackMe大赛】【第四组】