[文章資訊] 作者:中國電波傳播研究所青島分所郎銳時間:2003-06-16出處:yesky責任編輯:方舟 [文章導讀] 本文給出了一種在Visual C++下用原始通訊端來捕獲並分析在網路上傳輸的資料包的簡單方法
引言
從事網路安全的技術人員和相當一部分准駭客(指那些使用現成的駭客軟體進行攻擊而不是根據需要去自己編寫代碼的人)都一定不會對網路嗅探器(sniffer)感到陌生,網路嗅探器無論是在網路安全還是在駭客攻擊方面均扮演了很重要的角色。通過使用網路嗅探器可以把網卡設定於混雜模式,並可實現對網路上傳輸的資料包的捕獲與分析。此分析結果可供網路安全分析之用,但如為駭客所利用也可以為其發動進一步的攻擊提供有價值的資訊。可見,嗅探器實際是一把雙刃劍。雖然網路嗅探器技術被駭客利用後會對網路安全構成一定的威脅,但嗅探器本身的危害並不是很大,主要是用來為其他駭客軟體提供網路情報,真正的攻擊主要是由其他黑軟來完成的。而在網路安全方面,網路嗅探手段可以有效地探測在網路上傳輸的資料包資訊,通過對這些資訊的分析利用是有助於網路安全維護的。權衡利弊,有必要對網路嗅探器的實現原理進行介紹。
嗅探器設計原理
嗅探器作為一種網路通訊程式,也是通過對網卡的編程來實現網路通訊的,對網卡的編程也是使用通常的通訊端(socket)方式來進行。但是,通常的通訊端程式只能響應與自己硬體地址相匹配的或是以廣播形式發出的資料幀,對於其他形式的資料幀比如已到達網路介面但卻不是發給此地址的資料幀,網路介面在驗證投遞地址並非自身地址之後將不引起響應,也就是說應用程式無法收取到達的資料包。而網路嗅探器的目的恰恰在於從網卡接收所有經過它的資料包,這些資料包即可以是發給它的也可以是發往別處的。顯然,要達到此目的就不能再讓網卡按通常的正常模式工作,而必須將其設定為混雜模式。
具體到編程實現上,這種對網卡混雜模式的設定是通過原始通訊端(raw socket)來實現的,這也有別於通常經常使用的資料流通訊端和資料通訊端。在建立了原始通訊端後,需要通過setsockopt()函數來設定IP頭操作選項,然後再通過bind()函數將原始通訊端綁定到本地網卡。為了讓原始通訊端能接受所有的資料,還需要通過ioctlsocket()來進行設定,而且還可以指定是否親自處理IP頭。至此,實際就可以開始對網路資料包進行嗅探了,對資料包的擷取仍象流式通訊端或資料通訊端那樣通過recv()函數來完成。但是與其他兩種通訊端不同的是,原始通訊端此時捕獲到的資料包並不僅僅是單純的資料資訊,而是包含有 IP頭、 TCP頭等資訊頭的最原始的資料資訊,這些資訊保留了它在網路傳輸時的原貌。通過對這些在低層傳輸的原始資訊的分析可以得到有關網路的一些資訊。由於這些資料經過了網路層和傳輸層的打包,因此需要根據其附加的幀頭對資料包進行分析。下面先給出結構.資料包的總體結構:
資料在從應用程式層到達傳輸層時,將添加TCP資料區段頭,或是UDP資料區段頭。其中UDP資料區段頭比較簡單,由一個8位元組的頭和資料部分組成,具體格式如下:
| 16位 |
16位 |
| 源連接埠 |
目的連接埠 |
| UDP長度 |
UDP校正和 |
而TCP資料頭則比較複雜,以20個固定位元組開始,在固定頭後面還可以有一些長度不固定的可選項,下面給出TCP資料區段頭的格式組成:
| 16位 |
16位 |
| 源連接埠 |
目的連接埠 |
| 順序號 |
| 確認號 |
| TCP頭長 |
(保留)7位 |
URG |
ACK |
PSH |
RST |
SYN |
FIN |
視窗大小 |
| 校正和 |
緊急指標 |
| 可選項(0或更多的32位字) |
| 資料(可選項) |
對於此TCP資料區段頭的分析在編程實現中可通過資料結構_TCP來定義:
typedef struct _TCP{ WORD SrcPort; // 源連接埠 WORD DstPort; // 目的連接埠 DWORD SeqNum; // 順序號 DWORD AckNum; // 確認號 BYTE DataOff; // TCP頭長 BYTE Flags; // 標誌(URG、ACK等) WORD Window; // 視窗大小 WORD Chksum; // 校正和 WORD UrgPtr; // 緊急指標 } TCP; typedef TCP *LPTCP; typedef TCP UNALIGNED * ULPTCP; |
在網路層,還要給TCP資料包添加一個IP資料區段頭以組成IP資料報。IP資料頭以大端點機次序傳送,從左至右,版本欄位的高位位元組先傳輸(SPARC是大端點機;Pentium是小端點機)。如果是小端點機,就要在發送和接收時先行轉換然後才能進行傳輸。IP資料區段頭格式如下:
| 16位 |
16位 |
| 版本 |
IHL |
服務類型 |
總長 |
| 標識 |
標誌 |
分段位移 |
| 生命期 |
協議 |
頭校正和 |
| 源地址 |
| 目的地址 |
| 選項(0或更多) |
同樣,在實際編程中也需要通過一個資料結構來表示此IP資料區段頭,下面給出此資料結構的定義:
typedef struct _IP{ union{ BYTE Version; // 版本 BYTE HdrLen; // IHL }; BYTE ServiceType; // 服務類型 WORD TotalLen; // 總長 WORD ID; // 標識 union{ WORD Flags; // 標誌 WORD FragOff; // 分段位移 }; BYTE TimeToLive; // 生命期 BYTE Protocol; // 協議 WORD HdrChksum; // 頭校正和 DWORD SrcAddr; // 源地址 DWORD DstAddr; // 目的地址 BYTE Options; // 選項 } IP; typedef IP * LPIP; typedef IP UNALIGNED * ULPIP; |
在明確了以上幾個資料區段頭的組成結構後,就可以對捕獲到的資料包進行分析了。
嗅探器的具體實現
根據前面的設計思路,不難寫出網路嗅探器的實現代碼,下面就給出一個簡單的樣本,該樣本可以捕獲到所有經過本地網卡的資料包,並可從中分析出協議、IP源地址、IP目標地址、TCP源連接埠號碼、TCP目標連接埠號碼以及資料包長度等資訊。由於前面已經將程式的設計流程講述的比較清楚了,因此這裡就不在贅述了,下面就結合注釋對程式的具體是實現進行講解,同時為程式流程的清晰起見,去掉了錯誤檢查等保護性代碼。主要代碼實現清單為:
// 檢查 Winsock 版本號碼,WSAData為WSADATA結構對象
WSAStartup(MAKEWORD(2, 2), &WSAData);
// 建立原始通訊端
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW));
// 設定IP頭操作選項,其中flag 設定為ture,親自對IP頭進行處理
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag));
// 擷取本機名
gethostname((char*)LocalName, sizeof(LocalName)-1);
// 擷取本地 IP 位址
pHost = gethostbyname((char*)LocalName));
// 填充SOCKADDR_IN結構
addr_in.sin_addr = *(in_addr *)pHost-> h_addr_list[0]; //IP
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(57274);
// 把原始通訊端sock 綁定到本地網卡地址上
bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in));
// dwValue為輸入輸出參數,為1時執行,0時取消
DWORD dwValue = 1;
// 設定 SOCK_RAW 為SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL
// 的定義為: #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
ioctlsocket(sock, SIO_RCVALL, &dwValue);
前面的工作基本上都是對原始通訊端進行設定,在將原始通訊端設定完畢,使其能按預期目的工作時,就可以通過recv()函數從網卡接收資料了,接收到的未經處理資料包存放在緩衝RecvBuf[]中,緩衝區長度BUFFER_SIZE定義為65535。然後就可以根據前面對IP資料區段頭、TCP資料區段頭的結構描述而對捕獲的資料包進行分析:
while (true)
{
// 接收未經處理資料包資訊
int ret = recv(sock, RecvBuf, BUFFER_SIZE, 0);
if (ret > 0)
{
// 對資料包進行分析,並輸出分析結果
ip = *(IP*)RecvBuf;
tcp = *(TCP*)(RecvBuf + ip.HdrLen);
TRACE("協議: %s/r/n",GetProtocolTxt(ip.Protocol));
TRACE("IP源地址: %s/r/n",inet_ntoa(*(in_addr*)&ip.SrcAddr));
TRACE("IP目標地址: %s/r/n",inet_ntoa(*(in_addr*)&ip.DstAddr));
TRACE("TCP源連接埠號碼: %d/r/n",tcp.SrcPort);
TRACE("TCP目標連接埠號碼:%d/r/n",tcp.DstPort);
TRACE("資料包長度: %d/r/n/r/n/r/n",ntohs(ip.TotalLen));
}
}
其中,在進行協議分析時,使用了GetProtocolTxt()函數,該函數負責將IP包中的協議(數位識別碼的)轉化為文字輸出,該函數實現如下:
#define PROTOCOL_STRING_ICMP_TXT "ICMP"
#define PROTOCOL_STRING_TCP_TXT "TCP"
#define PROTOCOL_STRING_UDP_TXT "UDP"
#define PROTOCOL_STRING_SPX_TXT "SPX"
#define PROTOCOL_STRING_NCP_TXT "NCP"
#define PROTOCOL_STRING_UNKNOW_TXT "UNKNOW"
……
CString CSnifferDlg::GetProtocolTxt(int Protocol)
{
switch (Protocol){
case IPPROTO_ICMP : //1 /* control message protocol */
return PROTOCOL_STRING_ICMP_TXT;
case IPPROTO_TCP : //6 /* tcp */
return PROTOCOL_STRING_TCP_TXT;
case IPPROTO_UDP : //17 /* user datagram protocol */
return PROTOCOL_STRING_UDP_TXT;
default:
return PROTOCOL_STRING_UNKNOW_TXT;
}
最後,為了使程式能成功編譯,需要包含標頭檔winsock2.h和ws2tcpip.h。在本樣本中將分析結果用TRACE()宏進行輸出,在調試狀態下運行,得到的一個分析結果如下:
協議: UDP
IP源地址: 172.168.1.5
IP目標地址: 172.168.1.255
TCP源連接埠號碼: 16707
TCP目標連接埠號碼:19522
資料包長度: 78
……
協議: TCP
IP源地址: 172.168.1.17
IP目標地址: 172.168.1.1
TCP源連接埠號碼: 19714
TCP目標連接埠號碼:10
資料包長度: 200
……
從分析結果可以看出,此程式完全具備了嗅探器的資料擷取以及對資料包的分析等準系統。
小結
本文介紹的以原始通訊端方式對網路資料進行捕獲的方法實現起來比較簡單,尤其是不需要編寫VxD虛擬設備驅動程式就可以實現抓包,使得其編寫過程變的非常簡便,但由於捕獲到的資料包頭不包含有幀資訊,因此不能接收到與 IP 同屬網路層的其它資料包, 如 ARP資料包、RARP資料包等。在前面給出的樣本程式中考慮到安全因素,沒有對資料包做進一步的分析,而是僅僅給出了對一般資訊的分析方法。通過本文的介紹,可對原始通訊端的使用方法以及TCP/IP協議結構原理等知識有一個基本的認識。本文所述代碼在Windows 2000下由Microsoft Visual C++ 6.0編譯調試通過 .