【吾爱破解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: 附件为爆破后的程序以及分析的程序的源代码,求指点。
糟糕,忘记设置权限了。。。。求帮忙设置一下 {:1_906:}一开始我也在EXE里苦转, 然后找另一个JAVA过来看看发现有源码, 但是找不到调试工具 所以没弄下去了~恒心啊~ 只要做出来的都是大牛。 谢谢楼主的分析学习到了.. 谢谢楼主的指导 支持楼主分享,好 不错,学习了! 受教了!厉害!
新手搞不懂懂666666666