好友
阅读权限 10
听众
最后登录 1970-1-1
本帖最后由 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:[EBP-22]
004017E8 . 50 PUSH EAX
004017E9 . E8 72F8FFFF CALL crackme.00401060
进去后,发现里面的结构比较规整,一开始是对局部变量一连串的赋值,但是大多数都是被赋成了0,暂时还看不出来有什么作用,其中有几个比较关键:
……
0040108D . 8D85 D0FEFFFF LEA EAX,DWORD PTR SS:[EBP-130]
00401093 . 8945 F1 MOV DWORD PTR SS:[EBP-F],EAX
00401096 . 8B45 14 MOV EAX,DWORD PTR SS:[EBP+14]
00401099 . 8945 E0 MOV DWORD PTR SS:[EBP-20],EAX
0040109C . 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040109F . 8945 D0 MOV DWORD PTR SS:[EBP-30],EAX
004010A2 . 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
004010A5 . 8945 D8 MOV DWORD PTR SS:[EBP-28],EAX
……
004010B6 . 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
004010B9 . 8945 EC MOV DWORD PTR SS:[EBP-14],EAX
从以上代码可以看出[EBP-F]指向了新开辟堆栈空间的栈顶(因为一开始有ADD ESP,-130),[EBP-20]指向第四个参数,[EBP-30]指向第一个参数,[EBP-28]指向第二个参数,[EBP-14]指向第三个参数。
【0x04 深入分析】
再往下走,看到一个jmp表:
004010BC > /FF45 EC INC DWORD PTR SS:[EBP-14]
004010BF . |8B45 EC MOV EAX,DWORD PTR SS:[EBP-14]
004010C2 . |8A00 MOV AL,BYTE PTR DS:[EAX]
004010C4 . |8845 F0 MOV BYTE PTR SS:[EBP-10],AL
004010C7 . |B8 00204000 MOV EAX,crackme.00402000
004010CC . |0FB65D F0 MOVZX EBX,BYTE PTR SS:[EBP-10]
004010D0 . |C1E3 02 SHL EBX,2
004010D3 . |03C3 ADD EAX,EBX
004010D5 . |FF20 JMP DWORD PTR DS:[EAX]
这个表根据函数第三个参数指向的地址的值,跳转到不同的地方执行代码,执行完后又跳转回来,很像虚拟机 技术,但到底是不是呢?我们接着分析。首先查看一下函数第三个参数指向的地址的值:
004020D0 01 30 0E 05 02 02 08 02 01 04 03 01 02 02 01 0A
004020E0 08 03 02 01 30 0E 07 01 00 05 0D 13 00 03 0D 01
004020F0 EB 02 01 05 03 01 06 00 01 05 02 01 05 02 00 05
00402100 12 00 2A 01 41 01 13 04 0F 01 61 01 11 04 0F 01
00402110 61 01 14 04 0F 01 61 01 04 04 0F 01 00 0F 01 40
00402120 03 03 06 00 00 00 00 00 00 00 00 00 00 00 00 00
第一个字节是0x01,函数跳转到下面这里执行:
004010D9 . 8345 F1 04 ADD DWORD PTR SS:[EBP-F],4
004010DD . 8B45 F1 MOV EAX,DWORD PTR SS:[EBP-F]
004010E0 . 8945 E4 MOV DWORD PTR SS:[EBP-1C],EAX
004010E3 . FF45 EC INC DWORD PTR SS:[EBP-14]
004010E6 . 8B45 EC MOV EAX,DWORD PTR SS:[EBP-14]
004010E9 . 8A00 MOV AL,BYTE PTR DS:[EAX]
004010EB . 8845 F0 MOV BYTE PTR SS:[EBP-10],AL
004010EE . 0FB645 F0 MOVZX EAX,BYTE PTR SS:[EBP-10]
004010F2 . 8945 F5 MOV DWORD PTR SS:[EBP-B],EAX
004010F5 . 8945 E8 MOV DWORD PTR SS:[EBP-18],EAX
004010F8 . 8B5D E4 MOV EBX,DWORD PTR SS:[EBP-1C]
004010FB . 8903 MOV DWORD PTR DS:[EBX],EAX
004010FD .^ EB BD JMP SHORT crackme.004010BC
刚才说过了,[EBP-F]是指向新开辟堆栈空间的栈顶的,那么可以推测它的作用类似于esp寄存器,这里先加了个4,就很可能是入栈(push)或者出栈(pop)操作了。完成加4操作后,这个值被赋给了eax寄存器和[EBP-1C]这个局部变量。程序接着读取了函数第三个参数指向地址处的第二个字节(0x30),并把它同时赋给了eax,[EBP-10],[EBP-B]和[EBP-18],最后又将[EBP-1C]赋给ebx,[EBP-1C]就是开头[EBP-F]+4的值,然后0x30被存放在了这个地址之中。到此为止,我们可以推测出这一段代码的作用是将一个立即数压入堆栈之中,其对应的字节码为01 xx,xx表示要压入堆栈的立即数。
接下来一个字节是0x0E,我们看看它做了什么:
004012E6 . 8B45 D0 MOV EAX,DWORD PTR SS:[EBP-30]
004012E9 . 0345 D4 ADD EAX,DWORD PTR SS:[EBP-2C]
004012EC . FF45 D4 INC DWORD PTR SS:[EBP-2C]
004012EF . 0FB600 MOVZX EAX,BYTE PTR DS:[EAX]
004012F2 . 8945 F5 MOV DWORD PTR SS:[EBP-B],EAX
004012F5 . 8345 F1 04 ADD DWORD PTR SS:[EBP-F],4
004012F9 . 8B5D F1 MOV EBX,DWORD PTR SS:[EBP-F]
004012FC . 895D E4 MOV DWORD PTR SS:[EBP-1C],EBX
004012FF . 8945 E8 MOV DWORD PTR SS:[EBP-18],EAX
00401302 . 8903 MOV DWORD PTR DS:[EBX],EAX
00401304 .^ E9 B3FDFFFF JMP crackme.004010BC
[EBP-30]指向的是我们输入的注册码,它先与[EBP-2C]相加,[EBP-2C]被赋予的初值为零,相加后[EBP-2C] 又立刻自增1,然后注册码的第一位被读到eax中,由此可见,这一段代码是在读取注册码的每一位,其中的[EBP-2C]就是用于记录下次要读取的位数。接下来的那几句代码和刚才的压栈操作很相似,只不过这次压入的是注册码的第一位。
此时堆栈里面被压入了两个数字,一个是立即数0x30,一个是注册码第一位,接下来的字节码0x05又做了什么呢?
00401196 . 8B45 F5 MOV EAX,DWORD PTR SS:[EBP-B]
00401199 . 836D F1 04 SUB DWORD PTR SS:[EBP-F],4
0040119D . 8B5D F1 MOV EBX,DWORD PTR SS:[EBP-F]
004011A0 . 895D E4 MOV DWORD PTR SS:[EBP-1C],EBX
004011A3 . 8B13 MOV EDX,DWORD PTR DS:[EBX]
004011A5 . 8955 E8 MOV DWORD PTR SS:[EBP-18],EDX
004011A8 . 8745 E8 XCHG DWORD PTR SS:[EBP-18],EAX
004011AB . 2945 E8 SUB DWORD PTR SS:[EBP-18],EAX
004011AE . 0F9445 FE SETE BYTE PTR SS:[EBP-2]
004011B2 . 0F9845 FD SETS BYTE PTR SS:[EBP-3]
004011B6 . FF75 E8 PUSH DWORD PTR SS:[EBP-18]
004011B9 . 8F45 F5 POP DWORD PTR SS:[EBP-B]
004011BC . 8B45 E8 MOV EAX,DWORD PTR SS:[EBP-18]
004011BF . 8903 MOV DWORD PTR DS:[EBX],EAX
004011C1 .^ E9 F6FEFFFF JMP crackme.004010BC
这段代码跟刚才那两段有点不一样,看起来是在做什么运算。一开始将[EBP-B]读到eax,[EBP-B]存放的是刚才读取的注册码的第一位,接着对[EBP-F]做了减4操作,看来是要出栈了,被压入的立即数0x30被读到了edx和[EBP-18]中,接着交换了eax和[EBP-18]的值,然后相减,相减的结果放在[EBP-18],如果结果为零[EBP-2]被置为1,如果结果为负,[EBP-3]被置为1(这里的sete表示set if equal,即如果zf=1,则置1;sets表示set if sign,即如果sf=1,则置1),接下来的push和pop操作只是简单的赋值操作而已,最后相减的结果被压入了堆栈之中。
至此,我们分析了三段代码,总结一下,这三段代码其实完成了下面这样的功能:将立即数0x30入栈,将注册码第一位入栈,用注册码第一位减去0x30,结果放在0x30的位置。其实这是一种比较典型的基于堆栈模式的虚拟机代码,这三段代码用汇编可以这样描述(仅表述大意,不完整):
MOV ESI,[EBP+8] ;函数第一个参数,指向注册码
MOV AL,BYTE PTR [ESI] ;取注册码第一位
SUB AL,30H ;注册码第一位减去0x30
【0x05 提炼归纳】
有了上面的分析,相信其它的字节码就比较好理解了,这里就不再复述了。如果按照长度分类,字节码主要有三种形式,分别为一字节形式,二字节形式和三字节形式。01 xx这种属于二字节的形式,0E 和 05这种属于一字节的形式,后面还有与跳转相关的13 xx xx就是三字节的了。
【0x06 算法简述】
分析出了字节码的含义,算法就很好分析了,可以将字节码直接还原成相应的汇编代码再做分析。由于此CM算法并不复杂,所以我就偷偷懒,不再详细阐述了,仅给出算法逻辑,剩下的留给感兴趣的朋友慢慢研究。
算法:(用户名ASCII码乘积 * 2 + 0x7F) & 0xFFFFFFFF = 注册码逆序 - 0xEB – 5
【0x07 关于爆破】
爆破相对而言就比较简单了,只需要修改倒数第二个跳转表里面的跳转语句,将004013BA处的JNZ SHORT crackme.004013C2修改为JE就行了。顺便说一下,这段代码的作用就相当于JNE,其中有一句:
004013B6 . 807D FE 00 CMP BYTE PTR SS:[EBP-2],0
还记得前面我们分析过SETE BYTE PTR SS:[EBP-2]吧,如果结果为零,[EBP-2]会被置1,但是置1后有什么用呢,看上面这一句你应该就明白了吧。
【0x08 关于注册机】
既然算法清楚了,注册机就很好写了,还是留给大家自己动手吧O(∩_∩)O~
【0x09 总结】
本文主要阐述了如何将虚拟机的字节码进行还原,由于本人水平有限,没说清楚的地方请大家提出来,我会进行补充。本文写给和我一样的菜鸟学习交流,高手请飘过。如果还有其它破解 思路,欢迎交流。
Littlefater
2011年2月24日 星期四
免费评分
查看全部评分