【转载】解析Winpcap截获的数据包

首先要清楚的是,Winpcap截获的数据包与Windows Raw socket截获的数据包不同的是,raw socket截获的数据包只局限于传输层(请参考有关OSI模型知识),也就是所只能够截获tcp,udp,icmp等高层协议,而Winpcap截获的数据包是从数据链路层开始的,它的层次更低,从而可以更深入,更详细地分析。
由于Winpcap数据包查找网卡,打开网卡,设置过滤器等代码到处可见,
参考http://www.coffeecat.net.cn/WinPcap/html/group__wpcap__tut6.html
所以,前面部分的代码我在这里就不介绍了。我将专门介绍如何分析数据包。
当一有通过过滤器的数据包被截获之后,一个回调函数将被调用,这个函数的原型是:

/* 回调函数原型 */
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);
其中     header为一个结构体指针: 


struct pcap_pkthdr {

    struct timeval ts;   /* time stamp */

    bpf_u_int32 caplen; /* length of portion present */

    bpf_u_int32 len; /* length this packet (off wire) */

};

caplen为截获的数据包总长度,pkt_data为截获的数据包的字节指针。
我现在以一个我截获的数据包为例进行分析,数据包如下:

上图为winpcap截获的数据包,现在要知道的是,数据包中到底是什么样的层次呢?
首先是以太网帧,它的格式为:


它的结构可定义为:
// DLC Header
typedef struct tagDLCHeader               /*以太网数据帧头部结构*/
{
    unsigned char      DesMAC[6];      /* destination HW addrress */
    unsigned char      SrcMAC[6];      /* source HW addresss */
    unsigned short     Ethertype;      /* ethernet type */
} DLCHEADER, *PDLCHEADER;

 

开始6个字节为目标MAC地址(00 06 5B F4 9F 68),接着的6个字节为源MAC地址(00 09 6B 8F D4 57
),接着2个字节是以太网协议类型(08 00),
参考以太网类型速查表
上例中的Ethertype为0x0008,这个数值是网络顺序,要先转为主机顺序才能用,转换的函数为
u_short ntohs( u_short netshort );
一般类型长度大于1个字节(如unsigned short、unsigned long)的数据在网络中传输都是以网络顺序传播的,都要先转为主机顺序(其实就是顺序反过来)。上述的Ethertype经转换之后成为0x0800,在以太网类型速查表中可以查找到它对应的协议类型为IP协议。
因为以太网协议类型为IP协议,所以之后接下来的就是IP协议首部,它的结构图为:

可定义为结构体如下:
typedef struct _IPHeaer  
{
    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;
然后计算所需的内容,如:
ip头长度=(iphVerLen*0x0f)*4;
ip报文总长度(ip首部长度+协议(如tcp)首部长度+真实数据(如http数据内容))=ntohs(ipLength);
协议类型=ipProtocol(常用的有 1为ICMP,6为TCP,17为UDP)=6,所以这个数据包为tcp数据包。
源IP地址:ipSource为四个字节的ip地址,通常转为Ip地址字符串(其实也就是把每个字节的十六进制转为十进制),如这里的ipSource为“C0 A8 00 10”,转为字符串即为"192.168.0.16"
转换方法为:
in_addr src,dest;
src.S_un.S_addr = ipSource;
dest.S_un.S_addr = ipDestination;
char* ip = inet_ntoa(src);即获得源ip地址
同样可以获得目的ip地址。
因为ip首部中的协议类型是tcp,所以接下来就是tcp首部,


可以定义为结构如下:
typedef struct _TCPHeader    //20个字节
{   
    USHORT    sourcePort;        //16位源端口号
    USHORT    destinationPort;//16位目的端口号
    ULONG    sequenceNumber;    //32位序列号
    ULONG    acknowledgeNumber;//32位确认号
    UCHAR    dataoffset;        //4位首部长度/6位保留字
    UCHAR    flags;            //6位标志位
    USHORT    windows;        //16位窗口大小
    USHORT    checksum;        //16位校验和
    USHORT    urgentPointer;    //16位紧急数据偏移量
}TCPHeader,*PTCPHeader;
同样可以提取所需的数据,如:
源端口=ntohs(sourcePort);
目的端口=ntohs(destinationPort);
tcp首部长度=((dataoffset*0xf0)>>4)*4;
tcp首部之后,就是携带的真实数据了,它的长度等于IP报文总长度-IP首部长度-TCP首部长度,这几个结构长度都在上面有计算方法了。
   好了,上面已经把一个tcp数据包介绍完了,同样,可以采用相同的方法来分析ARP,UDP,ICMP,IGMP等协议数据包。
   下面给出一些常用的协议首部结构:
// ARP 数据帧
typedef struct tagARPFrame
{
    unsigned short     HW_Type;            /* hardware type */
    unsigned short     Prot_Type;        /* protocol type */
    unsigned char      HW_Addr_Len;     /* length of hardware address */
    unsigned char      Prot_Addr_Len;   /* length of protocol address */
    unsigned short     Opcode;            /* ARP/RARP */
    unsigned char      Send_HW_Addr[6]; /* sender hardware address */
    unsigned long      Send_Prot_Addr; /* sender protocol address */
    unsigned char      Targ_HW_Addr[6]; /* target hardware address */
    unsigned long      Targ_Prot_Addr; /* target protocol address */
    unsigned char      padding[18];
} ARPFRAME, *PARPFRAME;
typedef struct _UDPHeader
{
    USHORT    sourcePort;        //源端口号
    USHORT    destinationPort;//目的端口号
    USHORT    len;            //封包长度
    USHORT    checksum;        //校验和
}UDPHeader,*PUDPHeader;
typedef struct _ICMPHeader
{
    UCHAR    icmp_type;        //消息类型
    UCHAR    icmp_code;        //代码
    USHORT    icmp_checksum;    //校验和
    //下面是回显头
    USHORT    icmp_id;        //用来惟一标识此请求的ID号,通常设置为进程ID
    USHORT    icmp_sequence;    //序列号
    ULONG    icmp_timestamp;    //时间戳
}ICMPHeader,*PICMPHeader;
typedef struct _IGMPHeader //8字节
{
    UCHAR   hVerType;      //版本号和类型(各4位)
    UCHAR   uReserved;     //未用
    USHORT uCheckSum;     //校验和
    ULONG    dwGroupAddress;//32为组地址(D类IP地址)
}IGMPHeader,*PIGMPHeader;
   好了,就写到这了,希望此文对大家会有所帮助。

posted @ 2011-12-01 22:19  dorothychai  阅读(1459)  评论(0编辑  收藏  举报