返回首页

QQ 协议2005beta2 版协议分析

时间:2005-09-06 来源: 作者: 点击:
概述 我所作的 协议 是在黑箱基础上面作出, 并参考了LumaQQ 的文档, 还有网上流传很广泛的一份文档, 以及 中国 协议 分析网 的部分资料。 所分析的结果仅供学习借签参考, 不能用于其他的违法行为。 分析环境 我在我的Linux 工作站(Redhat Fedore 3 + Linux-2.6.10)
  

概述
我所作的协议是在黑箱基础上面作出, 并参考了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

------分隔线----------------------------
顶一下
(5)
100%
踩一下
(0)
0%
------分隔线----------------------------
最新评论 查看所有评论
发表评论 查看所有评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 密码: 验证码:
推荐内容
  • SKYPE协议分析

    1 、概述 Skype是由Kazaa于2003年发明的基于P2P 技术的VoIP客户端,用户可以通过Skype...

  • 让IRIS嗅探器可以在Win2003 SP1上运行

    IRISSniffer是著名网络安全eeye公司的一个用于网络诊断的嗅探器程序,与SnifferPro相...

  • QQ 协议2005beta2 版协议分析

    概述 我所作的 协议 是在黑箱基础上面作出, 并参考了LumaQQ 的文档, 还有网上流传很...

  • QQ协议分析之TCPF包结构

    包结构类型: TCPF包我们把它分为5类: 登录请求包(LIP,LogIn Packet),它是由客...

  • QQ协议概述

    QQ的版本: QQ的版本升级比较频繁,而且与多数的软件不同的是,它客户端的升级往往伴...