計算機網路 課程設計

mirkerson發表於2010-09-20

計算機網路

課程設計報告

 

題目:網路嗅探器的設計與實現   

 

 

 

 

一:實驗目的

隨著網路技術的發展和網路應用的普及,越來越多的資訊資源放在了網際網路上,網路的安全性和可靠性顯得越發重要。因此,對於能夠分析、診斷網路,測試網路效能與安全性的工具軟體的需求也越來越迫切。網路嗅探器具有兩面性,攻擊者可以用它來監聽網路中資料,達到非法獲得資訊的目的,網路管理者可以通過使用嗅探器捕獲網路中傳輸的資料包並對其進行分析,分析結果可供網路安全分析之用。

本次課程設計的主要目的:

1瞭解什麼是網路嗅探器及其主要功能

瞭解網路嗅探器的原理並程式設計實現一個簡單的網路嗅探器

3提高網路程式設計和應用能力,熟悉一些簡單的網路方面的程式設計。

 

二:設計思路

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

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

 建立原始套接字

    可以用它來傳送和接收 IP 層以上的原始資料包 ICMP, TCP, UDP...

SOCKET rawsock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);   

這樣我們就建立了一個 Raw Socket

把網路卡置於混雜模式

      在正常的情況下,一個網路介面應該只響應兩種資料幀:

1.與自己硬體地址相匹配的資料幀

2.發向所有機器的廣播資料幀

假如要網路卡接收所有通過它的資料而不管是不是發給它的那麼必須把網路卡置於混雜模式也就是說讓它的思維混亂不按正常的方式工作 Raw Socket 實現程式碼如下:

setsockopt(rawsock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag));

//設定 IP 頭操作選項

      bind(rawsock,(SOCKADDR *)&addr,sizeof(addr)); // rawsock 繫結到本地網路卡上

      ioctlsocket(rawsock,SIO_RCVALL,&dwvalue);   // rawsock 接受所有的資料

  

    flag 標誌是用來設定 IP 頭操作的也就是說要親自處理 IP : bool flag = ture;

    addr 為本地地址: SOCKADDR_IN addr;

    dwValue 為輸入輸出引數 1 時執行, 0 時取消: DWord dwValue = 1;

  

  三捕捉資料包

    網路卡已經在工作了下一步是抓包,讓網路卡能捕獲所有的資料包!

      recv(rawsock, RecvBuf,sizeof(RecvBuf),0); //接受任意資料包

四.對資料包處理


依據資料包的結構。對資料的內容分別進行讀取和分類處理,下面是幾個主要的資料包的包頭結構。

IP header structure:

TCP header structure:

 

 

UDP header structure:

依據對RecvBuf 中對應資料結果的位置,找到IP資料包中的協議型別Protocol:

EGP 8     ICMP 1   HMP 20  RAW 255      RDP 27   RCD 66   TCP 6     UDP 17   INS-IDP 22

以及對應的程式碼。依次判斷是屬於那種資料包。這裡只處理了最常見的兩種資料包TCPUDP,依次列印IP包頭中的Source address  Destination address    以及在TCP或者UCP包結構中的Source port  Destination port,和對應的Data .

 

三:除錯過程及流程圖

程式編譯時出現如下幾個錯誤,經過在網上查詢,找到其解決辦法,具體如下:

1

錯誤分析:程式編譯正常,但是連線錯誤,原因是缺少ws2_32.lib這個庫檔案

解決辦法:在程式開頭加條語句:“#pragma comment(lib,"ws2_32.lib")”, 相當於是把ws2_32.lib 這個庫加入到工程檔案中

2

程式除錯好後,但執行是發現資料包的源IP地址和目的IP地址始終一樣,如下圖所示:

錯誤分析:inet_ntoa函式返回一個char *,而這個char *的空間是在inet_ntoa裡面靜態分配的,所以inet_ntoa後面的呼叫會覆蓋上一次的呼叫。第一句printf的結果只能說明在printf裡面的可變引數的求值是從右到左的,僅此而已。

解決辦法:將原來的程式碼

printf("Address %s->%s/n",inet_ntoa(pIpheader->srcaddr),inet_ntoa(pIpheader->dstaddr));

改成兩行,如下:

printf("Address %s->",inet_ntoa(pIpheader->srcaddr));

printf("%s/n",inet_ntoa(pIpheader->dstaddr));

程式流程圖

四:程式結果及截圖

結果分析:由程式的第一條輸出Capture 0 顯示 捕獲到172.17.67.28傳送到219.133.48.52的資料包。該資料包是個UDP資料包。埠為40008000 資料內容長度為 39Byte 傳輸的內容在後面列印出來(省略)

細心的朋友的可能會注意到,該程度所捕獲的所有的包只含有兩種型別的包。

1.       本機和以它計算機同學的資料包

2.       本機廣播的資料包

難道該程式有錯誤。沒有。該程式的執行結果,取決於它所出的網路環境和安裝位置這裡有兩個概念:共享環境  交換環境 

共享網路也成為集線器網路資料包到達集線器以後集線器會把資料包轉發到每個集線器的埠換句話說集線器連線的每個網路節點都有權利收到所有的資料包執行Sniffer以後, Sniffer會把網路卡設定為混雜模式一旦設定為混雜模式, Sniffer就可以接受所有的資料包這樣也就達到了Sniffer的目的.

   
交換環境通過使用交換機代替共享環境下的集線器能夠解決集線器的幾個安全問題交換機通過自己的ARP快取列表來決定把資料包傳送到某個埠這樣就不是把一個資料包轉發到各個埠了這樣的做法一方面大大提高了網路的效能另一方面也提高了安全性在交換環境下即使網路卡設定為混雜模式也只能監聽本機的資料包因為交換機不會把其他節點的資料包轉發給嗅探主機了.

 

五:心得體會

通過本實驗熟悉了winsock的工作原理以及RAW模式的socket程式設計,初步掌握了rawsock程式的設計,並能利用它初步測試判斷網路連通效能。

網路是一個實踐性比較強的課程,通過此設計和應用,加深了網路的理解。

 

 

六:附錄(原始碼)


/*  ipheader.h

*

*  (C) 2010  kerson

*/

#pragma comment(lib,"ws2_32.lib")

#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)

 

 

char *proto[]={"TCP","UDP","ICMP","IGMP"};

 

// i386 is little_endian.

#ifndef LITTLE_ENDIAN

#define LITTLE_ENDIAN   (1)   //BYTE ORDER

#else

#error Redefine LITTLE_ORDER

#endif

 

//Mac頭部,總長度14位元組

typedef struct eth_hdr

{

unsigned char dstmac[6]; //目標mac地址

unsigned char srcmac[6]; //mac地址

unsigned short eth_type; //乙太網型別

};

 

//IP頭部,總長度20位元組

typedef struct ip_hdr

{

#if LITTLE_ENDIAN

unsigned char ihl:4;   //首部長度

unsigned char version:4, //版本

#else

unsigned char version:4, //版本

unsigned char ihl:4;   //首部長度

#endif

unsigned char tos;   //服務型別

unsigned short tot_len; //總長度

unsigned short id;    //標誌

unsigned short frag_off; //分片偏移

unsigned char ttl;   //生存時間

unsigned char protocol; //協議

unsigned short chk_sum; //檢驗和

struct in_addr srcaddr; //IP地址

struct in_addr dstaddr; //目的IP地址

};

 

//TCP頭部,總長度20位元組

typedef struct tcp_hdr

{

unsigned short src_port;   //源埠號

unsigned short dst_port;   //目的埠號

unsigned int seq_no;    //序列號

unsigned int ack_no;    //確認號

#if LITTLE_ENDIAN

unsigned char reserved_1:4; //保留6位中的4位首部長度

unsigned char thl:4;    //tcp頭部長度

unsigned char flag:6;    //6位標誌

unsigned char reseverd_2:2; //保留6位中的2

#else

unsigned char thl:4;    //tcp頭部長度

unsigned char reserved_1:4; //保留6位中的4位首部長度

unsigned char reseverd_2:2; //保留6位中的2

unsigned char flag:6;    //6位標誌

#endif

unsigned short wnd_size;   //16位視窗大小

unsigned short chk_sum;   //16TCP檢驗和

unsigned short urgt_p;    //16為緊急指標

};

 

//UDP頭部,總長度8位元組

typedef struct udp_hdr

{

unsigned short src_port; //遠埠號

unsigned short dst_port; //目的埠號

unsigned short uhl;   //udp頭部長度

unsigned short chk_sum; //16udp檢驗和

};

 

//ICMP頭部,總長度4位元組

typedef struct icmp_hdr

{

unsigned char icmp_type;   //型別

unsigned char code;    //程式碼

unsigned short chk_sum;   //16位檢驗和

};

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

/*  sniffer.cpp

*

*  (C) 2010  kerson

*/

#include <stdio.h>

#include <limits.h>

#include <Winsock2.h>

#include <WS2TCPIP.H>

#include "ipheader.h"

 

void main()

{

         char RecvBuf[USHRT_MAX];

 

  struct ip_hdr  *pIpheader  = (struct ip_hdr  *)RecvBuf;

  struct tcp_hdr *pTcpheader = (struct tcp_hdr *)(RecvBuf+sizeof(struct ip_hdr));

  struct udp_hdr *pUdpheader = (struct udp_hdr *)(RecvBuf+sizeof(struct ip_hdr));

 

         WSADATA wsaData;

         WSAStartup(MAKEWORD(2,1), &wsaData );

        

         SOCKET rawsock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);

        

         bool flag=true;

         setsockopt(rawsock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag));

 

         gethostname(RecvBuf,sizeof(RecvBuf));

         struct hostent *pHostent=gethostbyname(RecvBuf);

        

         SOCKADDR_IN addr;

         memcpy(&addr.sin_addr.S_un.S_addr, pHostent->h_addr_list[0], pHostent->h_length);

         addr.sin_family=AF_INET;

 

         bind(rawsock,(SOCKADDR *)&addr,sizeof(addr));

 

         DWORD dwvalue=1;

         ioctlsocket(rawsock,SIO_RCVALL,&dwvalue);

        

         unsigned short src_port,dst_port,len,n=0;

        

char *protocol,*ptr;

         while(true)

                   {       

                            recv(rawsock,RecvBuf,sizeof(RecvBuf),0);

//**********************解析資料流***************************************//

                            switch(pIpheader->protocol)

                            {

                            case IPPROTO_TCP :protocol=proto[0];

                                                                   src_port=pTcpheader->src_port;

                                                                   dst_port=pTcpheader->dst_port;

                                                                   ptr=(char *)(RecvBuf+sizeof(struct ip_hdr)+sizeof(struct tcp_hdr));

                                                                  len=ntohs(pIpheader->tot_len)-sizeof(structip_hdr)-sizeof(struct tcp_hdr);break;

                            case IPPROTO_UDP :protocol=proto[1];

                                                                   src_port=pUdpheader->src_port;

                                                                   dst_port=pUdpheader->dst_port;

                                                                   ptr=(char *)(RecvBuf+sizeof(struct ip_hdr)+sizeof(struct udp_hdr));

                                                                  len=ntohs(pIpheader->tot_len)-sizeof(structip_hdr)-sizeof(struct udp_hdr);break;

                            case IPPROTO_ICMP :protocol=proto[2];break;

                            case IPPROTO_IGMP :protocol=proto[3];break;

                     default :continue;break;

                            }

//**********************解析資料流***************************************//

//*************************輸出******************************************//

                            printf("****************Capture:%d*****************/n",n++);

                            printf("Address %s->",inet_ntoa(pIpheader->srcaddr));

                            printf("%s/n",inet_ntoa(pIpheader->dstaddr));

 

                            printf("Protocol:%s/n",protocol);                         

                            if (pIpheader->protocol==IPPROTO_TCP||pIpheader->protocol==IPPROTO_UDP){         

                                     printf("Port %d->%d/n",ntohs(src_port),ntohs(dst_port));

                                     printf("Data length :%d/n",len);       

                                     printf("%s",len?"Data:":"/0");

                                     for(int i=0;i<len;i++)

                                     {

                                               if (i%50==0) printf("/n");

                                               if (*(ptr+i)<=127&&*(ptr+i)>=20)       printf("%c",*(ptr+i));

                                               else              printf(".");

                                     }

                                     printf("%s",len?"/n":"/0");

                            }

                            printf("********************************************/n/n");

                   //       Sleep(2000);

                   }                

//*************************輸出******************************************//

}

相關文章