這一節是本系列教程的結尾了,內容也比較簡單,主要是對網絡協議進行分析,其實學過計算機網絡的同學完全可以略過
在整個項目中需要有一個頭文件存放各層協議的頭部定義,我把它們放在了head.h中,這個頭文件都有什么呢,首先放幾個關於協議的宏定義,這樣可以讓整個程序顯得更加清晰:
1 /* 網絡層協議類型 */ 2 #define IP 0x0800 3 #define ARP 0x0806 4 #define RARP 0x8035 5 6 /* 傳輸層類型 */ 7 #define ICMP 0x01 8 #define IGMP 0x02 9 #define TCP 0x06 10 #define EGP 0x08 11 #define UDP 0x11 12 #define IPv6 0x29 13 #define OSPF 0x59
之后就是存放協議的頭部定義了,對照着頭部定義,用合適的類型定義各個字段,比如IP協議頭:
1 typedef struct ip_address 2 { 3 u_char byte1; 4 u_char byte2; 5 u_char byte3; 6 u_char byte4; 7 }ip_address; 8 9 /* IPv4 首部 */ 10 typedef struct ip_header 11 { 12 u_char ver_ihl; // 版本 (4 bits) + 首部長度 (4 bits) 13 u_char tos; // 服務類型(Type of service) 14 u_short tlen; // 總長(Total length) 15 u_short identification; // 標識(Identification) 16 u_short flags_fo; // 標志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits) 17 u_char ttl; // 生存時間(Time to live) 18 u_char type; // 協議(Protocol) 19 u_short crc; // 首部校驗和(Header checksum) 20 ip_address saddr; // 源地址(Source address) 21 ip_address daddr; // 目的地址(Destination address) 22 u_int op_pad; // 選項與填充(Option + Padding) 23 }ip_header;
當然有些位置的意義是小於一個字節大小的,比如TCP協議中的標志位字段,有URG,ACK,PSH,RST,SYN,FIN,這些在之后用&&運算可以方便求得
當數據包被pcap_next_ex()函數捕獲后,我們需要其中的兩個參數,pcap_next_ex()函數原型如下:
1 int pcap_next_ex(pcap_t *, struct pcap_pkthdr **, const u_char **);
第一個參數是一個已打開的捕捉實例,也就是之前我們選擇的適配器,第二個參數是一個結構體,其中內容有:
1 struct pcap_pkthdr 2 { 3 struct timeval ts; ts是一個結構struct timeval,它有兩個部分,第一部分是1900開始以來的秒數,第二部分是當前秒之后的毫秒數 4 bpf_u_int32 caplen; 表示抓到的數據長度 5 bpf_u_int32 len; 表示數據包的實際長度 6 }
因為在某些情況下你不能保證捕獲的包是完整的,例如一個包長1480,但是你捕獲到1000的時候,可能因為某些原因就中止捕獲了,所以caplen是記錄實際捕獲的包長,也就是1000,而len就是1480。第三個參數就是數據包的內容后兩個參數是對我們有用的,將其傳遞到顯示數據包概略的函數ShowPacketList中
每當捕獲以個數據包,需要進行保存,在MFC中可以用CArray,CArray是一個動態數組,它的用法是:CArray<Object,Object> Var1;前一個參數是指定存儲在數組中的對象的類型,后一個參數是指定用於訪問存儲在數組中對象的參數類型。其實這兩個參數一般是一樣的類型,程序中存儲每次抓包的pkt_header和pkt_data就用如下CArray表示:
CArray<const struct pcap_pkthdr *,const struct pcap_pkthdr *> m_pktHeaders; CArray<const u_char *,const u_char *> m_pktDatas;
CArray類的一些常用成員函數有:GetAt返回在給定索引上的值;Add增加一個元素;GetSize獲得此數組中的元素數
存儲完捕獲的數據包,就可以利用pkt_data的信息來分析數據包,在分析數據包中的數據時注意,數據包的數據是按照小端模式存儲的,對於大於1字節的內容需要用noths或是nothl進行轉化(前者對應short,后者對應Long)。舉一個例子,比如數字0x12 34 56 78在內存中的表示形式為:0x78 | 0x56 | 0x34 | 0x12 ,這時就需要noths來轉化為正確的順序。顯示數據包概要的函數部分代碼如下:
1 ethernet_header *eh; 2 eh = (ethernet_header *)pkt_data; 3 str.Format(_T("%x:%x:%x:%x:%x:%x"),eh->saddr.byte1,eh->saddr.byte2,eh->saddr.byte3,eh->saddr.byte4,eh->saddr.byte5,eh->saddr.byte6); 4 m_list1.SetItemText(nCount,2,str); 5 str.Format(_T("%x:%x:%x:%x:%x:%x"),eh->daddr.byte1,eh->daddr.byte2,eh->daddr.byte3,eh->daddr.byte4,eh->daddr.byte5,eh->daddr.byte6); 6 m_list1.SetItemText(nCount,3,str); 7 str.Format(_T("%ld"),pHeader->len); 8 m_list1.SetItemText(nCount,4,str); 9 /*處理網絡層*/ 10 switch(ntohs(eh->type)) 11 { 12 case IP: 13 { 14 ip_header *ih; 15 const u_char *ip_data; 16 ip_data=pkt_data+14; 17 ih = (ip_header *)ip_data; 18 u_int ip_len;//IP首部長度 19 ip_len = (ih->ver_ihl & 0xf) * 4; 20 str.Format(_T("%d.%d.%d.%d"),ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4); 21 m_list1.SetItemText(nCount,6,str); 22 str.Format(_T("%d.%d.%d.%d"),ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4); 23 m_list1.SetItemText(nCount,7,str); 24 /*處理傳輸層*/ 25 switch(ih->type) 26 { 27 case TCP:
就這樣陷入一層一層的分析中,對於數據包的詳細分析,也就是在樹形結構中顯示的代碼也與次類似,不做分析。
在統計部分,除了有統計各種數據包的數目外,還有一部分是流量分析:
這一部分的實現也比較簡單,對於某些常用的軟件,都是通過固定端口進行數據傳輸的,比如QQ 默認采用 UDP 通訊方式,端口 8000,8001。如果 UDP 的兩個端口不通,會自動轉換到 TCP 80 端口或者 TCP 443 端口進行通訊。 QQ 同時也支持 HTTP 代理模式及 SOCK5代理模式。阿里旺旺采用 TCP 通訊方式,默認登錄端口為 16000,當 16000 端口不通時,則跳轉到 443 端口進行通訊。所以通過對固定端口的監聽,可以知道哪些軟件產生了流量(這樣做並不完美,有興趣的可以分析應用層協議)
1 if(ntohs( th->sport ) == 0x3E80 || ntohs( th->dport ) == 0x3E80) //阿里旺旺流量為TCP端口16000 2 m_wangCount++; 3 if(ntohs( th->sport ) == 0x747 || ntohs( th->dport ) == 0x747) //MSN流量為TCP端口1863 4 m_msnCount++;
寫在最后:
整體感覺編寫一個嗅探器的技術層面上並不困難,但是比較繁瑣,尤其是在界面方面,但是對理解網絡協議還是很有幫助的。如果您需要源代碼可以點此下載。源碼中有一些冗余,有些數據在傳遞過程中並不安全,有些變量為了方便放在了public中,還請酌情參考。