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