好友
阅读权限 30
听众
最后登录 1970-1-1
本帖最后由 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的调试功能了。。。结果发现,有点厉害,简直就是发现了新大陆一样。哈哈,附上一个截图,来爽一下。
最后,还是等评委的点评吧,弄了这么久,也就只有这么一点的成果。估计大大们都已经keygen出来了,先膜拜一下写出cm来的nooby牛,然后膜拜写出keygen的大大。最后, 求分析。谢谢
PS: 附件为爆破后的程序以及分析的程序的源代码,求指点。
免费评分
查看全部评分