Java 解析Pcap文件(1)
@author:Jingdai
@date:2021.03.11
由于毕业实验是关于TLS流量分析的,所以最近学习了一下Pcap文件的解析,现记录一下。
Pcap文件结构
如果所示,Pcap文件由一个Global Header后面接着若干组Packet Header 和 Packet Data 组成。
先看一下 Global Header 的结构,它由 24B 组成,字段按照Pcap文件的顺序列出。
- magic,占4B,如果它的值是0xa1b2c3d4,代表 Pcap 文件是大端模式存储的;如果它的值是 0xd4c3b2a1,代表 Pcap 文件是小端模式存储的。这里注意这里的大端小端仅仅是指 Pcap 文件的 Global Header 和 Packet Header,而无关Packet Data 里的内容。Packet Data里面就是利用抓包工具如wireshark捕获的数据包,他们都是符合网络字节序的,而网络字节序就是大端模式。如果对大端模式和小端模式不熟悉的童靴可以先去看一下,在回来学习,因为不知道这个解析包一定会有问题。
- major,占2B,文件的主版本号,一般为0x0200。
- minor,占2B,文件的次要版本号,一般为0x0400。
- thisZone,占4B,当地标准时间,如果是GMT则全为0。
- sigFigs,占4B,时间戳精度,一般全0。
- snapLen,占4B,最长存储长度。
- linkType,占4B,链路类型,以太网则为1,一般为1。
下面看一下 Packet Header 的结构,它由 16 B组成。字段按文件顺序列出。
- timestamp,占4B,时间戳高位,精确到seconds,这是Unix时间戳。捕获数据包的时间一般是根据这个值。
- timestamp,占4B,时间戳低位,能够精确到microseconds。
- capLen,占4B,当前数据区的长度,即抓取到的数据帧长度,由此可以得到下一个数据帧的位置。
- len,占4B,离线数据长度,网路中实际数据帧的长度,一般不大于capLen,多数情况下和capLen值一样。如果文件中保存不是完整的数据包,那么这个值可能要比前面的数据包长度的值大。
Pcap 文件解析
看完上面Pcap文件结构,可以据此将 Pcap 中一个个的 Packet Data 解析出来。先看 Global Header,要用到的就是 magic 和 linkType 字段(如果其他字段要用到稍微改一点就行)。据此构建出 Global Header 类,代码如下。
package com.jingdai.pcapanalyzer.entity.format;
/**
* GlobalHeader 文件头结构
*/
public class GlobalHeader {
public static final int LINK_TYPE_ETHERNET = 1;
private int magic;
private int linkType;
public int getMagic() {
return magic;
}
public void setMagic(int magic) {
this.magic = magic;
}
public int getLinkType() {
return linkType;
}
public void setLinkType(int linkType) {
this.linkType = linkType;
}
public GlobalHeader() {
}
@Override
public String toString() {
return "GlobalHeader{" +
"magic=" + magic +
", linkType=" + linkType +
'}';
}
}
接下来看 Packet Header,这次前面两个是时间,后面两个是长度,都会用到,同样创建一个Packet Header类。代码如下。
package com.jingdai.pcapanalyzer.entity.format;
/**
* Pcap 数据包头
*/
public class PacketHeader {
/**
* 时间戳 高位(秒):记录数据包抓获的时间
* 记录方式是从格林尼治时间的1970年1月1日 00:00:00 到抓包时经过的秒数(4个字节)
*/
private int timeS;
/**
* 时间戳 低位(微秒):抓取数据包时的微秒值(4个字节)
*/
private int timeMs;
/**
* 数据包长度:标识所抓获的数据包保存在 pcap 文件中的实际长度,以字节为单位(4个字节)
*/
private int capLen;
/**
* 数据包实际长度: 所抓获的数据包的真实长度(4个字节)
*/
private int len;
@Override
public String toString() {
return "PacketHeader{" +
"timeS=" + timeS +
", timeMs=" + timeMs +
", capLen=" + capLen +
", len=" + len +
'}';
}
public int getTimeMs() {
return timeMs;
}
public void setTimeMs(int timeMs) {
this.timeMs = timeMs;
}
public int getCapLen() {
return capLen;
}
public void setCapLen(int capLen) {
this.capLen = capLen;
}
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public int getTimeS() {
return timeS;
}
public void setTimeS(int timeS) {
this.timeS = timeS;
}
public PacketHeader() {
}
}
接下来就可以进行解析了,首先从Pcap文件中读取 24B,这 24B 就是Global Header的数据。解析部分如下。
/**
* 解析Global Header
*/
private GlobalHeader parseGlobalHeader(byte[] globalHeaderBuffer) {
GlobalHeader globalHeader = new GlobalHeader();
byte[] magicBuffer = Arrays.copyOfRange(globalHeaderBuffer, 0, 4);
byte[] linkTypeBuffer = Arrays.copyOfRange(globalHeaderBuffer, 20, 24);
int magic = DataUtils.byteArray2Int(magicBuffer, 4);
DataUtils.reverseByteArray(linkTypeBuffer);
int linkType = DataUtils.byteArray2Int(linkTypeBuffer, 4);
globalHeader.setMagic(magic);
globalHeader.setLinkType(linkType);
return globalHeader;
}
如前所述,这里代码仅仅解析了 magic 和 linkType 字段,如果需要其他字段方法也是一样。还有就是本机测试magic 字段的值是 0xd4c3b2a1,即小端模式,所以后面 Global Header 和 Packet Header 的字段读取都需要是小端模式读取,在这里就是需要将 buffer 逆序后再存储值。
利用同样的方式,再读取 16B,这16B就是 Packet Header 的数据,代码如下。
/**
* 解析Packet Header
*/
private PacketHeader parsePacketHeader(byte[] dataHeaderBuffer){
byte[] timeSBuffer = Arrays.copyOfRange(dataHeaderBuffer, 0, 4);
byte[] timeMsBuffer = Arrays.copyOfRange(dataHeaderBuffer, 4, 8);
byte[] capLenBuffer = Arrays.copyOfRange(dataHeaderBuffer, 8, 12);
byte[] lenBuffer = Arrays.copyOfRange(dataHeaderBuffer, 12, 16);
PacketHeader packetHeader = new PacketHeader();
DataUtils.reverseByteArray(timeSBuffer);
DataUtils.reverseByteArray(timeMsBuffer);
DataUtils.reverseByteArray(capLenBuffer);
DataUtils.reverseByteArray(lenBuffer);
int timeS = DataUtils.byteArray2Int(timeSBuffer, 4);
int timeMs = DataUtils.byteArray2Int(timeMsBuffer, 4);
int capLen = DataUtils.byteArray2Int(capLenBuffer, 4);
int len = DataUtils.byteArray2Int(lenBuffer, 4);
packetHeader.setTimeS(timeS);
packetHeader.setTimeMs(timeMs);
packetHeader.setCapLen(capLen);
packetHeader.setLen(len);
return packetHeader;
}
之后就可以根据上面 packetHeader 的 capLen 的值读取 capLen 字节数的数据,这就是真正的 Packet Data 的数据即数据链路层的帧。之后再继续读取16B的 Packet Header,根据 packetHeader 的 capLen 读取对应字节数的数据,循环往复直至读取完毕。
未完待续,解析数据链路层、IP层和传输层的数据。