天天看點

用ARP僞裝廣播探測網絡中的Sniffer

用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來可以說是小巫見大巫,但是,萬變不離其綜,它們的基本原理都是一樣的。

繼續閱讀