最近想在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。

  头文件:

 #ifndef __SNIFFER_H__
#define __SNIFFER_H__ #include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h> /* 以太网帧头部 */
#define ETHER_ADDR_LEN 6 struct sniff_ethernet{
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
u_short ether_type;
}; /* IP数据包的头部 */
struct sniff_ip{
#if BYTE_ORDER == LITTLE_ENDIAN
u_int ip_hl:, /* 头部长度 */
ip_v:; /* 版本号 */
#if BYTE_ORDER == BIG_ENDIAN
u_int ip_v:, /* 版本号 */
ip_hl:; /* 头部长度 */
#endif
#endif /* not _IP_VHL */
u_char ip_tos; /* 服务的类型 */
u_short ip_len; /* 总长度 */
u_short ip_id; /* 包标志号 */
u_short ip_off; /* 碎片偏移 */
#define IP_RF 0x8000 /* 保留的碎片标志 */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* 多碎片标志*/
#define IP_OFFMASK 0x1fff /* 分段位 */
u_char ip_ttl; /* 数据包的生存时间 */
u_char ip_p; /* 所使用的协议 */
u_short ip_sum; /* 校验和 */
struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
}; /* TCP 数据包的头部 */
typedef u_int tcp_seq; struct sniff_tcp{
u_short th_sport; /* 源端口 */
u_short th_dport; /* 目的端口 */
tcp_seq th_seq; /* 包序号 */
tcp_seq th_ack; /* 确认序号 */
#if BYTE_ORDER == LITTLE_ENDIAN
u_int th_x2:, /* 还没有用到 */
th_off:; /* 数据偏移 */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_int th_off:, /* 数据偏移*/
th_x2:; /* 还没有用到 */
#endif
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
u_short th_win; /* TCP滑动窗口 */
u_short th_sum; /* 头部校验和 */
u_short th_urp; /* 紧急服务位 */
}; #endif /* __SNIFFER_H__ */

  源码:

 #include "sniffer.h"

 void getPacket(u_char *arg, const struct pcap_pkthdr *pkthdr, const u_char *packet)
{
static int id = ;
const struct sniff_ethernet *ethernet; /* 以太网帧头部*/
const struct sniff_ip *ip; /* IP包头部 */
const struct sniff_tcp *tcp; /* TCP包头部 */
const char *payload; /* 数据包的有效载荷*/ int size_ethernet = sizeof(struct sniff_ethernet);
int size_ip = sizeof(struct sniff_ip);
int size_tcp = sizeof(struct sniff_tcp); ethernet = (struct sniff_ethernet*)(packet);
ip = (struct sniff_ip*)(packet + size_ethernet);
tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp); int sport = ntohs(tcp->th_sport);
int dport = ntohs(tcp->th_dport); //for QQ
if (dport != || sport > )
{
return ;
}
printf("packet: %d\n", ++id);
printf("%s:%d -> ", inet_ntoa(ip->ip_src), sport);
printf("%s:%d \n", inet_ntoa(ip->ip_dst), dport);
printf("QQ:%d\n", packet[]****** +
packet[]**** +
packet[]** +
packet[]); /*for test
int i;
for(i=0; i<pkthdr->len; ++i)
{
printf(" %02x", packet[i]);
if ((i + 1) % 16 == 0 )
{
printf("\n");
}
if ((i + 1) % 8 == 0 )
{
printf(" ");
}
}*/ printf("\n");
} int main(int argc, char **argv)
{
pcap_t *devic = NULL;
char *devStr = NULL;
char errBuf[PCAP_ERRBUF_SIZE] = "";
char *filter_rule = "dst port 8000";
struct bpf_program filter; devStr = pcap_lookupdev(errBuf);
if (!devStr)
{
printf("Error: %s\n", errBuf);
return -;
}
printf("Success: %s\n", devStr); devic = pcap_open_live(devStr, , , , errBuf);
if (!devic)
{
printf("Error: %s\n", errBuf);
return -;
} pcap_compile(devic, &filter, filter_rule, , );
pcap_setfilter(devic, &filter); pcap_loop(devic, -, getPacket, NULL); pcap_close(devic); return ;
}

测试结果:

  

备注:

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

最新文章

  1. angularjs——插值字符串
  2. C# 创建Windows Service
  3. iOS---SQLite数据库框架之FMDB -Swift
  4. LeetCode题解-----First Missing Positive
  5. zepto源码--整体框架--学习笔记
  6. 给用户添加sudo权限
  7. 关于padding
  8. 同一个页面多个CALayer重绘的办法
  9. Apriori算法Python实现
  10. Java 测试并行编程(三)
  11. github的拉取、提交,创建分支与合并
  12. 读取FTP上的某个文本文档内容到本地
  13. 将golang中变量重置为零的reflect方法
  14. hello C#
  15. sql的一个查询,情景:a表中存在的数据,且在b表中不存在 (not in,not exists
  16. maven工程插件配置
  17. python寻找list中最大值、最小值并返回其所在位置
  18. SQL一些问题
  19. timer Compliant Controller project (3)--bom and sch
  20. Django基础之Model操作

热门文章

  1. HDU 1045 Fire Net(图匹配)
  2. POJ 1734 Sightseeing trip
  3. GOTO (Transact-SQL)
  4. xamarin提供在线检查.net代码是否支援xamarin,ios,android
  5. Best Time to Buy and Sell Stock II ——LeetCode
  6. Event — Windows API
  7. java的 IO流之缓冲流(转载)
  8. 关于Web Api的HelpPage文档注释问题
  9. Sublime Text 常用快捷键
  10. qDebug 学习小结