C++实现pcap包解析,并提取指定特征帧

目录

        最近这几天观察到了一个木马的网络通信特征,于是就打算通过代码实现针对该木马的检测过滤。刚开始学长提供了python编写的代码。但是在实际的工作中,每天所捕获的pcap包的数据总量很大,通常在5到15个G左右。这种情况下,python的运行时间极长,且占用大量的系统运行内存,甚至会出现卡死情况。于是我尝试用C去实现木马的过滤,因为C是偏底层的编程语言,可以自定义通信协议,所以其运行效率会更高。


一、木马通信特征

1.流量结构

        该木马拥有其自定义的通信协议,通信数据包具有一定的结构,数据在构造后会通过异或XOR加密(加密密钥是第二个字节),部分命令会有附加数据(如上传文件),附加数据部分会通过GZIP方式压缩。

2.类C结构

struct  packet_format{
	char dec_xor[2];
	char unknow1[4];
	char constant_l;
	char random_32[32];
	char command_type[2];
	char unknow2[4];
	char error_status[2];
	char payload_length[4];
	char command_sub_type;
	char unknow3[4];
    char unknow4[4];
    char unknow5[4];
	char has_payload;
    char payload[payload_length];
};

3.指令类型 

        对该木马来说,指令解析和响应包的构造是在同一个函数实现的,在接收到服务端发送的数据包后逐字节解析后被传入该函数,对解析得到的指令类型执行对应的操作,其具体支持的指令类型如下表所示

说明类型
上线信息0x5C37
命令执行/运行插件0x5C7C
交付文件0xB616、0x1CE3
心跳(指定睡眠时间)0xDAFE

二、代码示例

1.引入头文件

         在编程时,由于预想到用到的功能会较多,本人又较懒,所以就直接调用了<bits/stdc++.h>这个万能头文件。但是由于本机的编译环境为VS2019,所以在编译这个文件时出现了错误,为解决这一问题,我在对应目录下手动创建了这个文件,详细方法参考以下链接VS2019 添加bits/stdc++.h万能头文件库

#include <bits/stdc++.h>    //万能头文件
#include <cstdint>
#include <io.h>

2.设置全局变量

        全局变量中主要是将各长度的数据类型赋予别名,以方便后续代码中的调用,比如将long long型数据(64位比特)定义别名为u64,其次则是定义了IPv4地址和IPv6地址的长度即字节数 。

#define u64 unsigned long long
#define uint32 uint32_t
#define uint16 uint16_t
#define uint8 uint8_t
#define v4ADDRByte 4     //IPv4地址字节数
#define v6ADDRByte 16    //IPv6地址字节数

        随后则是在TCP/IP协议栈中由下至上地定义一系列结构体,以针对抓到的pcap包逐层地进行解析 ,首先是对pcap文件类型进行一个头部的定义(单纯地从TCP/IP协议栈中去看的话,其实压根没有文件头这号人物,但是实际上操作系统会为各类型文件都配一个头部,若想实现pcap文件内容的读取,则需要根据其文件头去逐字节地将文件指针向下移,从而跳过这一部分)

/*
 Pcap文件头24B各字段说明:
 Magic:4B:0x1A 2B 3C 4D:用来标示文件的开始
 Major:2B,0x02 00:当前文件主要的版本号
 Minor:2B,0x04 00当前文件次要的版本号
 ThisZone:4B当地的标准时间;全零
 SigFigs:4B时间戳的精度;全零
 SnapLen:4B最大的存储长度
 LinkType:4B链路类型
 常用类型:
 0            BSD loopback devices, except for later OpenBSD
 1            Ethernet, and Linux loopback devices
 6            802.5 Token Ring
 7            ARCnet
 8            SLIP
 9            PPP
 */
typedef struct pcap_header {
	uint32 magic;
	uint16 version_major;
	uint16 version_minor;
	uint32 thiszone;
	uint32 sigfigs;
	uint32 snaplen;
	uint32 linktype;   
}PcapHeader;

        由于一个pcap文件通常情况下是由一系列packet组成的,所以读取pcap内容时实际上是相当于在其内部遍历一遍所有的packet,挨个读取里面的数据。这也就相当于你在一个文件夹里存着好多好多小文件,需要一个个地点开看,因此需要把packet的头也加进去,好在提取内容的时候把它跳过。

//pcap 数据包头部 16B
typedef struct packet_header {
	uint32 te_sec;
	uint32 ts_usec;
	uint32 capture_len;            //整个数据包的物理长度
	uint32 len;
}PacketHeader;

         好了现在终于能进到协议栈里面了,最下面的是链路层(Q:为什么不是物理层?A:丫的物理层传输单元是比特流你去给我找个结构出来),帧头部结构是一个很简单的三维组

//以太网帧头部 14B
typedef struct ethhdr {
	unsigned char	dest_mac[6];	/* destination eth addr	*/
	unsigned char	src_mac[6];	    /* source ether addr	*/
	unsigned short	frame_type;		/* 指定网络层版本	*/
}FrameHeader;

         然后是网络层,目前最常用的网络层协议无非就是IPv4和IPv6(事实上IPv6还是有点略多余,起码对这个木马的已有通信来看并没有使用过v6协议,但是为了让以后的我看这篇博客的时候不杠,我决定还是把v6的头也加进来)

//IPv4头部 20B
typedef struct ip4_hdr {
	uint8	ver_len;   //前4B为IP版本,后4B为头长度
	uint8	tos;
	uint16	total_len;
	uint16	id;
	uint16	frag_off;
	uint8	ttl;
	uint8	protocol;
	uint16	check;
	unsigned char saddr[4];
	unsigned char daddr[4];
}IPv4Header;

//IPv6头部 40B
typedef struct ip6_hdr {
	uint32  var_tarclass_flowlab;
	uint16	payload_len;     //载荷长度
	uint8	next_header;
	uint8	hop_limit;
	unsigned char saddr[16];
	unsigned char daddr[16];
}IPv6Header;

        接下来是传输层,虽然木马已知通信中只用了TCP,但是我也把UDP的头放进来了,道理同上 。有一点需要注意的是,TCP的头并不是20字节固定不变的,如果有选项字段的话TCP头部就会增长,最多可增至60字节。如果是在python中大可不必考虑长度变化,因为反正有封装好的scapy库给你兜底,但是在C里忽略这一点就可能导致代码运行时出现误差(没错就是我),关于误差的具体原因我会在后文详述。

//TCP头部 固定20B
typedef struct tcp_hdr {
	uint16 src_port;      //源端口
	uint16 dst_port;      //目的端口
	uint32 seq;
	uint32 ack;
	uint16 flag;
	uint16 window;
	uint16 checksum;      //校验和
	uint16 urgptr;
	// options可变项 12B
}TCPHeader;

//UDP头部
typedef struct udp_hdr {
	uint16 src_port;
	uint16 dst_port;
	uint16 len;
	uint16 checksum;
}UDPHeader;

        协议栈最后则是应用层,对于本文而言,应用层其实就是木马的攻击载荷,所以只需要把前面提到的木马的类C结构稍稍改一下就能拿来当作头部了。和之前提到的类C结构作比较,可以看到做出的改动其实就是把那些在后续过程中需要单字节计算的字段拆开了(比如command_type、payload_length)

//海莲花buni木马载荷头结构 65B
typedef struct apt32_payload {
	uint8 dec_xor1;
	uint8 dec_xor2;
	unsigned char unknow1[4];
	unsigned char constant_l;
	unsigned char random_32[32];
	uint8 command_type_1;
	uint8 command_type_2;
	unsigned char unknow2[4];
	unsigned char error_status[2];
	uint8 payload_length_1;
	uint8 payload_length_2;
	uint8 payload_length_3;
	uint8 payload_length_4;
	unsigned char command_sub_type;
	unsigned char unknow3[12];
	unsigned char has_payload;
}PAYLOAD;

         接下来为方便最后的结果输出,定义一个结构体去承载检测出的对应木马特征(正常来说应该是两个,因为网络层有v4和v6两种协议,这两种协议的地址长度是不同的。在本文中只提供v4对应的结构体作为示例)

//结果五元组ipv4版本
typedef struct res_v4 {
	string path;              //木马特征包的文件路径
	uint32 frame_id;          //所属帧序号
	uint32 cmd_code;          //命令编码
	string cmd_label;         //对应命令含义
	unsigned char src_ip[4];
	unsigned char dst_ip[4];
	uint16 src_port;
	uint16 dst_port;
	uint8 protocol;
}Res4;

         定义各类型数据包头部的合法长度,用以在后续检测木马通信包时判断所读取的数据是否合理。所谓各类型,其实就是两种IP协议v4和v6、与两种传输层协议TCP和UDP的排列组合。

//各协议类型包的合法长度
const int v4_tcp_len = sizeof(FrameHeader) + sizeof(IPv4Header) + sizeof(TCPHeader) + sizeof(PAYLOAD);
const int v4_udp_len = sizeof(FrameHeader) + sizeof(IPv4Header) + sizeof(UDPHeader) + sizeof(PAYLOAD);
const int v6_tcp_len = sizeof(FrameHeader) + sizeof(IPv6Header) + sizeof(TCPHeader) + sizeof(PAYLOAD);
const int v6_udp_len = sizeof(FrameHeader) + sizeof(IPv6Header) + sizeof(UDPHeader) + sizeof(PAYLOAD);

 3.函数

1.高低字节交换

        由于在intel处理器上,网络传输数据是小端存储的,所以在对长整型数据进行计算、十进制输出等操作之前,需要将高低字节进行转换。为稳妥起见,使用位移和与运算,将各字节的位置准确到二进制的层次(Q:为什么说是二进制层次?A:因为&运算是按位与,所以结果会精确到两个操作数的每一个比特)

//长整型高低字节交换
#define swap16(A) ((((uint16)(A) & 0xff00) >> 8) | (((uint16)(A) & 0x00ff) << 8))
#define swap32(A) ((((uint32)(A) & 0xff000000) >> 24) | (((uint32)(A) & 0x00ff0000) >> 8) | (((uint32)(A) & 0x0000ff00) << 8) | (((uint32)(A) & 0x000000ff) << 24))

2. 十六字节拼接

        在前文提到过,木马载荷中payload_length这种字段是需要按字节分开进行异或加密的。但是在计算其所表达的数值的时候,需要将4个payload_length字节拼接成完整的字段再转换成十进制。举个例子:解密后的四个字节分别是0x12,0x34,0x56,0x78;那么在计算其数值时则需要将它们先逆序(小端存储)拼接成为完整的十六进制字段0x78563412,再计算其对应的十进制。函数的具体实现原理和高低字节转换差不多。

//十六进制字节拼接,参数由低位到高位
int rev_data(int a, int b, int c, int d)
{
	int e = 0;
	e = ((d << 24) & 0xff000000) | ((c << 16) & 0x00ff0000) | ((b << 8) & 0x0000ff00) | (a & 0x000000ff);
	return e;
}

3.将木马检测结果写入csv文件 

        当你很幸运地发现这个木马异常地活跃,也就是说你跑出了好多好多个木马通信包的时候,就需要一个精美的csv来存储你的结果了。

//结果写入out.csv
void out2csv(FILE* fp, PAYLOAD* pld_hdr, Res4* res4, Res6* res6, bool is_v4) {
	if (is_v4) {
		for (int i = 0; i < v4ADDRByte; i++) fprintf(fp, "%d%c", res4->src_ip[i], ".,"[i == v4ADDRByte - 1]);
		fprintf(fp, "%d", swap16(res4->src_port));
		for (int i = 0; i < v4ADDRByte; i++) fprintf(fp, "%d%c", res4->dst_ip[i], ".,"[i == v4ADDRByte - 1]);
		fprintf(fp, "%d", swap16(res4->dst_port));
		fprintf(fp, ",");
		fprintf(fp, "%s", res4->path.c_str());
		fprintf(fp, ",");
		fprintf(fp, "%d", res4->frame_id);
		fprintf(fp, ",");
		fprintf(fp, "0x%X", pld_hdr->command_type_2);
		fprintf(fp, "%X", pld_hdr->command_type_1);
		fprintf(fp, ",");
		fprintf(fp, "%s", res4->cmd_label.c_str());
		fprintf(fp, "\n");
	}
}

        值得一提的是,参数is_v4是个bool型变量(非真即假),它是用来判断当前packet所承载的网络层协议是否是IPv4的,是则真,否则假。至于怎么能让csv文件换列这个问题,其实只需要地简单fprintf一个逗号就好了。 

4.获取文件夹下特定格式的文件名

        通常情况下(只要wireshark没坏掉),总是会有很多pcap文件待检测的。也就是说我们需要循环打开这么多的文件,而打开文件显然需要它的完整路径。这个函数就是用来获取某文件夹下所有指定格式的文件名的。(当然,除了pcap格式以外,别的格式也都支持)

//获取文件夹下特定格式的文件名
void get_need_file(string path, vector<string>& file, string ext)
{
	intptr_t file_handle = 0;
	struct _finddata_t file_info;
	string temp;
	if ((file_handle = _findfirst(temp.assign(path).append("/*" + ext).c_str(), &file_info)) != -1)
	{
		do
		{
			file.push_back(temp.assign(path).append("/").append(file_info.name));
		} while (_findnext(file_handle, &file_info) == 0);
		_findclose(file_handle);
	}
}

        这个函数的具体讲解可参考C++遍历文件夹获取指定格式文件名 

5.逐层解析pcap包

        好了终于把所有的伏笔都埋好了,现在就到我们的重头戏了:解析pcap包。首先传递进来两个参数,一个是待检测的pcap包的路径:pacp_path,一个是用来存放结果的csv的文件指针:out然后按照上文中的全局变量去定义指向各类型头部的指针

void solver(string pacp_path, FILE* out) {
	//创建各种类型的header方便提取字段
	PacketHeader* pkt_hdr = (PacketHeader*)malloc(sizeof(PacketHeader));
	FrameHeader* ethhdr = (FrameHeader*)malloc(sizeof(FrameHeader));
	IPv4Header* ip4_hdr = (IPv4Header*)malloc(sizeof(IPv4Header));
	IPv6Header* ip6_hdr = (IPv6Header*)malloc(sizeof(IPv6Header));
	TCPHeader* tcp_hdr = (TCPHeader*)malloc(sizeof(TCPHeader));
	UDPHeader* udp_hdr = (UDPHeader*)malloc(sizeof(UDPHeader));
	PAYLOAD* pld_hdr = (PAYLOAD*)malloc(sizeof(PAYLOAD));
	Res4* res4 = (Res4*)malloc(sizeof(Res4));
	Res6* res6 = (Res6*)malloc(sizeof(Res6));

        通过传入的路径参数打开对应的pcap文件

	FILE* pFile = fopen(pacp_path.c_str(), "rb");  //c_str与C兼容,C中没有string类型    'rb'以字节方式读取文件中数据
	if (pFile == NULL) {
		cout << "文件读取错误!" <<  endl;
		exit(1);
	}
	//读取Pacp文件头长度24B
	u64 packet_len = sizeof(PcapHeader);
	uint32 count = 0;

         紧接着开启一个while循环去遍历这一pcap文件中的每个packet

	//重定位文件指针,跳过文件前24B,逐包读取pcap文件
	while (fseek(pFile, packet_len, SEEK_SET) == 0) {
		count++;
		//读取本包数据
		FILE* temp = pFile;
		memset(res4, 0, sizeof(Res4));
		memset(res6, 0, sizeof(Res6));
		//packet_header
		memset(pkt_hdr, 0, sizeof(PacketHeader));
		if (fread(pkt_hdr, sizeof(PacketHeader), 1, temp) != 1) {
			cout << "读取包头部数据失败" << endl;
			break;
		}
		//帧的长度,同时确保while循环中的文件指针在后移(以实现读取下一个packet的数据)
		packet_len += sizeof(PacketHeader) + pkt_hdr->capture_len;

        解析链路层、网络层、传输层。(Q:说了这么半天解析解析,到底什么是解析啊?A:其实所谓的解析就是将文件中的数据读到之前所定义的头部结构中,这其中的关键只在于你这个赋值等式两头的数据位置是要一致的,比如说我要解析以太帧,那么我就要确保我所调用的fread函数中指针的位置是确确实实指向以太帧的首字节。这样的话我就可以通过头部结构体的各个字段去推理出长度、下层协议、地址等重要的变量)

		//解析以太帧
		memset(ethhdr, 0, sizeof(FrameHeader));
		if (fread(ethhdr, sizeof(FrameHeader), 1, temp) != 1) {
			cout << "读取以太帧头部数据失败" << endl;
			continue;
		}
		//解析IP层
		//判断类型v4或v6,读取对应头部数据
		bool is_v4;
		if (ethhdr->frame_type == 0x0008) {
			is_v4 = true;
		}else if (ethhdr->frame_type == 0xdd86) {
			is_v4 = false;
		}else {
			cout << "不是ip包,未能解析" << endl;
			continue;    //指向下一个packet
		}
		memset(ip4_hdr, 0, sizeof(IPv4Header));
		memset(ip6_hdr, 0, sizeof(IPv6Header));
		bool is_tcp;
		if (is_v4) {
			if (fread(ip4_hdr, sizeof(IPv4Header), 1, temp) != 1) {
				cout << "读取IPv4头失败" << endl;
				continue;
			}
			memcpy(res4->src_ip, ip4_hdr->saddr, 4);
			memcpy(res4->dst_ip, ip4_hdr->daddr, 4);
			res4->protocol = ip4_hdr->protocol;
			is_tcp = ip4_hdr->protocol == 0x06;     //判断协议类型是否为TCP
		}else {
			if (fread(ip6_hdr, sizeof(IPv6Header), 1, temp) != 1) {
				cout << "读取IPv6头失败" << endl;
				continue;
			}
			memcpy(res6->src_ip, ip6_hdr->saddr, 16);
			memcpy(res6->dst_ip, ip6_hdr->daddr, 16);
			res6->protocol = ip6_hdr->next_header;
			is_tcp = ip6_hdr->next_header == 0x06;     //判断协议类型是否为TCP
		}
		//解析传输层
		//判断传输协议类型,读取对应头部数据
		memset(tcp_hdr, 0, sizeof(TCPHeader));
		memset(udp_hdr, 0, sizeof(UDPHeader));
		if (is_tcp) { //TCP协议
			if (fread(tcp_hdr, sizeof(TCPHeader), 1, temp) != 1) {
				cout << "读取TCP头失败" << endl;
				continue;
			}
			if (is_v4) res4->src_port = tcp_hdr->src_port, res4->dst_port = tcp_hdr->dst_port;
			else res6->src_port = tcp_hdr->src_port, res6->dst_port = tcp_hdr->dst_port;
		}else { //UDP协议
			if (fread(udp_hdr, sizeof(UDPHeader), 1, temp) != 1) {
				cout << "读取UDP头失败" << endl;
				continue;
			}
			if (is_v4) res4->src_port = udp_hdr->src_port, res4->dst_port = udp_hdr->dst_port;
			else res6->src_port = udp_hdr->src_port, res6->dst_port = udp_hdr->dst_port;
		}

        读取完这几层的数据之后,为了稳妥起见,我们先判断一下实际上所读到的数据长度是否和理论上的长度一致 ,如果不一致就说明当前这个packet长得奇奇怪怪的,我们就直接抬走下一位(continue;)

		//检查包的长度
		if (is_v4) {
			if (is_tcp) { if (pkt_hdr->capture_len < v4_tcp_len) continue; }
			else { if (pkt_hdr->capture_len < v4_udp_len) continue;}
		}else {
			if (is_tcp) { if (pkt_hdr->capture_len < v6_tcp_len) continue; }
			else { if (pkt_hdr->capture_len < v6_udp_len) continue; }
		}

         做完这些之后我们就试着去读取木马的攻击载荷。为什么说是试着呢,因为其实读到的东西不一定真的是木马载荷的。这个代码逻辑其实就是把数据硬往这个payload结构体里面去塞,然后通过字段值去判断它对不对。

        关于怎么去判断对不对这个问题,答案很简单。就是根据长度字段payload_length去计算。我先算一遍实际的载荷长度_payload,然后再根据目前塞进来的payload_length字段去计算一遍它所表示的长度,如果两者相等,就说明我这个数据塞对地方了,就说明当前这个packet就是木马通信数据。

		//读取木马攻击载荷
		memset(pld_hdr, 0, sizeof(PAYLOAD));
		if (fread(pld_hdr, sizeof(PAYLOAD), 1, temp) != 1) {
			cout << "读取payload头失败" << endl;
			continue;
		}
		//求实际载荷长度
		int _paylen = pkt_hdr->capture_len - 119;
		//求理论载荷长度
		pld_hdr->payload_length_1 = pld_hdr->dec_xor2 ^ pld_hdr->payload_length_1;
		pld_hdr->payload_length_2 = pld_hdr->dec_xor2 ^ pld_hdr->payload_length_2;
		pld_hdr->payload_length_3 = pld_hdr->dec_xor2 ^ pld_hdr->payload_length_3;
		pld_hdr->payload_length_4 = pld_hdr->dec_xor2 ^ pld_hdr->payload_length_4;

		int paylen = rev_data(pld_hdr->payload_length_1, pld_hdr->payload_length_2, pld_hdr->payload_length_3, pld_hdr->payload_length_4);

		pld_hdr->command_type_1 = pld_hdr->dec_xor2 ^ pld_hdr->command_type_1;
		pld_hdr->command_type_2 = pld_hdr->dec_xor2 ^ pld_hdr->command_type_2;

       值得一提的是这个实际载荷长度_payload的求解,它是用当前帧的总长度去减去所有的头的长度(frame头14B + IPv4头部20B + TCP固定头部20B + 木马头部65B = 119)。那么问题来了,之前不是说TCP的头部长度不是固定的吗,为什么在这里只减去固定长度20B,万一TCP头部是32B、60B怎么办?解决办法很简单,再写几个cpp,把tcp的头结构改一下,该减几B就减几B。

        至于我为什么要把payload_length和command_type这两个字段拆成字节去和dec_xor异或,因为这个木马把dec_xor这个字节作为密钥,载荷的全部字节均与这个密钥异或加密形成的,那么如果我要解密的话,显然需要将所有字节都与这个密钥重新异或一遍(异或的逆运算是异或)

        当确认是木马通信数据后,需要做的就是这个木马到底干了什么,也就是说把它所传递的命令给解析出来。做法很简单就是去比较pld_hdr->command_type字段,看看是不是木马特征中所提到的那五种,这里拿出第一种(0x5C37 : 上线信息)做例子

		if (paylen == _paylen) {
			if (pld_hdr->command_type_1 == 0x37 && pld_hdr->command_type_2 == 0x5C) {
				if (is_v4) {
					res4->frame_id = count;
					res4->cmd_label = "上线信息";
					res4->path = pacp_path;
					//输出结果
					cout << res4->path << " 序号 " << res4->frame_id << endl;
					printf("命令码: 0x%X", pld_hdr->command_type_2);
					printf("%X", pld_hdr->command_type_1);
					printf("上线信息");
					printf("%s", res4->cmd_label.c_str());
					cout << endl << "源ip:";
					for (int i = 0; i < v4ADDRByte; i++) printf("%d%c", res4->src_ip[i], ".,"[i == v4ADDRByte - 1]);
					printf(" ");
					printf("源端口:%d", swap16(res4->src_port));
					printf(",目的ip:");
					for (int i = 0; i < v4ADDRByte; i++) printf("%d%c", res4->dst_ip[i], ".,"[i == v4ADDRByte - 1]);
					printf(" ");
					printf("目的端口:%d", swap16(res4->dst_port));
					cout << endl << endl;
				}
			}

        最后将检测结果写入csv

			//结果写入csv
			if (is_v4) out2csv(out, pld_hdr, res4, NULL, is_v4);

6.main函数 

int main(void) {
	string file_path = R"(F:\数据分析_邮件还原\3 数据(含初始数据&中间及最终分析结果)-供教员测试程序用\1 捕获的邮件数据包\jy给出的TOM数据(以做进一步测试)\初始数据)";
	vector<string> my_file;
	string need_extension = ".pcap";
	string out_path = "./out.csv";
	FILE* out = fopen(out_path.c_str(), "w");
	fprintf(out, "源ip,源port,目的ip,目的port,文件路径,帧序号,指令编码,指令含义\n");
	get_need_file(file_path, my_file, need_extension);
	for (int i = 0; i < my_file.size(); i++)
	{
		cout << "File " << i + 1 << " is:" << endl;
		cout << my_file[i] << endl;
		solver(my_file[i], out);
	}
	if (my_file.size() == 0)
	{
		cout << "No file can be found!" << endl;
	}
	else
	{
		cout << endl << "Find " << my_file.size() << " file(s)." << endl;
	}
	cout << endl << endl << "过滤完成" << endl;
	return 0;
}

总结

       这是我的第一篇博客,可能在大佬的眼中会觉得很菜,而且我也确实很菜,但是不管怎么说,这三四天来把这些东西断断续续地写完还是非常有成就感的。这一套代码编下来之后,怎么说呢,最大的感触就是不管什么事可能乍一看头皮很麻,其实真去经历了的话感觉也就那样。人缺乏的不是能力而是选择开始的这份勇气。再有一点就是,遇到困难问百度,度哥是不会让你失望的哈哈哈哈哈哈哈哈哈哈哈哈哈。

  • 9
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 从pcap提取TCP会话并重组是一种网络分析的常见任务,它可以帮助我们理解和分析网络流量。 首先,我们需要使用适当的工具打开pcap文件,如Wireshark或tshark。这些工具可以读取pcap文件并显示其中的网络流量。 接下来,我们需要筛选出TCP协议的网络流量,可以通过过滤器来实现。在Wireshark中,我们可以使用表达式"tcp"来过滤出所有的TCP流量。 一旦我们得到了TCP流量,我们需要根据IP地址和端口号来识别和重组TCP会话。每一对IP地址和端口号组合代表一次TCP会话。在Wireshark中,我们可以使用右键单击一个TCP流,然后选择"Follow",再选择"TCP Stream"来查看完整的重组会话。 如果我们想以程序的方式从pcap提取并重组TCP会话,我们可以使用特定的编程库或工具,例如Scapy、dpkt等。这些工具提供了API和函数,可以让我们编写代码来读取pcap文件,提取TCP流量,并进行会话重组。 通过以上步骤,我们就可以从pcap提取并重组TCP会话,以便于进一步的分析和调查。这对于网络管理员、安全分析师或研究人员来说,都是非常有价值的工作。 ### 回答2: 从pcap提取TCP会话并重组是一种常见的网络流量分析技术,用于对网络通信进行深入研究和分析。下面是一个简单的步骤,用于从pcap提取TCP会话并重组: 1. 使用网络流量分析工具(如Wireshark)打开pcap文件,并过滤出TCP流量。 2. 根据TCP协议的特性,使用源端口和目的端口以及IP地址信息来区分不同的TCP会话。 3. 对于每个TCP会话,按照TCP协议的顺序对进行排序,确保按照传输顺序进行重组。 4. 通过比较每个TCP的序列号和确认号,以及TCP首部中的ACK标志位来确定数据的重组顺序。 5. 将重组后的TCP按照正确的顺序进行拼接,以恢复原始的TCP会话数据。 6. 对重组后的TCP会话数据进行进一步分析,可以提取出TCP会话中的各种信息,如源IP和目的IP、源端口和目的端口、连接建立时间、连接关闭时间、数据传输的总大小等。 通过从pcap提取TCP会话并重组,可以更好地理解网络通信的细节,如数据传输顺序、传输延迟、丢情况等。同时,还可以利用这些数据进行网络性能分析、故障排除和安全检测等工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值