1、WinPcap
WinPcap是一個基于Win32平台的,用于捕獲網絡資料包并進行分析的開源庫。WinPcap提供了以下功能:
- 捕獲原始資料包,無論它是發往某台機器的,還是在其他裝置(共享媒介)上進行交換的;
- 在資料包發送給某應用程式前,根據使用者指定的規則過濾資料包;
- 将原始資料包通過網絡發送出去;
- 收集并統計網絡流量資訊。
WinPcap的功能與LibPcap的功能很相似,但是WinPcap主要用在Win32平台,且提供了發送網絡資料包的功能。
1.1、WinPcap的下載下傳與安裝
要使用WinPcap開發程式需要下載下傳兩部分:一部分是WinPcap的驅動和運作庫,一部分是WinPcap的開發包。WinPcap驅動和運作庫的下載下傳可以通路:https://www.winpcap.org/install/default.htm,如下所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSdW5WZwhWblZGZtJGa4dVYopkMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4UjMwEDNwETM4ATNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
WinPcap開發包的下載下傳可以通路https://www.winpcap.org/devel.htm,如下圖所示:
需要注意的是雖然下載下傳的是4.1.2版本的開發包,但是仍然相容4.1.3版本的WinPcap驅動和動态庫。
WinPcap程式的安裝很簡單,隻需按照提示進行就可以,需要注意的是,需要保持如下圖中的複選框的選中狀态:
然後點選Install安裝即可。
下載下傳WinPcap的開發包并解壓:
需要用到的隻有Include和Lib兩個檔案夾。重建立立一個名為WinPcap的檔案夾,将這兩個檔案夾拷貝到WinPcap中,然後将WinPcap拷貝到程式工程目錄下。
1.2、WinPcap開發環境配置
在進行開發前,需要進行一些配置:
1、在每一個使用了庫的源程式中,将 pcap.h 頭檔案包含(include)進來:
2、如果你的程式使用了WinPcap的遠端捕獲功能,那麼在預處理定義中加入HAVE_REMOTE。不要直接把remote-ext.h直接加入到源檔案中:
3、設定VC++的連結器(Linker),把wpcap.lib庫檔案和ws2_32.lib庫檔案包含進來:
4、将頭檔案目錄和庫檔案目錄添加到工程目錄下:
當配置完成之後就可以進行WinPcap程式的開發了。
2、捕獲資料包
2.1、擷取本地網絡裝置清單
在WinPcap中,擷取本地網絡裝置清單是通過函數pcap_findalldevs實作的,函數原型如下所示:
int pcap_findalldevs (pcap_if_t **alldevsp, char *errbuf);
// alldevsp: 存儲本地網絡裝置清單
// errbuf: 存儲錯誤資訊
// 傳回值:int,如果發生錯誤将傳回-1,執行成功将傳回0
具體執行個體如下所示:
pcap_if_t* alldevs;
char errbuf[PCAP_ERRBUF_SIZE];
// 擷取本地機器的裝置清單
if( pcap_findalldevs(&alldevs, errbuf) == -1)
{
printf(">>> Error - Error in pcap_findalldevs: %s\n", errbuf);
}
捕獲的網絡裝置清單均儲存在結構體連結清單alldevs中。
2.2、列印本地網絡裝置清單
要列印網絡裝置的資訊,首先要知道pcap_if_t結構體的具體組成,pcap_if_t結構體的原型如下所示:
struct pcap_if
{
pcap_if* next, //指向下一個元素,為NULL則表示該元素為清單的最後一個元素
char* name,//表示該網絡裝置的名稱
char* description,//表示該網絡裝置的描述
pcap_addr* address,//該網絡裝置中的位址資訊,包括IPV4、IPV6、子網路遮罩等
u_int flags,//用于表示是否為回環端口(loopback interface)
};
typedef struct pcap_if pcap_if_t;
可以看出,pcap_if_t結構體是pcap_if結構體的另外一種表示。結構體内包含了網絡裝置的名稱、描述、位址資訊等。
2.3、打開要操作的網絡裝置
打開網絡裝置采用pcap_open函數,pcap_open函數原型如下所示:
pcap_t* pcap_open(const char* source, //接口裝置名,pcap_if_t結構體中的name
int snaplen, //捕獲資料的儲存長度,一本可以設定為65535,65536保證能捕獲到不同資料鍊路層上的每個資料包的全部内容
int flags,//是否設定為混雜模式,為0則表示不設定為混雜模式
int read_timeout, //捕獲資料時的逾時時間,設定為0表示不抓取到資料不會傳回,設定為-1表示不管有沒有抓到資料都會傳回,設定為1000表示,如果沒有抓取到資料則會等待1000ms再傳回
struct pcap_rmtauth* auth, //遠端機器驗證,如果不是遠端捕獲,則設為NULL
char* errbuf //存儲錯誤資訊
);
傳回值為pcap_t類型的指針,可以了解為打開的接口裝置的句柄。接下來的操作都是針對該句柄進行的。如果函數執行錯誤則傳回NULL并在errbuf中存儲錯誤資訊。
具體示例如下所示:
// 打開想要捕獲的網絡裝置
pcap_t* g_Capture_Adhandle = NULL;
if((g_Capture_Adhandle = pcap_open(d->name,// 要捕獲的接口裝置名
65536, // 65536保證能捕獲到不同資料鍊路層上的每個資料包的全部内容
0, // 不設定為混雜模式
0, // 讀取逾時時間。設定為0,表示如果沒有資料包到達的話,則讀取操作永遠不會傳回。
NULL, // 遠端機器驗證
errbuf)) == NULL)// 錯誤緩沖區
{
printf("\nUnable to open the adpater. %s is not supported by Winpcap.\n", d->name);
pcap_freealldevs(alldevs);//釋放所有的機器清單
return;
}
2.4、設定捕獲過濾器
在網絡中傳輸的有各種各樣的資料,為了準确抓取網絡中傳輸的資料,需要在抓取資料時設定捕獲過濾器,将我們不需要的資料過濾掉,之抓取我們想要的資料。設定過濾器時需要用到兩個函數:pcap_compile和pcap_setfilter。
pcap_compile函數是用來編譯過濾器,pcap_compile函數的原型如下所示:
int pcap_compile(pcap_t* p, //打開的接口裝置的句柄,pcap_open函數的傳回值
struct bpf_program* fp, //bpf_program結構體指針,函數執行成功後會填入内容
char* str, //存儲過濾規則的字元串
int optimize, //用于控制是否會在結果代碼(resulting code)上執行優化,一般設定為1
bpf_u_int32 netmask //用于指定要捕獲的網絡裝置上的IPV4的掩碼
);
函數如果執行失敗,則會傳回-1。
pcap_setfilter函數是用來設定過濾器的,是将一個過濾器與核心捕獲回話相關聯,pcap_setfilter函數原型如下所示:
int pcap_setfilter(pcap_t* p, //打開的接口裝置的句柄,pcap_open函數的傳回值
struct bpf_program* fp, //pcap_compile函數執行後生成的bpf_program結構體指針,已包含相關資料
);
函數如果執行失敗,則會傳回-1;執行成功則會傳回0。
設定過濾器的執行個體如下所示:
// 首先獲得掩碼
u_int netmask;
pcap_if_t* d; //選中的網絡接口裝置
pcap_t* g_Capture_Adhandle; //打開的網絡裝置的句柄
if(d->addresses != NULL)
{
// 獲得該接口的第一個位址的掩碼
netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
}
else
netmask = 0xffffffff; // 如果接口沒有位址, 那麼就設定一個C類掩碼
struct bpf_program fcode;
char packet_filter[] = "ip and udp";//過濾器,表示隻抓取UDP封包
if(pcap_compile(g_Capture_Adhandle, &fcode, packet_filter, 1, netmask) < 0)
{
printf("\nUnable to compile the packet filter.Check the Syntax!\n");
pcap_freealldevs(alldevs);
return;
}
// 設定過濾器
if(pcap_setfilter(g_Capture_Adhandle, &fcode) < 0)
{
fprintf(stderr, "\nError setting the filter!\n");
pcap_freealldevs(alldevs);
return;
}
2.5、開始捕獲資料
捕獲資料可以采取多種方式進行,可以采用pcap_loop通過回調函數的方式接收資料,也可以通過pcap_next_ex直接捕獲,下面主要介紹通過pcap_next_ex函數捕獲資料的方式。pcap_next_ex函數原型如下所示:
int pcap_next_ex(pcap_t* p, //要捕獲資料的接口裝置的句柄,pcap_open函數的傳回值
struct pcap_pkthdr** pkt_header, //捕獲資料的一些資訊
const u_char** pkt_data //捕獲的資料
);
函數的傳回值:
1:函數執行成功
0:讀取逾時
-1:發生錯誤
-2:讀取離線檔案時,當讀取到EOF時會傳回-2
結構體pkt_header存儲了關于捕獲到的資料一些基本資訊,結構體原型如下所示:
struct pcap_pkthdr
{
timeval ts, //時間戳
bpf_u_int32 caplen,//捕獲到的資料長度
bpf_u_int32 len //資料長度
};
捕獲資料的具體執行個體如下所示:
int res;
struct pcap_pkthdr* header;
const u_char* pkt_data;
if( (res = pcap_next_ex(g_Capture_Adhandle, &header, &pkt_data)) >= 0)
{
if(res == 0)
{
printf("\n>>> Time out.\n");
continue;
}
else
{
int recv_data_length = header->len;//接收到的資料長度
char msg[MAX_LENGTH] = {0}; // MAX_LENGTH > recv_data_length
memcpy(msg, (void *)(pkt_data), recv_data_length);
}
}
3、發送資料
winpcap另外一個很強大的功能就是資料包的發送功能,它能夠發送任何類型/協定的資料包,隻需要将資料包的格式轉換為對應協定的資料格式即可。
3.1 單個發送資料包
winpcap中發送單個資料包的函數是pcap_sendpacket,函數原型如下所示:
int pcap_sendpacket(pcap_t* p, //要發送資料的接口裝置的句柄,pcap_open函數的傳回值
u_char* buf, //要發送的資料
int size //要發送的資料長度
);
函數的傳回值:
1:函數執行發送資料失敗
0:函數執行成功
具體執行個體如下所示:
pcap_t* g_Capture_Adhandle; // 要發送資料的網絡接口裝置的句柄
int len; // 要發送的資料的長度
u_char* data; //要發送的資料,資料長度為len
if( pcap_sendpacket(g_Capture_Adhandle, data, len) != 0 )
{
printf("\nError - Error sending the packet;\n", pcap_geterr(g_Capture_Adhandle));
return 0;
}
3.2、隊列發送資料包
pcap_sendpacket雖然能夠向目标網絡接口裝置發送資料,但是隻能單個發送,且執行效率并不是很高。winpcap中有另外一個機制用于發送資料,那就是pcap_sendqueue_transmit,即通過隊列的方式進行發送,采用這種方式,會一次性向目标裝置發送隊列内的資料,效率比較高。相關的函數有pcap_sendqueue_alloc、pcap_sendqueue_queue、pcap_sendqueue_transmit。
其中pcap_sendqueue_alloc是用來申請隊列的長度,機關為位元組,是以計算出來的長度需要精确到位元組,函數原型:pcap_send_queue* pcap_sendqueue_alloc(u_int memsize);其中傳回值為該發送隊列的句柄。
pcap_sendqueue_queue是用來将單個資料添加到隊列中,函數原型為:int pcap_sendqueue_queue(pcap_send_queue* queue, const struct pcap_pkthdr* pkt_header, const u_char* pkt_data);其中queue為pcap_sendqueue_alloc函數建立的隊列句柄,pkt_header為為資料包添加的資料標頭,pkt_data為要發送的資料。
pcap_sendqueue_transmit是用來将資料發送出去的,函數原型為:u_int pcap_sendqueue_transmit(pcap_t* p, pcap_send_queue* queue, int sync);其中p為要發送資料的網絡接口裝置句柄,queue為建立的發送隊列,sync為同步标志,如果非0表示發送過程将是同步進行,即隻有時間戳相符的資料包才會被處理。這個操作會消耗大量的CPU資源,但是資料包的處理很精确。
通過隊列發送資料包的具體執行個體如下所示:
pcap_t* g_Capture_Adhandle; // 要發送資料的網絡接口裝置的句柄
pcap_send_queue* s_queue;
u_char winpcap_send_data[DATA_LENGTH] = {0}; //要發送的資料,資料長度為DATA_LENGTH
// 計算發送隊列的大小= (包大小+標頭長度)* 隊列最大個數
u_int alloc_len = (DATA_LENGTH + sizeof(struct pcap_pkthdr)) * QueueLen;
s_queue = pcap_sendqueue_alloc(alloc_len); // 配置設定長度為alloc_len的發送隊列
struct pcap_pkthdr pkt_header;
pkt_header.ts = ts;
pkt_header.caplen = packet_send_len;
pkt_header.len = packet_send_len;
//會執行多次,将多有的資料都添加到隊列中
if(pcap_sendqueue_queue(s_queue, &pkt_header, send_data) == -1)
{
printf(">>> Warning: Packet buffer is too small, not all the packets will be sent!\n");
}
int sent_len; //真實發送出去的資料隊列的長度
if( (sent_len = pcap_sendqueue_transmit(g_Capture_Adhandle, s_queue, FALSE)) < s_queue->len)
{
printf(">>> Error: An error occurred sending the packets: %s, only %d bytes were sent.\n", pcap_geterr(g_Capture_Adhandle), sent_len);
//continue;
}
cur_queue_len = 0;
s_queue->len = 0; // 發送完一個隊列之後一定記得将隊列的長度置為0
pcap_sendqueue_destroy(s_queue);// 隊列使用完之後一定記得要釋放隊列