最近想在QQ登录时把QQ号码信息记录下来,百度了很多都没有找到具体方式,最近用Wireshark分析报文+libpcap库嗅探实现了这个小功能。

 

通讯背景:

  QQ客户端在通讯时使用UDP协议,其中数据消息报文为UDP协议,控制报文为OICQ协议(UDP协议的一种封装),控制报文命令常见如下(括号内为改命令在OICQ报文中对应二进制编码的十进制表示):

"log out(1)",
"Heart Message(2)",
"Set status(13)",
"Receive message(23)",
"Request KEY(29)",                //登录时
"Get friend online(39)",
"Group name operation(60)",
"MEMO Operation(62)",
"Download group friend(88)",
"Get level(92)",
"Request login(98)",              //离线时
"Request extra information(101)",
"Signature operation(103)",
"Get status of friend(129)",
"Get friend's status of group(181)",

QQ客户端使用的端口为4000,服务器的端口为8000,当存在多个QQ客户端时,端口号从4000依次向上累加。

 

报文分析:

  在Windows下,由Wireshark抓包分析,QQ在登录与运行时,会向服务器发送UDP以及OICQ报文,这里假定一台机器上少于100个QQ号码登录,定义过滤器如下:

从oicq过滤中发现可以百分百命中含有QQ号码的报文,确定位置在以太网数据包的第49~52字节,以4字节的无符号整形数表示。但libpcap的过滤器仅支持到udp的过滤,于是按下面的filter来过滤测试:

发现,在udp数据包同样的位置也存放有明文的qq号码信息,于是确认了抓取条件(udp.srcport<4100 是为了避免某些不符合规则报文信息的干扰)。

 

调试代码:

  运行环境为Linux,需要安装libpcap,并且在链接时 -lpcap。

  头文件:

 1 #ifndef __SNIFFER_H__
 2 #define __SNIFFER_H__
 3 
 4 #include <pcap.h>
 5 #include <stdio.h>
 6 #include <string.h>
 7 #include <stdlib.h>
 8 #include <ctype.h>
 9 #include <errno.h>
10 #include <sys/types.h>
11 #include <sys/socket.h>
12 #include <netinet/in.h>
13 #include <arpa/inet.h>
14 #include <time.h>
15 
16 /* 以太网帧头部 */
17 #define ETHER_ADDR_LEN 6
18 
19 struct sniff_ethernet{
20     u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
21     u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
22     u_short ether_type; 
23 };
24 
25 /* IP数据包的头部 */
26 struct sniff_ip{
27     #if BYTE_ORDER == LITTLE_ENDIAN
28     u_int ip_hl:4,                         /* 头部长度 */
29     ip_v:4;                             /* 版本号 */
30     #if BYTE_ORDER == BIG_ENDIAN
31     u_int ip_v:4,                         /* 版本号 */
32     ip_hl:4;                             /* 头部长度 */
33     #endif
34     #endif /* not _IP_VHL */
35     u_char ip_tos;                         /* 服务的类型 */
36     u_short ip_len;                     /* 总长度 */
37     u_short ip_id;                         /* 包标志号 */
38     u_short ip_off;                     /* 碎片偏移 */
39     #define IP_RF 0x8000                 /* 保留的碎片标志 */
40     #define IP_DF 0x4000                 /* dont fragment flag */
41     #define IP_MF 0x2000                 /* 多碎片标志*/
42     #define IP_OFFMASK 0x1fff             /* 分段位 */
43     u_char ip_ttl;                         /* 数据包的生存时间 */
44     u_char ip_p;                         /* 所使用的协议 */
45     u_short ip_sum;                     /* 校验和 */
46     struct in_addr ip_src,ip_dst;         /* 源地址、目的地址*/
47 };
48 
49 /* TCP 数据包的头部 */
50 typedef u_int tcp_seq;
51 
52 struct sniff_tcp{
53     u_short th_sport;                     /* 源端口 */
54     u_short th_dport;                     /* 目的端口 */
55     tcp_seq th_seq;                     /* 包序号 */
56     tcp_seq th_ack;                     /* 确认序号 */
57     #if BYTE_ORDER == LITTLE_ENDIAN
58     u_int th_x2:4,                         /* 还没有用到 */
59     th_off:4;                             /* 数据偏移 */
60     #endif
61     #if BYTE_ORDER == BIG_ENDIAN
62     u_int th_off:4,                     /* 数据偏移*/
63     th_x2:4;                             /* 还没有用到 */
64     #endif
65     u_char th_flags;
66     #define TH_FIN 0x01
67     #define TH_SYN 0x02
68     #define TH_RST 0x04
69     #define TH_PUSH 0x08
70     #define TH_ACK 0x10
71     #define TH_URG 0x20
72     #define TH_ECE 0x40
73     #define TH_CWR 0x80
74     #define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
75     u_short th_win;                        /* TCP滑动窗口 */
76     u_short th_sum;                     /* 头部校验和 */
77     u_short th_urp;                     /* 紧急服务位 */
78 };
79 
80 
81 #endif /* __SNIFFER_H__ */

  源码:

 1 #include "sniffer.h"
 2 
 3 void getPacket(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet)
 4 {
 5     static int id = 0;
 6     const struct sniff_ethernet *ethernet;             /* 以太网帧头部*/
 7     const struct sniff_ip *ip;                         /* IP包头部 */
 8     const struct sniff_tcp *tcp;                     /* TCP包头部 */
 9     const char *payload;                             /* 数据包的有效载荷*/
10     
11     int size_ethernet = sizeof(struct sniff_ethernet);
12     int size_ip = sizeof(struct sniff_ip);
13     int size_tcp = sizeof(struct sniff_tcp);
14     
15     ethernet = (struct sniff_ethernet*)(packet);
16     ip = (struct sniff_ip*)(packet + size_ethernet);
17     tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
18     payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);
19     
20     int sport = ntohs(tcp->th_sport);
21     int dport = ntohs(tcp->th_dport);
22     
23     //for QQ
24     if (dport != 8000 || sport > 4100)
25     {
26         return ;
27     }
28     printf("packet: %d\n", ++id);
29     printf("%s:%d -> ", inet_ntoa(ip->ip_src), sport);
30     printf("%s:%d \n", inet_ntoa(ip->ip_dst), dport);
31     printf("QQ:%d\n", packet[49]*16*16*16*16*16*16 +
32                       packet[50]*16*16*16*16 +
33                       packet[51]*16*16 +
34                       packet[52]);
35     
36     /*for test
37     int i;
38     for(i=0; i<pkthdr->len; ++i)
39     {
40         printf(" %02x", packet[i]);
41         if ((i + 1) % 16 == 0 )
42         {
43             printf("\n");
44         }
45         if ((i + 1) % 8 == 0 )
46         {
47             printf(" ");
48         }
49     }*/
50     
51     printf("\n");    
52 }
53 
54 int main(int argc, char **argv)
55 {
56     pcap_t *devic = NULL;
57     char *devStr = NULL;
58     char errBuf[PCAP_ERRBUF_SIZE] = "";
59     char *filter_rule = "dst port 8000";
60     struct bpf_program filter;
61     
62     devStr = pcap_lookupdev(errBuf);
63     if (!devStr)
64     {
65         printf("Error: %s\n", errBuf);
66         return -1;
67     }
68     printf("Success: %s\n", devStr);
69     
70     devic = pcap_open_live(devStr, 65535, 1, 0, errBuf);
71     if (!devic)
72     {
73         printf("Error: %s\n", errBuf);
74         return -1;
75     }
76     
77     pcap_compile(devic, &filter, filter_rule, 1, 0);
78     pcap_setfilter(devic, &filter);
79     
80     pcap_loop(devic, -1, getPacket, NULL);
81     
82     pcap_close(devic);
83     
84     return 0;
85 }    
86     

测试结果:

  

备注:

  在测试时发现,极少的情况OICQ协议里,含有"MEMO Operation(62)"的数据包中,会概率性出现非该测试QQ的另一个号码,原因未知... 当时忘了记录,最近实验了几次又一直没出现,没有图片了。

 

posted on 2015-04-03 16:29  小不r  阅读(3787)  评论(0编辑  收藏  举报