WinPcap開發(一):零基礎入門
0×00 前言
網絡編程在網絡安全方面具有舉足輕重的作用,如何快捷高效的監聽、分析、構造網絡流量,成為很多安全從業者需要解決的重點問題。而winpcap這一免費開源項目恰好可以為win32應用程序提供訪問網絡底層的能力,所以其成為了相關網絡編程的首選開發工具。
0×01 winpcap是什么?
winpcap(windows packet capture)是windows平臺下一個免費的網絡訪問系統,可用于windows系統下的網絡編程。著名的wireshark便是基于winpcap開發的,大家在安裝wireshark中可以看到winpcap驅動程序的安裝過程。
有關winpcap的介紹網絡上很多,百科里面介紹的也很詳細,我就不再copy了。需要注意的一點是,winpcap并不是一個簡單的library,而是一個針對Win32平臺上的抓包和網絡分析的一個架構,它包括一個核心態的包過濾器,一個底層的動態鏈接庫(packet.dll)和一個高層的不依賴于系統的庫(wpcap.dll)。所以它只能“嗅探”到物理線路上的數據包,而不具備攔截的能力,因此不適用于個人防火墻等項目。
0×02 你需要準備些什么?
本系列文章主要帶大家認識和了解如何利用winpcap網絡編程技術進行網絡的協議分析、流量統計及網絡探測掃描等,這里我們并不會去深硬的解讀相關源代碼,而是以輕松的方式結合實驗來對相關原理進行深入理解。在本系列文章中,筆者從簡到難,簡明介紹winpcap架構原理、相關環境搭建及快速編寫核心代碼。但是在開始前,讀者需要有一些相關基礎:了解網絡協議相關基礎知識,掌握一門winpcap開發庫支持的編程語言,自己能動手實踐編寫一些例子。Winpcap提供的開發接口原生是c語言的,不過熱心腸的程序猿們已經為其他語言的使用提供了封裝,比如java、.net、python,好像連易語言都有。本系列文章將使用c語言來進行各種實驗,有興趣的讀者可以將其轉換成自己熟悉的語言來動手實踐。
0×03 你能學到什么?
有關winpcap開發的文章在網上很容易找到,但是更多的都是對于代碼的講解,筆者在本文盡量系統性的從原理層面結合各個應用場景來介紹相關知識:
1. Winpcap獲取網卡基本信息及收發數據包
2. 存活主機探測
3. 端口掃描
4. Arp欺騙
5. 中間人攻擊的簡單實現
6. 流量統計與分析
0×04 知識補充
進行下面的介紹前,我們需要了解幾個名詞的關系。
winpcap(windows packet capture)是windows平臺下一個免費的網絡訪問系統,可用于windows系統下的網絡編程。linux 平臺下對應的開發包是libpcap。
Wireshark是基于winpcap處理網絡驅動層。
Wpdpack是winpcap的開發包,提供開發相關程序的接口。
0×05 環境準備
首先根據你所選擇的開發語言選擇對應的編譯器,筆者使用c語言,利用VS2012進行相關開發。
安裝好編譯器后,進行相關配置。下載 wpdpack 點擊這里
初學者可以選擇里面的Examples進行編譯,可以看到找不到頭文件,及相關庫。
這是因為 wpdpack 中的相關庫還沒有引入到編譯環境中
將 wpdpack 包中的 Include 和 lib 文件夾中的文件添加到 VS 的相關目錄下即可編譯通過。將編譯后的程序進行運行則出現以下錯誤。
這是由于運行時缺乏動態鏈接庫導致,最簡單的方法是直接下載并安裝 winpcap 驅動程序 下載
如果你覺得這樣子很麻煩,也可以采用簡易方法。程序在運行時只需要 winpcap 在 system32 下面釋放的 wpcap.dll 和 packet.dll ,還有 driver 下面的 npf.sys ,所以不需要完整安裝 winpcap ,而選擇只復制以上三個文件到對應目錄中即可。
本節筆者將采用著名的 Arpspoof 源碼進行相關講解,源碼下載地址 http://www.verysource.com/code/2287464_1/arpspoof.cpp.html
0×06 枚舉可用網絡適配器資源
在使用 winpcap 進行收發數據包時,需指定對應的網卡,所以有必要列出計算機上所有可用的網絡適配資源。
列取網卡信息的核心代碼:
// 該函數在 Arpspoof 程序中,筆者進行了詳細注釋
void ListAdapters()
{
pcap_if_t alldevs; //用于存儲網卡鏈表的頭指針
pcap_if_t d; //用于遍歷網卡鏈表的臨時變量
int i = 0; //記錄網卡個數
char errbuf[ PCAP_ERRBUF_SIZE ] ;//存儲錯誤信息
char szGateIPAddr[16] ;//網卡對應網關地址
char *p; //網卡名詞
char szIPAddr[16]; //網卡對應IP
unsigned char ucPhysicalAddr[6]; //網卡對應的MAC地址
if (pcap_findalldevs(&alldevs, errbuf) == -1) //獲取網卡鏈表
{
fprintf( stderr , "Error in pcap_findalldevs: %s\n" , errbuf);
return ;
}
for (d=alldevs; d; d=d->next) //遍歷網卡鏈表
{
if (d->addresses != NULL && (p = strchr(d->name, '{' )) != NULL
&& Getadapterbyname(p, szIPAddr, ucPhysicalAddr,szGateIPAddr)) //獲取網卡的對應信息
{
for ( int j = strlen(d->description) - 1; j > 0; j--) //對網卡的描述信息格式化
{
if (d->description[j] == 0x20) d->description[j] = '' ;
else break;
}
printf( "\n %d. %s\n\tIP Address. . . . . : %s\n" , i, d->description, szIPAddr); //格式化輸出網卡信息
printf( "\tPhysical Address. . : %.2X-%.2X-%.2X-%.2X-%.2X-%.2X\n" ,
ucPhysicalAddr[0], ucPhysicalAddr[1], ucPhysicalAddr[2],
ucPhysicalAddr[3], ucPhysicalAddr[4], ucPhysicalAddr[5]);
printf( "\tDefault Gateway . . : %s\n" , szGateIPAddr);
i ++;
}
}
if (i==0)
{
printf( "\nNo interfaces found! Make sure WinPcap is installed.\n" );
return ;
}
pcap_freealldevs(alldevs); //釋放網卡鏈表
}</code></pre>
科普 Tips :
“網卡”是神馬?
計算機與外界局域網的連接是通過主機箱內插入一塊網絡接口板(或者是在筆記本電腦中插入一塊 PCMCIA 卡)。網絡接口板又稱為通信適配器或網絡適配器( network adapter )或網絡接口卡 NIC ( Network Interface Card ),但是更多的人愿意使用更為簡單的名稱“網卡”。
利用上面的程序,我們可以查看計算機上可利用的所有網卡資源,以便選擇相應的網卡資源進行相關操作。為了便于觀察,我們在 VMware 虛擬機中進行,首先在對應的虛擬機設置中增加硬件選項中添加多塊網卡,然后配置相應的 IP ,運行程序,可以得到對應網卡的名稱描述、 IP 地址、 MAC 地址等。


0×07 如何構造和發送數據包
上一步我們列出了所有的可用網卡資源,在發送數據包前,需要打開對應的網卡來進行發送數據包的操作。這里使用的函數是 pcap_open_live :
函數名稱:pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
函數功能:獲得用于捕獲網絡數據包的數據包捕獲描述字。
參數說明: device 參數為指定打開的網絡設備名。 snaplen 參數定義捕獲數據的最大字節數。 promisc 指定是否將網絡接口置于混雜模式。 to_ms 參數指定超時時間(毫秒)。 ebuf 參數則僅在 pcap_open_live() 函數出錯返回 NULL 時用于傳遞錯誤消息。
返回值:打開的網卡句柄
Arpspoof 中將網卡的打開操作進行了如下封裝,調用時直接輸入網卡序號即可,程序會對參數 2 、 3 、 4 進行初始化設置:
pcap_t OpenAdapter( int uIndexofAdapter, char szIPSelf[],
unsigned char ucPhysicalAddr[], char szGateIPAddr[] )
{
pcap_if_t alldevs;
pcap_if_t d;
pcap_t fp = NULL;
int i = 0;
char errbuf[PCAP_ERRBUF_SIZE], p;
/ 這個API用來獲得網卡的列表 */
if ( pcap_findalldevs( &alldevs, errbuf ) == -1 )
{
fprintf( stderr, "Error in pcap_findalldevs: %s\n", errbuf );
return( NULL);
}
/* 顯示列表的響應字段的內容 */
for ( d = alldevs; d; d = d->next )
{
if ( d->addresses != NULL && (p = strchr( d->name, '{' ) ) != NULL
&& Getadapterbyname( p, szIPSelf, ucPhysicalAddr, szGateIPAddr ) )
{
if ( i == uIndexofAdapter )
{
if ( (fp = pcap_open_live( d->name, /* 設備名稱 */
65536, /* portion of the packet to capture. */
/* 65536 grants that the whole packet will be captured on all the MACs. */
1, /* 混雜模式 */
1, /* 讀超時為1ms,越小越好 */
errbuf /* error buffer */
) ) == NULL )
{
fprintf( stderr, "\nUnable to open the adapter. \
%s is not supported by WinPcap\n", d->name );
pcap_freealldevs( alldevs );
return( NULL);
}else {
/ 去掉網卡注釋右邊的空格 /
for ( int j = strlen( d->description ) - 1; j > 0; j-- )
{
if ( d->description[j] == 0x20 )
d->description[j] = '';
else
break;
}
printf( "[*] Bind on %s %s ...\n", szIPSelf, d->description );
return( fp);
}
}
i++;
}
}
if ( i == 0 )
{
printf( "\nNo interfaces found! Make sure WinPcap is installed.\n" );
return( FALSE);
}
/* We don't need any more the device list. Free it */
pcap_freealldevs( alldevs );
return( NULL);
}</code></pre>
使用范例:
pcap_t *adhandle; // 網卡句柄
unsigned char ucSelf[6];
char szIPSelf[16], szIPGate[16];
if ((adhandle = OpenAdapter( 0 , szIPSelf, ucSelf, szIPGate)) == NULL )
{
printf( "[!] Open adatper error!\n" );
return FALSE ;
}</code></pre>
在獲取到網卡句柄并打開后,發送數據包就很容易了
if (pcap_sendpacket(adhandle, ( const unsigned char *) ucFrame,ucFrame Len ) < 0)
{
printf( "Send Packet Error\n" );
return FALSE ;
}
ucFrame 是封裝好的數據包, ucFrameLen 為數據包的長度。
下面我們封裝一個例子,使用上述代碼發送 ARP 請求包,用于查詢某 IP 對應的 MAC 地址。
ARP 協議格式

關鍵代碼
bool sendARPData( pcap_t adhandle )
{
u_char ucFrame[ARP_LEN];
/ 設置Ethernet頭 /
ETHeader eh = { 0 };
memset( eh.dhost, 0xff, 6 ); / ARP廣播包目的地址為ffffffffffff /
memcpy( eh.shost, ucSelf, 6 );
eh.type = htons( ETHERTYPE_ARP ); / 幀類型為ARP /
memcpy( ucFrame, &eh, sizeof(eh) );
/ 設置Arp頭 /
ARPHeader ah = { 0 };
ah.hrd = htons( ARPHRD_ETHER );
ah.eth_type = htons( ETHERTYPE_IP );
ah.maclen = 6; / 硬件地址長度 /
ah.iplen = 4; / IP地址長度 /
ah.opcode = htons( ARP_REQUEST ); / ARP請求包類型 /
memcpy( ah.smac, ucSelf, 6 );
ah.saddr = inet_addr( szIPSelf );
memset( ah.dmac, 0x00, 6 ); / ARP請求包中目的MAC地址均置0 /
ah.daddr = inet_addr( "192.168.0.2" ); / ARP請求的目的IP地址 */
memcpy( &ucFrame[sizeof(ETHeader)], &ah, sizeof(ah) );
/* 發送 ARP數據包 */
if ( pcap_sendpacket( adhandle, (const unsigned char *)ucFrame, ARP_LEN ) < 0 )
{
printf( "Send Packet Error\n" );
return( FALSE);
}
return( TRUE);
}</code></pre>
啟動 wireshark 進行監聽,運行程序,我們可以看到如下結果,程序發出了一個 ARP 廣播包,用于查詢 192.168.0.2 的主機 MAC ,并且目標機在收到該查詢包后,進行了回復,將自己的 MAC 地址告訴了查詢發起的機器。

0 × 08 如何監聽分析數據包
在監聽數據包時,使用的關鍵函數為pcap_loop
函數名稱:int pcap_loop(pcap_t * p,int cnt, pcap_handler callback, uchar * user);
參數說明:
p 是由 pcap_open_live() 返回的所打開的網卡的指針 ;
cnt 用于設置所捕獲數據包的個數 ;
callback 是回調函數,其原型為 pcap_callback(u_char* argument,const struct pcap_pkthdr* packet_header,const u_char* packet_content)
;user 值一般為 NULL
結合上面的代碼,我們在獲得并打開網卡句柄 adhandle,使用下面的代碼并可捕獲數據包
//每次捕捉到數據包時,pcap都會自動調用這個回調函數
void packet_handler( u_char * param, const struct pcap_pkthdr * header, const u_char * pkt_data )
{
struct tm *ltime;
char timestr[16];
time_t local_tv_sec;
/* 將時間戳轉換成可識別的格式 */
local_tv_sec = header->ts.tv_sec;
printf( "%d \n", local_tv_sec );
ltime = localtime( &local_tv_sec );
strftime( timestr, sizeof(timestr), "%H %M %S", ltime );
printf( "%s,%.6d len:%d\n", ctime( &local_tv_sec ), header->ts.tv_usec, header->len );
}
pcap_loop( adhandle, 0, packet_handler, NULL );
程序在每一個數據包到來時,都會自動調用回調函數 packet_handler 來對數據包進行處理,其第三個參數便是數據包內容。

上圖為運行結果圖,可以看到每一個數據包的時間戳信息,和長度信息。
值得注意的是,原始套接字也可以完成數據包的發送和監聽工作,但是與 winpcap 相比,在監聽數據包方面是有區別的,由于 winpcap 更接近與底層,所以在混雜模式下,凡是到達網卡的數據包不管目的地址是否為自身主機, winpcap 均能接收到;而原始套接字只能接收到投送給自己的數據包。
0×09 總結與預告
本章中我們簡單認識了 winpcap 的相關基礎知識,學習了發送數據包和接收數據包的方法,其實不難發現,發送和接收數據包的過程都比較簡單,只需要調用相關庫函數即可,而更多的精力在數據包的組織和拆分上,同時在一些場景中,算法的使用也較為重要。在接下來的章節中,我們會看到下面的內容:
1.掃描存活主機
2.Arp欺騙的實現與應用
3.端口掃描
4.流量監控與統計分析
來自: http://www.freebuf.com/articles/system/103526.html