網絡嗅探器

1、網絡嗅探器原理

一、首先回顧一下網卡的四種接收模式:

①廣播模式:該模式下的網卡可以接收網絡中的廣播信息;
②組播模式:設置在該模式下的網卡可以接收組播數據;
③直接模式:在這種模式下,只有目的網卡才能接收該數據;
④混雜模式:在這種模式下的網卡可以接收一切經過它的數據,而無論該數據是不是傳給它的

二、原始套接字工做原理

首先介紹下套接字 ,套接字,是支持TCP/IP的網絡通訊的基本操做單元,能夠看作是不一樣主機之間的進程進行雙向通訊的端點,簡單的說就是通訊的兩方的一種約定,用套接字中的相關函數來完成通訊過程。經常使用的TCP/IP協議的3種套接字類型是:流套接字(SOCK_STREAM)、數據報套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)windows

三、嗅探器原理:

一般的套接字只響應廣播模式和直接模式發出的數據幀,對於其餘形式的數據幀網絡接口會直接丟棄。用戶模式下,對網卡混雜模式的設置是經過原始套接字實現的、建立原始套接字以後,將其綁定到一個明確的本地地址,而後向套接字發送SIO_RCVALL控制命令,讓它接收全部的IP包,這樣網卡就進入了混雜模式。api

2、代碼實現

首先建立原始套接字,將原始套接字綁定到一個明確的本地地址(不能爲ADDR_ANY),而後經過ioctlsocket()函數(是控制套接口的模式),向套接字發送SIO_RCVALL控制命令,將網卡設置爲混雜模式,這樣就能接收到全部通過網卡的封包;而後已鏈接的數據報或流式套接口經過不斷調用recv()函數來接收IP數據包,將接收到的數據存入緩衝數組;隨後再經過自定義的IP數據包解析函數對接收到的數據進行解析,經過定義的IP地址結構體的指針對象從接收到的數據中取得數據包的IP頭;而後從IP頭中取出源IP地址和目的IP地址。數組

sniffer.cpp:服務器

#include "../common/initsock.h"
#include "../common/protoinfo.h" 

#include <stdio.h>
#include <mstcpip.h> //用於處理TCP/IP協議的一個頭文件,這裏麪包含了WinSockets一系列相關函數.

#pragma comment(lib, "Advapi32.lib") //連接Advapi32.lib庫文件

CInitSock theSock;      //建立CInitSock類的對象

//自定義的函數,用於解析收到的TCP數據封包,解析出封包中的TCP頭
void DecodeTCPPacket(char *pData){

    TCPHeader *pTCPHdr = (TCPHeader *)pData;
    /*ntohs()是一個函數名,做用是將一個16位數由網絡字節順序轉換爲主機字節順序*/
    printf(" Port: %d -> %d \n", ntohs(pTCPHdr->sourcePort), ntohs(pTCPHdr->destinationPort));

    // 下面還能夠根據目的端口號進一步解析應用層協議
    switch(::ntohs(pTCPHdr->destinationPort)){
    case 21:
        break;
    case 80:
    case 8080:
        break;
    }
}

/* 自定義的函數,用於解析收到的IP數據封包,解析出封包中的IP頭 */
void DecodeIPPacket(char *pData){
    IPHeader *pIPHdr = (IPHeader*)pData;    
    in_addr source, dest;       //in_addr是一個結構體,能夠用來表示一個32位的IPv4地址
    char szSourceIp[32], szDestIp[32];      // 定義兩個數組用於存儲源IP地址和目的IP地址
    printf("\n\n*************************************************************************");    
    // 從IP頭中取出源IP地址和目的IP地址
    source.S_un.S_addr = pIPHdr->ipSource;      // 從IP頭中取出源IP地址
    dest.S_un.S_addr = pIPHdr->ipDestination;       // 從IP頭中取出目的IP地址
    strcpy(szSourceIp, ::inet_ntoa(source));
    strcpy(szDestIp, ::inet_ntoa(dest));

    printf(" %s ---> %s \n", szSourceIp, szDestIp);      //打印出源IP地址和目的IP地址
    // IP頭長度
    int nHeaderLen = (pIPHdr->iphVerLen & 0xf) * sizeof(ULONG);

    switch(pIPHdr->ipProtocol)
    {
    case IPPROTO_TCP: // TCP協議
        DecodeTCPPacket(pData + nHeaderLen);
        break;
    case IPPROTO_UDP:
        break;
    case IPPROTO_ICMP:
        break; 
    }
}

void main(){
    // 建立原始套節字sRaw
    SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
    // 獲取本地IP地址
    char szHostName[56];
    SOCKADDR_IN addr_in;
    struct  hostent *pHost; //hostent是host entry的縮寫,該結構記錄主機的信息,包括主機名、別名、地址類型、地址長度和地址列表
    gethostname(szHostName, 56);
    if((pHost = gethostbyname((char*)szHostName)) == NULL)  
        return ;
    // 在調用ioctl以前,套節字必須綁定
    addr_in.sin_family  = AF_INET;
    addr_in.sin_port    = htons(0);
    memcpy(&addr_in.sin_addr.S_un.S_addr, pHost->h_addr_list[0], pHost->h_length);
    printf(" Binding to interface : %s \n", ::inet_ntoa(addr_in.sin_addr));
    if(bind(sRaw, (PSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR)        //將原始套接字綁定到一個明確的本機地址
        return;

    // 設置SIO_RCVALL控制代碼,以便接收全部的IP包 
    DWORD dwValue = 1;
    /* ioctlsocket()是控制套接口的模式,向套接字發送SIO_RCVALL控制命令,將網卡設置爲混雜模式,這樣就能接收到全部通過網卡的封包 回顧一下網卡的四種接收模式: ①廣播模式:該模式下的網卡可以接收網絡中的廣播信息; ②組播模式:設置在該模式下的網卡可以接收組播數據; ③直接模式:在這種模式下,只有目的網卡才能接收該數據; ④混雜模式:在這種模式下的網卡可以接收一切經過它的數據,而無論該數據是不是傳給它的 */
    if(ioctlsocket(sRaw, SIO_RCVALL, &dwValue) != 0)    
        return ;

    // 開始接收封包
    char buff[1024];        //用於接受數據的數組
    int nRet;
    while(true){
        /* recv函數用於已鏈接的數據報或流式套接口進行數據的接收 該函數的第一個參數指定接收端套接字描述符; 第二個參數指明一個緩衝區,該緩衝區用來存放recv函數接收到的數據; 第三個參數指明buf的長度; 第四個參數通常置0 */
        nRet = recv(sRaw, buff, 1024, 0);   //recv函數接收消息出錯的話,會返回 0
        if(nRet > 0){
            DecodeIPPacket(buff);       //調用該函數對IP封包進行解析
        }
    }
    closesocket(sRaw);          //關閉原始套接字
}

protoinfo.h:markdown

/* 定義協議格式 定義協議中使用的宏 */
#ifndef __PROTOINFO_H__
#define __PROTOINFO_H__
#define ETHERTYPE_IP 0x0800
#define ETHERTYPE_ARP 0x0806

typedef struct _ETHeader         // 14字節的以太頭
{
    UCHAR   dhost[6];           // 目的MAC地址destination mac address
    UCHAR   shost[6];           // 源MAC地址source mac address
    USHORT  type;               // 下層協議類型,如IP(ETHERTYPE_IP)、ARP(ETHERTYPE_ARP)等
} ETHeader, *PETHeader;
#define ARPHRD_ETHER 1

// ARP協議opcodes
#define ARPOP_REQUEST 1 // ARP 請求 
#define ARPOP_REPLY 2 // ARP 響應


typedef struct _ARPHeader       // 28字節的ARP頭
{
    USHORT  hrd;                // 硬件地址空間,以太網中爲ARPHRD_ETHER
    USHORT  eth_type;           // 以太網類型,ETHERTYPE_IP ??
    UCHAR   maclen;             // MAC地址的長度,爲6
    UCHAR   iplen;              // IP地址的長度,爲4
    USHORT  opcode;             // 操做代碼,ARPOP_REQUEST爲請求,ARPOP_REPLY爲響應
    UCHAR   smac[6];            // 源MAC地址
    UCHAR   saddr[4];           // 源IP地址
    UCHAR   dmac[6];            // 目的MAC地址
    UCHAR   daddr[4];           // 目的IP地址
} ARPHeader, *PARPHeader;


// 協議
#define PROTO_ICMP 1
#define PROTO_IGMP 2
#define PROTO_TCP 6
#define PROTO_UDP 17

typedef struct _IPHeader        // 20字節的IP頭
{
    UCHAR     iphVerLen;      // 版本號和頭長度(各佔4位)
    UCHAR     ipTOS;          // 服務類型 
    USHORT    ipLength;       // 封包總長度,即整個IP報的長度
    USHORT    ipID;           // 封包標識,唯一標識發送的每個數據報
    USHORT    ipFlags;        // 標誌
    UCHAR     ipTTL;          // 生存時間,就是TTL
    UCHAR     ipProtocol;     // 協議,多是TCP、UDP、ICMP等
    USHORT    ipChecksum;     // 校驗和
    ULONG     ipSource;       // 源IP地址
    ULONG     ipDestination;  // 目標IP地址
} IPHeader, *PIPHeader; 


// 定義TCP標誌
#define TCP_FIN 0x01
#define TCP_SYN 0x02
#define TCP_RST 0x04
#define TCP_PSH 0x08
#define TCP_ACK 0x10
#define TCP_URG 0x20
#define TCP_ACE 0x40
#define TCP_CWR 0x80

typedef struct _TCPHeader       // 20字節的TCP頭
{
    USHORT  sourcePort;         // 16位源端口號
    USHORT  destinationPort;    // 16位目的端口號
    ULONG   sequenceNumber;     // 32位序列號
    ULONG   acknowledgeNumber;  // 32位確認號
    UCHAR   dataoffset;         // 高4位表示數據偏移
    UCHAR   flags;              // 6位標誌位
                                //FIN - 0x01
                                //SYN - 0x02
                                //RST - 0x04 
                                //PUSH- 0x08
                                //ACK- 0x10
                                //URG- 0x20
                                //ACE- 0x40
                                //CWR- 0x80

    USHORT  windows;            // 16位窗口大小
    USHORT  checksum;           // 16位校驗和
    USHORT  urgentPointer;      // 16位緊急數據偏移量 
} TCPHeader, *PTCPHeader;

typedef struct _UDPHeader
{
    USHORT          sourcePort;     // 源端口號 
    USHORT          destinationPort;// 目的端口號 
    USHORT          len;            // 封包長度
    USHORT          checksum;       // 校驗和
} UDPHeader, *PUDPHeader;

#endif // __PROTOINFO_H__

initsock.h:網絡

#include <winsock2.h>
#pragma comment(lib, "WS2_32")  // 連接到WS2_32.lib庫文件

class CInitSock{
public:
    CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) {
        // 初始化WS2_32.dll
        WSADATA wsaData;
        WORD sockVersion = MAKEWORD(minorVer, majorVer);            //Winsock版本
        /*爲了在應用程序當中調用任何一個Winsock API函數,首先第一件事情就是必須經過WSAStartup函數完成對 Winsock服務的初始化,所以須要調用WSAStartup函數。使用Socket的程序在使用Socket以前必須調用WSAStartup函數。 該函數的第一個參數指明程序請求使用的Socket版本,其中高位字節指明副版本、低位字節指明主版本; 操做系統利用第二個參數返回請求的Socket的版本信息。當一個應用程序調用WSAStartup函數時, 操做系統根據請求的Socket版原本搜索相應的Socket庫,而後綁定找到的Socket庫到該應用程序中。 之後應用程序就能夠調用所請求的Socket庫中的其它Socket函數了。*/
        if(::WSAStartup(sockVersion, &wsaData) != 0){       //(Windows異步套接字的啓動命令)加載套接字庫
            exit(0);
        }
    }
    ~CInitSock() { //析構函數
        ::WSACleanup();         //關閉加載的套接字庫
    }
};

3、總結和拓展

幾個重要的函數:

一、WSAStartup函數
爲了在應用程序當中調用任何一個Winsock API函數,首先第一件事情就是必須經過WSAStartup函數完成對Winsock服務的初始化,所以須要調用WSAStartup函數。使用Socket的程序在使用Socket以前必須調用WSAStartup函數。
該函數的第一個參數指明程序請求使用的Socket版本,其中高位字節指明副版本、低位字節指明主版本;操做系統利用第二個參數返回請求的Socket的版本信息。當一個應用程序調用WSAStartup函數時,操做系統根據請求的Socket版原本搜索相應的Socket庫,而後綁定找到的Socket庫到該應用程序中之後應用程序就能夠調用所請求的Socket庫中的其它Socket函數了
二、recv函數
recv函數用於已鏈接的數據報或流式套接口進行數據的接收。
該函數的第一個參數指定接收端套接字描述符;
第二個參數指明一個緩衝區,該緩衝區用來存放recv函數接收到的數據;
第三個參數指明buf的長度;
第四個參數通常置0
三、拓展
在檢查IP封包的端口時,咱們能夠根據端口號知道數據傳輸所使用的協議。若是是21端口,說明使用的就是FTP協議。而後經過比較字符串找出含有username和password的字段就能找出程序訪問這些服務器所使用的用戶名和密碼。異步