最近想在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的另一個號碼,原因未知... 當時忘了記錄,最近實驗了幾次又一直沒出現,沒有圖片了。