Java 解析Pcap文件(1)

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;
    }

如前所述,这里代码仅仅解析了 magiclinkType 字段,如果需要其他字段方法也是一样。还有就是本机测试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层和传输层的数据。

猜你喜欢

转载自blog.csdn.net/qq_41512783/article/details/114654785