我是靠谱客的博主 魁梧纸鹤,最近开发中收集的这篇文章主要介绍pcap包结构&SNI字段的解析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

    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

Type

2

Length

2

Data

 

 

……

 

实际抓包:

 

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]='';
    				        printf("sniLen=%u sni=%sn",sinLen,sni);
				}
			}
			
		}
	}
	fclose(fp);
}

 

最后

以上就是魁梧纸鹤为你收集整理的pcap包结构&SNI字段的解析的全部内容,希望文章能够帮你解决pcap包结构&SNI字段的解析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部