MFC+WinPcap編寫一個嗅探器之七(協議)


這一節是本系列教程的結尾了,內容也比較簡單,主要是對網絡協議進行分析,其實學過計算機網絡的同學完全可以略過

在整個項目中需要有一個頭文件存放各層協議的頭部定義,我把它們放在了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中,還請酌情參考。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM