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 |
|
||||||
|
…… |
|
实际抓包:

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]='\0';
printf("sniLen=%u sni=%s\n",sinLen,sni);
}
}
}
}
fclose(fp);
}
智能推荐
利用Python库Scapy解析pcap文件
每次写博客都是源于纳闷,python解析pcap这么常用的例子网上竟然没有,全是一堆命令行执行的python,能用吗?玩呢? pip安装scapy,然后解析pcap: 打印s,是为了看到所有的字段: 然后就能用data['UDP'].sport打印udp的源端口了。 现在基本的字段都能看到了,如果还想看别的协议的字段,可以参考源码: https://github.com/secdev/scapy/...
Android 炫酷的横向和环形进度条的实例
一、概述 最近需要用进度条,秉着不重复造轮子的原则,上github上搜索了一番,看了几个觉得比较好看的ProgressBar,比如:daimajia的等。简单看了下代码,基本都是继承自View,彻彻底底的自定义了一个进度条。盯着那绚丽滚动条,忽然觉得,为什么要通过View去写一个滚动条,系统已经提供了ProgressBar以及属于它的特性,我们没必要重新去构建一个,但是系统的又比较丑,不同版本变现...
pcap包结构&SNI字段的解析
pcap文件格式是常用的数据报存储格式,包括wireshark在内的主流抓包软件都可以生成这种格式的数据包。 文件格式: Pcap文件头(24字节)+数据包头(wireshark增加的)+数据包(网络中抓取的)+…… 1.pcap文件头结构 各字段说明: Magic:4B:0×...
MC9S12XEP100的IIC模块(IICV3)
最近在写DS3231时钟芯片的驱动,这个芯片使用IIC进行通讯,以前没有用过IIC模块,照着教材和示例程序写程序后发现各种问题。没办法,还是官方数据手册靠谱,遂把相应部分又翻译了一遍。果然发现示例程序纯粹就是个玩具,一点用都没有。。。 第15章 集成电路总线(IICV3) 译者注:译者博客(http://blog.csdn.net/lin_strong),转载请保留这条。此为 MC9S12XEP1...
Eureka服务注册与发现
文章目录 二、SpringCLoud中遇到的技术 (一)、Eureka 1、功能: 2、什么是服务治理. 3、什么是服务注册与发现 4、Eureka包含两个组件: Eureka Server和Eureka Client 5、单机Eureka构建步骤 5.1 建model:cloud-eureka-server7001 5.2 写pom 5.3 写yml 5.4 主启动类 5.5 测试 5.6 将消...
猜你喜欢
Hard Problem【HDU-3551】【一般图最大匹配 带花树】
题目链接 题意:有N个点,M条边,给出每个点的度限制,问能不能用M条边中的几条达成这个目的? 很明显的就是一个建图的问题,很明显的,少于等于度为1的,是可以直接连的,不用限制增广,而大于度为1的,需要限制增广,就可以用这样的限流的方法: ...
idea 打包 报错maven-shade-plugin:2.4.3:shade (default) on project xxx : Error creating shaded jar: null
idea 打包 报错maven-shade-plugin:2.4.3:shade (default) on project xxx : Error creating shaded jar: null 文章目录 idea 打包 报错maven-shade-plugin:2.4.3:shade (default) on project xxx : Error creating shaded jar: ...
大数据入门:Spark+Kudu的广告业务项目实战笔记(一)
1.简介 这是PK老师在Spark-SQL课程中的项目实战笔记 2.项目需求 数据ETL:原始文件为JSON格式数据,需原始文件与IP库中数据进行解析 统计各省市的地域分布情况 统计广告投放的地域分布情况 统计广告投放APP分布情况 3.项目架构 4.日志字段 5.IP规则库解析 本项目利用IP规则库进行解析,在生产中应该需要专门的公司提供的IP服务。IP规则库中的一条如下: 其中第三列是该段ip...
springBoot子项目中排除掉定义在Parent中的插件(Disable a Maven plugin defined in a parent POM)
最近在和别的项目集成,需要在我们的pom中添加上对方的parent,打包的时候就遇到了问题,对方在parent中添加了一个插件,干啥的就不说了, 反正人家是用到了, 我们是用不到,但是还给我们打包带来了一些问题。 这时候就必须排除掉对方这个插件了 1,第一种方法是 有的插件提供了不加载配置,例如: 如果不想用这个插件,就把skip标签配置成true,这样在构建的时候就不会使用这个插件了。 可是有的...
