用ARP僞裝廣播探測網絡中的Sniffer |
嗅探器(Sniffer)一直以來都是一種讓人惱火的黑客工具,因為它是一種靜态的攻擊軟體,它的存在不會留下任何痕迹,是以人們很難将它揪出來。可是,它的危害性卻又是相當大的(它就像一個螢幕,你的“一舉一動”都在它的監視之下,你說危害大不大)。是以,我們不能不要想個辦法出來檢查網絡中是否存在Sniffer,這是非常必要的。 1. Sniffer原理 所謂知己知彼方能百戰不殆,要了解探測Sniffer的方法,就先得了解Sniffer的原理。首先,讓我們來看一看區域網路中是怎樣傳輸資料的。當一個資料包的目的地是區域網路内的某台計算機時,此資料包将以廣播的形式被發送到網内每一台計算機上。而每台計算機的網卡将分析資料包中的目的Mac位址(即以太網位址),如果此位址為本計算機Mac位址或為廣播位址(FF-FF-FF-FF-FF-FF),那麼,資料包将被接收,而如果不是,網卡将直接将其丢棄。但是,這裡有一個前提,就是接收端計算機的網卡是在正常模式下工作的。而如果網卡被設定為混雜模式,那麼它就可以接收所有經過的資料包了(當然也包括目的地不是本機的資料包)。就是說,隻要是發送到區域網路内的資料包,都會被設定成混雜模式的網卡所接收!這也就是Sniffer的基本原理了。至于Sniffer的具體實作和一些細節,這裡就不多講了,大家有興趣可以參考相關資料。 2. 以太網中傳輸的ARP資料報 知道了Sniffer的基本原理,現在,我們就要想想怎麼才能将區域網路中隐藏的Sniffer揪出來,這才是本篇文章的主題。這裡,我們需要自己構造ARP資料包,是以,就先簡單介紹一下ARP請求和應答資料報的結構: typedef struct _et_header //以太網頭部 { unsigned char eh_dst[6]; unsigned char eh_src[6]; unsigned short eh_type; }ET_HEADER; typedef struct _arp_header //ARP頭部 { unsigned short arp_hdr; unsigned short arp_pro; unsigned char arp_hln; unsigned char arp_pln; unsigned short arp_opt; unsigned char arp_sha[6]; unsigned long arp_spa; unsigned char arp_tha[6]; unsigned long arp_tpa; }ARP_HEADER;
以上就是網絡中傳輸的ARP資料包的結構了。至于結構中每個字段所表示的具體含義以及如何初始化,超出了本文章的讨論範圍,大家有興趣可以參看《TCP-IP協定詳解》一書。 3. 探測區域網路中的Sniffer 終于進入主題了。既然Sniffer是一種靜态的黑軟,不會留下任何日志,那麼我們就要主動的去探測它。鑒于Sniffer的原理是設定網卡為混雜模式,那麼,我們就可以想辦法探測網絡中被設定為混雜模式的網卡,以此來判斷是否存在Sniffer。 這裡,讓我們再來看看計算機接收資料包的規則。前面已經講過,在正常模式下,首先由網卡判斷資料包的目的Mac位址,如果為本機Mac位址或為廣播位址,那麼資料包将被接收進入系統核心,否則将被丢棄。而如果網卡被設定為混雜模式,那麼所有的資料包都将直接進入系統核心。資料包到達系統核心後,系統還将進一步對資料包進行篩選:系統隻會對目的Mac位址為本機Mac位址或廣播位址的資料包做出響應――如果接收到的是ARP請求封包,那麼系統将回饋一個ARP應答封包。但是,不同的是,系統核心和網卡對廣播位址的判斷有些不一樣:以Windows系統為例,網卡會判斷Mac位址的所有六位,而系統核心隻判斷Mac位址的前兩位(Win98甚至隻判斷前一位),也就是說,對于系統核心而言,正确的廣播位址FF-FF-FF-FF-FF-FF和錯誤的廣播位址FF-FF-FF-FF-FF-FE是一樣的,都被認為是廣播位址,甚至FF-FF-00-00-00-00也會被系統核心認為是廣播位址! 寫到這裡,聰明的讀者大概已經知道該怎麼做了。如果我們構造一個目的Mac位址為FF-FF-FF-FF-FF-FE的ARP請求封包,那麼,對于在正常工作模式下的網卡,資料包将被丢棄,當然也就不會回饋任何封包;而對于在混雜模式下網卡,資料包将被接收進入系統核心。而系統核心會認為這個Mac位址是廣播位址,是以就會回饋一個ARP應答封包。這樣,我們就可以判斷出這台機器上存在Sniffer了。 4. 主要源碼分析 由以上分析可知,程式大概分為兩個子產品,一個是發送僞裝廣播位址的ARP請求封包,另一個是接收回饋的ARP應答封包并做出分析。我們就分别用兩個線程來實作。主線程負責發送,監聽線程負責接收。 首先是建立以太網頭部和ARP頭部的結構: ★ typedef struct _et_header //以太網頭部 { unsigned char eh_dst[6]; unsigned char eh_src[6]; unsigned short eh_type; }ET_HEADER; typedef struct _arp_header //ARP頭部 { unsigned short arp_hdr; unsigned short arp_pro; unsigned char arp_hln; unsigned char arp_pln; unsigned short arp_opt; unsigned char arp_sha[6]; unsigned long arp_spa; unsigned char arp_tha[6]; unsigned long arp_tpa; }ARP_HEADER; ★ 然後是發送ARP請求封包的主線程,取得所有擴充卡的名字。其中,“adapter_name”表示一個用于存放擴充卡名字的緩沖區,而這些擴充卡名字将以UNICODE編碼方式存入此緩沖區中。UNICODE編碼方式就是用一個字的空間(兩個位元組)來存放一個字元。這樣,每個字元間自然會出現一個'/0'。而兩個擴充卡名字之間将會有一個字為'/0'作為間隔。adapter_length:這個緩沖區的大小: ★ if(PacketGetAdapterNames((char*)adapter_name, &adapter_length)==FALSE) { printf("PacketGetAdapterNames error:%d/n",GetLastError()); return 0; } ★ 打開擴充卡,此處我預設打開第一塊擴充卡: ★ lpAdapter=(LPADAPTER)PacketOpenAdapter((LPTSTR)adapter_list[0]); if (!lpAdapter||(lpAdapter->hFile==INVALID_HANDLE_VALUE)) { printf("Unable to open the driver, Error Code : %lx/n", GetLastError()); return 0; } ★ 以太網頭部和ARP頭部結構指派,StrToMac函數是筆者自定義的字元串轉換為Mac位址的函數: ★ StrToMac("00E06E41508F",s_Mac); //"00E06E41508F"是筆者測試程式所用的本地機的網卡位址,測試者應将其改為測試機網卡位址 memcpy(et_header.eh_src,s_Mac,6); StrToMac("FFFFFFFFFFFE",d_Mac); //目的實體位址設定為FFFFFFFFFFFE。 memcpy(et_header.eh_dst,d_Mac,6); et_header.eh_type=htons(0x0806); //類型為0x0806表示這是ARP包 arp_header.arp_hdr=htons(0x0001); //硬體位址類型以太網位址 arp_header.arp_pro=htons(0x0800); //協定位址類型為IP協定 arp_header.arp_hln=6; //硬體位址長度為6 arp_header.arp_pln=4; //協定位址長度為4 arp_header.arp_opt=htons(0x0001); //辨別為ARP請求 arp_header.arp_spa=inet_addr("172.24.21.10"); //"172.24.21.10"是我測試程式所用的本地機的IP,測試者應将其改為測試機IP memcpy(arp_header.arp_sha,et_header.eh_src,6); arp_header.arp_tpa=inet_addr(argv[1]); memcpy(arp_header.arp_tha,et_header.eh_dst,6); ★ 發送資料包: ★ lpPacket=PacketAllocatePacket(); //給PACKET結構指針配置設定記憶體 PacketInitPacket(lpPacket,buffer,512); //初始化PACKET結構指針 PacketSetNumWrites(lpAdapter,5); //設定發送次數 PacketSendPacket(lpAdapter,lpPacket,TRUE);//發送ARP請求包 ★ 最後别忘了掃尾工作: ★ PacketFreePacket(lpPacket); //釋放PACKET結構指針 PacketCloseAdapter(lpAdapter); //關閉擴充卡 ★ 最後是監聽線程: 設定接收資料包的系列參數: ★ PacketSetHwFilter(lpAdapter, NDIS_PACKET_TYPE_DIRECTED); //設定網卡為直接模式 PacketSetBuff(lpAdapter,1024); //設定網卡接收資料包的緩沖區大小 PacketSetReadTimeout(lpAdapter,2); //設定接收到一個包後的“休息”時間 ★ 接收資料包: ★PacketReceivePacket(lpAdapter, lpPacket, TRUE); //接收資料包★ 對資料包進行分析,以得出結論: ★ char *buf; bpf_hdr *lpBpfhdr; ET_HEADER *lpEthdr; in_addr addr={0}; buf=(char *)lpPacket->Buffer; lpBpfhdr=(bpf_hdr *)buf; lpEthdr=(ET_HEADER *)(buf+lpBpfhdr->bh_hdrlen); if(lpEthdr->eh_type==htons(0x0806)) //判斷是否為ARP包 { ARP_HEADER *lpArphdr=(ARP_HEADER*)(buf+lpBpfhdr->bh_hdrlen+sizeof(ET_HEADER)); char source_ip[20]={0},dest_ip[20]={0}; addr.S_un.S_addr=lpArphdr->arp_spa; memcpy(source_ip,inet_ntoa(addr),strlen(inet_ntoa(addr))); memset(&addr,0,sizeof(in_addr)); addr.S_un.S_addr=lpArphdr->arp_tpa; memcpy(dest_ip,inet_ntoa(addr),strlen(inet_ntoa(addr))); if(!strcmp(source_ip,ip) && !strcmp(dest_ip,"172.24.21.10")) //判斷接收到的包的源IP與目的IP是否正确(字元串變量ip是從主線程傳遞過來的被探測機的ip) { if(lpArphdr->arp_opt==htons(0x0002)) //判斷是否為ARP應答 { printf("There is a Sniffer!/n"); } } } ★ 5. 結尾 真的是所謂一物降一物,Sniffer雖然厲害,但我們終究找到了破解它的辦法了。由于這個原理,軟體Anti-sniff應運而生。當然,我寫的這個程式比起Anti-sniff來可以說是小巫見大巫,但是,萬變不離其綜,它們的基本原理都是一樣的。 |