————————————————
版權聲明:本文為CSDN博主「Ezioooooo」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u012877472/article/details/49817875
一、配置(VS2015)
項目右鍵屬性,在C/C++目錄預處理器添加WPCAP和HAVE_REMOTE,預編譯頭改為不使用預編譯頭,鏈接器輸入wpcap.lib和ws2_32.lib,VC++目錄包含目錄添加下載的include文件,庫目錄添加下載的lib文件。
二、獲取設備列表
(編寫winpcap程序的第一件事)
pcap_findalldevs_ex()函數來實現這個功能:創建一個可以被函數pcap_open()打開的網絡設備鏈表
pcap_findalldevs_ex(
char * source, //字符指針,保存來源的位置,設為PCAP_SRC_IF_STRING;
struct pcap_rmthauth* auth, //指向pcap_rmtauth結構體的指針,保存需要遠程設備捕獲協議認證的信息。捕獲本地設備設為NULL;
pcap_if_t** alldevs, //一個pcap_if_t結構體指針,函數返回時用來保存找到的適配器的信息;
char* errbuf //用來保存錯誤信息;
); //返回值:如果返回0,表示函數運行成功,說明找到合適的適配器,通過alldevs參數返回;如果返回-1,發生錯誤,或者沒有在本地找到合適的適配器;
pcap_if_t結構體定義如下:
pcap_if* next; //指向鏈表的下一個節點,如果不為空指向一個pcap_if元素;
char* name; //字符串指針,存儲傳向pcap_open_live函數的設備名稱;
char* description; //設備的描述;
pcap_addr* addresses;//這項這個設備接口地址鏈表的第一個元素;
u_int flags; //當前唯一的值是PCAP_IF_LOOPBACK,當當前接口是回路接口的時候設置;
當我們完成設備列表的使用后,應當調用pcap_freealldevs()函數釋放內存資源。
三、獲取已安裝設備的詳細信息
函數pcap_findalldevs_ex()返回的pcap_if結構體中,都有一個pcap_addr的結構體,這個結構體用來保存地址信息。
pcap_addr* next;//指向下一個節點
sockaddr* addr;//一個地址列表
sockaddr* netmask;//一個掩碼列表
sockaddr* broadaddr;//一個廣播地址列表
sockaddr* dstaddr;//一個目的地址列表
sockaddr結構用來存儲與Windows套接字通訊的計算機上的一個IP地址。
四、打開適配器並捕獲數據包
①打開適配器:函數pcap_open():
pcap_t* pcap_open (
const char * source, //設備名稱
int snaplen, //制定要捕獲數據包中的哪些部分,將值定為65535確信總能收到完整數據包(比最大MTU大
int flags, //*最重要,用來指示適配器是否要被設置成混雜模式(不管這個數據包是不是發給我的,我都會去捕獲。WinPcap能捕獲其他主機的所有的數據包)
int read_timeout, //讀取超時時間
struct pcap_rmtauth * auth, //遠程機器驗證
char * errbuf //錯誤緩沖池,存儲錯誤信息
);
函數返回:一個可以作為調用參數的指向pcap_t結構的指針,並且指定一個WinPcap會話。如果出現錯誤,返回NULL,errbuf中存儲錯誤信息。
typedef struct pcap pcap_t //一個已打開的捕捉實例的描述符
②捕獲工作:
pcap_dispatch() 或 pcap_loop()函數:
int pcap_loop (
pcap_t * p, //pcap的句柄
int cnt, //捕獲包的數量,如果是負數表示永不停止直到出現錯誤
pcap_handler callback, //回調函數,當捕獲到數據包時調用此函數
u_char * user //留給用戶使用的
);
其中回調函數:
void(*) pcap_handler(
u_char *user, //函數pcap_loop()傳遞過來的,就是pcap_loop()函數中的user參數
const struct pcap_pkthdr *pkt_header, //表示捕獲到的數據包的基本信息
const u_char *pkt_data //表示捕獲到的數據包的內容
);
結構體:
struct pcap_pkthdr {
struct timeval ts; /* 時間戳 */
bpf_u_int32 caplen; /* 已捕獲部分的長度 */
bpf_u_int32 len; /* 該包的脫機長度 */
}; //保存捕獲到的包的基本信息,由pcap_loop函數自動填充
③不用回調方法捕獲:
pcap_next_ex()函數代替pcap_loop()函數來實現捕獲數據包,當只有顯示調用時才能捕獲數據包。
int pcap_next_ex (
pcap_t * p, //句柄
struct pcap_pkthdr ** pkt_header, //指向一個pcap_pkthdr結構的指針,用於保存捕獲數據包的基本信息
const u_char ** pkt_data //保存捕獲的數據
);
//捕獲一個數據包,然后用捕獲的數據包填充pkt_header和pkt_data參數,根據結果不同,返回不同的數字(1成功)。
五、過濾數據包
pcap_compile() 和 pcap_setfilter() 函數:
int pcap_compile(
pcap_t *p, //返回數據包捕獲的指針
struct bpf_program *fp, //一個bpf_program結構的指針,用於過濾,在pcap_compile()函數中被賦值。
char *str, //規定過濾規則
int optimize, //控制結果代碼的優化。規定了在結果代碼上的選擇是否被執行
bpf_u_int32 netmask); //指定本地網絡的網絡掩碼(該網卡的子網掩碼),通過pcap_lookupnet()獲取
將str指定的規則整合到分fp過濾程序中,並生成過濾程序入口地址,用於過濾選擇期望的數據包。成功返回0,否則返回-1。
過濾規則str由一個或多個原語組成,如果為""表示不進行過濾。原語通常由一個標識id(名字或序列)和限定詞(輸入(type,指明哪些東西是id所代表的,可能的輸入是host,net和port,默認host),方向(dir,id指明了一個特定的傳輸方向,可能的方向是src,dst,src or dst,默認src/dst),協議(proto,所匹配的協議ether,fddi,tr,ip,ip6,arp,rarp,decnet,tcp和udp,默認所有都可))組成。很復雜,記得查詢。
六、分析數據包
此時已經可以可以捕獲並過濾網絡流量,以TCP/UDP為例分析。

網絡中的數據包每經過一個層次都會加上那個層的報頭來標注一些重要的信息。
捕獲到的數據包首先有個mac報頭,14字節,包含6字節目的mac地址、6字節源mac地址,和2字節上一層協議(不關注mac),接下來是IP數據包有20字節的報頭。

IP報頭定義:
/* IPv4 首部 */
typedef struct ip_header {
u_char ver_ihl; // 版本 (4 bits) + 首部長度 (4 bits)
u_char tos; // 服務類型(Type of service)
u_short tlen; // 總長(Total length)
u_short identification; // 標識(Identification)
u_short flags_fo; // 標志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits)
u_char ttl; // 存活時間(Time to live)
u_char proto; // 協議(Protocol)
u_short crc; // 首部校驗和(Header checksum)
ip_address saddr; // 源地址(Source address)
ip_address daddr; // 目的地址(Destination address)
u_int op_pad; // 選項與填充(Option + Padding)
}ip_header;
通過首部長度(ip_len = (ih->ver_ihl & 0xf) * 4;)得到UDP數據包的位置。
UDP報頭定義:
/* UDP 首部*/
typedef struct udp_header {
u_short sport; // 源端口(Source port)
u_short dport; // 目的端口(Destination port)
u_short len; // UDP數據包長度(Datagram length)
u_short crc; // 校驗和(Checksum)
TCP報頭定義:
/* tcp 首部 */
typedef struct tcp_header {
u_short sport; //源端口
u_short dport; //目的端口
u_int th_seq; //序列號
u_int th_ack; //確認號
u_short doff : 4, hlen : 4, fin : 1, syn : 1, rst : 1, psh : 1, ack : 1, urg : 1, ece : 1, cwr : 1; //4 bits 首部長度,6 bits 保留位,6 bits 標志位
u_short th_window; //窗口大小
u_short th_sum; //校驗和
u_short th_urp; //緊急指針
}tcp_header;
}udp_header;
UDP報頭:

TCP報頭:

七、保存數據包到堆文件
①pcap_dump_open()函數打開一個可以保存數據的文件。
pcap_dumper_t* pcap_dump_open (
pcap_t * p, //一個libpcap存儲文件的描述符,對用戶不可見
const char * fname //要寫入的文件名
);//只有當接口打開時,調用 pcap_dump_open() 才是有效的。 這個調用將打開一個堆文件,並將它關聯到特定的接口上。
②pcap_dump()函數將數據包寫入用戶指定的文件中。
void pcap_dump (
u_char * user, //文件描述符dumpfp
const struct pcap_pkthdr * h, //pkt_header
const u_char * sp //要用pkt_data
);
八、從堆文件讀取數據包
pcap_open_offline()函數將堆文件打開。
pcap_t* pcap_open_offline (
const char * fname, //要打開的文件名
char * errbuf //保存錯誤信息
);
通過pcap_createsrcstr()函數根據新WinPcap語法創建一個源字符串。
int pcap_createsrcstr (
char * source, //要存入的源字符串
int type, //要創建的源字符串類型
const char * host, //遠程主機
const char * port, //遠程端口
const char * name, //我們要打開的文件名
char * errbuf //存儲錯誤信息
); //返回值:0:沒有錯誤;-1:創建出錯,錯誤寫入errbuf中
