最近想在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的另一个号码,原因未知... 当时忘了记录,最近实验了几次又一直没出现,没有图片了。