天天看點

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

1、WinPcap

WinPcap是一個基于Win32平台的,用于捕獲網絡資料包并進行分析的開源庫。WinPcap提供了以下功能:

  • 捕獲原始資料包,無論它是發往某台機器的,還是在其他裝置(共享媒介)上進行交換的;
  • 在資料包發送給某應用程式前,根據使用者指定的規則過濾資料包;
  • 将原始資料包通過網絡發送出去;
  • 收集并統計網絡流量資訊。

WinPcap的功能與LibPcap的功能很相似,但是WinPcap主要用在Win32平台,且提供了發送網絡資料包的功能。

1.1、WinPcap的下載下傳與安裝

要使用WinPcap開發程式需要下載下傳兩部分:一部分是WinPcap的驅動和運作庫,一部分是WinPcap的開發包。WinPcap驅動和運作庫的下載下傳可以通路:https://www.winpcap.org/install/default.htm,如下所示: 

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

WinPcap開發包的下載下傳可以通路https://www.winpcap.org/devel.htm,如下圖所示:

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

 需要注意的是雖然下載下傳的是4.1.2版本的開發包,但是仍然相容4.1.3版本的WinPcap驅動和動态庫。

WinPcap程式的安裝很簡單,隻需按照提示進行就可以,需要注意的是,需要保持如下圖中的複選框的選中狀态:

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

 然後點選Install安裝即可。

下載下傳WinPcap的開發包并解壓:

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

 需要用到的隻有Include和Lib兩個檔案夾。重建立立一個名為WinPcap的檔案夾,将這兩個檔案夾拷貝到WinPcap中,然後将WinPcap拷貝到程式工程目錄下。

1.2、WinPcap開發環境配置

在進行開發前,需要進行一些配置:

1、在每一個使用了庫的源程式中,将 pcap.h 頭檔案包含(include)進來:

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

 2、如果你的程式使用了WinPcap的遠端捕獲功能,那麼在預處理定義中加入HAVE_REMOTE。不要直接把remote-ext.h直接加入到源檔案中:

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

 3、設定VC++的連結器(Linker),把wpcap.lib庫檔案和ws2_32.lib庫檔案包含進來:

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

4、将頭檔案目錄和庫檔案目錄添加到工程目錄下:

WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料
WinPcap - 捕獲資料、發送資料1、WinPcap2、捕獲資料包3、發送資料

 當配置完成之後就可以進行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);// 隊列使用完之後一定記得要釋放隊列