| QQ 协议2005beta2 版协议分析 |
| 作者:未知 文章来源:本站整理 点击数: 更新时间:2006-11-20 23:11:48 |
概述 我所作的协议是在黑箱基础上面作出, 并参考了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 x\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:

第一位是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[i] = 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()e535; *((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 |
x\n",buff_rx[0]); |
| // |
tmp16 = |
*((uint16_t*)&p[1]); |
| // |
printf("respond source tag |
x\n",ntohs(tmp16)); |
| // |
tmp16 = |
*((uint16_t*)&p[3]); |
| // |
printf("respond command code x\n",ntohs(tmp16)); |
| // |
tmp16 = |
*((uint16_t*)&p[5]); |
| // |
printf("respond sequence |
%d\n",ntohs(tmp16)); |
| // |
printf("respond tail |
tag |
x\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 x\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 x\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;
}
相关源码请进入中国协议分析网论坛下载:http://www.cnpaf.net/Forum/read.php?tid=2312&fpage=1
|
|
|
|
|
|
|