如今,网络在我们生活中起到不可或缺的作用,同时也催生出很多与网络相关的问题。比如恶意软件检测、流量识别等。机器学习和深度学习的相关算法已经被广泛应用于这些问题上面了。
本人在进行Botnet detection学习的时候,遇到第一个问题就是TCP数据的获取。但是一般我们通过抓包工具获得的就是一些pcap文件。从这些文件中我们利用如dpkt和scapy等库,很容易可以获得文件中的数据。但是如果想把从pcap文件中获得更好的特征,我们一般需要把流量数据按照一定单位分隔开来。
代码借鉴:https://github.com/hmishra2250/Botnet-Detection-using-Machine-Learning/tree/master/Custom%20Flow%20Generator
这里仅讨论TCP数据的分割。一般来说分割的单位有:TCP flow 和 TCP session。
TCP flow: (源地址、源端口、目的地址、目的端口、传输层协议) 相同的流数据就是一个flow。
TCP session: 包含两个方向的TCP flow
从理解上来说,TCP session就是一次TCP connection中,服务器和客户端相互之间传送的所有packet。而TCP flow指得是一次TCP连接中,客户端->服务器的所有packet,或者是服务器->客户端的所有packet。
获得pcap文件后,可以通过dpkt和scapy等库直接读入数据。但是我觉得如果能够直接操作.csv文件会更方便。
wireshark是著名的网络工具,当我们获得一个pcap文件的之后,我们可以增加或者删除我们想要的columns。
比如我们想要添加Sequence number为特征columns,我们找到相关信息点击右键,再选择用用为列即可。把所有需要的特征都选上之后,点击左上角"文件",选择“导出分组解析结果”,在选择.CSV文件即可。
这样就拥有了一个包含raw data的.csv文件数据集。
这里利用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的数据