本項目實現基於 libpcap 的網絡數據包捕獲程序,針對 QQ 客戶端登錄過程中的 UDP/OICQ 協議數據包,從以太網幀中提取 QQ 號碼(位於固定偏移量 49-52 字節,4 字節無符號整數)。涵蓋協議逆向分析、BPF 過濾器設計、數據包解析架構、端口特徵識別、異常處理等完整工程流程。
Request KEY (29)、Request login (98) 等攜帶 QQ 號。# 精確匹配 OICQ 協議包含 QQ 號的包 oicq && udp.srcport < 4100 # 僅 UDP 端口過濾(libpcap 支持) udp dst port 8000 and udp src port < 4100
經驗證,UDP 負載同偏移位置同樣包含明文 QQ 號,無需解析 OICQ 協議層,大幅簡化解析邏輯。
sniffer.h)#ifndef __SNIFFER_H__ #define __SNIFFER_H__ #include#include #include // ETHER_ADDR_LEN // 以太網幀頭 (14 字節) struct sniff_ethernet { u_char ether_dhost[ETHER_ADDR_LEN]; u_char ether_shost[ETHER_ADDR_LEN]; u_short ether_type; }; // IP 頭部 (最小 20 字節,含選項可變) struct sniff_ip { u_char ip_vhl; // 版本(4位) + 首部長度(4位) u_char ip_tos; u_short ip_len; u_short ip_id; u_short ip_off; u_char ip_ttl; u_char ip_p; // 協議號 (IPPROTO_UDP = 17) u_short ip_sum; struct in_addr ip_src, ip_dst; }; // UDP 頭部 (固定 8 字節) struct sniff_udp { u_short uh_sport; // 源端口 u_short uh_dport; // 目的端口 u_short uh_ulen; // 長度 u_short uh_sum; // 校驗和 }; #endif
ntohs()/ntohl() 轉換端口、長度、IP 地址。#pragma pack(1),或手動計算偏移(推薦手動偏移,避免編譯器差異)。getPacket)#include "sniffer.h"
void getPacket(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {
static int count = 0;
// 1. 手動計算偏移解析(避免結構體對齊陷阱)
const int eth_len = 14;
const u_char *ip_ptr = packet + eth_len;
// IP 版本與首部長度
u_char ip_vhl = ip_ptr[0];
int ip_hl = (ip_vhl & 0x0F) * 4; // 低4位 * 4 = 首部字節長度
if (ip_hl < 20) return; // 異常包
// 協議判斷:IP 头第 9 字節 (偏移 8) 為協議號
if (ip_ptr[9] != IPPROTO_UDP) return; // 非 UDP 丟棄
const u_char *udp_ptr = ip_ptr + ip_hl;
int udp_len = 8; // UDP 固定頭部 8 字節
// 端口提取 (網絡序轉主機序)
u_short sport = ntohs(*(u_short*)(udp_ptr + 0));
u_short dport = ntohs(*(u_short*)(udp_ptr + 2));
// 2. 過濾邏輯:目標端口 8000,源端口 4000-4099 (單機多 QQ 實例)
if (dport != 8000 || sport < 4000 || sport >= 4100) return;
// 3. 提取 QQ 號:UDP 負載起始 + 41 字節偏移 (UDP頭8 + 負載偏移41 = 總偏移49)
// 實測:以太網幀第 49-52 字節 (索引 48-51)
const u_char *payload = udp_ptr + udp_len;
if (header->caplen < eth_len + ip_hl + udp_len + 41 + 4) return; // 長度不足
uint32_t qq = ntohl(*(uint32_t*)(payload + 41)); // 大端序轉主機序
// 4. 輸出結果
char src_ip[INET_ADDRSTRLEN], dst_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, (void*)(ip_ptr+12), src_ip, sizeof(src_ip));
inet_ntop(AF_INET, (void*)(ip_ptr+16), dst_ip, sizeof(dst_ip));
printf("Packet #%d: %s:%d -> %s:%d QQ: %u\n",
++count, src_ip, sport, dst_ip, dport, qq);
}
int main(int argc, char **argv) {
char errbuf[PCAP_ERRBUF_SIZE];
char *dev = pcap_lookupdev(errbuf);
if (!dev) { fprintf(stderr, "找不到設備: %s\n", errbuf); return -1; }
printf("監聽設備: %s\n", dev);
// 打開設備:混雜模式、快照長度 65535、超時 1ms
pcap_t *handle = pcap_open_live(dev, 65535, 1, 1000, errbuf);
if (!handle) { fprintf(stderr, "打開失敗: %s\n", errbuf); return -1; }
// 編譯並設置 BPF 過濾器:只抓目的端口 8000 的 UDP
struct bpf_program fp;
char filter_exp[] = "udp dst port 8000";
if (pcap_compile(handle, &fp, filter_exp, 1, PCAP_NETMASK_UNKNOWN) == -1) {
fprintf(stderr, "過濾器編譯失敗: %s\n", pcap_geterr(handle));
return -1;
}
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "設置過濾器失敗: %s\n", pcap_geterr(handle));
return -1;
}
pcap_freecode(&fp);
// 進入無限循環抓包
pcap_loop(handle, -1, getPacket, NULL);
pcap_close(handle);
return 0;
}
# 編譯 (需鏈接 libpcap) gcc sniffer.c -o sniffer -lpcap # 運行 (需要 root 權限或 CAP_NET_RAW) sudo ./sniffer
MEMO Operation (62) 包中出現非目標 QQ 號,原因未明(可能為群消息轉發、好友狀態同步等)。建議結合命令字節 (負載偏移 0-1) 進一步過濾登錄相關命令 (29, 98)。pcap_loop 回調開銷大,可用 pcap_dispatch 批量處理或 PF_RING/AF_XDP 零拷貝框架。警告:網絡嗅探屬於敏感技術。僅授權在自己管理的網絡、針對自己帳號、用於學習協議分析/安全審計時使用。未經授權捕獲他人數據包、提取帳號信息,違反《網絡安全法》《計算機信息網絡國際聯網管理暫行規定》等法律法規,構成非法獲取計算機信息系統數據罪風險。本代碼僅供教學研究參考,請嚴格遵守法律法規與道德規範。
技術總結:本項目展示了從協議逆向分析 (Wireshark) → 特徵提取 (固定偏移 QQ 號) → BPF 過濾器設計 (udp dst port 8000) → libpcap 異步回調架構 → 手動偏移解析避免對齊坑 → 字節序轉換 (ntohs/ntohl) 的完整網絡嗅探工程閉環。核心難點在於協議層疊偏移精確計算與異常包健壯性處理。