现在Winpcap库已经被广泛应用于网络程序,特别是用于网络分析,而多数初学者对于如何分析所截获的数据包还不太清楚。现在就以我的经验来介绍一下,希望能对大家有所帮助。
首先要清楚的是,Winpcap截获的数据包与Windows Raw socket截获的数据包不同的是,raw socket截获的数据包只局限于传输层(
),也就是所只能够截获tcp,udp,icmp等高层协议,而Winpcap截获的数据包是从数据链路层开始的,它的层次更低,从而可以更深入,更详细地分析。
上图为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;
好了,就写到这了,希望此文对大家会有所帮助。