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