用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;}
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值