littlefater 发表于 2011-2-24 11:48

【轻松CM游戏】破解思路

本帖最后由 littlefater 于 2011-2-24 20:14 编辑

【轻松CM游戏】传送门http://www.52pojie.cn/thread-81083-2-1.html

【0x01 前言】

   昨天玩了玩此CM,感觉很有价值,能学到很多东西,今天略作总结,仅叙述思路,具体流程写起来太麻烦,大家看起来也太麻烦。思路我会尽量写的比较清晰,但本人文采有限,望大家海涵。

【0x02 初次尝试】

   首先请出PEID搜集信息,可惜Nothing found *,有两个区段.text和.data,无TLS。
   
   随便输入一组注册码,发现有错误提示,OD载入,搜索字符串,定位到字符串“wrong!”处,发现是在消息循环里面,估计离关键算法处不远,查看附近有无可以利用的跳转,但是没有找到。

【0x03 找寻关键点】

   运行程序,找到离提示字符串最近的那个CALL,发现这个CALL接受了四个参数,其中第一个参数是我们输入的注册码,第二个参数是0,其他的暂时还看不出有什么作用:

004017D9   .68 BF204000         PUSH crackme.004020BF
004017DE   .68 CF204000         PUSH crackme.004020CF
004017E3   .6A 00                   PUSH 0
004017E5   .8D45 DE               LEA EAX,DWORD PTR SS:
004017E8   .50                        PUSH EAX
004017E9   .E8 72F8FFFF         CALL crackme.00401060

    进去后,发现里面的结构比较规整,一开始是对局部变量一连串的赋值,但是大多数都是被赋成了0,暂时还看不出来有什么作用,其中有几个比较关键:

……
0040108D   .8D85 D0FEFFFF       LEA EAX,DWORD PTR SS:
00401093   .8945 F1                   MOV DWORD PTR SS:,EAX
00401096   .8B45 14                   MOV EAX,DWORD PTR SS:
00401099   .8945 E0                   MOV DWORD PTR SS:,EAX
0040109C   .8B45 08                   MOV EAX,DWORD PTR SS:
0040109F   .8945 D0                   MOV DWORD PTR SS:,EAX
004010A2   .8B45 0C                   MOV EAX,DWORD PTR SS:
004010A5   .8945 D8                   MOV DWORD PTR SS:,EAX
……
004010B6   .8B45 10                   MOV EAX,DWORD PTR SS:
004010B9   .8945 EC                   MOV DWORD PTR SS:,EAX

   从以上代码可以看出指向了新开辟堆栈空间的栈顶(因为一开始有ADD ESP,-130),指向第四个参数,指向第一个参数,指向第二个参数,指向第三个参数。

【0x04 深入分析】

   再往下走,看到一个jmp表:

004010BC   > /FF45 EC             INC DWORD PTR SS:
004010BF   . |8B45 EC               MOV EAX,DWORD PTR SS:
004010C2   . |8A00                   MOV AL,BYTE PTR DS:
004010C4   . |8845 F0               MOV BYTE PTR SS:,AL
004010C7   . |B8 00204000      MOV EAX,crackme.00402000
004010CC   . |0FB65D F0         MOVZX EBX,BYTE PTR SS:
004010D0   . |C1E3 02               SHL EBX,2
004010D3   . |03C3                   ADD EAX,EBX
004010D5   . |FF20                   JMP DWORD PTR DS:

    这个表根据函数第三个参数指向的地址的值,跳转到不同的地方执行代码,执行完后又跳转回来,很像虚拟机技术,但到底是不是呢?我们接着分析。首先查看一下函数第三个参数指向的地址的值:

004020D001 30 0E 05 02 02 08 02 01 04 03 01 02 02 01 0A
004020E008 03 02 01 30 0E 07 01 00 05 0D 13 00 03 0D 01
004020F0EB 02 01 05 03 01 06 00 01 05 02 01 05 02 00 05
0040210012 00 2A 01 41 01 13 04 0F 01 61 01 11 04 0F 01
0040211061 01 14 04 0F 01 61 01 04 04 0F 01 00 0F 01 40
0040212003 03 06 00 00 00 00 00 00 00 00 00 00 00 00 00

    第一个字节是0x01,函数跳转到下面这里执行:

004010D9   .8345 F1 04          ADD DWORD PTR SS:,4
004010DD   .8B45 F1            MOV EAX,DWORD PTR SS:
004010E0   .8945 E4               MOV DWORD PTR SS:,EAX
004010E3   .FF45 EC               INC DWORD PTR SS:
004010E6   .8B45 EC               MOV EAX,DWORD PTR SS:
004010E9   .8A00                   MOV AL,BYTE PTR DS:
004010EB   .8845 F0               MOV BYTE PTR SS:,AL
004010EE   .0FB645 F0         MOVZX EAX,BYTE PTR SS:
004010F2   .8945 F5               MOV DWORD PTR SS:,EAX
004010F5   .8945 E8               MOV DWORD PTR SS:,EAX
004010F8   .8B5D E4            MOV EBX,DWORD PTR SS:
004010FB   .8903                   MOV DWORD PTR DS:,EAX
004010FD   .^ EB BD               JMP SHORT crackme.004010BC

    刚才说过了,是指向新开辟堆栈空间的栈顶的,那么可以推测它的作用类似于esp寄存器,这里先加了个4,就很可能是入栈(push)或者出栈(pop)操作了。完成加4操作后,这个值被赋给了eax寄存器和这个局部变量。程序接着读取了函数第三个参数指向地址处的第二个字节(0x30),并把它同时赋给了eax,,和,最后又将赋给ebx,就是开头+4的值,然后0x30被存放在了这个地址之中。到此为止,我们可以推测出这一段代码的作用是将一个立即数压入堆栈之中,其对应的字节码为01 xx,xx表示要压入堆栈的立即数。

    接下来一个字节是0x0E,我们看看它做了什么:

004012E6   .8B45 D0             MOV EAX,DWORD PTR SS:
004012E9   .0345 D4             ADD EAX,DWORD PTR SS:
004012EC   .FF45 D4             INC DWORD PTR SS:
004012EF   .0FB600            MOVZX EAX,BYTE PTR DS:
004012F2   .8945 F5             MOV DWORD PTR SS:,EAX
004012F5   .8345 F1 04         ADD DWORD PTR SS:,4
004012F9   .8B5D F1             MOV EBX,DWORD PTR SS:
004012FC   .895D E4             MOV DWORD PTR SS:,EBX
004012FF   .8945 E8            MOV DWORD PTR SS:,EAX
00401302   .8903                  MOV DWORD PTR DS:,EAX
00401304   .^ E9 B3FDFFFF   JMP crackme.004010BC

    指向的是我们输入的注册码,它先与相加,被赋予的初值为零,相加后 又立刻自增1,然后注册码的第一位被读到eax中,由此可见,这一段代码是在读取注册码的每一位,其中的就是用于记录下次要读取的位数。接下来的那几句代码和刚才的压栈操作很相似,只不过这次压入的是注册码的第一位。

    此时堆栈里面被压入了两个数字,一个是立即数0x30,一个是注册码第一位,接下来的字节码0x05又做了什么呢?

00401196   .8B45 F5             MOV EAX,DWORD PTR SS:
00401199   .836D F1 04      SUB DWORD PTR SS:,4
0040119D   .8B5D F1            MOV EBX,DWORD PTR SS:
004011A0   .895D E4            MOV DWORD PTR SS:,EBX
004011A3   .8B13               MOV EDX,DWORD PTR DS:
004011A5   .8955 E8             MOV DWORD PTR SS:,EDX
004011A8   .8745 E8             XCHG DWORD PTR SS:,EAX
004011AB   .2945 E8             SUB DWORD PTR SS:,EAX
004011AE   .0F9445 FE          SETE BYTE PTR SS:
004011B2   .0F9845 FD          SETS BYTE PTR SS:
004011B6   .FF75 E8             PUSH DWORD PTR SS:
004011B9   .8F45 F5             POP DWORD PTR SS:
004011BC   .8B45 E8             MOV EAX,DWORD PTR SS:
004011BF   .8903                  MOV DWORD PTR DS:,EAX
004011C1   .^ E9 F6FEFFFF   JMP crackme.004010BC

    这段代码跟刚才那两段有点不一样,看起来是在做什么运算。一开始将读到eax,存放的是刚才读取的注册码的第一位,接着对做了减4操作,看来是要出栈了,被压入的立即数0x30被读到了edx和中,接着交换了eax和的值,然后相减,相减的结果放在,如果结果为零被置为1,如果结果为负,被置为1(这里的sete表示set if equal,即如果zf=1,则置1;sets表示set if sign,即如果sf=1,则置1),接下来的push和pop操作只是简单的赋值操作而已,最后相减的结果被压入了堆栈之中。

    至此,我们分析了三段代码,总结一下,这三段代码其实完成了下面这样的功能:将立即数0x30入栈,将注册码第一位入栈,用注册码第一位减去0x30,结果放在0x30的位置。其实这是一种比较典型的基于堆栈模式的虚拟机代码,这三段代码用汇编可以这样描述(仅表述大意,不完整):

MOV ESI,                ;函数第一个参数,指向注册码
MOV AL,BYTE PTR        ;取注册码第一位
SUB AL,30H                        ;注册码第一位减去0x30

【0x05 提炼归纳】

   有了上面的分析,相信其它的字节码就比较好理解了,这里就不再复述了。如果按照长度分类,字节码主要有三种形式,分别为一字节形式,二字节形式和三字节形式。01 xx这种属于二字节的形式,0E 和 05这种属于一字节的形式,后面还有与跳转相关的13xxxx就是三字节的了。

【0x06算法简述】

   分析出了字节码的含义,算法就很好分析了,可以将字节码直接还原成相应的汇编代码再做分析。由于此CM算法并不复杂,所以我就偷偷懒,不再详细阐述了,仅给出算法逻辑,剩下的留给感兴趣的朋友慢慢研究。

    算法:(用户名ASCII码乘积 * 2 + 0x7F) & 0xFFFFFFFF = 注册码逆序 - 0xEB – 5

【0x07 关于爆破】

   爆破相对而言就比较简单了,只需要修改倒数第二个跳转表里面的跳转语句,将004013BA处的JNZ SHORT crackme.004013C2修改为JE就行了。顺便说一下,这段代码的作用就相当于JNE,其中有一句:

004013B6   .807D FE 00          CMP BYTE PTR SS:,0

    还记得前面我们分析过SETE BYTE PTR SS:吧,如果结果为零,会被置1,但是置1后有什么用呢,看上面这一句你应该就明白了吧。

【0x08 关于注册机】

   既然算法清楚了,注册机就很好写了,还是留给大家自己动手吧O(∩_∩)O~

【0x09 总结】

   本文主要阐述了如何将虚拟机的字节码进行还原,由于本人水平有限,没说清楚的地方请大家提出来,我会进行补充。本文写给和我一样的菜鸟学习交流,高手请飘过。如果还有其它破解思路,欢迎交流。

      Littlefater
2011年2月24日 星期四



532098613 发表于 2011-2-24 12:02

貌似很强悍啊。。。给力..

duoluo211 发表于 2011-2-24 14:33

感谢LZ分享!!!

gxwtk 发表于 2011-2-24 14:38

很详细。厉害的菜鸟。哈哈

死亡骑士 发表于 2011-2-24 14:47

CM哪个?

mycc 发表于 2011-2-24 15:24

无法学习。。。。。顶一下

littlefater 发表于 2011-2-24 16:15

死亡骑士 发表于 2011-2-24 14:47 static/image/common/back.gif
CM哪个?

最上面有链接哈

littlefater 发表于 2011-2-24 16:21

回复 mycc 的帖子

膜拜以前就分析过这个程序的大牛。。。。。。。。。。{:1_931:}

zone0826 发表于 2011-2-24 16:21

真是很详细,肯定没少花时间整理,我弄的时候脑子里一团糟,难得你能理清楚,真不容易。

missviola 发表于 2011-2-24 18:05

[ 本帖最后由 missviola 于 2011-2-24 18:07 编辑 ]

既然有人爆料了,那我也扔点东西出来,需要的请拿走:
4020A1:

01 01//push 0x00000001
01 02//push 0x00000002

loop:
08//VMUL result = 01 * 02 * name
0E//push name ascii
07//push eax
01 00//push 0x00000000
05//0x00000000 - name ascii
0D//pop name ascii
13 00 04//VJS loop

0D//pop result
01 7F//push 0x0000007F
04//vadd result + 0x7F
03//save result
06//quit VM

4020CF:
buff2 = 1
buff1 = 0
01 30//push 0x00000030
0E//push serial ascii

loop:
05//serial ascii - 0x30
02 02//load buff2
08//result = buff2 * serial
02 01//load buff1
04//vadd buff1 = result + buff1
03 01//save buff1
02 02//load buff2
01 0A//push 0x0A
08//buff2 * 0x0A
03 02//save buff2
01 30//push 0x30
0E//push serial ascii
07//push eax
01 00//push 0x0
05//0x0 - serial ascii
0D//pop serial ascii
13 00 03//VJS loop

0D//pop 30
01 EB//push 0xEB
02 01//load buff1
05//buff1 -=0xEB
03 //save result
06//quit vm

4020F7:

01 05//push 0x5
02 01//load buff1
05//buff1 - 0x05
02 00//load name hash
05//name hash - buff1
12 00 2A//jz _success

IDA分析的idb文件,分析的比较粗浅:


页: [1] 2 3
查看完整版本: 【轻松CM游戏】破解思路