這個程序來源于我一個專業選修課程的實驗之一,用來檢測一個內網網段中,網卡開了混合模式的主機。要做到這個功能,有以下幾種方法:
(一)識別惡意主機的原理
使用“廣播地址”進行判斷
原理是:正常情況下,網卡檢測接收到的數據包是不是廣播數據包,要看收到的數據幀的目的MAC地址是否等于ff.ff.ff.ff.ff.ff,如果是則認為是廣播地址,但當網卡的工作模式為“混雜模式”時,網卡檢測是不是廣播包只看收到的數據幀的目的MAC地址的第一個八位組值,是0xf則認為是廣播地址。利用這點細微差別就可以檢測出Sniffer。
測試時,測試主機首先向被測試的局域網內的所有設備發送偽造的ARP請求包,偽造目標主機MAC地址,比如:ff.00.00.00.00.00或ff.ff.00.00.00.00等。如果接收到包的目標主機沒有處于混雜模式,它將不會回復,但如果處于混雜模式,它將會回應測試主機的ARP請求,通過監視向測試主機發送的回應信息就可以知道哪個目標主機處于混雜模式。
由于某些網卡在正常工作時,識別廣播包時僅僅識別數據包中的目的MAC地址的的第一個字段是否是“0xf”,所以這類網卡不適合用此方法檢測,對于這種情況,可以通過修改偽造的目標主機的MAC地址進行檢測。
2.使用ping的響應時間進行判斷
測試時,測試主機首先利用ICMP請求及響應計算出目標機器的平均響應時間。在得到這個數據后,測試主機再次向本地網絡發送大量的偽造數據包,與此同時再次發送測試數據包以確定目標主機的平均響應時間的變化值。
非混雜模式的機器的響應時間變化量會很小,而混雜模式的機器的響應時間變化量則通常會有1―4個數量級。這種測試已被證明是最有效的,它能夠發現網絡中處于混雜模式的機器,而不管其操作系統是什么,但缺點是這個測試會在很短的時間內產生巨大的通訊流量。
最終我選擇了第一種方法,因為理解起來也比較簡單。
(二)ARP協議基礎
編程識別的前提是要能夠發出自己構造的ARP報文,下面是ARP報文的格式,編程之前,首先要熟悉這個結構:
一個ARP報文是由ARP首部和以太網首部共同組成的。 以太幀首部中2字節的幀類型字段指定了其上層所承載的具體協議,常見的有0x0800表示是IP報文、0x0806表示RARP協議、0x0806即為我們將要討論的ARP協議。
硬件類型: 1表示以太網。
協議類型: 0x0800表示IP地址(ARP在TCP/IP中位于網絡層)。和以太頭部中幀類型字段相同。
硬件地址長度和協議地址長度:對于以太網中的ARP協議而言,分別為6和4;
操作碼:1表示ARP請求;2表示ARP應答;3表示RARP請求;4表示RARP應答。
我們這里只討論硬件地址為以太網地址、協議地址為IP地址的情形,所以剩下四個字段就分別表示發送方的MAC和IP地址、接收方的MAC和IP地址了。
那么有了協議的基礎,我們該如何發出鏈路層的包呢?我們知道,系統自帶的socket系列API是位于傳輸層的,并且報文字段的填充是由網絡驅動程序自動完成,那么如何自行發出位于傳輸層之下的包呢?這就要用到原始套接字。
(三)編程基礎
為了實現直接從鏈路層收發數據幀,我們要用到原始套接字的如下形式:
socket(PF_PACKET, type, protocol)
1、其中type字段可取SOCK_RAW或SOCK_DGRAM。它們兩個都使用一種與設備無關的標準物理層地址結構struct sockaddr_ll{},但具體操作的報文格式不同:
SOCK_RAW:直接向網絡硬件驅動程序發送(或從網絡硬件驅動程序接收)沒有任何處理的完整數據報文(包括物理幀的幀頭),這就要求我們必須了解對應設備的物理幀幀頭結構,才能正確地裝載和分析報文。也就是說我們用這種套接字從網卡驅動上收上來的報文包含了MAC頭部,如果我們要用這種形式的套接字直接向網卡發送數據幀,那么我們必須自己組裝我們MAC頭部。這正符合我們的需求。
SOCK_DGRAM:這種類型的套接字對于收到的數據報文的物理幀幀頭會被系統自動去掉,然后再將其往協議棧上層傳遞;同樣地,在發送時數據時,系統將會根據sockaddr_ll結構中的目的地址信息為數據報文添加一個合適的MAC幀頭。
2、protocol字段,常見的,一般情況下該字段取ETH_P_IP,ETH_P_ARP,ETH_P_RARP或ETH_P_ALL,當然鏈路層協議很多,肯定不止我們說的這幾個,但我們一般只關心這幾個就夠我們用了。
另外,由于要自己構造鏈路層的報文,那么在傳輸層我們使用的struct sockaddr_in這種地址顯然不能再用了。而是使用sockaddr_in{}:
sll_ifindex需要我們去指定一個可用的網卡設備。
然后我們就可以按照我們自己的需要去填充ARP報文了,比如,將以太網首部中的目的MAC填成一個“偽廣播地址”:0xff:0xff:0x00:0x00:0x00:0x00,然后使用socket把這個包發出去就行了。驗證的時候,只需要打開wireshark或者tcpdump或者其他的抓包工具即可,如果發現目標IP確實發來一個回應報文,那么就能確定這個設備的網卡可能開了混合模式,為什么說是可能呢?因為某些特殊網卡在不開混合模式時,也是這么判斷廣播地址的。
(四)利用Libpcap抓包自動識別
本來可以不做到這一步的,但是我想了想,覺得手動+腦力判斷似乎太挫了,所以我覺得自己寫個抓包的代碼段實現自動識別。
在Linux上進行抓包通常使用Libpcap,與Windows平臺上的Winpcap一樣。都是用來抓取網絡數據包的。Linux平臺上在gcc編譯時,要加上相應的編譯項,否則會報錯。
由于抓包為永真循環,如果把這段程序設置在主程序中,會產生阻塞而無法進行下一步工作,所以我就另外開啟一個子線程專門用來抓包,主線程中設置這個子線程的超時時間即可。
用到的一些重要函數:
descr =pcap_open_live(message->interface,MAXBYTES2CAPTURE,1,512,errbuf) ;
descr是一個pcap_t類型的指針,用來描述網絡設備,我這里把它理解為一種文件描述符。
上面的pcap_open_live就是用來打開一個可用的網卡設備,返回一個描述符。
由于我們使用libpcap進行抓包時,會把所以類型的包全抓上來,但是我們只需要捕獲ARP,這時候就要使用libpcap中的BPF程序設置過濾語法了。
//獲取網卡的IP和掩碼
pcap_lookupnet(message->interface,&netaddr,&mask,errbuf);
//BPF過濾程序編譯
pcap_compile(descr,&filter,"arp",1,mask);
pcap_setfilter(descr,&filter) ;
抓包時就使用packet = pcap_next(descr,&pkthdr)即可,packet是一個指向具體信息的指針,用packet指針做偏移就能拿到我們想要的部分。Pcap_next一次只能抓一個包,只需要做個永真循環就能持續抓包。在這個永真循環中我們就可以控制,一旦發現目標就exit出去,當然如果長時間沒收到目標來信,那么就默認目標主機是“清白”的。實現這個,只需要在主線程中去設置子線程的超時時間就行了:
//等待子線程
struct timespec joinDelay ;
clock_gettime(CLOCK_REALTIME,&joinDelay) ;
joinDelay.tv_sec += 5 ;
pthread_timedjoin_np(subthread, NULL,&joinDelay);
(五)運行測試
測試開了混雜模式的主機:
測試正常主機:
(六)代碼分享
anti_arp_test.c代碼如下,gcc編譯命令:
gcc anti_arp_test.c -lpthread -lpcap -lrt-o anti_arp_test
【過幾天放源碼】