概述
pcap文件格式是常用的数据报存储格式,包括wireshark在内的主流抓包软件都可以生成这种格式的数据包。
文件格式:
Pcap文件头(24字节)+数据包头(wireshark增加的)+数据包(网络中抓取的)+……
1.pcap文件头结构
各字段说明:
Magic:4B:0×1A 2B 3C 4D:用来识别文件自己和字节顺序。0xa1b2c3d4用来表示按照原来的顺序读取,0xd4c3b2a1表示下面的字节都要交换顺序读取。一般,我们使用0xa1b2c3d4
Major:2B,0×02 00:当前文件主要的版本号
Minor:2B,0×04 00当前文件次要的版本号
ThisZone:4B 时区。GMT和本地时间的相差,用秒来表示。如果本地的时区是GMT,那么这个值就设置为0.这个值一般也设置为0 SigFigs:4B时间戳的精度;全零
SnapLen:4B最大的存储长度(该值设置所抓获的数据包的最大长度,如果所有数据包都要抓获,将该值设置为65535; 例如:想获取数据包的前64字节,可将该值设置为64)
LinkType:4B链路类型
结构体定义:
//pacp文件头结构体
struct pcap_file_header
{
u_int32_t magic;
/* 0xa1b2c3d4 */
u_int16_t version_major;
/* magjor Version 2 */
u_int16_t version_minor;
/* magjor Version 4 */
int32_t thiszone;
/* gmt to local correction */
u_int32_t sigfigs;
/* accuracy of timestamps */
u_int32_t snaplen;
/* max length saved portion of each pkt */
u_int32_t linktype;
/* data link type (LINKTYPE_*) */
};
2.数据包头结构
各字段说明:
Timestamp:时间戳高位,精确到seconds(值是自从January 1, 1970 00:00:00 GMT以来的秒数来记)
Timestamp:时间戳低位,精确到microseconds (数据包被捕获时候的微秒(microseconds)数,是自ts-sec的偏移量)
Caplen:当前数据区的长度,即抓取到的数据帧长度,由此可以得到下一个数据帧的位置。
Len:离线数据长度:网络中实际数据帧的长度,一般不大于caplen,多数情况下和Caplen数值相等。
(例如,实际上有一个包长度是1500 bytes(Len=1500),但是因为在Global Header的snaplen=1300有限制,所以只能抓取这个包的前1300个字节,这个时候,Caplen = 1300 )
Wireshark中的实际样子:
结构体定义:
//时间戳
struct time_val
{
int tv_sec;
/* seconds 含义同 time_t 对象的值 */
int tv_usec;
/* and microseconds */
}
;
//pcap数据包头结构体
struct pcap_pkthdr
{
struct time_val ts;
/* time stamp */
u_int32_t caplen;
/* length of portion present */
u_int32_t len;
/* length this packet (off wire) */
};
3.数据包
即 Packet(通常就是链路层的数据帧)具体内容,长度就是Caplen,这个长度的后面,就是当前PCAP文件中存放的下一个Packet数据包,也就 是说:PCAP文件里面并没有规定捕获的Packet数据包之间有什么间隔字符串,下一组数据在文件中的起始位置。我们需要靠第一个Packet包确定,以TLS数据包的client hello消息为例来分析结构。
数据帧头,即数据链路层的头部
帧头的结构:
FCS:Frame Check Sequence(帧校验序列),俗称帧尾,即计算机网络数据链路层的协议数据单元(帧)的尾部字段,是一段4个字节的循环冗余校验码。源节点发送数据帧时,由帧的帧头和数据部分计算得出FCS,目的节点接收到后,用同样的方式再计算一遍FCS,如果与接收到的FCS不同,则认为帧在传输过程中发生了错误,从而选择丢弃这个帧。(早期链路情况不好的时候会使用,现在的网络一般都舍弃这个字段)
结构体定义:
//数据帧头
typedef struct FramHeader_t
{
//Pcap捕获的数据帧头
u_int8_t DstMAC[6];
//目的MAC地址
u_int8_t SrcMAC[6];
//源MAC地址
u_int16_t FrameType;
//帧类型
}t;
实际抓包:
网路层头部
IP数据包头结构
结构体定义:
//IP数据报头
typedef struct IPHeader_t
{
//IP数据报头
u_int8_t Ver_HLen;
//版本+报头长度
u_int8_t TOS;
//服务类型
u_int16_t TotalLen;
//总长度
u_int16_t ID;
//标识
u_int16_t Flag_Segment;
//标志+片偏移
u_int8_t TTL;
//生存周期
u_int8_t Protocol;
//协议类型
u_int16_t Checksum;
//头部校验和
u_int32_t SrcIP;
//源IP地址
u_int32_t DstIP;
//目的IP地址
};
实际抓包:
传输层头部
TCP数据包头
结构体定义:
//TCP数据报头
typedef struct TCPHeader_t
{
//TCP数据报头
u_int16_t SrcPort;
//源端口
u_int16_t DstPort;
//目的端口
u_int32_t SeqNO;
//序号
u_int32_t AckNO;
//确认号
u_int8_t HeaderLen;
//数据报头的长度(4 bit) + 保留(4 bit)
u_int8_t Flags;
//标识TCP不同的控制消息
u_int16_t Window;
//窗口大小
u_int16_t Checksum;
//校验和
u_int16_t UrgentPointer;
//紧急指针
};
实际抓包:
安全套接层头部
结构说明:
Content type 1B | Version 2B | Length 2B |
各字段说明:
Content type:TLS数据包内容的类型,包括:change cipher(20)、alert(21)、handshake(22)、Application Data(23)
Version:TLS版本号
Length:除去本头部5字节后的剩下的数据的长度
结构体定义:
//TLS 头部
typedef struct TLSHeader_t
{
u_int8_t ContentType;
u_int16_t Version;
u_int16_t Length;
};
实际抓包:
TLS载荷内容(Client Hello):
结构说明:
HandShake Type | 1 | ||||||
Length | 3 | ||||||
Version | 2 | ||||||
Random | 32 | ||||||
Session ID Length | 2 | ||||||
Session ID |
| ||||||
Cipher Suites Length | 2 | ||||||
Cipher Suites |
| ||||||
Compression Methods Length | 1 | ||||||
Compression Methods |
| ||||||
Extension Lengths | 2 | ||||||
Extension |
| ||||||
…… |
|
实际抓包:
SNI(Server Name Indication extension)字段在Extension: server_name中
SNI字段:
SNI (Server Name Indication)是用来改善服务器与客户端 SSL (Secure Socket Layer)和 TLS (Transport Layer Security) 的一个扩展。主要解决一台服务器只能使用一个证书(一个域名)的缺点,随着服务器对虚拟主机的支持,一个服务器上可以为多个域名提供服务,因此SNI必须得到支持才能满足需求。
基于名称的虚拟主机允许多个DNS主机名由同一IP地址上的单个服务器(通常为Web服务器)托管。为了实现这一点,服务器使用客户端提供的主机名作为协议的一部分(对于HTTP,名称显示在主机头中)。但是,当使用HTTPS时,TLS握手发生在服务器看到任何HTTP头之前。因此,服务器不可能使用HTTP主机头中的信息来决定呈现哪个证书,并且因此只有由同一证书覆盖的名称才能由同一IP地址提供。
所以,需要由SNI协议在握手时提供主机名的信息。
完整程序:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
//pacp文件头结构体
struct pcap_file_header
{
u_int32_t magic;
/* 0xa1b2c3d4 */
u_int16_t version_major;
/* magjor Version 2 */
u_int16_t version_minor;
/* magjor Version 4 */
int32_t thiszone;
/* gmt to local correction */
u_int32_t sigfigs;
/* accuracy of timestamps */
u_int32_t snaplen;
/* max length saved portion of each pkt */
u_int32_t linktype;
/* data link type (LINKTYPE_*) */
}
;
//时间戳
struct time_val
{
int tv_sec;
/* seconds 含义同 time_t 对象的值 */
int tv_usec;
/* and microseconds */
}
;
//pcap数据包头结构体
struct pcap_pkthdr
{
struct time_val ts;
/* time stamp */
u_int32_t caplen;
/* length of portion present */
u_int32_t len;
/* length this packet (off wire) */
}
;
//数据帧头
typedef struct FramHeader_t
{
//Pcap捕获的数据帧头
u_int8_t DstMAC[6];
//目的MAC地址
u_int8_t SrcMAC[6];
//源MAC地址
u_int16_t FrameType;
//帧类型
}
FramHeader_t;
//IP数据报头
typedef struct IPHeader_t
{
//IP数据报头
u_int8_t Ver_HLen;
//版本+报头长度
u_int8_t TOS;
//服务类型
u_int16_t TotalLen;
//总长度
u_int16_t ID;
//标识
u_int16_t Flag_Segment;
//标志+片偏移
u_int8_t TTL;
//生存周期
u_int8_t Protocol;
//协议类型
u_int16_t Checksum;
//头部校验和
u_int32_t SrcIP;
//源IP地址
u_int32_t DstIP;
//目的IP地址
}
IPHeader_t;
//TCP数据报头
typedef struct TCPHeader_t
{
//TCP数据报头
u_int16_t SrcPort;
//源端口
u_int16_t DstPort;
//目的端口
u_int32_t SeqNO;
//序号
u_int32_t AckNO;
//确认号
u_int8_t HeaderLen;
//数据报头的长度(4 bit) + 保留(4 bit)
u_int8_t Flags;
//标识TCP不同的控制消息
u_int16_t Window;
//窗口大小
u_int16_t Checksum;
//校验和
u_int16_t UrgentPointer;
//紧急指针
}
TCPHeader_t;
//TLS header
typedef struct TLSHeader_t
{
u_int8_t ContentType;
u_int16_t Version;
u_int16_t Length;
}
TLSHeader_t;
char* getSniFromSslHello(uint8_t *tcpData, uint32_t dataLen, uint8_t *sinLen){
if(dataLen <= 44) return NULL;
int index = 0;// content type
if(tcpData[index] != 0x16) return NULL; // content type check
index+=5;// handshake type;
if(tcpData[index] != 0x01) return NULL; // handshake type check
index+=38;//sessionLen
uint8_t sessionLen = tcpData[index];
index+=(sessionLen+1);
if(index >= dataLen-1) return NULL;
uint16_t cipherLen = tcpData[index]<<8 | tcpData[++index];
index+=(cipherLen+1);//cipherLen
index++;
if(index > dataLen-1) return NULL;
uint8_t compressionLen = tcpData[index];
index+=(compressionLen+1);//compressionLen
if(index >= dataLen-1) return NULL;
uint16_t extensionLen = tcpData[index] << 8 | tcpData[++index];
if(extensionLen <= 4) return NULL; // no sin
index++;
if(index >= dataLen-1) return NULL;
while(tcpData[index] << 8 | tcpData[++index] != 0x0000){
index++;
uint16_t extenssionLength = tcpData[index] << 8 | tcpData[++index];
index+=extenssionLength+1;
if(index >= dataLen-1 ) return NULL;
}
index++;
if(index >= dataLen-1) return NULL;
*sinLen = tcpData[index] << 8 | tcpData[++index];//sinLen
index++;// sin begin
return &tcpData[index+5];
}
void main(){
parse("test.pcap");
}
u_int16_t reverse(u_int16_t num)
{
return (u_int16_t)(num&0x00FF)<<8|(u_int16_t)(num&0xFF00)>>8;
}
void parse(char *argv)
{
struct pcap_file_header *file_header = NULL;
struct pcap_pkthdr *ptk_header = NULL;
FramHeader_t *eth_header = NULL;
IPHeader_t *ip_header = NULL;
TCPHeader_t *tcp_header = NULL;
TLSHeader_t *tls_header = NULL;
FILE *fp = NULL;
u_int64_t pkt_offset, i=0;
int ip_len, http_len, ip_proto, tls_type;
int src_port, dst_port, tcp_flags;
int last_time = 0;
//初始化
file_header = (struct pcap_file_header *)malloc(sizeof(struct pcap_file_header));
ptk_header = (struct pcap_pkthdr *)malloc(sizeof(struct pcap_pkthdr));
eth_header = (FramHeader_t *)malloc(sizeof(FramHeader_t));
ip_header = (IPHeader_t *)malloc(sizeof(IPHeader_t));
tcp_header = (TCPHeader_t *)malloc(sizeof(TCPHeader_t));
tls_header = (TLSHeader_t *)malloc(sizeof(TLSHeader_t));
if((fp = fopen(argv,"r")) == NULL)
{
exit(0);
}
//开始读数据包
pkt_offset = 24;
//pcap文件头结构 24个字节
while(fseek(fp, pkt_offset, SEEK_SET) == 0) //遍历数据包
{
i++;
memset(ptk_header, 0, sizeof(struct pcap_pkthdr));
//pcap_pkt_header 16 byte
if(fread(ptk_header, 16, 1, fp) != 1) //读pcap数据包头结构
{
break;
}
pkt_offset += 16 + ptk_header->caplen;
//下一个数据包的偏移值
memset(eth_header , 0, 14);
//数据帧头 14字为ethnet协议大小,注意指定网卡抓包 不指定是16字节的linuxcooked
if(fread(eth_header, 14, 1, fp) != 1) //读Ethernet数据
{
break;
}
if(eth_header->FrameType != 0x0008)
{
continue;
}
//IP数据报头 20字节 不考虑>20字节
memset(ip_header, 0, sizeof(IPHeader_t));
if(fread(ip_header, sizeof(IPHeader_t), 1, fp) != 1)
{
continue;
}
ip_proto = ip_header->Protocol;
ip_len = ip_header->TotalLen;
//IP数据报总长度
if(ip_proto == 0x06) //判断是否是 TCP 协议
{
//TCP头 20字节
if(fread(tcp_header, sizeof(TCPHeader_t), 1, fp) != 1)
{
continue;
}
src_port = ntohs(tcp_header->SrcPort);
dst_port = ntohs(tcp_header->DstPort);
tcp_flags = tcp_header->Flags;
if(reverse(tcp_header -> DstPort) != 443)
{
continue;
}
u_int8_t tls_head[5];
// start to read secure
if(fread(tls_head, sizeof(tls_head), 1, fp) != 1)
{
continue;
}
// handshake
if(tls_head[0]==22)
{
u_int8_t tls_head_hello[6];
if(fread(tls_head_hello, sizeof(tls_head_hello), 1, fp) != 1)
{
continue;
}
// client hello
if(tls_head_hello[0]==1)
{
uint32_t dataLen = tls_head_hello[1]<<16|tls_head_hello[2]<<8|tls_head_hello[3];
dataLen+=5;
uint8_t sinLen;
fseek(fp, -11, SEEK_CUR);
uint8_t tcpData[dataLen];
fread(tcpData,sizeof(tcpData),1,fp);
char* sni;
sni = getSniFromSslHello(tcpData, dataLen, &sinLen);
sni[sinLen]='