RAW_SOCKET捕包

 Win2K下的Sniffer源代碼

IP包監聽程序(For 9x)源代碼 詳細信息 < 局域網 > IP包監聽程序源代碼(包含VXD源代碼)

一個RAW_SOCKET的例子

庫winpcap提供了抓包的接口


從事網絡安全的技術人員和相當一部分準黑客(指那些使用現成的黑客軟件進行攻擊而不是根據需要去自己編寫代碼的人)都一定不會對網絡嗅探器(sniffer)感到陌生,網絡嗅探器無論是在網絡安全還是在黑客攻擊方面均扮演了很重要的角色。通過使用網絡嗅探器可以把網卡設置於混雜模式,並可實現對網絡上傳輸的數據包的捕獲與分析。此分析結果可供網絡安全分析之用,但如爲黑客所利用也可以爲其發動進一步的攻擊提供有價值的信息。可見,嗅探器實際是一把雙刃劍。 雖然網絡嗅探器技術被黑客利用後會對網絡安全構成一定的威脅,但嗅探器本身的危害並不是很大,主要是用來爲其他黑客軟件提供網絡情報,真正的攻擊主要是由其他黑軟來完成的。而在網絡安全方面,網絡嗅探手段可以有效地探測在網絡上傳輸的數據包信息,通過對這些信息的分析利用是有助於網絡安全維護的。權衡利弊,有必要對網絡嗅探器的實現原理進行介紹。

  嗅探器設計原理

  嗅探器作爲一種網絡通訊程序,也是通過對網卡的編程來實現網絡通訊的,對網卡的編程也是使用通常的套接字(socket)方式來進行。但是,通常的套接字程序只能響應與自己硬件地址相匹配的或是以廣播形式發出的數據幀,對於其他形式的數據幀比如已到達網絡接口但卻不是發給此地址的數據幀,網絡接口在驗證投遞地址並非自身地址之後將不引起響應,也就是說應用程序無法收取到達的數據包。而網絡嗅探器的目的恰恰在於從網卡接收所有經過它的數據包,這些數據包即可以是發給它的也可以是發往別處的。顯然,要達到此目的就不能再讓網卡按通常的正常模式工作,而必須將其設置爲混雜模式。

  具體到編程實現上,這種對網卡混雜模式的設置是通過原始套接字(raw socket)來實現的,這也有別於通常經常使用的數據流套接字和數據報套接字。在創建了原始套接字後,需要通過setsockopt()函數來設置IP頭操作選項,然後再通過bind()函數將原始套接字綁定到本地網卡。爲了讓原始套接字能接受所有的數據,還需要通過ioctlsocket()來進行設置,而且還可以指定是否親自處理IP頭。至此,實際就可以開始對網絡數據包進行嗅探了,對數據包的獲取仍象流式套接字或數據報套接字那樣通過recv()函數來完成。但是與其他兩種套接字不同的是,原始套接字此時捕獲到的數據包並不僅僅是單純的數據信息,而是包含有 IP頭、 TCP頭等信息頭的最原始的數據信息,這些信息保留了它在網絡傳輸時的原貌。通過對這些在低層傳輸的原始信息的分析可以得到有關網絡的一些信息。由於這些數據經過了網絡層和傳輸層的打包,因此需要根據其附加的幀頭對數據包進行分析。下面先給出結構.數據包的總體結構:

數據包
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("協議: %srn",GetProtocolTxt(ip.Protocol));
TRACE("IP源地址: %srn",inet_ntoa(*(in_addr*)&ip.SrcAddr));
TRACE("IP目標地址: %srn",inet_ntoa(*(in_addr*)&ip.DstAddr));
TRACE("TCP源端口號: %drn",tcp.SrcPort);
TRACE("TCP目標端口號:%drn",tcp.DstPort);
TRACE("數據包長度: %drnrnrn",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編譯調試通過。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章