暑假里迷起了irc,所以拿到了mIRC就顺便把它keygen了。我用的是mIRC 6.02版。此软件采用的是用户名/注册码的保护,在软件的帮助菜单里有注册选项。
首先我们看看基本的注册情况。填好相关的注册码和用户名之后,点注册会发现提示错误。之后自动清除了输入的内容,但是还是可以继续注册。了解这些就够了。
现在开始我们的破解之旅。
输入好注册码和用户名之后,进入trw,下断点,bpx
GetDlgItemTextA,bpx GetWindowTextA。
因为这两个是常用的获取EDIT控件文本内容的函数啦。
点注册,发现没有拦截下来。
那么就有两种可能性了:第一,程序采用了其他的方法获得文本内容,比如除了刚才的两个断点之外,还可以向EDIT控件发送WM_GETTEXT消息来获得文本;第二,程序是用delphi编写的,有自己独立的获取方式而不依靠windows提供的函数。
无论是哪种可能性,都可以用一种方法来解决,那就是下断点bpx hmemcpy。这个号称是9x平台下的万能断点,只要是输入注册码的,都可以用这个断点来拦截。
下断点,点注册。终于拦截下来了。拦下后,下命令pmodule,回到mIRC程序里,发现我们在call SendDlgItemMessageA的下面,原来它是用这个函数给对话框里的EDIT控件发送WM_GETTEXT消息。
016F:004C688D 68A8A85700 PUSH DWORD
0057A8A8 <==============以后名字存放的地点
016F:004C6892 68E7030000
PUSH DWORD 03E7
016F:004C6897 6A0D
PUSH BYTE +0D <====================WM_GETTEXT的值
016F:004C6899 6883000000 PUSH DWORD 83
016F:004C689E FF7508 PUSH DWORD
[EBP+08]
016F:004C68A1 E8284B0900 CALL
`USER32!SendDlgItemMessageA`
016F:004C68A6 688FAC5700
PUSH DWORD 0057AC8F <=======+=======pmodule后我们就在这里了
016F:004C68AB
68E7030000 PUSH DWORD 03E7
|_______以后我们输入的注册码
016F:004C68B0 6A0D
PUSH BYTE +0D
存放的地点
016F:004C68B2 6882000000
PUSH DWORD 82
016F:004C68B7 FF7508
PUSH DWORD [EBP+08]
016F:004C68BA E80F4B0900
CALL `USER32!SendDlgItemMessageA`
016F:004C68BF
688FAC5700 PUSH DWORD 0057AC8F <==============注册码作为参数二
016F:004C68C4 68A8A85700 PUSH DWORD 0057A8A8
<==============姓名作为参数一
016F:004C68C9 E88FFBFFFF CALL
004C645D <====================调用函数(关键的函数)!!
016F:004C68CE 85C0
TEST EAX,EAX <====================测试函数返回值
016F:004C68D0 0F84B7000000 JZ NEAR 004C698D
<===============返回值如果等于0
就跳走(出错的地方)
从我们看到的代码来看,004c645d处函数肯定是产生注册码并且进行比较的函数.因此我们应当跟进去,走到004C68C9的时候,F8跟进去.看到:
016F:004C645D 55 PUSH
EBP
016F:004C645E 8BEC MOV
EBP,ESP
016F:004C6460 53
PUSH EBX
016F:004C6461 56
PUSH ESI
016F:004C6462 57
PUSH EDI
016F:004C6463
8B750C MOV ESI,[EBP+0C]
016F:004C6466 8B5D08 MOV
EBX,[EBP+08]
016F:004C6469 BF440E5800 MOV
EDI,00580E44
016F:004C646E 6804010000 PUSH
DWORD 0104
016F:004C6473 6A00
PUSH BYTE +00
016F:004C6475 57
PUSH EDI
016F:004C6476 E8D55C0800
CALL 0054C150
016F:004C647B 83C40C
ADD ESP,BYTE +0C
016F:004C647E 6804010000
PUSH DWORD 0104
016F:004C6483 6A00
PUSH BYTE +00
016F:004C6485 68480F5800
PUSH DWORD 00580F48
016F:004C648A E8C15C0800
CALL 0054C150
016F:004C648F 83C40C
ADD ESP,BYTE +0C
016F:004C6492 56
PUSH ESI
016F:004C6493
57 PUSH EDI
016F:004C6494 8BF7 MOV
ESI,EDI
016F:004C6496 8BFB MOV
EDI,EBX
016F:004C6498 33C0
XOR EAX,EAX
016F:004C649A 83C9FF
OR ECX,BYTE -01
016F:004C649D F2AE
REPNE SCASB
016F:004C649F F7D1
NOT ECX
016F:004C64A1 2BF9
SUB EDI,ECX
016F:004C64A3
87F7 XCHG ESI,EDI
016F:004C64A5
8BC7 MOV EAX,EDI
016F:004C64A7 8BD1 MOV
EDX,ECX
016F:004C64A9 C1E902 SHR
ECX,02
016F:004C64AC F3A5
REP MOVSD
016F:004C64AE 8BCA
MOV ECX,EDX
016F:004C64B0 83E103
AND ECX,BYTE +03
016F:004C64B3 F3A4
REP MOVSB
016F:004C64B5 5F
POP EDI
016F:004C64B6
5E POP ESI
016F:004C64B7 56 PUSH
ESI
016F:004C64B8 57
PUSH EDI
016F:004C64B9 8BFE
MOV EDI,ESI
016F:004C64BB BE480F5800
MOV ESI,00580F48
016F:004C64C0 33C0
XOR EAX,EAX
016F:004C64C2
83C9FF OR ECX,BYTE -01
016F:004C64C5 F2AE REPNE SCASB
016F:004C64C7
F7D1 NOT ECX
016F:004C64C9 2BF9 SUB
EDI,ECX
016F:004C64CB 87F7 XCHG
ESI,EDI
016F:004C64CD 8BC7
MOV EAX,EDI
016F:004C64CF 8BD1
MOV EDX,ECX
016F:004C64D1 C1E902
SHR ECX,02
016F:004C64D4
F3A5 REP MOVSD
016F:004C64D6 8BCA
MOV ECX,EDX
016F:004C64D8
83E103 AND ECX,BYTE +03
016F:004C64DB F3A4 REP MOVSB
016F:004C64DD 5F POP
EDI
016F:004C64DE 5E
POP ESI
016F:004C64DF 68480F5800
PUSH DWORD 00580F48
016F:004C64E4 57
PUSH EDI
016F:004C64E5 E880FEFFFF
CALL 004C636A <==================call 后面紧跟test和跳转的
016F:004C64EA 85C0 TEST
EAX,EAX 就要留意了
016F:004C64EC 740A JZ
004C64F8 <===eax=0就跳到失败,所以在函数返回前不能
让eax=0
016F:004C64EE B801000000 MOV
EAX,01 <========令eax=1,然后跳转,不远处就返回了,
016F:004C64F3 E991000000
JMP 004C6589 回到上面004c68ce的地方,上面要求
eax不等于0就注册成功,这里是1正好
满足,所以这个004664E5大有文章.
前面的一大票代码看起来很难分析,其实暂时可以不管,因为这还是我们第一遍跟踪.第一遍只要大概掌握代码的情况就可以了.我们注意到004C64E5
处的call和后面的test jz又构成了一个调用函数然后比较返回值的情况.而且它后面有mov eax,1 jmp 004c6589.这就可以肯定是判断注册码是否合法的函数了.看看在call前面push
入栈的是哪些东西.在执行到0x4c64e4的时候,下命令d 580f48 可以看到注册码,d edi 看到名字.所以,这更加肯定了我们的推测,004c636a处的函数就是核心.在跟进去之前要记住,不能让eax=0,不然就是注册失败的标志.好了跟进来.
016F:004C636A 55 PUSH
EBP
016F:004C636B 8BEC MOV
EBP,ESP
016F:004C636D 83C4F4
ADD ESP,BYTE -0C
016F:004C6370 53
PUSH EBX
016F:004C6371 56
PUSH ESI
016F:004C6372
57 PUSH EDI
016F:004C6373 8B750C MOV
ESI,[EBP+0C]
016F:004C6376 FF7508 PUSH
DWORD [EBP+08] <==========d *(ebp+8)发现这是名字
016F:004C6379
E8525F0800 CALL 0054C2D0 <====这个函数是干什么的?
016F:004C637E 59 POP
ECX <=====执行完函数到了这里,看看eax里是什么?是名字的
016F:004C637F 83F805
CMP EAX,BYTE +05 <==比较名字是不是大于5个字母
长度.
016F:004C6382 7307
JNC 004C638B
016F:004C6384 33C0
XOR EAX,EAX <==小于5就把eax清零,返回,就注册失败了.
016F:004C6386 E9C9000000 JMP 004C6454
016F:004C638B 6A2D PUSH
BYTE +2D <==0x2d就是字符"-"
016F:004C638D 56
PUSH ESI <==注册码
016F:004C638E
E89D5E0800 CALL 0054C230 <==这个函数跟踪后发现是返回"-"在注册码中
016F:004C6393 83C408 ADD
ESP,BYTE +08 的位置.如果你输入的注册码不含"-"就失败
016F:004C6396 8BD8
MOV EBX,EAX
016F:004C6398 85DB
TEST EBX,EBX
016F:004C639A
7507 JNZ 004C63A3
016F:004C639C 33C0 XOR
EAX,EAX
016F:004C639E E9B1000000 JMP
004C6454
016F:004C63A3 C60300 MOV
BYTE [EBX],00 <===把"-"替换成0,就是说把我们输入的
016F:004C63A6
56 PUSH ESI
注册码变成两个字符串
016F:004C63A7
E878ED0800 CALL 00555124
<===把注册码第一部分转化成数值
(原来输入的是字符)
016F:004C63AC 59
POP ECX
016F:004C63AD 8945FC
MOV [EBP-04],EAX <===把第一部分的数值放入这个地方
016F:004C63B0
C6032D MOV BYTE [EBX],2D
<===把注册码恢复成员来的样子
016F:004C63B3 43
INC EBX
016F:004C63B4 803B00
CMP BYTE [EBX],00
016F:004C63B7 7507
JNZ 004C63C0
016F:004C63B9 33C0 XOR
EAX,EAX
016F:004C63BB E994000000 JMP
004C6454
016F:004C63C0 53
PUSH EBX
016F:004C63C1 E85EED0800
CALL 00555124 <===把注册码第二部分转化成数值
016F:004C63C6 59 POP
ECX
016F:004C63C7 8945F8 MOV
[EBP-08],EAX <===存好
016F:004C63CA FF7508
PUSH DWORD [EBP+08] <===姓名
016F:004C63CD
E8FE5E0800 CALL 0054C2D0
<===取得姓名长度
016F:004C63D2 59
POP ECX
016F:004C63D3 8945F4
MOV [EBP-0C],EAX <===把长度存好
016F:004C63D6
33C0 XOR EAX,EAX
<===开始计算了
016F:004C63D8 33DB
XOR EBX,EBX
016F:004C63DA BA03000000
MOV EDX,03
016F:004C63DF 8B4D08
MOV ECX,[EBP+08]
016F:004C63E2 83C103
ADD ECX,BYTE +03 <===从姓名第三个字开始算
016F:004C63E5 3B55F4 CMP
EDX,[EBP-0C]
016F:004C63E8 7D1C
JNL 004C6406
016F:004C63EA 0FB631
MOVZX ESI,BYTE [ECX] <===取姓名里的一个字符
016F:004C63ED
0FAF3485B8835600 IMUL ESI,[EAX*4+005683B8] <==与一个数相乘保存在esi中
016F:004C63F5 03DE ADD
EBX,ESI <=累加起来 (这个数具体是什么后面讲)
016F:004C63F7 40
INC EAX
016F:004C63F8
83F826 CMP EAX,BYTE +26
016F:004C63FB 7E02 JNG
004C63FF
016F:004C63FD 33C0
XOR EAX,EAX
016F:004C63FF 42
INC EDX
016F:004C6400 41
INC ECX
016F:004C6401
3B55F4 CMP EDX,[EBP-0C]
016F:004C6404 7CE4 JL
004C63EA 循环完成循环计算后,ebx里面就是计算的结果
016F:004C6406 3B5DFC
CMP EBX,[EBP-04] 比较ebx和开始获得的注册码第一部分数值
016F:004C6409 7404 JZ
004C640F 相等就继续计算第二部分
016F:004C640B 33C0
XOR EAX,EAX
不然就清0 eax滚蛋
016F:004C640D EB45 JMP
SHORT 004C6454
016F:004C640F 33C0
XOR EAX,EAX <==第二部分开始了
016F:004C6411 33DB XOR
EBX,EBX
016F:004C6413 BA03000000 MOV
EDX,03
016F:004C6418 8B4D08 MOV
ECX,[EBP+08]
016F:004C641B 83C103
ADD ECX,BYTE +03 <=还是从姓名的第3个字开始算
016F:004C641E
3B55F4 CMP EDX,[EBP-0C]
016F:004C6421 7D23 JNL
004C6446
016F:004C6423 0FB631 MOVZX
ESI,BYTE [ECX] <=取姓名里的一个字
016F:004C6426 0FB679FF
MOVZX EDI,BYTE [ECX-01] <=取前一个字
016F:004C642A
0FAFF7 IMUL ESI,EDI
<=相乘
016F:004C642D 0FAF3485B8835600 IMUL
ESI,[EAX*4+005683B8] <=再与另一个数相乘
016F:004C6435 03DE
ADD EBX,ESI <=累加
(和上面的那个一样后面讲)
016F:004C6437 40
INC EAX
016F:004C6438 83F826
CMP EAX,BYTE +26
016F:004C643B 7E02
JNG 004C643F
016F:004C643D
33C0 XOR EAX,EAX
016F:004C643F 42 INC
EDX
016F:004C6440 41
INC ECX
016F:004C6441 3B55F4
CMP EDX,[EBP-0C]
016F:004C6444 7CDD
JL 004C6423
循环计算
016F:004C6446 3B5DF8 CMP
EBX,[EBP-08] 比较计算结果,和注册码第二部分数值
016F:004C6449 7404
JZ 004C644F 相同则跳到成功标志
016F:004C644B 33C0 XOR
EAX,EAX
016F:004C644D EB05 JMP
SHORT 004C6454
016F:004C644F B801000000
MOV EAX,01 设立注册码比较成功标志
016F:004C6454
5F POP EDI
016F:004C6455 5E POP
ESI
016F:004C6456 5B
POP EBX
016F:004C6457 8BE5
MOV ESP,EBP
016F:004C6459 5D
POP EBP
016F:004C645A
C20800 RET 08
好了,现在讲一讲上面两次出现的[EAX*4+005683B8]是什么意思.
在跟踪到那个乘法的时候,下d 005683B8,会看到数据栏里是这个样子
0B000000 06000000 11000000 0C000000
0C000000 0E000000 05000000 0C000000
10000000 0A000000 0B000000 06000000
0E000000 0E000000 04000000 0B000000
06000000 0E000000 0E000000 04000000
0B000000 09000000 0C000000 0B000000
0A000000 08000000 0A000000 0A000000
10000000 08000000 04000000 06000000
0A000000 0C000000 10000000 08000000
0A000000 04000000 10000000 00000000
发现数据都是每4字节出现一次,这就是为什么用eax*4了.其实005683B8这个地址里出现的数据是固定的,就是说这是个字典,每次计算注册码的时候到里面来查一个数字然后跟名字的一个字母做乘法运算.那么我怎么才能知道这个字典是多大呢?那就看
016F:004C63F8 83F826 CMP
EAX,BYTE +26
和
016F:004C6438 83F826
CMP EAX,BYTE +26
都是把eax和0x26,也就是38来比较,所以一共有39个数据.就是除了0之外的那些.
至此,我们完全摸清了,mIRC的注册码的计算情况.当我们执行到016F:004C6406和016F:004C6446的时候各做一次? ebx显示出十进制值,然后把两个值用"-"链接起来就是我们输入名字所对应的注册码了,比如我的lllaaa[BCG]对应7591-674568.
好了,既然搞清楚了算法,写注册机也就容易了.下面我贴出注册机的c语言源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int
argc,char *argv[])
{
char dict[39]=
{
0x0b,0x06,0x11,0x0c,0x0c,0x0e,0x05,0x0c,0x10,0x0a,
0x0b,0x06,0x0e,0x0e,0x04,0x0b,0x06,0x0e,0x0e,0x04,
0x0b,0x09,0x0c,0x0b,0x0a,0x08,0x0a,0x0a,0x10,0x08,
0x04,0x06,0x0a,0x0c,0x10,0x08,0x0a,0x04,0x10
};
char name[255];
printf("Input your name please:");
gets(name);
int length=strlen(name);
if (length>=39)
{
printf("\nYour name is longer than 39 chars,please choose another
one.");
return 1;
}
int i=0;
long serial1=0,serial2=0;
for
(i=0;i<length-3;i++)
{
serial1+=name[i+3]*dict[i];
serial2+=name[i+3]*name[i+2]*dict[i];
}
printf("The
serial number is:%d-%d",serial1,serial2);
return
1;
}
本来源程序是支持名字超过39字节的,但是我为了编程的简单,就做了限制.
- 标 题:制作mIRC6.02注册机(给别人写的初学者注册机教材) (14千字)
- 作 者:lllaaa[BCG]
- 时 间:2002-8-23
21:27:17
- 链 接:http://bbs.pediy.com