Python: 从pcap文件中提取每个TCP session的payload

如今,网络在我们生活中起到不可或缺的作用,同时也催生出很多与网络相关的问题。比如恶意软件检测、流量识别等。机器学习和深度学习的相关算法已经被广泛应用于这些问题上面了。

本人在进行Botnet detection学习的时候,遇到第一个问题就是TCP数据的获取。但是一般我们通过抓包工具获得的就是一些pcap文件。从这些文件中我们利用如dpkt和scapy等库,很容易可以获得文件中的数据。但是如果想把从pcap文件中获得更好的特征,我们一般需要把流量数据按照一定单位分隔开来。

代码借鉴:https://github.com/hmishra2250/Botnet-Detection-using-Machine-Learning/tree/master/Custom%20Flow%20Generator

1.数据分割的单位

 这里仅讨论TCP数据的分割。一般来说分割的单位有:TCP flow 和 TCP session。

      TCP flow: (源地址、源端口、目的地址、目的端口、传输层协议) 相同的流数据就是一个flow。

      TCP session: 包含两个方向的TCP flow

 从理解上来说,TCP session就是一次TCP connection中,服务器和客户端相互之间传送的所有packet。而TCP flow指得是一次TCP连接中,客户端->服务器的所有packet,或者是服务器->客户端的所有packet。

 

2.利用wireshark导出csv文件

         获得pcap文件后,可以通过dpkt和scapy等库直接读入数据。但是我觉得如果能够直接操作.csv文件会更方便。

 wireshark是著名的网络工具,当我们获得一个pcap文件的之后,我们可以增加或者删除我们想要的columns。

        比如我们想要添加Sequence number为特征columns,我们找到相关信息点击右键,再选择用用为列即可。把所有需要的特征都选上之后,点击左上角"文件",选择“导出分组解析结果”,在选择.CSV文件即可。

        这样就拥有了一个包含raw data的.csv文件数据集。

  Python: 从pcap文件中提取每个TCP session的payload_第1张图片

 

3.实现TCP session分割

     这里利用Python实现将raw data分割成一个一个的TCP session。在分辨前后两行数据是否属于同一个TCP session时一般有如下标准:

    1. 前后两行数据的(源地址、源端口、目的地址、目的端口、传输层协议) 是否相同。(这里说的是主机号pair相同即可,即认为服务器->客户端跟客户端->服务器是相同的)

    2.前后两行数据的时间差是否大于某个阈值。

    3.标志位。

    我们知道每次TCP连接由三次握手开始,四次握手结束。因此在数据分割时,看到许多人会在检测到packet的FIN标志位为1时,就认为这是TCP connection结束。但是这其中隐含着一些问题。

     首先我们抓包经常会抓到一些深色的带有Bad TCP标志的包,这些包是因为没收到确认重发,或者是顺序混乱等问题导致的。因此有可能会在一次Session中遇到多于两个带有FIN的包。另外四次挥手过程中,一共会有两个packet带有FIN标志位。所以以FIN为标准判断Session结束是不合理的。

     于是本人使用的方法是:通过SYN标志为判断Session开始和结束。首先我们把数据按照(源地址、源端口、目的地址、目的端口、传输层协议)划分为不同的组,然后按照时间排序。接着一次遍历数据。如果当前数据的主机对与上一条数据的主机对不相同,代表Session结束,下一个Session开始。而如果遇到SYN标志位,也表示这是另一个Session的开始,上一个Session结束。(这里SYN判断主要是为了分辨相同主机对之间的多次TCP session)

     所以具体程序如下,该程序没有考虑重传等问题,即获得的包有可能是重复的。因此想要修正的朋友,可以在读入时把所有Bad TCP的packet删除,或者通过Sequence number和ACK number排除重传的包:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import keras.Sequential


'''
Dataset 包括的特征:
'No.', 'Time', 'Source Port', 'Answer RRs', 'Source', 'Destination',
       'Destination Port', 'Length', 'Protocol', 'TCP Segment Len',
       'Protocols in frame', 'IP_Flags', 'Next sequence number',
       'Sequence number', 'tcp_Flags', 'udp_Length', 'TCP payload',
       'Coloring Rule Name'
'''

def flow_id(x):
    if pd.isnull(x['udp_Length']):
        protocal = 'TCP'
    else:
        protocal = 'UDP'
    if x['Source']>x['Destination']:
        return x['Source']+'-'+x['Destination']+'-'+str(x['Source Port'])+'-'+str(x['Destination Port'])+'-'+protocal
    else:
        return x['Destination']+'-'+x['Source']+'-'+str(x['Destination Port'])+'-'+str(x['Source Port'])+'-'+protocal

raw = pd.read_csv('wal') 

#只保留TCP数据
raw = raw[(raw['Source Port'].notnull()) & (raw['Destination Port'].notnull()) & (raw['Coloring Rule Name']!='Bad TCP')]
#raw = raw[(raw['Source Port'].notnull()) & (raw['Destination Port'].notnull())]
raw['tcp_Flags'] = raw['tcp_Flags'].apply(lambda x:int(x,16) if x!='' else 0)


#定义数据流动方向和主机对+协议
raw['Forward'] = raw.apply(lambda x: 1 if x['Source'] > x['Destination'] else 0,axis=1)
raw['UFid'] = raw.apply(lambda x:flow_id(x),axis=1)

#按照主机对和时间排序
raw = raw.sort_values(['UFid','Time'],ascending=True)
raw.reset_index(drop=True,inplace=True)



#分离数据
raw_dict = raw.to_dict() #将dataset转换为字典,提高迭代速度

prev = None
Flow = []
FlowNo = 0
count = 0


for row in raw_dict['Source']:
    if prev == None:       
        #new flow  
        Flow.append(FlowNo)
        count = 1
        prev = raw_dict['UFid'][row]
    
    elif raw_dict['UFid'][row] == prev:
        #flow is already existing
        if raw_dict['tcp_Flags'][row] & 0x02: #SYN
            if raw_dict['tcp_Flags'][row] & 0x10:  #SYN + ACK
                Flow.append(FlowNo)
                count+=1
            else:
                if count == 1: #当连续遇到多个SYNpacket时候,直接略过
                    Flow.append(FlowNo)
                else:
                    FlowNo = FlowNo + 1
                    Flow.append(FlowNo)
                    count = 1
        
        else:
            #New packet in a pre-existing flow
            Flow.append(FlowNo)
            count += 1
    else:
        #Previous Flow tuple didn't receive any more packets, start a new flow
        FlowNo = FlowNo + 1
        Flow.append(FlowNo)
        count = 1
        prev = raw_dict['UFid'][row]
        
raw['FlowNo'] = Flow  #获得每一个packet所属的Session的编号

raw = raw[raw['TCP payload'].notnull()]  #仅保留有payload的数据

      

你可能感兴趣的:(计算机网络)