用winsock和winpcap分別實現嗅探器

1.兩者實現的區別
用 Raw Socket 實現 Sniffer 的方法,實現起來比較簡單,但有個缺點就是隻能截獲 IP 層以上的包,數據包頭不含幀信息。但是相對於來說實現起來會比較簡單。使用的話就見仁見智了,根據自己的需要去選擇即可。

2.Raw Socket
操作環境:vs2005 其他的環境下代碼有可能需要改動

#include <stdio.h>  
#include <winsock2.h>  
#include <ws2tcpip.h>  
#include "fstream"
#include <iostream>
using namespace std;
#pragma comment (lib,"ws2_32.lib")  
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)  
#define IP_HDRINCL 2 


struct IPHEAD  
{  
    unsigned char h_len:4;//4位首部長度+4位IP版本號  
    unsigned char ver:4;  
    unsigned char tos;//8位服務類型TOS  
    unsigned short total_len;//16位總長度(字節)  
    unsigned short ident;//16位標識  
    unsigned short frag_and_flags;//3位標誌位  
    unsigned char ttl;//8位生存時間 TTL  
    unsigned char proto;//8位協議 (TCP, UDP 或其他)  
    unsigned short checksum;//16位IP首部校驗和  
    unsigned int sourceip;//32位源IP地址  
    unsigned int destip;//32位目的IP地址  
};  

struct TCPHEAD //定義TCP首部  
{  
    USHORT th_sport; //16位源端口  
    USHORT th_dport; //16位目的端口  
    unsigned int th_seq; //32位序列號  
    unsigned int th_ack; //32位確認號  
    unsigned char th_lenres; //4位首部長度/6位保留字  
    unsigned char th_flag; //6位標誌位  
    USHORT th_win; //16位窗口大小  
    USHORT th_sum; //16位校驗和  
    USHORT th_urp; //16位緊急數據偏移量  
};  

char *phostlist[10];//列舉主機網卡的數組  

DWORD _stdcall listen(void *p)  
{  
    SOCKET s;  
    struct sockaddr_in addr;  
    int itimeout=10000;  
    int ret;  
    BOOL flag; 
    char cbuf[1500];//接收數據緩衝區  
    struct IPHEAD *piphd;//定義IP頭結構  
    struct TCPHEAD *ptcphd;//定義TCP頭結構  

    s=socket(AF_INET,SOCK_RAW,IPPROTO_IP); //創建一個原始套接字  
    if (s == INVALID_SOCKET)
        {
        printf("創建原始套接字失敗\n");
        system("pause");
        }
    //設置 IP 頭操作選項
    setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag)); 

    memset(&addr,0,sizeof(addr));  
    addr.sin_family=AF_INET;  
    addr.sin_addr.S_un.S_addr=inet_addr((char *)p);  
    addr.sin_port=htons(6000);//設置本地端口號  
    bind(s,(struct sockaddr *)&addr,sizeof(addr));//綁定端口  

    //設置sock_raw爲sio_rcvall,以便接收所有IP包  
    DWORD dwin=1;  
    ioctlsocket(s,SIO_RCVALL,&dwin);   
    for(;;)  
    {  
        memset(cbuf,0,sizeof(cbuf));
        ret=recv(s,cbuf,sizeof(cbuf),0);//接收數據  
        if(ret==SOCKET_ERROR)  
        {  
            if(WSAGetLastError()==WSAETIMEDOUT)continue;  
            closesocket(s);  
            return 0;  
        }  

        piphd=(struct IPHEAD *)cbuf;//取得IP頭數據的地址  
        int iIphLen = sizeof(unsigned long) * (piphd->h_len  & 0xf);  
        ptcphd=(struct TCPHEAD *)(cbuf+iIphLen);//取得TCP頭數據的地址  

        printf("From : %s \t port %d\t",inet_ntoa(*(struct in_addr*)&piphd->sourceip),ntohs(ptcphd->th_sport) );  
        printf("To : %s \t port %d  ",inet_ntoa(*(struct in_addr*)&piphd->destip),ntohs(ptcphd->th_dport)); 

        switch(piphd->proto)//根據IP頭的協議判斷數據包協議類型  
        {  
        case 1:  
            printf("ICMP\n");  
            break;  
        case 2:  
            printf("IGMP\n");  
            break;  
        case 6:  
            printf("TCP\n");  
            break;  
        case 17:  
            printf("UDP\n");  
            break;  
        default:  
            printf("unknow:%d\n",piphd->proto);  
        }  

    }  

    return 1;  
}  

int main()  
{  
    //初始化sock  
    WSADATA wsa;  
    int i=0;  
    DWORD dwtid;  
    char chname[128];  
    hostent *host;  

    WSAStartup(MAKEWORD(2,2),&wsa);  
    gethostname(chname,sizeof(chname));  
    host=gethostbyname(chname);  
    while(host->h_addr_list[i]!=NULL)//取所有網卡序號,爲每個網卡開啓一個監聽線程  
    {  
        phostlist[i]=(char *)malloc(16);  
        sprintf(phostlist[i],"%s",inet_ntoa(*(struct in_addr *)host->h_addr_list[i]));  
        printf("Bind to %s\n",phostlist[i]);  
        CreateThread(NULL,0,listen,phostlist[i],0,&dwtid); //用於定義新線程的安全屬性,一般設置成NULL  分配以字節數表示的線程堆棧的大小,默認值是0; 返回新創建的線程的ID編號 
        i++;  
    }  

    for(;;)//爲每個網卡創建監聽線程後要用一個循環防止主線程退出  
    {  
        Sleep(10);  
    }  
}  

主要函數:

int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag));

設置套接字的選項。具體的可以看這個博文https://www.cnblogs.com/eeexu123/p/5275783.html
本程序設置SockRaw這個套接字的ip選項中的IP_HDRINCL即在數據包中包含IP首部。

int ioctlsocket( SOCKET s, long cmd, u_long FAR *argp );
s:一個標識套接口的描述字。
cmd:對套接口s的操作命令。
argp:指向cmd命令所帶參數的指針。
即設置sock_raw爲sio_rcvall,以便接收所有IP包

3.winpcap
關於這個的代碼到處都是,我也是直接在網上粘了一段,稍微改了一下。
1)配置環境
參考自:https://blog.csdn.net/gk405128494/article/details/39314467
1.到http://www.winpcap.org/install/default.htm下載winpcap的安裝包,然後到http://www.winpcap.org/devel.htm程序員開發包。
2.執行安裝包,這樣你的機子就能運行winpcap程序了。
3.解壓開發包,在VC6.0的Tools–>Option–>Directories的Include fils 和library files加入winpcap的include和lib目錄。
4.開始編寫wpcap程序。

vs2005:
第一步:下載WinPcap的安裝包;有不同操作系統環境下的包,我下的是win32版本的。下載地址:www.winpcap.org;最新的Release版本是4.0.1的,最高的版本一般是Beta的。這個安裝包主要是註冊一個wpcap.dll的庫到操作系統中。必須安裝,如果不安裝,在運行例子的時候會彈出窗口提示,找不到wpcap.dll文件;
第二步:到上面的網站下載它的開發包,包括一些頭文件和庫文件;解壓到自己指定的目錄中;目錄中還有HTML格式的說明文檔,用於自己學習比較方便;
第三步:設置VS2005;1)設置環境目錄;在菜單:工具->選項;彈出的選項窗體左邊點擊:項目和解決方案->VC++目錄;在右邊:“顯示以下內容的目錄”標籤下面的下拉框中找到“包含文件”,然後對應到第二步下載開發包的Include目錄;在同一下拉框中找到“庫文件”,然後對應到第二步下載開發包的lib目錄;2)設置編譯條件;在項目屬性頁中:配置屬性->C/C++->預處理器->預處理器定義,增加;WPCAP;HAVE_REMOTE;每一個預定義符用”;”隔開;在項目屬性頁中:配置屬性->鏈接器->命令行->附加選項對應的文本框中增加:“wpcap.lib ws2_32.lib”;

第四步:非必要步驟;有的時候可能會有些意外錯誤;比如找不到u_char類型等;我的解決辦法是加上

這裏寫代碼片

#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#else
#include <winsock.h>
#endif

2)代碼段:
參考自:http://doc.okbase.net/29062706/archive/10224.html
正則表達式在設置時一定要注意

#define WIN32
#include <pcap.h>
#include <WinSock.h>
#ifndef WIN32
  #include<sys/socket.h>
  #include<netinet/in.h>
#else
  #include<WinSock.h>
#endif

#pragma comment(lib, "wpcap.lib")
/* 4 字節IP地址 */
typedef struct ip_address
{
    u_char byte1;
    u_char byte2;
    u_char byte3;
    u_char byte4;
}ip_address;

/*
// IP包如下所示,請參見 RFC791.
0                   1                   2                   3   
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|      Fragment Offset    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |         Header Checksum       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

/* IPv4 頭 */
typedef struct ip_header
{
    u_char      ver_ihl;            //4位首部長度,4位IP版本號 
    u_char      tos;                    //8位服務類型TOS 
    u_short          tlen;              //16位總長度(字節) 
    u_short          identification;        //16位標識 
    u_short          flags_fo;          //3位標誌位,13位偏移
    u_char      ttl;                    //8位生存時間 TTL 
    u_char        proto;                //8位協議 (TCP, UDP 或其他) 
    u_short          crc;                   //16位IP首部校驗和 
    ip_address  saddr;              //32位源IP地址 
    ip_address  daddr;              //32位目的IP地址
    u_int       op_pad;             //32位選項
}ip_header;

/*
// TCP包如下所示,請參見 RFC793.
0                   1                   2                   3   
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Data |           |U|A|P|R|S|F|                               |
| Offset| Reserved  |R|C|S|S|Y|I|            Window             |
|       |           |G|K|H|T|N|N|                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum            |         Urgent Pointer        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             data                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
//定義TCP首部
typedef struct tcp_header 
{
    u_short         sport;      //16位源端口 
    u_short         dport;      //16位目的端口 
    u_int           seq;            //32位序列號 
    u_int           ack;            //32位確認號 
    u_char          lenres;     //4位首部長度/6位保留字 
    u_char          flag;       //6位標誌位 
    u_short         win;            //16位窗口大小 
    u_short          sum;           //16位校驗和 
    u_short         urp;            //16位緊急數據偏移量
    u_int           op_pad;     //32位選項
}tcp_header;

/*
// UDP包如下所示,請參見 RFC768.
0      7 8     15 16    23 24    31  
+--------+--------+--------+--------+ 
|     Source      |   Destination   | 
|      Port       |      Port       | 
+--------+--------+--------+--------+ 
|                 |                 | 
|     Length      |    Checksum     | 
+--------+--------+--------+--------+ 
|                                     
|          data octets ...            
+---------------- ...                 
*/

/* UDP header */
typedef struct udp_header
{
    u_short sport;      //16位源端口
    u_short dport;      //16位目的端口
    u_short len;        //16位長度 
    u_short crc;        //16位校驗和
}udp_header;

// sniffer.c : 定義控制檯應用程序的入口點。
int main(int argc, char **argv)
{
    pcap_if_t* alldevs;
    pcap_if_t* d;
    int inum;
    int i = 0;
    pcap_t* fp;
    char *ofilename = "sniffer.txt";
    char errbuf[PCAP_ERRBUF_SIZE];//存儲錯誤信息

    u_int netmask;//掩碼
    int res;
    struct pcap_pkthdr *header;
    const u_char *pkt_data;

    char packet_filter[] = "tcp or udp";
    pcap_dumper_t *dumpfile;
    struct bpf_program fcode;

    ip_header* ih;
    tcp_header* th;
    udp_header* uh;
    u_int ip_len;
    u_short sport, dport;

    // 獲得網絡設備指針
    if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
    {
        fprintf(stderr, "pcap_findalldevs出錯: %s\n", errbuf);
        exit(1);
    }

    // 枚舉網卡信息,首地址
    for (d = alldevs; d; d = d->next)
    {
        printf("%d. %s", ++ i, d->name);
        if (d->description)
        {
            printf(" (%s)\n", d->description);
        }
        else
    {
        printf(" (無描述信息)\n");
    }
}

if (i == 0)
{
    printf("\n沒發現任何接口,請確認是否已經安裝WinPcap庫.\n");
    return -1;
}

printf("請輸入網卡接口號 (1 - %d):", i);
scanf("%d", &inum);

if (inum < 1 || inum > i)
{
    printf("\n接口號超出範圍.\n");
    // 釋放alldevs資源
    pcap_freealldevs(alldevs);
    return -1;
}

// 跳到選擇的網卡
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);

// 打開網卡
if ((fp = pcap_open(d->name,    // 設備名稱
65536,     
PCAP_OPENFLAG_PROMISCUOUS,      // 混雜模式
1000,                       // 讀取超時時間
NULL,                       // 遠程認證,本程序因是本地嗅探,不需要設置
errbuf     
)) == NULL)
{
    fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Winpcap\n");
    // 釋放alldevs資源
    pcap_freealldevs(alldevs);
    return -1;
}

// 檢查鏈路層,本程序只簡單的支持以太網
if (pcap_datalink(fp) != DLT_EN10MB)
{
    fprintf(stderr, "\n本程序只簡單的支持以太網.\n");
    // 釋放alldevs資源
    pcap_freealldevs(alldevs);
    return -1;
}

if (d->addresses != NULL)
{
    // 取得一個網絡接口的子網掩碼
    netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
}
else
{
    netmask = 0xffffffff;
}

// 編譯過濾器
if (pcap_compile(fp, &fcode, packet_filter, 1, netmask) < 0)
{
    fprintf(stderr, "\nU不能編譯包過濾,請檢查正則表達式.\n");
    // 釋放alldevs資源
    pcap_freealldevs(alldevs);
    return -1;
}

// 設置過濾器
if (pcap_setfilter(fp, &fcode) < 0)
{
    fprintf(stderr, "\n設置過濾器失敗.\n");
    // 釋放alldevs資源
    pcap_freealldevs(alldevs);
    return -1;
}

printf("\nlistening on %s ...\n", d->description);

// 釋放alldevs資源,因爲不再需要它
pcap_freealldevs(alldevs);

// 打開輸出文件
dumpfile= pcap_dump_open(fp, ofilename);
if (dumpfile == NULL)
{
    fprintf(stderr,"\n打開輸出文件錯誤\n");
    pcap_close(fp);
    return -1;
}

// 開始捕獲數據包
while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
{
    if(res == 0)
    /* 超時繼續 */
    continue;

    // 將捕獲的數據包存入文件
    pcap_dump((unsigned char *) dumpfile, header, pkt_data);

    // 取得IP頭,14爲以太網頭長度
    ih = (ip_header*)(pkt_data + 14);
    ip_len = (ih->ver_ihl & 0xf) * 4;

    if (ih->proto == 6) //TCP 
    {
        //取TCP頭
        th = (tcp_header*)((u_char*)ih + ip_len);

        /* convert from network byte order to host byte order */
        sport = ntohs(th->sport);   //網絡字節序轉主機字節序
        dport = ntohs(th->dport);   //網絡字節序轉主機字節序

        /* 打印IP地址和端口號 */
        printf("%d.%d.%d.%d:%d-> %d.%d.%d.%d:%d\n",
        ih->saddr.byte1,
        ih->saddr.byte2,
        ih->saddr.byte3,
        ih->saddr.byte4,
        sport,
        ih->daddr.byte1,
        ih->daddr.byte2,
        ih->daddr.byte3,
        ih->daddr.byte4,
        dport); 
    }
    else if (ih->proto == 17)   //UDP
    {
        //取UDP頭
        uh = (udp_header*)((u_char*)ih + ip_len);

        /* convert from network byte order to host byte order */
        sport = ntohs(uh->sport);   //網絡字節序轉主機字節序
        dport = ntohs(uh->dport);   //網絡字節序轉主機字節序

        /* 打印IP地址和端口號 */
        printf("%d.%d.%d.%d:%d-> %d.%d.%d.%d:%d\n",
        ih->saddr.byte1,
        ih->saddr.byte2,
        ih->saddr.byte3,
        ih->saddr.byte4,
        sport,
        ih->daddr.byte1,
        ih->daddr.byte2,
        ih->daddr.byte3,
        ih->daddr.byte4,
        dport); 
    }
}

pcap_close(fp);
pcap_dump_close(dumpfile);

return 0;}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章