手把手教你捕获数据包
原文鏈接:http://blog.csdn.net/piggyxp/article/details/24444
前???言
?????經常看到論壇有人問起關于數據包的截獲、分析等問題,幸好本人也對此略有所知,也寫過很多的sniffer,所以就想寫一系列的文章來詳細深入的探討關于數據包的知識。
我希望通過這一系列的文章,能使得關于數據包的知識得以普及,所以這系列的每一篇文章我都會有由淺入深的解釋、詳細的分析、以及編碼步驟,另外附上帶有詳細注釋的源碼(為了照顧大多數朋友,我提供的都是MFC的源碼)。
不過由于也是初學者,疏漏之處還望不吝指正。
本文凝聚著筆者心血,如要轉載,請指明原作者及出處,謝謝!^_^
?
OK,. Let’s go !??Have fun!! q^_^p
?
第二篇???手把手教你捕獲數據包
目錄:
一.捕獲數據包的實現原理
二.捕獲數據包的編程實現:
1.???raw socket的實現方法
2.???Winpcap的實現方法
a.??????枚舉本機網卡的信息
b.??????打開相應網卡并設置為混雜模式
c.???????截獲數據包并保存為文件
?
作者:
CSDN??VC/MFC?網絡編程版主?PiggyXP?
?
一.捕獲數據包的實現原理:--------------------------------------------------------------------
在通常情況下,網絡通信的套接字程序只能響應與自己硬件地址相匹配的或是以廣播形式發出的數據幀,對于其他形式的數據幀比如已到達網絡接口但卻不是發給此地址的數據幀,網絡接口在驗證投遞地址并非自身地址之后將不引起響應,也就是說應用程序無法收取與自己無關的的數據包。
所以我們要想實現截獲流經網絡設備的所有數據包,就要采取一點特別的手段了:
將網卡設置為混雜模式。
這樣一來,該主機的網卡就可以捕獲到所有流經其網卡的數據包和幀。
但是要注意一點,這種截獲僅僅是數據包的一份拷貝,而不能對其進行截斷,要想截斷網絡流量就要采用一些更底層的辦法了,不在本文的討論范圍之內。
?
二.?捕獲數據包的編程實現:
1.raw socket的實現方法--------------------------------------------------------------------
不同于我們常用的數據流套接字和數據報套接字,在創建了原始套接字后,需要用WSAIoctl()函數來設置一下,它的定義是這樣的
int WSAIoctl(
??SOCKET s,
??DWORD dwIoControlCode,
??LPVOID lpvInBuffer,
??DWORD cbInBuffer,
??LPVOID lpvOutBuffer,
??DWORD cbOutBuffer,
??LPDWORD lpcbBytesReturned,
??LPWSAOVERLAPPED lpOverlapped,
??LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
雖然咋一看參數比較多,但是其實我們最關心的只是其中的第二項而已,我們需要做的就是把第二項設置為SIO_RCVALL,講了這么多其實要做的就是這么一行代碼,很簡單吧?^_^
?當然我們還可以指定是否親自處理IP頭,但是這并不是必須的。
完整的代碼類似與如下這樣,加粗的代碼是與平常不同的需要注意的地方:
(?為了讓代碼一目了然,我把錯誤處理去掉了,下同)
?
#include “WinSock2.h”
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
?
SOCKET SnifferSocket
??WSADATA wsaData;
??iFlag=WSAStartup(MAKEWORD(2,2),&wsaData);???????????//開啟winsock.dll
?????????????????????????????????
SnifferSocket=WSASocket(AF_INET,?????????????//創建raw??socket
SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);
?
??char FAR name[128];????????????????????????????????//獲取本機IP地址
gethostname(name, sizeof(name));
??struct hostent FAR * pHostent;
??pHostent = gethostbyname(name);
?
??SOCKADDR_IN sa;???????????????????????????//填充SOCKADDR_IN結構的內容
??sa.sin_family = AF_INET;
??sa.sin_port = htons(6000);???????????//?端口號可以隨便改,當然與當然系統不能沖突
??memcpy(&(sa.sin_addr),pHostent->h_addr,pHostent->h_length);
?
bind(SnifferSocket,(LPSOCKADDR)&sa,sizeof(sa));????????????//綁定
?
??//?置ioctl來接收所有網絡數據,關鍵步驟
??DWORD dwBufferLen[10] ;
??DWORD dwBufferInLen = 1 ;
??DWORD dwBytesReturned = 0 ;
??WSAIoctl(SnifferSocket, IO_RCVALL,&dwBufferInLen, izeof(dwBufferInLen),
????????&dwBufferLen, sizeof(dwBufferLen),&dwBytesReturned , NULL , NULL );
?
至此,實際就可以開始對網絡數據包進行嗅探了,而對于數據包的接收還是和普通的socket一樣,通過recv()函數來完成,因為這里涉及到不同的socket模型,接收方法差別很大,所以在此就不提供接收的代碼了。
?
2.winpcap的實現方法:-----------------------------------------------------------------------
winpcap驅動包,是我們玩轉數據包不可或缺的好東東,winpcap的主要功能在于獨立于主機協議(如TCP-IP)而發送和接收原始數據報,主要為我們提供了四大功能:
功能:
??? 1>?捕獲原始數據報,包括在共享網絡上各主機發送/接收的以及相互之間交換的數據報;
??? 2>?在數據報發往應用程序之前,按照自定義的規則將某些特殊的數據報過濾掉;
??? 3>?在網絡上發送原始的數據報;
??? 4>?收集網絡通信過程中的統計信息
如果環境允許的話(比如你做的不是木馬程序),我還是推薦大家用winpcap來截獲數據包,因為它的功能更強大,工作效率更高,唯一的缺點就是在運行用winpcap開發的程序以前,都要在主機上先安裝winpcap的driver。
而且一會我們就會發現它比raw socket功能強大的多,而且工作得更為底層,最明顯的理由就是raw socket捕獲的數據包是沒有以太頭的,此乃后話。
至于怎么來安裝使用,請參考本系列的系列一《手把手教你玩轉ARP包中的》,里面有詳細的加載winpcap驅動的方法^_^
廢話不多說了,讓我們轉入正題,?具體用winpcap來截獲數據包需要做如下的一些工作:
A .?枚舉本機網卡的信息(主要是獲得網卡的名稱)
???其中要用到pcap_findalldevs函數,它是這樣定義的
???/*************************************************
int pcap_findalldevs??(??pcap_if_t **????alldevsp,?
?????????????????????????????char *????errbuf
??????????????????????????)?
?????功能:
?????????????枚舉系統所有網絡設備的信息
?????參數:??alldevsp:??是一個pcap_if_t結構體的指針,如果函數pcap_findalldevs函數執行成功,將獲得一個可用網卡的列表,而里面存儲的就是第一個元素的指針。
?????????????Errbuf:????存儲錯誤信息的字符串
?????返回值:?int?:???如果返回0?則執行成功,錯誤返回?-1。
??*************************************************/
???我們利用這個函數來獲得網卡名字的完整代碼如下:
?
???????pcap_if_t* alldevs;
???????pcap_if_t* d;
???????char?errbuf[PCAP_ERRBUF_SIZE];
???????pcap_findalldevs(&alldevs,errbuf);???????//?獲得網絡設備指針
???????for(d=alldevs;d;d=d->next)???????????????//?枚舉網卡然后添加到ComboBox中
???????{
d->name;????????????????????????????// d->name就是我們需要的網卡名字字符串,按照你//?自己的需要保存到你的相應變量中去
???????}
pcap_freealldevs(alldevs);???????????????//?釋放alldev資源
?
B.?打開相應網卡并設置為混雜模式:
???在此之前肯定要有一段讓用戶選擇網卡、并獲得用戶選擇的網卡的名字的代碼,既然上面已經可以獲得所有網卡的名字了,這段代碼就暫且略過了。
???我們主要是要用到?pcap_open_live?函數,不過這個函數winpcap的開發小組已經建議用pcap_open?函數來代替,不過因為我的代碼里面用的就是pcap_open_live,所以也不便于修改了,不過pcap_open_live使用起來也是沒有任何問題的,下面是pcap_open_live的函數聲明:
?/*************************************************
pcap_t* pcap_open_live??(??char *????device,?
?????????????????????????????int????snaplen,?
??int????promisc,?
??int????to_ms,?
??char *????ebuf
?)??
?????功能:
???????????根據網卡名字打開網卡,并設置為混雜模式,然后返回其句柄
?????參數:
???????????Device??:?就是前前面我們獲得的網卡的名字;
???????????Snaplen :??我們從每個數據包里取得數據的長度,比如設置為100,則每次我們只是獲得每個數據包?100?個長度的數據,沒有什么特殊需求的話就把它設置為65535最大值就可以了;
???????????Promisc:這個參數就是設置是否把網卡設置為“混雜模式”,設置為?1?即可;
???????????to_ms :???超時時間,毫秒,一般設置為?1000即可。
?????返回值:
???????????pcap_t :??類似于一個網卡“句柄”之類的,不過當然不是,這個參數是后面截獲數據要用到的。
******************************************************************************/
雖然看起來比較復雜,不過用起來還是非常簡單的,其實?1?行就OK了:
????pcap_t* adhandle;
???????char?errbuf[PCAP_ERRBUF_SIZE];
//?打開網卡,并且設置為混雜模式
// pCardName是前面傳來的網卡名字參數
adhandle = pcap_open_live(pCardName,65535,1,1000,errbuf);
?
C.?截獲數據包并保存為文件:------------------------------------------------------
?????當然,不把數據包保存為文件也可以,不過如果不保存的話,只能在截獲到數據包的那一瞬間進行分析,轉眼就沒了^_^
所以,為了便于日后分析,所以高手以及我個人經常是把數據包保存下來的慢慢分析的。
但是注意網絡流量,在流量非常大的時候注意硬盤空間呵呵,常常幾秒中就有好幾兆是很正常的事情。
下面首先來詳細講解一下,這個步驟中需要用到的winpcap函數:
/**************************************************************
pcap_dumper_t* pcap_dump_open??(??pcap_t *????p,?
??????????????????????????????????????const char *????fname
?)
功能:
??????建立或者打開存儲數據包內容的文件,并返回其句柄
參數:
???????pcap_t *????p?????:前面打開的網卡句柄;
??????const char * fname?:要保存的文件名字???
返回值:
???????pcap_dumper_t*?:?保存文件的描述句柄,具體細節我們不用關心
***************************************************************/
/***************************************************************
int pcap_next_ex??????????(??pcap_t *????p,?
??????????????????????????????struct pcap_pkthdr **????pkt_header,?
??u_char **????pkt_data
?)??
功能:
??????從網卡或者數據包文件中讀取數據內容
參數:
??????pcap_t *????p:????網卡句柄
??????struct pcap_pkthdr ** pkt_header:?并非是數據包的指針,只是與數據包捕獲驅動有關的一個Header
??????u_char ** pkt_data:指向數據包內容的指針?,包括了協議頭??
返回值:
??????????1 :?如果成功讀取數據包
??????????0?:pcap_open_live()設定的超時時間之內沒有讀取到內容
??????????-1:?出現錯誤
??????????-2:?讀文件時讀到了末尾
***************************************************************/
/***************************************************************
void pcap_dump??(??u_char *????user,?
???????????????????????const struct pcap_pkthdr *????h,?
??const u_char *????sp
?)???
功能:
??????將數據包內容依次寫入pcap_dump_open()指定的文件中
參數:
??????u_char * user???:??網卡句柄
??????const struct pcap_pkthdr * h:?并非是數據包的指針,只是與數據包捕獲驅動有關的一個Header
??????const u_char * sp:?數據包內容指針????
返回值:
??????????Void
****************************************************************/
?下面給出一段完整的捕獲數據包的代碼,是在線程中寫的,為了程序清晰,我去掉了錯誤處理代碼以及線程退出的代碼,完整代碼可下載文后的示例源碼,老規矩,重要的步驟用粗體字標出。
我們實際在捕獲數據包的時候也最好是把代碼放到另外的線程中。
/*********************************************************
*???進程:
*???????????????????這個是程序的核心部分,完成數據包的截獲
*?????參數:
*???????????????????pParam:?用戶選擇的用來捕獲數據的網卡的名字
*********************************************************/
UINT CaptureThread(LPVOID pParam)
{
???????const?char* pCardName=(char*)pParam;??????????//?轉換參數,獲得網卡名字???????????????????????
?
???????pcap_t* adhandle;
???????char?errbuf[PCAP_ERRBUF_SIZE];?????????????
???????//?打開網卡,并且設置為混雜模式
?adhandle=pcap_open_live(pCardName,65535,1,1000,errbuf);??????{
?
???????pcap_dumper_t* dumpfile;
//?建立存儲截獲數據包的文件
???????dumpfile=pcap_dump_open(adhandle, "Packet.dat");???
?
???????int?re;
???????pcap_pkthdr* header;??????// Header
???????u_char* pkt_data;?????????//?數據包內容指針
//?從網卡或者文件中不停讀取數據包信息
???????while((re=pcap_next_ex(adhandle,&header,(const?u_char**)&pkt_data))>=0)
??????{
??????????//?將捕獲的數據包存入文件
??????????????pcap_dump((unsigned?char*)dumpfile,header,pkt_data);?????
???????}
???????return?0;
}??
將個線程加入到程序里面啟動以后。。。等等,如何來啟動這個線程就不用我說了吧,類似這樣的代碼就可以
::AfxBeginThread(CaptureThread,chNIC);?????// chNIC是網卡的名字,char*?類型
啟動線程一段時間以后(幾秒中就有效果了),可以看到數據包已經被成功的截獲下來,并存儲到程序目錄下的Packet.dat文件中。
=====================================================
至此,數據包的截獲方法就講完了,大家看了這篇文章,其實你就一定也明白了,無論是raw socket的方法還是winpcap的方法,其實都很簡單的,真的沒有什么東西,只是會讓不明白原理的人看起來很神秘而已,isn’t it?
呵呵,不過也不要高興的太早,這個保存下來的數據包文件,你可以試著用UltraEdit打開這個文件看看,是不是大部分都是亂碼?基本上沒有什么可讀性,這是因為:
此時捕獲到的數據包并不僅僅是單純的數據信息,而是包含有?IP頭、?TCP頭等信息頭的最原始的數據信息,這些信息保留了它在網絡傳輸時的原貌。通過對這些在低層傳輸的原始信息的分析可以得到有關網絡的一些信息。由于這些數據經過了網絡層和傳輸層的打包,因此需要根據其附加的幀頭對數據包進行分析。
呵呵,所以我們要走的路還很長,這只是剛剛入門而已^_^
請期待本系列的下一部拙作
《手把手教你分析數據包》
本文的源碼也將在下一文中一起發布
?
?????????????????????????????????????????????????????????????????????????????????????????????????????????? ??????--------??Made In DLUT | DIP
??????????????????????????????????????????????????????????????????????????????????????????????????????????? ???????--------??Finished at?2004-06-23 超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的手把手教你捕获数据包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手把手教你玩转ARP包(四)
- 下一篇: 网卡MAC地址相关信息大全