找回密码
 注册
搜索
热搜: 超星 读书 找书
查看: 639|回复: 0

【转贴】QQ协议分析

[复制链接]
发表于 2006-7-3 00:36:21 | 显示全部楼层 |阅读模式
好东东,虽然是旧版本的,但是还有很多可以借鉴,刚才看到朋友放到群上,特转过来.

QQ 协议2005beta2 版分析
流浪的糖果
V1.2

概述
我所作的协议是在黑箱基础上面作出,并参考了LumaQQ 的文档,还有网上流传很广
泛的一份文档,以及中国协议分析网的部分资料。
所分析的结果仅供学习借签参考,不能用于其他的违法行为。

分析环境
我在我的Linux工作站(Redhat Fedore 3 + Linux-2.6.10)上面安装了Vmware虚拟机,然
后在虚拟上安装了QQ2005beta2,在我的Linux中使用ethereal抓包软件。
下图:
图上我我抓到数据

QQ加密算法
QQ协议中用到算法有MD5算法,用它来生成密码的HASH串,然后有用来加密传送
数据的TEA 算法,推荐的TEA 算法应该是32 轮,但是QQ 目前就使用了16 轮,TEA是通
过增加加密算法的轮数来提高安全性的,不是使用复杂的算法。
网上有许多的资料关于这两种算法。
由于下面会有代码分析,所以我们这里给出我的MD5算法和TEA相关算法:
下面是我的MD5 实现:

QQ数据报文
QQ的数据通过UDP方式传送,就是说每个独立的报文长度不会大于64K,发送到QQ
服务器的8000 端口(默认)。
所有的QQ 发送的数据报文格式如下:
字节 内容以及说明
0 报文的开头,所有的报文以0x02 开始
1-2 两个字节的以网络字节顺序表示的QQ版本号
3-4 两个字节的以网络字节顺序表示的命令号
5-6 两个字节的发送序号,接收回应的时候必须效验这个序号,
其实这个序号可以随机生成,我认为。
7 - N 具体的数据,可能加密,也可能不加密,这里的数据要看具
体的情况
N+1 报文的结束,必须以0x03 表示

QQ2005beta2 版本代码
下面的分析这个版本QQ 的版本号码是0x0d51

获取登录令牌
QQ2005beta2登录的时候首先会发送一个请求,向服务器请求登录令牌,目前这令牌是
24 个字节,但是其实可以是其他的,要看服务器发回给我们的数据了。
我抓的数据是13 个字节,如下:
02 0d 51 00 62 1a 15 14 c5 aa ea 00 03
02 是报文的开头,0x0d51 是版本,0x0062 是请求Lgin Token的命令
我登录的QQ 号码是348498666,表示位网络字节是0x00eaaac514
0x1a15 是序号
请求格式如下:
字节 内容
0 0x02 报文开始
1-2 网络字节的QQ 版本 0x0d51
3-4 请求登录令牌的命令号0x0062
5-6 序号,可以是随机的
7-10 网络字节顺序的QQ 号码
11 0x00
12 0x03 报文结尾
如果成功会收到到服务返来的数据,这时候需要检查数据的命令类型是否也为0x0062
并且序列号是否是发送时候采用的序列号,如果不是,表示有错误,可以继续接收下一个包,
直到超时!
目前我们抓到回应数据一般是34字节,
回应的格式如下:
字节 内容
0 0x02 报文开始
1-2 服务器标识,0x0000(一般是)
3-4 0x0062
5-6 序列号,和刚才发送采用的是一样的
7 0 表示成功
8 令牌数据的长度(现在是24)
9 – N 令牌数据
N+1 0x03 报文结束

下面是我们获取登录令牌的实现:
/* 这个函数用来获取登录令牌*/
int qq_request_login_token(struct qq_client *qc,unsigned char*token)
{
unsigned char buff_tx[65535]; /*64K数据发送缓存*/
unsigned char buff_rx[65535]; /*64K数据发送缓存*/
int len = -1;
fd_set fds;
struct timeval timeout;
int e = -1;
uint16_t tmp16 = 0;
uint32_t tmp32 = 0;
uint16_t seq = rand(); /*我们随机生成序号*/
/*检查传入的参数*/
if(!qc||qc->server<0||!token){
return -EFAULT;
}
/*清零数据是个好习惯!*/
bzero(buff_tx,sizeof(buff_tx));
bzero(buff_rx,sizeof(buff_rx));
/*构造发送数据*/
/*0x02表示报文开始*/
buff_tx[0] = 0x02;
/*QQ版本号码 0x0d51是QQ2005beta2 */
*((uint16_t*)&buff_tx[1]) = htons(0x0d51);
/*0x0062表示登录令牌请求*/
*((uint16_t*)&buff_tx[3]) = htons(0x0062);
/*序号*/
*((uint16_t*)&buff_tx[5]) = htons(seq);
/*QQ号码*/
*((uint32_t*)&buff_tx[7]) = htonl(qc->id);
/*这位设置位0*/
buff_tx[11] = 0x00;
/*报文结束*/
buff_tx[12] = 0x03;

/*发送这13个字节的报文到QQ服务器*/
len = write(qc->server,buff_tx,13);
/*我们采用了非阻塞的套节字,所以么设置超时,并检查socket事件! */
bzero(&timeout,sizeof(timeout));
/*超时一秒*/
timeout.tv_sec = 1;
timeout.tv_usec = 0;
/*设置需要监视的socket*/
FD_ZERO(&fds);
FD_SET(qc->server, &fds);
/*检查socket事件*/
e = select(qc->server+1,&fds,NULL,NULL,&timeout);
if(e==-1||e==0){
/*超时了… */
fprintf(stderr,\"request login token timeout.\\n\");
return -EFAULT;
}
/*好了!服务器回应数据出现了 把接收数据放入buff_rx */
len = read(qc->server,buff_rx,sizeof(buff_rx));
if(len<=0){
/*读入数据失败*/
fprintf(stderr,\"request login token error.\\n\");
return -EFAULT;
}
if(buff_rx[0] != 0x02 ){
/*不是QQ报文*/
fprintf(stderr,”not qq data .\\n”);
return –EFAULT;
}
#if 0
tmp16 = *((uint16_t*)&buff_rx[1]);
printf(\"respond source tag %04x\\n\",ntohs(tmp16));
#endif

tmp16 = *((uint16_t*)&buff_rx[3]);
if(htons(tmp16) != 0x0062 ){
/*不是登录令牌回应*/
fprintf(stderr,”not login token data.\\n”);
return –EFAULT;
}
if(ntohs(*((uint16_t*)&buff_rx[5])) != seq){
/*不是我们发出的数据,因为序号不对,起码是传输出错了*/
fprintf(stderr,\"request login token sequence incorrect.\\n\");
return -EFAULT;
}
/*检查是否含有令牌数据*/
if(buff_rx[7] != 0x00){
fprintf(stderr,\"failed to request login token.\\n\");
return -EINVAL;
}
/*令牌的长度*/
len = buff_rx[8];
printf(\"login token length is %d bytes\\n\",len);
/*复制令牌到我们的缓存*/
bcopy(&buff_rx[9],token,len);
printf(\"login token :\");
HEX_PRINT(&buff_rx[9],len);
return len;
}

登录
当我们获取登录令牌成功后,我们就可以开始我们的登录了过程了。
我捕获的数据长度(抛去协议数据)是460 字节
捕获的数据如下:
0x02, 协议开始
0x0d, 0x51, 版本 QQ2005beta2
0x00, 0x22, 登录请求
0x1a, 0x15, 报文序号
0x14, 0xc5, 0xaa, 0xea, QQ号码我的号码 348498666 = 0xeaaac514
0xd4,0xf3, 0x20, 0x2b, 0xc8, 0x65, 0x24, 0x55,
0xea, 0x61, 0x4a, 0xd5, 0xd3,0xae, 0x8e, 0xc8,登录数据数据密钥 16字节
…… 加密过的数据
0x03
我们除去报头的7 个字节,密钥16 字节,4字节的号码,还有0x03 这一个字节,所以
加密过的数据是460 - 7 -16 - 1 -4 = 432
其实我们在填充数据的时候,16 字节的初始密钥我们可以采用随即生成。这样安全性
也许会更好。
我在自己写了个程序,采用密钥0xd4,0xf3, 0x20, 0x2b, 0xc8, 0x65, 0x24, 0x55, 0xea,
0x61, 0x4a, 0xd5, 0xd3,0xae, 0x8e, 0xc8 对加密的432字节数据解密后,还原出416字节的
登录数据,表明,那16字节确实是初始的加密密钥。
下面说明这些数据的意义:
0 ― 15 共16字节
0x28 ,0xb0 ,0x5f ,0xec ,0x84 ,0x96 ,0x7a ,0xea ,
0x4d ,0xab ,0x72 ,0xc8 ,0xed ,0xdd ,0x14 ,0x92 ,
这16字节是先把密码作两次MD5-16运算得到一个HASH,然后把这个结果作为密钥用TEA加密
一个任意的字串,可以是空!得到这16字节,服务器其实只是看看能不能在服务器端解密,并不
关心解密后的内容!
16 - 51 共36字节
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x23 ,0xa2 ,0x10 ,0x55 ,0x97 ,
0x52 ,0xd8 ,0x1e ,0xb4 ,0xd7 ,0x87 ,0x89 ,0x4a ,
0x12 ,0x7d ,0xd1 ,0x01
看起来是固定的内容

52 – 52 共一个字节
0x0a ,
这里是登录的模式,是隐身的还是正常的
0x0a 是正常模式
0x28 是隐身模式
53 - 68 共16字节
0x61 ,0x78 ,0x3e ,0x2b ,0x13 ,0x76 ,0x43 ,0x4a ,
0xb5 ,0xdc ,0x46 ,0xce ,0x16 ,0x9b ,0x77 ,0xfc ,
不知道意思
69 – 69 共一个字节
0x18 ,
这里是登录令牌的长度,0x18 = 24
70 – 93 共24字节的登录令牌
0xc6 ,0x54 ,0x88 ,0x4e ,0x56 ,0xe6 ,0xbb ,0x13 ,
0x90 ,0x9c ,0xb2 ,0x2a ,0xb8 ,0x0d ,0xee ,0xc0 ,
0xb1 ,0x7a ,0xb4 ,0x70 ,0x38 ,0xe7 ,0x52 ,0xde ,
24字节的登录令牌,这24字节刚好是我们刚才得到的!
94-94 一个字节
0x01 ,
固定的0x01,目前不知道含义
95-95 一个字节
0x40 ,
目前不知含义,固定的
96 – 415 字节我观察到全部为0,不知道含义!
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,

0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
下面是登录请求460 字节的数据格式:
字节 内容
0 0x02 报文开始
1-2 QQ版本号码,QQ2005beta2 是0x0d51
3-4 QQ命令,这里是0x0022 表示登录请求
5-6 报文序号,可以是随即,但是我发现QQ2005 一直用不变
的,实际中我们用rand()函数生成
7-10 四字节的QQ 号码,网络字节顺序
11-26 16字节的随即密钥,以前是全部位0x01
27 – 458 加密的登录数据 432字节
459 0x03 报文结尾

下面是没有用TEA加密前的416 字节数据
字节 内容
0-15 先将密码用MD5-16 运算两轮,然后用这个结果
作为密钥用TEA 加密任意字串得到这16 字节的
内容
16-51 我不知道意思,看来是固定,LumaQQ 也是这样

52 登录模式,0x0a是正常,0x28 是隐身
53-68 不知道意思,共16 字节
69 登录令牌的长度,目前是24 字节
70 – 93 24 字节的登录令牌
94 固定内容0x01 我猜测是平台代码,也许
95 固定内容0x40
96 – 415 全部为0 不知道意思
当服务器收到我们发送的数据后,先尝试用TEA 解密,密钥是两轮MD5-16 得到的结
果。如果可以解密。
它的回应格式如下:
回应数据1:
字节 内容
0 0x00 表示登录成功
0x01 表示重新定向到其他服务器
0x05 密码错误
第一位是0x00 的情况
字节 内容
0 0x00
1-16 会话令牌,以后的会话加密会用到,以后的数据都
需要用这个令牌加密,共16 字节
17-20 你的QQ 号码,网络字节顺序
21-24 服务器测试到的你的登录IP,网络字节顺序
25-26 服务器测试到的你的登录port,网络字节顺序
27-30 服务器自己的倾听IP
31-32 服务器自己的倾听port
33-36 本次的登录时间
37-62 未知
63-66 未知的IP地址

67-68 未知的端口
69-72 未知的服务器IP
73-74 未知的端口
75-122 未知内容
123-126 上次的登录IP
127-130 上次登录时间
131-138 未知含义
第一位是0x01 的情况:
字节 内容
0 0x01
1-4 登录用的QQ 号码
5-8 重定向的新服务器IP
9-10 新服务器port
如果使用第一个密钥不可解密,就尝试用登录加密数据使用的随即密钥解密,一般我们
全部设置位0x01
如果第一个字节是0x01 也是重定向操作,0x02,0x05是密码错误。

下面的代码展示了简单的登录:
/* 登录代码 */
int qq_login( struct qq_client qc, /*客户端数据结构*/
const char*id, /*字符形式的QQ号*/
const char pass, /*密码*/
unsigned char login_mode,/*登录模式,0x0a=正常,0x28=隐身*/
const char*local_ip,/*本地倾听IP,设置为 “0.0.0.0”*/
int local_port, /*本地的端口,指定一个未用的*/
const char*server_ip,/*腾训的服务器*/
int server_port /*QQ服务器端口,一般是8000*/
)
{
struct sockaddr_in sin;
int i = 0;
int len = sizeof(struct sockaddr_in);
int len2 = 0;
uint16_t tmp16 = 0;
uint32_t tmp32 = 0;
struct in_addr in;
int e = 0;
fd_set fds;
struct timeval timeout;
unsigned char data_raw[1024];
unsigned char data_encrypted[1024+16];
unsigned char data_decrypted[1024];
unsigned char buff_tx[65535];
unsigned char buff_rx[65535];
unsigned char *p = NULL;
unsigned char login_token[256];
int login_token_len = 0;
unsigned char tmp[16];
md5_context ctx;
bzero(buff_tx,sizeof(buff_tx));
bzero(buff_rx,sizeof(buff_rx));
bzero(data_raw,sizeof(data_raw));
bzero(data_encrypted,sizeof(data_encrypted));

/*检查传入的参数*/
if(!qc||!id||!pass||!local_ip||!server_ip){
return -EFAULT;
}
/*是否已经登录*/
if(qc->logined){
return 0;
}
/*设置模式*/
qc->login_mode = login_mode;
if( qc->login_mode!=0x0a &&
qc->login_mode!=0x28 ){
qc->login_mode = 0x0a;
}
/*必要设置*/
qc->id = atol(id);
snprintf(qc->pass,sizeof(qc->pass),pass);
printf(\"login id = %d pass = \\\"%s\\\"\\n\",qc->id,qc->pass);
qc->local_port = local_port;
snprintf(qc->local_ip,sizeof(qc->local_ip),local_ip);
qc->server_port = server_port;
snprintf(qc->server_ip,sizeof(qc->server_ip),server_ip);
printf(\"local address %s:%d\\n\",qc->local_ip,qc->local_port);
printf(\"server address %s:%d\\n\",qc->server_ip,qc->server_port);
/*设置加密登录数据的密钥,随即设置即可*/
/*重新设置随机种子*/
srand(time(0));
for(i=0;i<16;i++){
qc->init_key = rand()&0xff;
}

/*创建一个套节字用来作UDP通讯*/
qc->server = socket(PF_INET,SOCK_DGRAM,0);
if(qc->server<0){
return -EFAULT;
}
/*设置套节字为非阻塞套节字*/
fcntl(qc->server,F_SETFL,O_NONBLOCK);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(qc->local_ip);
sin.sin_port = htons(qc->local_port);
len = sizeof(sin);
/*我们需要绑定这个UDP套节字,因为我们想用固定端口通讯!*/
if(bind(qc->server,(struct sockaddr*)&sin,len)<0){
close(qc->server);
qc->server = -1;
return -EFAULT;
}
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(qc->server_ip);
sin.sin_port = htons(qc->server_port);
len = sizeof(sin);
/*采用connect后我们就可以调用read/write系统调用了*/
/*连接到腾训服务器*/
if(connect(qc->server,(struct sockaddr*)&sin,len)<0){
close(qc->server);
qc->server = -1;
return -EFAULT;
}

/*请求登录令牌,重试8次*/
for(i=0;i<8;i++){
login_token_len = qq_request_login_token(qc,login_token);
if(login_token_len>0){
/*如果成功就退出*/
break;
}
}
/*看看是否请求到了登录令牌*/
if(i==8){
close(qc->server);
qc->server = -1;
return -EFAULT;
}
/*两轮MD5-16加密密码,保存在qc->pass_encrypted中*/
md5_starts(&ctx);
md5_update(&ctx,pass,strlen(pass));
md5_finish(&ctx,tmp);
md5_starts(&ctx);
md5_update(&ctx,tmp,16);
md5_finish(&ctx,qc->pass_encrypted);
/*000-015 用MD5加密任意字符*/
qq_encrypt(\"LINUXQQ\",7,qc->pass_encrypted,data_raw,&len);
/*016 - 051 固定内容*/
bcopy(login_16_51,&data_raw[16],35);
/*52-52 登录模式*/
data_raw[52] = qc->login_mode;
/*053 - 068 固定内容*/
bcopy(login_53_68,&data_raw[53],16);

/*69-69 登录令牌长度*/
data_raw[69] = login_token_len;
/*复制登录令牌*/
bcopy(login_token,&data_raw[70],login_token_len);
/*固定内容*/
data_raw[70 + login_token_len ] = 0x01;
data_raw[70 + login_token_len +1] = 0x40;
len = 70 + login_token_len +2;
/*未知内容*/
bcopy(login_unknown_fixed,&data_raw[len],sizeof(login_unknown_fixed));
len+=sizeof(login_unknown_fixed);
len = 416;
/*用TEA加密这416数据,密钥是我们随机得到的init_key*/
qq_encrypt(data_raw,416,qc->init_key,data_encrypted,&len);
p = buff_tx;
/*创建发送报文*/
/*所有报文用0x02开头*/
p[0] = 0x02;
/*版本 是QQ2005beta2*/
*((uint16_t*)&p[1]) = htons(0x0d51);
/*命令是0x0022表示登录请求*/
*((uint16_t*)&p[3]) = htons(0x0022);
/*随机得到一个报文序号*/
qc->seq = rand()%65535;
*((uint16_t*)&p[5]) = htons(qc->seq);
/*QQ号码*/
*((uint32_t*)&p[7]) = htonl(qc->id);
/*放置加密的密钥*/
bcopy(qc->init_key,&p[11],16);
/*我们加密过的登录数据*/
bcopy(data_encrypted,&p[27],len);

len = 27 + len;
/*报文结束*/
p[len] = 0x03;
/*发送登录数据*/
e = write(qc->server,buff_tx,len+1);
if(e!=(len+1)){
close(qc->server);
qc->server = -1;
return -EFAULT;
}
/**准备接收数据/
bzero(&timeout,sizeof(timeout));
/*1S超时*/
timeout.tv_sec = 1;
timeout.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(qc->server, &fds);
/*我们等待8个数据包*/
for(i=0;i<8;i++){
e = select(qc->server+1,&fds,NULL,NULL,&timeout);
if(e==-1||e==0){
fprintf(stderr,\"receive data timeout\\n\");
close(qc->server);
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16){
fprintf(stderr,\"too many times retried.\\n\");
return -EFAULT;
}
/*失败了重新试*/
return qq_login(qc,id,pass,login_mode,
local_ip,local_port,
inet_ntoa(in),ntohs(tmp16));
}

bzero(buff_rx,sizeof(buff_rx));
/*读入接收到的数据*/
len = read(qc->server,buff_rx,sizeof(buff_rx));
if(len<0){
close(qc->server);
qc->server = -1;
return -EFAULT;
}
p = buff_rx;
// printf(\"respond header tag %02x\\n\",buff_rx[0]);
// tmp16 = *((uint16_t*)&p[1]);
// printf(\"respond source tag %04x\\n\",ntohs(tmp16));
// tmp16 = *((uint16_t*)&p[3]);
// printf(\"respond command code %04x\\n\",ntohs(tmp16));
// tmp16 = *((uint16_t*)&p[5]);
// printf(\"respond sequence %d\\n\",ntohs(tmp16));
// printf(\"respond tail tag %02x\\n\",buff_rx[len-1]);
if(p[0]!=0x02 || p[len-1]!=0x03){
/*数据不对*/
fprintf(stderr,\"login failed tail/header tag error.\\n\");
/*继续等待看看能不能收到*/
continue;
}

if(htons(*((uint16_t*)&p[3])) != 0x0022){
fprintf(stderr,\"not login respond data. cmd =
%d\\n\",htons(*((uint16_t*)&p[3])));
/*不是登录回应,但是是其他的包,继续试试看看*/
continue;
}
if(ntohs(*((uint16_t*)&p[5])) != qc->seq){
fprintf(stderr,\"respond sequence error seq =
%d\\n\",htons(*((uint16_t*)&p[5])));
/*序列不对,继续等待回应*/
continue;
}
/*OK.是回应数据*/
break;
}
len = len - (7 + 1);
bzero(data_decrypted,sizeof(data_decrypted));
len2 = len;
/*解密接收的数据,先用密码的MD5作为密钥*/
e = qq_decrypt(&p[7],len,qc->pass_encrypted,data_decrypted,&len2);
if(e == 0) {
/*登录成功了*/
if(data_decrypted[0] == 0x00){

/*001-016 会话令牌*/
bcopy(&data_decrypted[1],qc->session_token,16);
printf(\"session token:\");
HEX_PRINT(data_decrypted,16);
//017-020: login uid
tmp32 = *((uint32_t*)&data_decrypted[17]);
tmp32 = ntohl(tmp32);
// printf(\"user id %d\\n\",tmp32);
// 021-024: server detected user public IP
tmp32 = *((uint32_t*)&data_decrypted[21]);
in.s_addr = tmp32;
sprintf(qc->detected_ip,\"%s\",inet_ntoa(in));
printf(\"server detected my ip %s\\n\",qc->detected_ip);
// 025-026: server detected user port
tmp16 = *((uint16_t*)&data_decrypted[25]);
tmp16 = ntohs(tmp16);
qc->detected_port = tmp16;
printf(\"server detected my port %d\\n\",qc->detected_port);
//027-030: server detected itself ip 127.0.0.1 ?
// 031-032: server listening port
// 033-036: login time for current session
tmp32 = *((uint32_t*)&data_decrypted[33]);
tmp32 = ntohl(tmp32);
printf(\"login time for current session %d\\n\",tmp32);
tmp32 = *((uint32_t*)&data_decrypted[123]);
in.s_addr = tmp32;
printf(\"last login ip %s\\n\",inet_ntoa(in));
// 127-130: login time of last session
// 131-138: 8 bytes unknown
//total 139 bytes
printf(\"id = %s pass = %s logined ok.\\n\",id,pass);
qc->logined = 1;

return 0;
} else if(data_decrypted[0] == 0x01){
printf(\"redirect to other server.\\n\");
// 000-000: reply code
//printf(\"server reply code %02x\\n\",data[0]);
// 001-004: login uid
tmp32 = ntohl(*((uint32_t*)&data_decrypted[1]));
printf(\"request id %d\\n\",tmp32);
// 005-008: redirected new server IP
tmp32 = *((uint32_t*)&data_decrypted[5]);
in.s_addr = tmp32;
printf(\"new server ip %s\\n\",inet_ntoa(in));
// 009-010: redirected new server port
tmp16 = *((uint16_t*)&data_decrypted[9]);
printf(\"new server port %d\\n\",ntohs(tmp16));
close(qc->server);
//bzero(qc,sizeof(struct qq_client));
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16){
fprintf(stderr,\"too many times retried.\\n\");
return -EFAULT;
}
return qq_login(qc,id,pass,login_mode,local_ip,local_port,
inet_ntoa(in),ntohs(tmp16));
}
else if(data_decrypted[0] == 0x05)
{
printf(\"id = %s pass = %s password error.\\n\",id,pass);
close(qc->server);
qc->server = -1;
return -EINVAL;
}
else
{
printf(\"unknown error.\\n\");
close(qc->server);
qc->server = -1;
return -EINVAL;

}
}
bzero(data_decrypted,sizeof(data_decrypted));
/*如果解密失败,试试用我们的init_key能缶解密*/
len2 = len;
e = qq_decrypt(&p[7],len,qc->init_key,data_decrypted,&len2);
if(e == 0){
switch(data_decrypted[0]){
case 0x01:
printf(\"redirect to other server.\\n\");
// 000-000: reply code
//printf(\"server reply code %02x\\n\",data[0]);
// 001-004: login uid
tmp32 = ntohl(*((uint32_t*)&data_decrypted[1]));
printf(\"request id %d\\n\",tmp32);
// 005-008: redirected new server IP
tmp32 = *((uint32_t*)&data_decrypted[5]);
in.s_addr = tmp32;
printf(\"new server ip %s \\n\",inet_ntoa(in));
// 009-010: redirected new server port
tmp16 = *((uint16_t*)&data_decrypted[9]);
printf(\"new server port %d\\n\",ntohs(tmp16));
close(qc->server);
//bzero(qc,sizeof(struct qq_client));
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16){
fprintf(stderr,\"too many times retried.\\n\");
return -EFAULT;
}
return
qq_login(qc,id,pass,login_mode,local_ip,local_port,inet_ntoa(in),ntohs(tmp16));
break;

case 0x02:
printf(\"id = %s pass = %s password error.\\n\",id,pass);
close(qc->server);
qc->server = -1;
return -EINVAL;
break;
case 0x05:
printf(\"id = %s pass = %s password error.\\n\",id,pass);
close(qc->server);
qc->server = -1;
return -EINVAL;
break;
default:
printf(\"unknow server error.\\n\");
close(qc->server);
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16){
fprintf(stderr,\"too many times retried.\\n\");
return -EFAULT;
}
return
qq_login(qc,id,pass,login_mode,local_ip,local_port,inet_ntoa(in),ntohs(tmp16));
break;
}/*switch*/
}
else
{
printf(\"decrypt data error\\n\");
close(qc->server);
qc->server = -1;
qc->login_retry++;
if(qc->login_retry>16){
fprintf(stderr,\"too many times retried.\\n\");
return -EFAULT;
}
return qq_login(qc,id,pass,login_mode,local_ip,
local_port,inet_ntoa(in),ntohs(tmp16));
}
qc->logined = 1;

return 0;
}
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|网上读书园地

GMT+8, 2024-12-23 02:17 , Processed in 0.696066 second(s), 4 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表