IP层网络数据抓包实现方法

本文介绍如何在Windows环境下使用WINSOCK库捕获网络数据包,包括初始化库、创建套接字、设置接收模式等步骤,并提供了一个示例程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

做过网管或协议分析的人一般都熟悉sniffer这个工具,它可以捕捉流经本地网卡的所有数据包。抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议、IP、UDP、TCP、甚至各种应用层协议),敏感数据的捕捉等。下面我们就来看看在windows下如何实现数据包的捕获。

编程大本营

 

    WINSOCK本身就提供了抓取流经网卡的所有数据包的函数,虽然只能在IP协议层上捕捉,但只要您的工作没有涉及到数据链路层的话,这也就足够用了。抓取数据包的编程方法基本和编写其它网络应用程序一样,只需多一个步骤,即将SOCKET设置为接收所有数据的模式,这是用WSAIoctl来实现的。 编程大本营HTTp://www.timihome.net

    编程实现主要有以下几个步骤:
    1. 初始化WINSOCK库;
    2. 创建SOCKET句柄;
    3. 绑定SOCKET句柄到一个本地地址;
    4. 设置该SOCKET为接收所有数据的模式;
    5. 接收数据包;
    6. 关闭SOCKET句柄,清理WINSOCK库; 编程大本营HTTp://www.timihome.net

    除第4个步骤外,其它的步骤都和编写其它网络应用程序一样。那我们就主要来看一下第4个步骤WSAIoctl。

    WSAIoctl是定义在mstcpip.h里面的。系统SDK里面本身就有。函数原型如下:
int WSAIoctl(
  SOCKET s,
  DWORD dwIoControlCode,
  LPVOID lpvInBuffer,
  DWORD cbInBuffer,
  LPVOID lpvOutBuffer,
  DWORD cbOutBuffer,
  LPDWORD lpcbBytesReturned,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
    参数很多,但我们只需用到5个。s是步骤2创建的句柄;dwIoControlCode设为SIO_RCVALL;lpvInBuffer和cbInBuffer是输入参数,SIO_RCVALL的输入参数是u_long,给它一个1的值就可以了;lpcbBytesReturned是返回字节数,给一个DWORD变量的地址;其它的都没用,传0就可以了.

u_long sioarg = 1;
DWORD wt=0;
WSAIoctl( h, SIO_RCVALL , &sioarg,sizeof(sioarg),NULL,0,&wt,NULL,NULL ) ;
    下面我们就来看一下完整的代码:


#include <winsock2.h>
#include <windows.h>
#include <Mstcpip.h> 不能采集本站

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

#include <iostream>
using namespace std; 编程大本营HTTp://www.timihome.net

//IP首部
typedef struct tIPPackHead
{
    enum PROTOCOL_TYPE{
        PROTOCOL_TCP = 6,
        PROTOCOL_UDP = 17,
        PROTOCOL_ICMP = 1,
        PROTOCOL_IGMP = 2   
    };
    inline unsigned HeadLen() const
    {
        //首部长度单位为4bytes。因此乘4
        return (ver_hlen & 0x0F) << 2;
    }
    inline unsigned PackLen() const
    {
        return wPacketLen;
    }
    BYTE ver_hlen;    //IP协议版本和IP首部长度。高4位为版本,低4位为首部的长度(单位为4bytes)
    BYTE byTOS;     //服务类型 编程大本营HTTp://www.timihome.net
    WORD wPacketLen; //IP包总长度。包括首部,单位为byte。[Big endian]
    WORD wSequence;  //序号,一般每个IP包的序号递增。[Big endian]
    WORD wMarkFragPoi; 
    BYTE byTTL;       //生存时间 
    BYTE byProtocolType; //协议类型,见PROTOCOL_TYPE定义
    WORD wHeadCheckSum;  //IP首部校验和[Big endian]
    DWORD dwIPSrc;       //源地址
    DWORD dwIPDes;       //目的地址
} IP_PK_HEAD; 防采集


int DecodeIP(char *buf, int len); 编程大本营HTTp://www.timihome.net

int DecodeIP(char *buf, int len)
{
    int n = len; 编程大本营

    if( n >= sizeof(IP_PK_HEAD) )
    {
        IP_PK_HEAD iphead;
        memcpy( &iphead, buf, sizeof(iphead) );

        //以下三个为Big Endian字节顺序,转换成主机字节顺序
        iphead.wPacketLen = ntohs( iphead.wPacketLen );
        iphead.wSequence = ntohs( iphead.wSequence );
        iphead.wHeadCheckSum = ntohs( iphead.wHeadCheckSum ); 请到HTTp://www.timihome.net访问

        in_addr src,dst;
        src.S_un.S_addr = iphead.dwIPSrc;
        dst.S_un.S_addr = iphead.dwIPDes;

        char strsrc[20],strdst[20];
        strcpy(strsrc, .Net_ntoa(src) );
        strcpy( strdst , .Net_ntoa(dst));

        printf( "IP数据包: ver=%d,hlen=%d,protocol=%d,pklen=%d,seq=%d,src=%s,dst=%s _fcksavedurl="%s,dst=%s" ",
            iphead.ver_hlen >> 4,
            (iphead.ver_hlen & 0x0F) << 2,
            iphead.byProtocolType,
            iphead.wPacketLen,
            iphead.wSequence,
            strsrc,
            strdst );
    } 编程大本营HTTp://www.timihome.net

    return 0;
}

void AutoWSACleanup()
{
    ::WSACleanup();
}

int _tmain(int argc, _TCHAR* argv[])
{
    //初始化winsock库,使用2.2版本
    u_short wVersionRequested = 0x0202;
    WSADATA wsaData;
    if( SOCKET_ERROR == WSAStartup( wVersionRequested, &wsaData ) )
    {       
        cout << WSAGetLastError();
        return 0;
    }
    atexit( AutoWSACleanup );

    //创建SOCKET
    SOCKET h = socket( AF_.Net, SOCK_RAW, IPPROTO_IP);
    if( h == INVALID_SOCKET )
    {
        cout << WSAGetLastError();
        return 0;
    }

    //获取本机地址
    char FAR name[128]; 
    if( -1 == gethostname(name, sizeof(name)) )
    {
        closesocket( h );
        cout << WSAGetLastError();
        return 0;
    }

    struct hostent FAR * pHostent;
    pHostent = gethostbyname(name);

    //绑定本地地址到SOCKET句柄
    sockaddr_in addr;
    addr.sin_family = AF_.Net;
    addr.sin_addr = *(in_addr*)pHostent->h_addr; //IP
    addr.sin_port = 0; //端口,IP层端口可随意填
    if( SOCKET_ERROR == bind( h,(sockaddr *)&addr,sizeof(addr) ) )
    {
        closesocket( h );
        cout << WSAGetLastError();
        return 0;
    }

 

 

    //设置该SOCKET为接收所有流经绑定的IP的网卡的所有数据,包括接收和发送的数据包
    //该函数在mstcpip.h里面,详见MSDN帮助
    u_long sioarg = 1;
    DWORD wt=0;
    if( SOCKET_ERROR == WSAIoctl( h, SIO_RCVALL , &sioarg,sizeof(sioarg),NULL,0,&wt,NULL,NULL ) )
    {
        closesocket( h );
        cout << WSAGetLastError();
        return 0;
    }

    //我们只需要接收数据,因此设置为阻塞IO,使用最简单的IO模型
    u_long bioarg =  0;
    if( SOCKET_ERROR == ioctlsocket( h, FIONBIO , &bioarg ) )
    {
        closesocket( h );
        cout << WSAGetLastError();
        return 0;
    }

    //开始接收数据
    //因为前面已经设置为阻塞IO,recv在接收到数据前不会返回。
    //当返回<=0时表示接收失败,退出循环
    //可以在另一个线程执行此循环,主线程closesocket可以使recv失败而结束循环
    char buf[102400];
    int len = 0;
    do
    {
        len = recv( h, buf, sizeof(buf),0);
        if( len > 0 )
        {
            DecodeIP( buf, len );
        }
    }while( len > 0 );

    closesocket( h );

    return 0;
}


    绑定地址时,必须获取到本机的一个真实IP地址,不能用0.0.0.0或127.0.0.0.1。端口就没有限制了。接收数据的方法和基于TCP或UDP的接收是一样的。也可以用非阻塞IO,并采用select、WSAAsyncSelect、WSAEventSelect等IO模型来处理。但我们要做的只是简单地接收数据而已,不需要任何交互,实在没有必要给自己找麻烦。这里我用的是阻塞IO,即在接收到数据前recv不会返回,一旦返回了,要不就是接收到数据,要不就是接收失败。当接收失败时循环就退出了。不过因为是阻塞IO的关系,主线程就无法进行其它操作,最好把接收数据的循环放到第二线程内。主线程通过closesocket就可以使接收失败从而结束接收数据的循环了。 不能采集本站

    通过这种方法接收到的数据是个IP层的数据包,每个数据包的开始是至少20个字节的IP首部,IP首部定义见tIPPackHead结构体,关于IP首部的更详细的资料可以参考《TCP-IP详解卷1:协议》第3章,在此就不多作描述。DecodeIP是对IP首部的解析,IP首部后面的数据则需要根据首部里面的协议类型进行各自不同处理。如果是TCP协议则按照TCP协议解析,UDP则按UDP协议解析。下面附上TCP和UDP首部的定义,更详细的信息请参考《TCP-IP详解卷1:协议》第11和17章。


//UDP首部
typedef struct tUDPPackHead
{
     inline unsigned HeadLen() const
    {
        return 8;
    }
    inline unsigned PackLen() const
    {
        return wLength;
    }
    WORD wSPort;     /**//*Source Port*/
     WORD wDPort;     /**//*Destinate Port*/
     WORD wLength;    /**//*UDP Length*/
     WORD wCheckSum;  /**//*UDP CheckSum*/
} UDP_PK_HEAD;

//TCP首部
typedef struct tTCPPackHead 
{
     inline unsigned HeadLen() const
    {
        return ((lenres>>4) & 0x0F) << 2;
    } 防采集

    WORD wSrcPort; //16位源端口
    WORD wDstPort; //16位目的端口
    DWORD dwSeq; //32位序列号
    DWORD dwAck; //32位确认号
    unsigned char lenres; //4位首部长度/6位保留字 (高4bit为head len)
    unsigned char flag; //6位标志位 (低6bit为标志)
    WORD wWin; //16位窗口大小
    WORD wSum; //16位校验和
    WORD wUrp; //16位紧急数据偏移量
}TCP_PK_HEAD;

 
一款改自IPHook的IP数据包监控过滤程序 By ccc 2009-6-13 完成(ch2zh1@tom.com) IP包过滤程序采用IPHook驱动器程序,在内核用钩子回调函数(IpHookFilter)勾住系统自带的IP过滤驱动器(System32\Drivers\IpFltDrv.sys),从而返回过滤驱动器传输的所有IP数据。可以通过回调函数的返回值(PF_DROP、PF_FORWARD、PF_PASS),指示滤波驱动器对包的处理,是抛弃还是通过。 应用启动驱动器过程可以使用驱动器的安装,或使用系统的服务控制器(ServiceControl)直接启动和停止驱动程序。 服务控制器利用OpenSCManager等服务控制函数控制驱动程序的加载与卸载。这就使得应用程序可以在运行时自动加载和卸载需要的驱动程序。而不必设置驱动程序的安装过程。 初始的IPHook.sys只提供了IP包的监视过程即,仅仅返回IP头信息。在应用中解释并显示这个头信息。经过写改的IPHook增加了对IP包的过滤处理,有几个设置命令: START_IP_HOOK :启动钩子过滤命令,建立连接IP滤波器的回调函数(原始命令) STOP_IP_HOOK :停止钩子过滤命令,撤销IP滤波器回调函数(原始命令) ADD_IP_HOOKADDR :添加过滤IP地址,回调函数判断IP包,以决定是放弃还是通过(新命令) DEL_IP_HOOKADDR :删除过滤IP地址(新命令) ADD_IP_HOOKPORT :添加过滤端口号(新命令) DEL_IP_HOOKPORT :删除过滤端口号(新命令) ADD_IP_OVER_HOOK :添加置换的IP地址,用于对指定目的的IP包置换源地址和端口号(New) DEL_IP_OVER_HOOK :取消IP地址置换。(New) 应用程序可一次设置和删除对个滤波地址和端口号。对于重复设置,IPHook可以正确识别并加以剔除。这是一个演示程序主要想说明windows2000驱动程序编程过程,及应用自动加载和卸载驱动的方式。 驱动程序的源码在DrvChecked.rar中,要想正确编译,必须安装windows2000 DDK,并在VS2003下设置包含目录指向DDK的目录,库目录也要指向DDK的库目录: C:\NTDDK\inc C:\NTDDK\inc\ddk C:\NTDDK\libchk\i386 并且在排列上一定要排在头两个位置上。 这个驱动程序在VC++下是可以正确编译通过的。驱动程序名:DrvChecked.sys。使用时应该改为:iphook.sys。delphi下的应用程序为:IPPacketMonitor.exe,它使用IPHook.sys。 任何人可以自己编译和修改这个驱动程序,使之能够按照一定的方式运行,这里给出的只是本人的一个想法,希望能给各位带来一点帮助。任何应用程序都可以想打开文件一样使用这个驱动程序,关于使用方法请参考Release Notes.htm提供的线索。 有什么问题和建议请电邮:ch2zh1@tom.com
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值