├── data.json ├── .gitattributes ├── img ├── bg1.jpg ├── bg2.jpg ├── bg3.jpg ├── bg4.jpg ├── bg5.jpg ├── bg6.jpg ├── bg7.jpg ├── bg8.jpg ├── bg9.jpg ├── go.png ├── ip.png ├── pkg.png ├── about.png ├── bg10.jpg ├── bg10_1.jpg ├── bg11.jpg ├── bg12.jpg ├── bg14.png ├── logo.ico ├── pause.png ├── save.png ├── search.png ├── start.png ├── stop.png ├── monitor.png ├── openfile.png ├── restart.png ├── restart1.png └── terminal.png ├── main.py ├── README.md ├── __pycache__ ├── main_ui.cpython-36.pyc ├── main_ui.cpython-37.pyc ├── testUI.cpython-37.pyc ├── tools.cpython-36.pyc ├── tools.cpython-37.pyc ├── capture_core.cpython-36.pyc ├── capture_core.cpython-37.pyc ├── flow_monitor.cpython-36.pyc ├── flow_monitor.cpython-37.pyc ├── monitor_system.cpython-36.pyc └── monitor_system.cpython-37.pyc ├── .idea ├── other.xml ├── misc.xml ├── modules.xml ├── Network monitoring(修改版).iml └── workspace.xml ├── testUI.py ├── tools.py ├── flow_monitor.py ├── monitor_system.py ├── main_ui.py └── capture_core.py /data.json: -------------------------------------------------------------------------------- 1 | {"imageUrl": "", "size": 11, "font": "Microsoft YaHei"} -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /img/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg1.jpg -------------------------------------------------------------------------------- /img/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg2.jpg -------------------------------------------------------------------------------- /img/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg3.jpg -------------------------------------------------------------------------------- /img/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg4.jpg -------------------------------------------------------------------------------- /img/bg5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg5.jpg -------------------------------------------------------------------------------- /img/bg6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg6.jpg -------------------------------------------------------------------------------- /img/bg7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg7.jpg -------------------------------------------------------------------------------- /img/bg8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg8.jpg -------------------------------------------------------------------------------- /img/bg9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg9.jpg -------------------------------------------------------------------------------- /img/go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/go.png -------------------------------------------------------------------------------- /img/ip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/ip.png -------------------------------------------------------------------------------- /img/pkg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/pkg.png -------------------------------------------------------------------------------- /img/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/about.png -------------------------------------------------------------------------------- /img/bg10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg10.jpg -------------------------------------------------------------------------------- /img/bg10_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg10_1.jpg -------------------------------------------------------------------------------- /img/bg11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg11.jpg -------------------------------------------------------------------------------- /img/bg12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg12.jpg -------------------------------------------------------------------------------- /img/bg14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/bg14.png -------------------------------------------------------------------------------- /img/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/logo.ico -------------------------------------------------------------------------------- /img/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/pause.png -------------------------------------------------------------------------------- /img/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/save.png -------------------------------------------------------------------------------- /img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/search.png -------------------------------------------------------------------------------- /img/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/start.png -------------------------------------------------------------------------------- /img/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/stop.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from main_ui import start 2 | 3 | if __name__ == "__main__": 4 | start() 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network-monitoring 2 | 仿照wireshark写的一个网络流量监测应用,前端用的pyqt,界面还蛮好看的,具体功能包括抓包,过滤,流量监控等等 3 | -------------------------------------------------------------------------------- /img/monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/monitor.png -------------------------------------------------------------------------------- /img/openfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/openfile.png -------------------------------------------------------------------------------- /img/restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/restart.png -------------------------------------------------------------------------------- /img/restart1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/restart1.png -------------------------------------------------------------------------------- /img/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/img/terminal.png -------------------------------------------------------------------------------- /__pycache__/main_ui.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/main_ui.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/main_ui.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/main_ui.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/testUI.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/testUI.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/tools.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/tools.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/tools.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/tools.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/capture_core.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/capture_core.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/capture_core.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/capture_core.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/flow_monitor.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/flow_monitor.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/flow_monitor.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/flow_monitor.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/monitor_system.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/monitor_system.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/monitor_system.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry-zhxf/Network-monitoring/HEAD/__pycache__/monitor_system.cpython-37.pyc -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/Network monitoring(修改版).iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /testUI.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QComboBox, QPushButton 3 | 4 | 5 | class Example(QMainWindow): 6 | 7 | def __init__(self): 8 | super().__init__() 9 | 10 | combo = QComboBox(self) 11 | combo.addItem("Apple") 12 | combo.addItem("Pear") 13 | combo.addItem("Lemon") 14 | combo.setEditable(True) 15 | combo.move(50, 50) 16 | 17 | self.qlabel = QLabel(self) 18 | self.qlabel.move(50, 16) 19 | 20 | combo.activated[str].connect(self.onChanged) 21 | 22 | self.setGeometry(50, 50, 320, 200) 23 | self.setWindowTitle("QLineEdit Example") 24 | self.show() 25 | 26 | def onChanged(self, text): 27 | self.qlabel.setText(text) 28 | self.qlabel.adjustSize() 29 | 30 | 31 | if __name__ == '__main__': 32 | app = QApplication(sys.argv) 33 | ex = Example() 34 | sys.exit(app.exec_()) 35 | 36 | ''' 37 | #!/usr/bin/env python 38 | # -*- coding: utf-8 -*- 39 | from PyQt5.QtCore import Qt, QSortFilterProxyModel 40 | from PyQt5.QtWidgets import QComboBox, QCompleter, QApplication 41 | from numpy import unicode 42 | 43 | 44 | class ExtendedComboBox(QComboBox): 45 | def __init__(self, parent=None): 46 | super(ExtendedComboBox, self).__init__(parent) 47 | 48 | self.setFocusPolicy(Qt.StrongFocus) 49 | self.setEditable(True) 50 | 51 | # add a filter model to filter matching items 52 | self.pFilterModel = QSortFilterProxyModel(self) 53 | self.pFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) 54 | self.pFilterModel.setSourceModel(self.model()) 55 | 56 | # add a completer, which uses the filter model 57 | self.completer = QCompleter(self.pFilterModel, self) 58 | # always show all (filtered) completions 59 | self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) 60 | self.setCompleter(self.completer) 61 | 62 | # connect signals 63 | self.lineEdit().textEdited[unicode].connect(self.pFilterModel.setFilterFixedString) 64 | self.completer.activated.connect(self.on_completer_activated) 65 | 66 | # on selection of an item from the completer, select the corresponding item from combobox 67 | def on_completer_activated(self, text): 68 | if text: 69 | index = self.findText(text) 70 | self.setCurrentIndex(index) 71 | 72 | # on model change, update the models of the filter and completer as well 73 | def setModel(self, model): 74 | super(ExtendedComboBox, self).setModel(model) 75 | self.pFilterModel.setSourceModel(model) 76 | self.completer.setModel(self.pFilterModel) 77 | 78 | # on model column change, update the model column of the filter and completer as well 79 | def setModelColumn(self, column): 80 | self.completer.setCompletionColumn(column) 81 | self.pFilterModel.setFilterKeyColumn(column) 82 | super(ExtendedComboBox, self).setModelColumn(column) 83 | 84 | 85 | if __name__ == "__main__": 86 | import sys 87 | 88 | app = QApplication(sys.argv) 89 | 90 | string_list = ['hola muchachos', 'adios amigos', 'hello world', 'good bye'] 91 | 92 | combo = ExtendedComboBox() 93 | 94 | # either fill the standard model of the combobox 95 | combo.addItems(string_list) 96 | 97 | # or use another model 98 | # combo.setModel(QStringListModel(string_list)) 99 | 100 | combo.resize(300, 40) 101 | combo.show() 102 | 103 | ''' -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 工具集 4 | 可获取网卡列表, 某个网卡的下载速度上传速度等 5 | """ 6 | 7 | import time 8 | from platform import system 9 | from psutil import net_if_addrs, net_io_counters 10 | 11 | 12 | def get_netcard_name(): 13 | ''' 14 | 获取网卡MAC和名字对应字典 15 | 如: {'9C-B6-D0-0E-70-D9': 'WLAN'} 16 | ''' 17 | netcard_info = {} 18 | info = net_if_addrs() 19 | for k, v in info.items(): 20 | for item in v: 21 | # 除去环路地址 22 | if item[0] == 2 and item[1] == '127.0.0.1': 23 | break 24 | # 创建字典 25 | elif item[0] == -1 or item[0] == 17: 26 | netcard_info.update({item[1]: k}) 27 | return netcard_info 28 | 29 | 30 | def get_nic_list(): 31 | ''' 32 | :return: (系统信息, 网卡字典或列表) 33 | Linux返回列表, Windows返回字典s 34 | 字典key为网卡名字, value为NIC信息 35 | 如: {'WLAN': 'Killer Wireless-n/a/ac 1535 Wireless Network Adapter'} 36 | ''' 37 | # 获取系统信息 38 | system_name = system() 39 | netcard_name = get_netcard_name() 40 | if system_name == "Windows": 41 | import wmi 42 | wmi_obj = wmi.WMI() 43 | data = {} 44 | for nic in wmi_obj.Win32_NetworkAdapterConfiguration(): 45 | if nic.MACAddress is not None: 46 | # 与前面的字典匹配 47 | mac_address = str(nic.MACAddress).replace(':', '-') 48 | if mac_address in netcard_name.keys(): 49 | net_card_name = netcard_name.get(mac_address) 50 | nic_name = str(nic.Caption)[11:] 51 | data.update({net_card_name: nic_name}) 52 | return (system_name, data) 53 | elif system_name == "Linux": 54 | List = list(netcard_name.values()) 55 | return (system_name, List) 56 | else: 57 | return None 58 | 59 | 60 | def get_net_flow(net_card): 61 | """ 62 | 返回流量发送和接收的信息, 输入为网卡名字 63 | """ 64 | net_info = net_io_counters(pernic=True).get(net_card) #获取流量统计信息 65 | # 字节数统计信息 66 | recv_bytes = net_info.bytes_recv 67 | sent_bytes = net_info.bytes_sent 68 | # 数据包统计信息 69 | recv_pak = net_info.packets_recv 70 | sent_pak = net_info.packets_sent 71 | return recv_bytes, sent_bytes, recv_pak, sent_pak 72 | 73 | 74 | def change_format(count): 75 | """ 76 | 改变字节数格式 77 | """ 78 | if count < 1024: 79 | return "%.2f B/s" % count 80 | if count < 1048576: 81 | return "%.2f KB/s" % (count / 1024) 82 | count >>= 10 83 | if count < 1048576: 84 | return "%.2f MB/s" % (count / 1024) 85 | count >>= 10 86 | return "%.2f GB/s" % (count / 1024) 87 | 88 | 89 | def get_rate(net_card): 90 | """ 91 | 统计每秒接收到的数据大小 92 | :parma net_card: 网卡名字 93 | :return : 返回未格式化的信息 94 | """ 95 | net_cards = [] 96 | old = [0, 0, 0, 0] 97 | new = [0, 0, 0, 0] 98 | if net_card is None: # 抓取全部网卡的速度 99 | net_cards = net_io_counters(pernic=True).keys() #Return network I/O statistics as a namedtuple 100 | else: 101 | net_cards.append(net_card) 102 | for card in net_cards: 103 | # 上一秒收集的数据 104 | info = get_net_flow(card) #获得收发数据包的字节数和包数信息【bytes_sent、bytes_recv、packets_sent、packets_recv】 105 | for i in range(4): 106 | old[i] += info[i] 107 | time.sleep(1) 108 | # 当前所收集的数据 109 | for card in net_cards: 110 | # 上一秒收集的数据 111 | info = get_net_flow(card) 112 | for i in range(4): 113 | new[i] += info[i] 114 | info = [] 115 | for i in range(4): 116 | info.append(new[i] - old[i]) # 这一秒的速度和上一秒的速度之差 117 | return info 118 | 119 | 120 | def get_formal_rate(info): 121 | """ 122 | 获取格式化的速率 123 | :parma info: 列表,包含recv_bytes, sent_bytes, recv_pak, sent_pak 124 | :return :返回格式化后的信息 125 | """ 126 | recv_bytes = change_format(info[0]) # 每秒接收的字节 127 | sent_bytes = change_format(info[1]) # 每秒发送的字节 128 | recv_pak = str(info[2]) + " pak/s" # 每秒接收的数据包 129 | sent_pak = str(info[3]) + " pak/s" # 每秒发送的数据包 130 | return recv_bytes, sent_bytes, recv_pak, sent_pak 131 | 132 | def time_to_formal(time_stamp): 133 | """ 134 | 将时间戳转换为标准的时间字符串 135 | 如: 2018-10-21 20:27:53.123456 136 | :parma time_stamp: 时间戳,ms为单位 137 | """ 138 | delta_ms = str(time_stamp - int(time_stamp)) 139 | time_temp = time.localtime(time_stamp) 140 | my_time = time.strftime("%Y-%m-%d %H:%M:%S", time_temp) 141 | my_time += delta_ms[1:8] 142 | return my_time -------------------------------------------------------------------------------- /flow_monitor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 流量监测系统 """ 3 | from threading import Thread, Event 4 | from scapy.sendrecv import sniff 5 | from scapy.layers.inet import * 6 | import psutil 7 | 8 | 9 | class Monitor: 10 | """ 11 | 流量监测 12 | """ 13 | # 程序使用的端口 14 | process_ports = [] 15 | # 监测系统是否开始 16 | start_flag = Event() 17 | window = None 18 | 19 | def __init__(self, window): 20 | self.window = window 21 | self.start_flag.set() 22 | 23 | def getProcessList(self): # 获得进程名列表 24 | """ 25 | 获取有网络连接的进程列表 26 | :return :返回进程列表 27 | """ 28 | process_list = set() 29 | for process in psutil.process_iter(): 30 | connections = process.connections() 31 | if connections: 32 | process_list.add(process.name()) 33 | return list(process_list) 34 | 35 | def getProcessConnections(self): # 返回进程名列表和UDP,TCP两端连接信息,显示在树形item的列表中【记录了tcp,icmp链接的情况】 36 | """ 37 | 获取进程使用的网络连接 38 | :return : 返回进程名字列表和进程对应的连接列表 39 | """ 40 | process_name = set() 41 | process_conn = {} 42 | for process in psutil.process_iter(): 43 | connections = process.connections() 44 | if connections: 45 | process_name.add(process.name()) 46 | for con in connections: 47 | if con.type == 1: # TCP 48 | protocol = 'TCP' 49 | elif con.type == 2: # UDP 50 | protocol = 'UDP' 51 | # 本地使用的IP及端口 52 | laddr = "%s:%d" % (con.laddr[0], con.laddr[1]) 53 | # 远端的IP和端口情况 54 | if con.raddr: # 如果能获得链接的ip和端口就直接获得 55 | raddr = "%s:%d" % (con.raddr[0], con.raddr[1]) 56 | elif con.family.value == 2: # 否则判断协议族 57 | # IPv4 58 | raddr = "0.0.0.0:0" 59 | elif con.family.value == 23: 60 | # IPv6 61 | raddr = "[::]:0" 62 | else: 63 | raddr = "*:*" 64 | info = "%s\t%s\nLocal: %s\nRemote: %s\n" % ( 65 | protocol, con.status, laddr, raddr) 66 | process_conn.setdefault(process.name(), set()).add(info) 67 | return list(process_name), process_conn 68 | 69 | def getPortList(self, process_name): #获得某进程process_name的端口列表 70 | """ 71 | 用于刷新某个进程的端口列表的函数 72 | 将获得的端口列表设置到self.process_ports 73 | :parma process_name: 输入为程序的名字 74 | """ 75 | ports = set() 76 | while not self.start_flag.is_set(): 77 | ports.clear() 78 | for process in psutil.process_iter(): #遍历正在运行的进程,以获取有关特定进程的详细信息 79 | connections = process.connections() 80 | if process.name() == process_name and connections: 81 | for con in connections: 82 | if con.laddr: 83 | ports.add(con.laddr[1]) 84 | if con.raddr: 85 | ports.add(con.raddr[1]) 86 | if ports: 87 | self.process_ports = list(ports) 88 | else: 89 | # 进程已不存在 90 | self.window.stop() 91 | self.window.refresh_process() 92 | self.window.alert("进程%s已停止运行!" % process_name) 93 | 94 | def getConnections(self, pak): # 对每个包的情况进行解析判断,实时图上面的包列表 95 | """ 96 | 获取应用的连接信息 97 | :parma pak: 数据包 98 | """ 99 | try: 100 | src = pak.payload.src 101 | dst = pak.payload.dst 102 | length = len(pak) 103 | if src == dst: 104 | # 相同源地址和目的地址,可能为Land攻击 105 | self.window.alert("数据包源地址与目的地址相同, 疑为Land攻击!") 106 | elif len(pak.payload) > 65535: 107 | # IP数据包的最大长度大于64KB(即65535B), 若大于, 则疑为Ping of Death攻击 108 | self.window.alert("收到IP数据包长度大于64KB, 疑为Ping拒绝服务攻击!") 109 | else: 110 | protocol = pak.payload.payload.name 111 | if protocol != 'ICMP': 112 | sport = pak.payload.payload.sport 113 | dport = pak.payload.payload.dport 114 | if sport in self.process_ports and dport in self.process_ports: #属于该进程连接端口的 115 | info = "%-7s%s:%d -> %s:%d%7d" % (protocol, src, sport, 116 | dst, dport, length) 117 | if protocol == 'TCP': 118 | info += '%5s' % str(pak.payload.payload.flags) # 24为PA(PSH[DATA数据传输]、ACK),16为A(ACK) 119 | self.window.conList.addItem(info) 120 | else: 121 | # ICMP报文 122 | self.window.conList.addItem( 123 | "%-7s%s -> %s%7d" % (protocol, src, dst, length)) 124 | except: 125 | pass 126 | 127 | def capture_packet(self): 128 | """ 129 | 设置过滤器, 只接收IP、IPv6、TCP、UDP 130 | """ 131 | sniff( 132 | store=False, 133 | filter="(tcp or udp or icmp) and (ip6 or ip)", 134 | prn=lambda x: self.getConnections(x), #对每个数据包调用connections 135 | stop_filter=lambda x: self.start_flag.is_set()) 136 | 137 | def start(self, process_name): 138 | """ 139 | 开始对某一进程的流量监视 140 | :parma process_name: 进程的名字 141 | """ 142 | # 开启刷新程序端口的线程 143 | self.start_flag.clear() 144 | self.window.conList.clear() 145 | Thread( #获取选定进程的端口列表,赋值process_ports 146 | target=self.getPortList, daemon=True, 147 | args=(process_name, )).start() 148 | Thread(target=self.capture_packet, daemon=True).start() # 将每个嗅探的包放置到window.conList(对关于这个进程的所有连接端口通过的流量进行检测) 149 | 150 | def stop(self): 151 | """ 152 | 停止监测 153 | """ 154 | self.start_flag.set() 155 | -------------------------------------------------------------------------------- /monitor_system.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #流量监测界面 3 | from threading import Thread 4 | from time import time 5 | from PyQt5 import QtCore, QtGui, QtWidgets 6 | from PyQt5.QtCore import QSize 7 | from PyQt5.QtGui import * 8 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 9 | from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar 10 | import matplotlib.pyplot as plt 11 | from tools import get_rate, time_to_formal 12 | from flow_monitor import Monitor 13 | 14 | 15 | class Ui_Form(object): 16 | def setupUi(self, Form): 17 | Form.setWindowTitle("流量监测系统") 18 | Form.resize(1150, 630) 19 | # 设置程序图标 20 | icon = QIcon() 21 | icon.addPixmap(QPixmap("img/logo.png"), QIcon.Normal, QIcon.Off) # 修改图标 22 | Form.setWindowIcon(icon) 23 | self.horizontalLayoutWidget = QtWidgets.QWidget(Form) 24 | self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 700, 30)) 25 | self.horizontalLayout = QtWidgets.QHBoxLayout( 26 | self.horizontalLayoutWidget) 27 | self.horizontalLayout.setContentsMargins(0, 0, 0, 0) 28 | self.horizontalLayout.setSpacing(10) 29 | Form.setFixedSize(Form.width(), Form.height()) 30 | self.monitor = Monitor(self) # 建立流量监测器 31 | """主字体""" 32 | font = QtGui.QFont() 33 | font.setFamily("Microsoft YaHei") 34 | font.setPointSize(10) 35 | """应用选择框""" 36 | self.comboBox = QtWidgets.QComboBox(self.horizontalLayoutWidget) 37 | self.comboBox.setFont(font) 38 | self.horizontalLayout.addWidget(self.comboBox) 39 | """流量预警线设置""" 40 | self.warn_line = QtWidgets.QLineEdit(self.horizontalLayoutWidget) 41 | self.warn_line.setText("1024") 42 | self.horizontalLayout.addWidget(self.warn_line) 43 | self.label = QtWidgets.QLabel(self.horizontalLayoutWidget) 44 | self.label.setText("kb/s") 45 | self.horizontalLayout.addWidget(self.label) 46 | self.start_button = QtWidgets.QPushButton(self.horizontalLayoutWidget) 47 | self.start_button.setText("开始监测") 48 | self.start_button.clicked.connect(self.start) 49 | self.horizontalLayout.addWidget(self.start_button) 50 | 51 | self.stop_button = QtWidgets.QPushButton(self.horizontalLayoutWidget) 52 | self.stop_button.setText("停止监测") 53 | self.stop_button.clicked.connect(self.stop) 54 | self.horizontalLayout.addWidget(self.stop_button) 55 | self.stop_button.setEnabled(False) 56 | 57 | self.update_button = QtWidgets.QPushButton(self.horizontalLayoutWidget) 58 | self.update_button.setText("更新列表") 59 | self.horizontalLayout.addWidget(self.update_button) 60 | self.horizontalLayout.setStretch(0, 2) 61 | self.horizontalLayout.setStretch(1, 1) 62 | self.horizontalLayout.setStretch(2, 1) 63 | self.horizontalLayout.setStretch(3, 1) 64 | self.horizontalLayout.setStretch(4, 1) 65 | 66 | # 右侧进程信息栏 67 | self.APPList_label = QtWidgets.QLabel(Form) 68 | self.APPList_label.setText("进程连接列表") 69 | self.APPList_label.setStyleSheet("font-size: 20px; font-family: 宋体") 70 | self.APPList_label.setGeometry(QtCore.QRect(850, 10, 150, 30)) 71 | """应用树列表""" 72 | self.App_Tree = QtWidgets.QTreeWidget(Form) 73 | self.App_Tree.header().setVisible(False) 74 | self.App_Tree.setFont(font) 75 | self.App_Tree.setStyleSheet("QTreeView::item{margin:2px;}") 76 | self.App_Tree.setGeometry(QtCore.QRect(730, 40, 380, 580)) 77 | 78 | self.update_button.clicked.connect(self.refresh_process) 79 | 80 | 81 | self.verticalLayoutWidget = QtWidgets.QWidget(Form) 82 | self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 60, 700, 570)) 83 | self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) 84 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 85 | 86 | # 抓包列表 87 | self.conList = QtWidgets.QListWidget(self.verticalLayoutWidget) 88 | self.conList.setFont(font) 89 | self.conList.setStyleSheet("QListView::item{margin:2px;}") 90 | self.conList.setMinimumSize(421, 200) 91 | self.verticalLayout.addWidget(self.conList) 92 | 93 | 94 | # 实时监控图 95 | self.figure = plt.figure(figsize=(6, 3)) 96 | self.upload_plot = self.figure.add_subplot(1, 1, 1) 97 | self.upload_plot.set_xlabel("Time (s)") 98 | self.upload_plot.set_ylabel("Speed (kB/s)") 99 | self.figure.tight_layout() 100 | self.canvas = FigureCanvas(self.figure) 101 | self.toolbar = NavigationToolbar(self.canvas, 102 | self.verticalLayoutWidget) 103 | self.toolbar.hide() 104 | self.verticalLayout.addWidget(self.toolbar) 105 | self.verticalLayout.addWidget(self.canvas) 106 | QtCore.QMetaObject.connectSlotsByName(Form) 107 | self.comboBox.addItems(self.monitor.getProcessList()) 108 | self.show_process_tree() 109 | self.timer = QtCore.QTimer(Form) 110 | self.timer.timeout.connect(self.conList.scrollToBottom) 111 | 112 | def show_process_tree(self): 113 | """ 114 | 添加节点 115 | """ 116 | self.App_Tree.clear() 117 | process_name, process_conn = self.monitor.getProcessConnections() #位于flow_monitor.py,利用psutil.process_iter()获取正在运行的进程信息 118 | for name in process_name: 119 | item1 = QtWidgets.QTreeWidgetItem(self.App_Tree) # 树形item结构 120 | item1.setText(0, name) 121 | for connections in process_conn[name]: 122 | item1_1 = QtWidgets.QTreeWidgetItem(item1) 123 | item1_1.setText(0, connections) 124 | 125 | def alert(self, info): 126 | """ 127 | 警告信息 128 | """ 129 | alert_font = QtGui.QFont() 130 | alert_font.setFamily("Microsoft YaHei") 131 | alert_font.setPointSize(14) 132 | now = time_to_formal(time()) 133 | item = QtWidgets.QListWidgetItem("%s\n%s" % (now, info), self.conList) 134 | item.setForeground(QtGui.QColor('red')) 135 | item.setFont(alert_font) 136 | 137 | def refresh_process(self): 138 | """ 139 | 刷新进程列表 140 | """ 141 | self.comboBox.clear() 142 | self.comboBox.addItems(self.monitor.getProcessList()) # 更新下拉框进程列表 143 | self.show_process_tree() 144 | 145 | def setSpeed(self): 146 | """ 147 | 设置速度图 148 | """ 149 | upload = [] 150 | download = [] 151 | speed = int(self.warn_line.text()) 152 | # 警告线 153 | alert = [speed for _ in range(60)] #1024 154 | while not self.monitor.start_flag.is_set(): #一直划线 155 | info = get_rate(None) #位于tools.py,默认设置所有网卡的抓包 156 | plt.cla() 157 | self.upload_plot.set_xlabel("Time (s)") 158 | self.upload_plot.set_ylabel("Speed (kB/s)") 159 | info[1] >>= 10 # 差值,转换B->KB 160 | info[0] >>= 10 161 | upload.append(info[1]) 162 | download.append(info[0]) 163 | if len(upload) >= 60: #超过60个数据,pop掉第一个数据,FIFO 164 | upload.pop(0) 165 | download.pop(0) 166 | # 画图,每次都根据整个数组重新画图 167 | self.upload_plot.plot( 168 | alert, 'red', linewidth='2', label="Warning") 169 | self.upload_plot.legend(loc='upper right') #图例 170 | self.upload_plot.plot( 171 | upload, 'darkorange', linewidth='1', label="Upload") 172 | self.upload_plot.legend(loc='upper right') 173 | self.upload_plot.plot( 174 | download, 'blue', linewidth='1', label="Download") 175 | self.upload_plot.legend(loc='upper right') 176 | self.canvas.draw() 177 | # 流量警告 178 | # 当速度大于设定值时流量警告 179 | if info[1] > speed or info[0] > speed: 180 | self.alert("警告: 流量已超过预警线 %dkB/s!" % speed) 181 | 182 | def start(self): 183 | """ 184 | 开始检测 185 | """ 186 | if self.monitor.start_flag.is_set(): 187 | self.start_button.setEnabled(False) 188 | self.stop_button.setEnabled(True) 189 | self.comboBox.setEnabled(False) 190 | self.warn_line.setEnabled(False) 191 | self.monitor.start(self.comboBox.currentText()) 192 | Thread(target=self.setSpeed, daemon=True).start() 193 | self.timer.start(1000) 194 | 195 | def stop(self): 196 | """ 197 | 停止检测 198 | """ 199 | if not self.monitor.start_flag.is_set(): 200 | self.monitor.stop() 201 | self.timer.stop() 202 | self.start_button.setEnabled(True) 203 | self.stop_button.setEnabled(False) 204 | self.comboBox.setEnabled(True) 205 | self.warn_line.setEnabled(True) 206 | 207 | 208 | def start_monitor(): 209 | """ 210 | 调用监测系统 211 | """ 212 | app = QtWidgets.QApplication([]) 213 | widget = QtWidgets.QWidget() 214 | ui = Ui_Form() 215 | ui.setupUi(widget) 216 | widget.show() 217 | app.exec() 218 | 219 | 220 | if __name__ == "__main__": 221 | start_monitor() 222 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 41 | 42 | 43 | 44 | 45 | 65 | 66 | 67 | 87 | 88 | 89 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 1574306406266 124 | 131 | 132 | 133 | 134 | 136 | 137 | 138 | 139 | 140 | file://$PROJECT_DIR$/main_ui.py 141 | 458 142 | 144 | 145 | file://$PROJECT_DIR$/main_ui.py 146 | 416 147 | 149 | 150 | file://$PROJECT_DIR$/main_ui.py 151 | 600 152 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /main_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from sys import exit 3 | from PyQt5.QtWidgets import * 4 | from PyQt5.QtGui import * 5 | from PyQt5.QtCore import * 6 | from capture_core import * 7 | # 使用matplotlib绘制柱状图 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import json 11 | from monitor_system import start_monitor 12 | from multiprocessing import Process 13 | 14 | 15 | 16 | class Ui_MainWindow(QMainWindow): 17 | 18 | core = None 19 | timer = None 20 | Monitor = None 21 | Forged = None 22 | 23 | def setbg(self): 24 | palette = QPalette() 25 | pix = QPixmap("img/bg10_1.jpg") #5,10,11,12 26 | pix = pix.scaled(self.width(), self.height()) 27 | palette.setBrush(QPalette.Background, QBrush(pix)) 28 | self.setPalette(palette) 29 | 30 | def setupUi(self): 31 | self.setWindowTitle("Network_monitoring") 32 | self.resize(1200, 750) 33 | self.setbg() 34 | 35 | #设置程序图标 36 | icon = QIcon() 37 | icon.addPixmap(QPixmap("img/logo.ico"), QIcon.Normal, QIcon.Off)#修改图标 38 | self.setWindowIcon(icon) 39 | self.setIconSize(QSize(20, 20)) 40 | #中间布局,设为透明 41 | self.centralWidget = QWidget(self) 42 | self.centralWidget.setStyleSheet("background:transparent;") 43 | 44 | #栅栏布局,使得窗口自适应 45 | self.gridLayout = QGridLayout(self.centralWidget) 46 | self.gridLayout.setContentsMargins(0, 0, 0, 0) 47 | self.gridLayout.setSpacing(6) 48 | 49 | #顶部控件布局 50 | self.horizontalLayout = QHBoxLayout() 51 | self.horizontalLayout.setContentsMargins(10, 2, 10, 1) 52 | self.horizontalLayout.setSpacing(20) 53 | 54 | #三个显示区布局 55 | self.verticalLayout = QVBoxLayout() 56 | self.verticalLayout.setContentsMargins(10, 0, 3, 10) 57 | self.verticalLayout.setSpacing(6) 58 | 59 | # 初始主窗口字体 60 | font = QFont() 61 | with open('data.json', 'r') as file_obj: 62 | '''读取json文件''' 63 | old_font = json.load(file_obj) # 返回列表数据,也支持字典 64 | if old_font["font"]: 65 | font.setFamily(old_font["font"]) 66 | font.setPointSize(int(old_font["size"])) 67 | else: 68 | if platform == 'Windows': 69 | font.setFamily("Microsoft YaHei") 70 | old_font["font"] = "Microsoft YaHei" 71 | if platform == "Linux": 72 | font.setFamily("Noto Mono") 73 | old_font["font"] = "Noto Mono" 74 | font.setPointSize(11) 75 | with open('data.json', 'w') as file_obj: 76 | '''写入json文件''' 77 | json.dump(old_font, file_obj) 78 | 79 | #数据包显示框 80 | self.info_tree = QTreeWidget(self.centralWidget) 81 | self.info_tree.setFrameStyle(QFrame.Box | QFrame.Plain) 82 | self.info_tree.setAutoScroll(True) 83 | self.info_tree.setRootIsDecorated(False) 84 | self.info_tree.setFont(font) 85 | self.info_tree.setColumnCount(7) #设置表格为7列 86 | #固定行高,取消每次刷新所有行,避免更新数据时不流畅 87 | self.info_tree.setUniformRowHeights(True) 88 | #设置表头 89 | self.info_tree.headerItem().setText(0, "No.") 90 | self.info_tree.headerItem().setText(1, "Time") 91 | self.info_tree.headerItem().setText(2, "Source") 92 | self.info_tree.headerItem().setText(3, "Destination") 93 | self.info_tree.headerItem().setText(4, "Protocol") 94 | self.info_tree.headerItem().setText(5, "Length") 95 | self.info_tree.headerItem().setText(6, "Info") 96 | self.info_tree.setStyleSheet("background:transparent;") 97 | self.info_tree.setSortingEnabled(True) 98 | self.info_tree.sortItems(0, Qt.AscendingOrder) 99 | self.info_tree.setColumnWidth(0, 75) 100 | self.info_tree.setColumnWidth(1, 130) 101 | self.info_tree.setColumnWidth(2, 150) 102 | self.info_tree.setColumnWidth(3, 180) 103 | self.info_tree.setColumnWidth(4, 140) 104 | self.info_tree.setColumnWidth(5, 100) 105 | for i in range(7): 106 | self.info_tree.headerItem().setBackground(i,QBrush(QColor(Qt.white))) 107 | self.info_tree.setSelectionBehavior(QTreeWidget.SelectRows) #设置选中时为整行选中 108 | self.info_tree.setSelectionMode(QTreeWidget.SingleSelection) #设置只能选中一行 109 | """显示排序图标""" 110 | self.info_tree.header().setSortIndicatorShown(True) 111 | self.info_tree.clicked.connect(self.on_tableview_clicked) 112 | 113 | #数据包详细内容显示框 114 | self.treeWidget = QTreeWidget(self.centralWidget) 115 | self.treeWidget.setAutoScroll(True) 116 | self.treeWidget.setTextElideMode(Qt.ElideMiddle) 117 | self.treeWidget.header().setStretchLastSection(True) 118 | self.treeWidget.setStyleSheet("background:transparent; color:black;") 119 | self.treeWidget.setStyleSheet("color:black;") 120 | self.treeWidget.header().hide() 121 | self.treeWidget.setFont(font) 122 | # 设为只有一列 123 | self.treeWidget.setColumnCount(1) 124 | self.treeWidget.setFrameStyle(QFrame.Box | QFrame.Plain) 125 | 126 | #hex显示区域 127 | self.hexBrowser = QTextBrowser(self.centralWidget) 128 | self.hexBrowser.setText("") 129 | self.hexBrowser.setFont(font) 130 | self.hexBrowser.setStyleSheet("color:white;") 131 | self.hexBrowser.setStyleSheet("background:transparent; color:white;") 132 | self.hexBrowser.setFrameStyle(QFrame.Box | QFrame.Plain) 133 | 134 | # 允许用户通过拖动三个显示框的边界来控制子组件的大小 135 | self.splitter = QSplitter(Qt.Vertical) 136 | self.splitter.addWidget(self.info_tree) 137 | self.splitter.addWidget(self.treeWidget) 138 | self.splitter.addWidget(self.hexBrowser) 139 | self.verticalLayout.addWidget(self.splitter) 140 | 141 | self.gridLayout.addLayout(self.verticalLayout, 1, 0, 1, 1) 142 | 143 | ''' 144 | #过滤器输入框 145 | self.Filter = QLineEdit(self.centralWidget) 146 | self.Filter.setPlaceholderText("Apply a capture filter … ") 147 | self.Filter.setStyleSheet("background:#ADD8E6") 148 | self.Filter.setFont(font) 149 | self.horizontalLayout.addWidget(self.Filter) 150 | ''' 151 | 152 | string_list = ["tcp port 80","not icmp","dst net 10.63"] 153 | self.Filter = QComboBox(self.centralWidget) 154 | self.Filter.addItems(string_list) 155 | self.Filter.setEditable(True) 156 | self.Filter.setCurrentIndex(-1) 157 | self.Filter.setInsertPolicy(QComboBox.InsertAtTop) 158 | self.Filter.setMaxCount(8) 159 | self.Filter.completer() 160 | self.Filter.setStyleSheet("background:#FAF0E6;min-height: 35px; ") 161 | self.horizontalLayout.addWidget(self.Filter) 162 | 163 | 164 | 165 | #过滤器按钮 166 | self.FilterButton = QPushButton(self.centralWidget) 167 | self.FilterButton.setText("过滤搜索") 168 | icon1 = QIcon() 169 | icon1.addPixmap(QPixmap("img/search.png"), QIcon.Normal, QIcon.Off) 170 | self.FilterButton.setIcon(icon1) 171 | self.FilterButton.setFixedSize(100,35) 172 | self.FilterButton.setIconSize(QSize(20, 20)) 173 | #self.FilterButton.setStyleSheet("background:white") 174 | self.FilterButton.setStyleSheet( "QPushButton{color: white}" #按键前景色 175 | "QPushButton{background-color:#CD5C5C;order-style: outset;border-width: 1px;border-radius: 10px;border-color: beige;font: bold 14px;min-width: 8em;padding: 6px;}" #按键背景色 176 | "QPushButton:hover{color: black}" #光标移动到上面后的前景色 177 | "QPushButton:pressed{background-color: #A52A2A; border: None;}") 178 | self.FilterButton.clicked.connect(self.on_start_action_clicked) 179 | self.horizontalLayout.addWidget(self.FilterButton) 180 | """ 181 | 网卡选择框 182 | """ 183 | self.choose_nicbox = QComboBox(self.centralWidget) 184 | self.choose_nicbox.setFont(font) 185 | self.choose_nicbox.setStyleSheet("background:#FAF0E6; color:black;") 186 | self.horizontalLayout.addWidget(self.choose_nicbox) 187 | 188 | self.horizontalLayout.setStretch(0, 8) 189 | self.horizontalLayout.setStretch(1, 1) 190 | self.horizontalLayout.setStretch(2, 4) 191 | self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) 192 | """初始网卡复选框""" 193 | row_num = len(keys) 194 | self.choose_nicbox.addItem("All") 195 | for i in range(row_num): 196 | self.choose_nicbox.addItem(keys[i]) 197 | 198 | self.setCentralWidget(self.centralWidget) 199 | """ 200 | 顶部菜单栏 201 | """ 202 | self.menuBar = QMenuBar(self) 203 | self.menuBar.setGeometry(QRect(0, 0, 953, 23)) 204 | self.menuBar.setAccessibleName("") 205 | self.menuBar.setStyleSheet(''' 206 | QMenu { 207 | background-color:rgb(89,87,87); /*整个背景*/ 208 | border: 3px solid #CD5C5C;/*整个菜单边缘*/ 209 | } 210 | QMenu::item { 211 | font-size: 7pt; 212 | color: rgb(225,225,225); /*字体颜色*/ 213 | border: 1px solid rgb(60,60,60); /*item选框*/ 214 | background-color:rgb(89,87,87); 215 | padding: 10px 9px; /*设置菜单项文字上下和左右的内边距,效果就是菜单中的条目左右上下有了间隔*/ 216 | margin:1px 1px;/*设置菜单项的外边距*/ 217 | } 218 | QMenu::item:selected { 219 | background-color:#CD5C5C;/*选中的样式*/ 220 | } 221 | QMenu::item:pressed {/*菜单项按下效果*/ 222 | border: 1px solid rgb(60,60,61); 223 | background-color: #A52A2A; 224 | }\ 225 | ''') 226 | self.menuBar.setDefaultUp(False) 227 | 228 | self.menu_F = QMenu(self.menuBar) 229 | self.menu_F.setTitle("文件") 230 | 231 | 232 | self.capture_menu = QMenu(self.menuBar) 233 | self.capture_menu.setTitle("捕获") 234 | 235 | self.menu_H = QMenu(self.menuBar) 236 | self.menu_H.setTitle("帮助") 237 | 238 | self.menu_Analysis = QMenu(self.menuBar) 239 | self.menu_Analysis.setTitle("分析") 240 | 241 | self.menu_Statistic = QMenu(self.menuBar) 242 | self.menu_Statistic.setTitle("统计") 243 | self.setMenuBar(self.menuBar) 244 | 245 | #顶部工具栏 246 | self.mainToolBar = QToolBar(self) 247 | self.addToolBar(Qt.TopToolBarArea, self.mainToolBar) 248 | self.statusBar = QStatusBar(self) 249 | self.mainToolBar.setStyleSheet("background: #f6ecb6;") 250 | self.mainToolBar.setFixedHeight(38) 251 | self.setStatusBar(self.statusBar) 252 | 253 | #开始键 254 | self.start_action = QAction(self) 255 | icon2 = QIcon() 256 | icon2.addPixmap(QPixmap("img/start.png"), QIcon.Normal, QIcon.Off) 257 | self.start_action.setIcon(icon2) 258 | self.start_action.setText("开始") 259 | self.start_action.setShortcut('F1') 260 | self.start_action.triggered.connect(self.on_start_action_clicked) 261 | 262 | #停止键 263 | self.stop_action = QAction(self) 264 | icon3 = QIcon() 265 | icon3.addPixmap(QPixmap("img/stop.png"), QIcon.Normal, QIcon.Off) 266 | self.stop_action.setIcon(icon3) 267 | self.stop_action.setText("停止") 268 | self.stop_action.setShortcut('F3') 269 | self.stop_action.setDisabled(True) #开始时该按钮不可点击 270 | self.stop_action.triggered.connect(self.on_stop_action_clicked) 271 | 272 | #暂停键 273 | self.pause_action = QAction(self) 274 | p_icon = QIcon() 275 | p_icon.addPixmap(QPixmap("img/terminal.png"), QIcon.Normal, QIcon.Off) 276 | self.pause_action.setIcon(p_icon) 277 | self.pause_action.setText("暂停") 278 | self.pause_action.setShortcut('F2') 279 | self.pause_action.setDisabled(True) # 开始时该按钮不可点击 280 | self.pause_action.triggered.connect(self.on_pause_action_clicked) 281 | 282 | #重新开始键 283 | self.actionRestart = QAction(self) 284 | icon4 = QIcon() 285 | icon4.addPixmap(QPixmap("img/restart.png"), QIcon.Normal, QIcon.Off) 286 | self.actionRestart.setIcon(icon4) 287 | self.actionRestart.setText("重新开始") 288 | self.actionRestart.setShortcut('F4') 289 | self.actionRestart.setDisabled(True) # 开始时该按钮不可点击 290 | self.actionRestart.triggered.connect(self.on_actionRestart_clicked) 291 | 292 | 293 | #帮助文档 294 | # action_readme = QAction(self) 295 | # action_readme.setText("使用文档") 296 | action_about = QAction(self) 297 | icon5 = QIcon() 298 | icon5.addPixmap(QPixmap("img/about.png"), QIcon.Normal, QIcon.Off) 299 | action_about.setIcon(icon5) 300 | action_about.setText("关于") 301 | action_about.setShortcut("F8") 302 | action_about.triggered.connect(self.on_action_about_clicked) 303 | 304 | #打开文件键 305 | action_openfile = QAction(self) 306 | icon6 = QIcon() 307 | icon6.addPixmap(QPixmap("img/openfile.png"), QIcon.Normal, QIcon.Off) 308 | action_openfile.setIcon(icon6) 309 | action_openfile.setText("打开") 310 | action_openfile.setShortcut("ctrl+O") 311 | action_openfile.triggered.connect(self.on_action_openfile_clicked) 312 | 313 | #保存文件键 314 | action_savefile = QAction(self) 315 | icon7 = QIcon() 316 | icon7.addPixmap(QPixmap("img/save.png"), QIcon.Normal, QIcon.Off) 317 | action_savefile.setIcon(icon7) 318 | action_savefile.setText("保存") 319 | action_savefile.setShortcut("ctrl+S") 320 | action_savefile.triggered.connect(self.on_action_savefile_clicked) 321 | 322 | 323 | 324 | #流量监测 325 | self.action_track = QAction(self) 326 | self.action_track.setText("流量监测") 327 | self.action_track.setShortcut('F5') 328 | self.action_track.triggered.connect(self.on_action_track_clicked) 329 | 330 | #IP地址类型统计图 331 | self.IP_statistics = QAction(self) 332 | iconip = QIcon() 333 | iconip.addPixmap(QPixmap("img/ip.png"), QIcon.Normal, QIcon.Off) 334 | self.IP_statistics.setIcon(iconip) 335 | self.IP_statistics.setText("IP地址类型统计") 336 | self.IP_statistics.setShortcut('F6') 337 | self.IP_statistics.triggered.connect(self.on_IP_statistics_clicked) 338 | 339 | #报文类型统计图 340 | self.message_statistics = QAction(self) 341 | iconpkg = QIcon() 342 | iconpkg.addPixmap(QPixmap("img/pkg.png"), QIcon.Normal, QIcon.Off) 343 | self.message_statistics.setIcon(iconpkg) 344 | self.message_statistics.setText("报文类型统计") 345 | self.message_statistics.setShortcut('F7') 346 | self.message_statistics.triggered.connect( 347 | self.on_message_statistics_clicked) 348 | """ 349 | 添加工具栏:开始,暂停,停止,重新开始 350 | """ 351 | self.mainToolBar.addAction(self.start_action) 352 | self.mainToolBar.addAction(self.pause_action) 353 | self.mainToolBar.addAction(self.stop_action) 354 | self.mainToolBar.addAction(self.actionRestart) 355 | 356 | self.menu_F.addAction(action_openfile) 357 | self.menu_F.addAction(action_savefile) 358 | self.menu_F.showFullScreen() 359 | 360 | #捕获菜单栏添加子菜单 361 | self.capture_menu.addAction(self.start_action) 362 | self.capture_menu.addAction(self.pause_action) 363 | self.capture_menu.addAction(self.stop_action) 364 | self.capture_menu.addAction(self.actionRestart) 365 | 366 | #self.menu_H.addAction(action_readme) 367 | self.menu_H.addAction(action_about) 368 | 369 | #self.menu_Analysis.addAction(self.forged_action) 370 | self.menu_Analysis.addAction(self.action_track) 371 | 372 | self.menu_Statistic.addAction(self.IP_statistics) 373 | self.menu_Statistic.addAction(self.message_statistics) 374 | 375 | self.menuBar.addAction(self.menu_F.menuAction()) 376 | # self.menuBar.addAction(self.edit_menu.menuAction()) 377 | self.menuBar.addAction(self.capture_menu.menuAction()) 378 | self.menuBar.addAction(self.menu_Analysis.menuAction()) 379 | self.menuBar.addAction(self.menu_Statistic.menuAction()) 380 | self.menuBar.addAction(self.menu_H.menuAction()) 381 | 382 | # self.statusBar.showMessage('实时更新的信息', 0) # 状态栏本身显示的信息 第二个参数是信息停留的时间,单位是毫秒,默认是0(0表示在下一个操作来临前一直显示) 383 | """底部状态栏 384 | 利用self.comNum.setText()实时更新状态栏信息 385 | """ 386 | self.comNum = QLabel('下载速度:') 387 | self.baudNum = QLabel('上传速度:') 388 | self.getSpeed = QLabel('收包速度:') 389 | self.sendSpeed = QLabel('发包速度:') 390 | self.netNic = QLabel('From 杨老师的抓包小队') 391 | self.statusBar.setStyleSheet("background: #EDEDED;") 392 | """各个单元空间占比""" 393 | self.statusBar.addPermanentWidget(self.netNic, stretch=2) 394 | self.statusBar.addPermanentWidget(self.getSpeed, stretch=1) 395 | self.statusBar.addPermanentWidget(self.sendSpeed, stretch=1) 396 | self.statusBar.addPermanentWidget(self.comNum, stretch=1) 397 | self.statusBar.addPermanentWidget(self.baudNum, stretch=1) 398 | 399 | QMetaObject.connectSlotsByName(self) 400 | self.core = Core(self) 401 | # 设置定时器将抓包列表置底 402 | self.timer = QTimer(self) 403 | self.timer.timeout.connect(self.info_tree.scrollToBottom) 404 | self.show() 405 | 406 | """ 407 | 重写窗口关闭事件 408 | """ 409 | 410 | def closeEvent(self, QCloseEvent): 411 | def close_to_do(): 412 | self.core.clean_out() 413 | if self.Monitor and self.Monitor.is_alive(): 414 | self.Monitor.terminate() 415 | if self.Forged and self.Forged.is_alive(): 416 | self.Forged.terminate() 417 | exit() 418 | 419 | if self.core.start_flag or self.core.pause_flag: 420 | # 没有停止抓包 421 | reply = QMessageBox.question( 422 | self, 'Message', "您是否要停止捕获,并保存已捕获的分组?\n警告:若不保存,您捕获的分组将会丢失", 423 | QMessageBox.Save | QMessageBox.Close | QMessageBox.Cancel, 424 | QMessageBox.Cancel) 425 | if reply == QMessageBox.Cancel: 426 | QCloseEvent.ignore() 427 | if reply == QMessageBox.Close: 428 | self.core.stop_capture() 429 | close_to_do() 430 | elif reply == QMessageBox.Save: 431 | self.core.stop_capture() 432 | self.on_action_savefile_clicked() 433 | close_to_do() 434 | elif self.core.stop_flag and not self.core.save_flag: 435 | """ 436 | 已停止,但没有保存文件 437 | """ 438 | reply = QMessageBox.question( 439 | self, 'Message', "您是否保存已捕获的分组?\n警告:若不保存,您捕获的分组将会丢失", 440 | QMessageBox.Save | QMessageBox.Close | QMessageBox.Cancel, 441 | QMessageBox.Cancel) 442 | if reply == QMessageBox.Cancel: 443 | QCloseEvent.ignore() 444 | elif reply == QMessageBox.Save: 445 | self.on_action_savefile_clicked() 446 | close_to_do() 447 | else: 448 | close_to_do() 449 | elif self.core.save_flag or not self.core.start_flag: 450 | """ 451 | 未工作状态 452 | """ 453 | reply = QMessageBox.question(self, 'Message', "您是否要退出本程序?", 454 | QMessageBox.Yes | QMessageBox.No, 455 | QMessageBox.No) 456 | if reply == QMessageBox.Yes: 457 | close_to_do() 458 | else: 459 | QCloseEvent.ignore() 460 | 461 | 462 | """ 463 | 数据包视图 数据记录点击事件 464 | 点击列表中一条记录时,在下面的frame框中显示帧的详细信息 465 | """ 466 | 467 | def on_tableview_clicked(self): 468 | selected_row = self.info_tree.currentItem().text(0) #当前选择的编号 469 | #表格停止追踪更新 470 | if selected_row and selected_row.isdigit(): 471 | self.timer.stop() 472 | self.show_infoTree((int)(selected_row)) 473 | 474 | """ 475 | 展开帧的详细信息 476 | """ 477 | 478 | def show_infoTree(self, selected_row): 479 | """ 480 | 清空Frame Information内容 481 | """ 482 | self.treeWidget.clear() 483 | """ 484 | 添加树节点 485 | Item1: 第一层树节点 486 | Item1_1: 第二层树节点,Item1的子节点 487 | QTreeWidgetItem(parentNode, text) parentNode:父节点 text:当前节点内容 488 | """ 489 | parentList, childList, hex_dump = self.core.on_click_item(selected_row) 490 | p_num = len(parentList) 491 | for i in range(p_num): 492 | item1 = QTreeWidgetItem(self.treeWidget) 493 | item1.setText(0, parentList[i]) 494 | c_num = len(childList[i]) 495 | for j in range(c_num): 496 | item1_1 = QTreeWidgetItem(item1) 497 | item1_1.setText(0, childList[i][j]) 498 | self.set_hex_text(hex_dump) 499 | 500 | """ 501 | 获取当前选择的网卡 502 | """ 503 | 504 | def get_choose_nic(self): 505 | card = self.choose_nicbox.currentText() 506 | self.netNic.setText('当前网卡:' + card) 507 | if (card == 'All'): 508 | a = None 509 | elif platform == 'Windows': 510 | a = netcards[card] 511 | elif platform == 'Linux': 512 | a = card 513 | else: 514 | a = None 515 | return a 516 | 517 | """ 518 | 设置hex区文本 519 | """ 520 | 521 | def set_hex_text(self, text): 522 | self.hexBrowser.setText(text) 523 | 524 | """ 525 | 开始键点击事件 526 | """ 527 | 528 | def on_start_action_clicked(self): 529 | if self.core.stop_flag: 530 | # 重新开始清空面板内容 531 | self.info_tree.clear() 532 | self.treeWidget.clear() 533 | self.set_hex_text("") 534 | if self.Filter.currentText() != "": 535 | insertflag = True 536 | currentT = self.Filter.currentText() 537 | for i in range(0,self.Filter.count()): 538 | if currentT == self.Filter.itemText(i): 539 | insertflag = False 540 | if insertflag: 541 | self.Filter.insertItem(0,currentT) 542 | self.core.start_capture(self.get_choose_nic(), self.Filter.currentText()) 543 | """ 544 | 点击开始后,过滤器不可编辑,开始按钮、网卡选择框全部设为不可选 545 | 激活暂停、停止键、重新开始键 546 | """ 547 | self.start_action.setDisabled(True) 548 | self.Filter.setEnabled(False) 549 | self.FilterButton.setEnabled(False) 550 | self.choose_nicbox.setEnabled(False) 551 | self.actionRestart.setDisabled(False) 552 | self.pause_action.setEnabled(True) 553 | self.stop_action.setEnabled(True) 554 | self.timer.start(flush_time) 555 | 556 | """ 557 | 暂停事件点击事件 558 | """ 559 | 560 | def on_pause_action_clicked(self): 561 | self.core.pause_capture() 562 | """ 563 | 激活开始、停止、重新开始键、过滤器、网卡选择框 564 | """ 565 | self.start_action.setEnabled(True) 566 | self.stop_action.setDisabled(False) 567 | self.actionRestart.setDisabled(False) 568 | self.Filter.setDisabled(True) 569 | self.FilterButton.setDisabled(True) 570 | self.choose_nicbox.setDisabled(False) 571 | self.pause_action.setDisabled(True) 572 | self.timer.stop() 573 | 574 | """ 575 | 菜单栏停止键点击事件 576 | """ 577 | 578 | def on_stop_action_clicked(self): 579 | self.core.stop_capture() 580 | """ 581 | 激活开始键、重新开始键、过滤器、网卡选择框 582 | """ 583 | self.stop_action.setDisabled(True) 584 | self.pause_action.setDisabled(True) 585 | self.start_action.setEnabled(True) 586 | self.Filter.setDisabled(False) 587 | self.FilterButton.setDisabled(False) 588 | self.choose_nicbox.setDisabled(False) 589 | self.timer.stop() 590 | 591 | """ 592 | 重新开始键响应事件 593 | """ 594 | 595 | def on_actionRestart_clicked(self): 596 | # 重新开始清空面板内容 597 | self.timer.stop() 598 | self.core.restart_capture(self.get_choose_nic(), self.Filter.currentText()) 599 | self.info_tree.clear() 600 | self.treeWidget.clear() 601 | self.set_hex_text("") 602 | """ 603 | 点击开始后,过滤器不可编辑,开始按钮,网卡选择框全部设为不可选 604 | 激活暂停、停止键、重新开始键 605 | """ 606 | self.actionRestart.setDisabled(False) 607 | self.start_action.setDisabled(True) 608 | self.Filter.setEnabled(False) 609 | self.FilterButton.setEnabled(False) 610 | self.choose_nicbox.setEnabled(False) 611 | self.pause_action.setEnabled(True) 612 | self.stop_action.setEnabled(True) 613 | self.timer.start(flush_time) 614 | 615 | """ 616 | IP地址类型统计图绘制 617 | """ 618 | 619 | def on_IP_statistics_clicked(self): 620 | IP = self.core.get_network_count() 621 | IPv4_count = IP["ipv4"] 622 | IPv6_count = IP["ipv6"] 623 | IP_count = IPv4_count + IPv6_count 624 | if IP_count == 0: 625 | reply = QMessageBox.information(self, "提示", "你还没有抓包!", 626 | QMessageBox.Cancel) 627 | 628 | else: 629 | IPv4_fre = IPv4_count / IP_count 630 | IPv6_fre = IPv6_count / IP_count 631 | data = { 632 | 'IPv4': (IPv4_fre, '#7199cf'), 633 | 'IPv6': (IPv6_fre, '#4fc4aa'), 634 | } 635 | 636 | fig = plt.figure(figsize=(6, 4)) 637 | 638 | # 创建绘图区域 639 | ax1 = fig.add_subplot(111) 640 | ax1.set_title('IPv4 & IPv6 Statistical Chart') 641 | 642 | # 生成x轴的每个元素的位置,列表是[1,2,3,4] 643 | xticks = np.arange(1, 3) 644 | 645 | # 自定义柱状图的每个柱的宽度 646 | bar_width = 0.6 647 | 648 | IP_type = data.keys() 649 | values = [x[0] for x in data.values()] 650 | colors = [x[1] for x in data.values()] 651 | 652 | # 画柱状图,设置柱的边缘为透明 653 | bars = ax1.bar(xticks, values, width=bar_width, edgecolor='none') 654 | 655 | # 设置y轴的标签 656 | ax1.set_ylabel('Proportion') 657 | 658 | ax1.set_xticks(xticks) 659 | ax1.set_xticklabels(IP_type) 660 | 661 | # 设置x,y轴的范围 662 | ax1.set_xlim([0, 3.5]) 663 | ax1.set_ylim([0, 1]) 664 | 665 | # 给每一个bar分配颜色 666 | for bar, color in zip(bars, colors): 667 | bar.set_color(color) 668 | plt.show() 669 | 670 | """ 671 | 报文类型数量统计 672 | """ 673 | 674 | def on_message_statistics_clicked(self): 675 | trans = self.core.get_transport_count() 676 | 677 | TCP_count = trans["tcp"] 678 | UDP_count = trans["udp"] 679 | ARP_count = trans["arp"] 680 | ICMP_count = trans["icmp"] 681 | 682 | if TCP_count + UDP_count + ARP_count + ICMP_count == 0: 683 | reply = QMessageBox.information(self, "提示", "你还没有抓包!", 684 | QMessageBox.Cancel) 685 | 686 | else: 687 | 688 | labels = 'TCP', 'ICMP', 'UDP', 'ARP' 689 | fracs = [TCP_count, ICMP_count, UDP_count, ARP_count] 690 | explode = [0.1, 0.1, 0.1, 0.1] # 0.1 凸出这部分, 691 | plt.axes( 692 | aspect=1 693 | ) # set this , Figure is round, otherwise it is an ellipse 694 | # autopct ,show percet 695 | plt.pie( 696 | x=fracs, 697 | labels=labels, 698 | explode=explode, 699 | autopct='%3.1f %%', 700 | shadow=True, 701 | labeldistance=1.1, 702 | startangle=90, 703 | pctdistance=0.6) 704 | plt.show() 705 | 706 | """ 707 | 打开文件事件 708 | """ 709 | 710 | def on_action_openfile_clicked(self): 711 | if self.core.start_flag or self.core.pause_flag: 712 | QMessageBox.warning(self, "警告", "请停止当前抓包!") 713 | return 714 | self.core.open_pcap_file() 715 | 716 | """ 717 | 保存文件点击事件 718 | """ 719 | 720 | def on_action_savefile_clicked(self): 721 | if self.core.start_flag or self.core.pause_flag: 722 | QMessageBox.warning(self, "警告", "请停止当前抓包!") 723 | return 724 | self.core.save_captured_to_pcap() 725 | 726 | """ 727 | 菜单栏追踪流键点击事件 728 | """ 729 | 730 | def on_action_track_clicked(self): 731 | if not self.Monitor or not self.Monitor.is_alive(): 732 | self.Monitor = Process(target=start_monitor) 733 | self.Monitor.start() 734 | 735 | '' 736 | 737 | about = "安全系统设计 " + "软件主要功能如下:\n" + "1. 侦听指定网卡或所有网卡,抓取流经网卡的数据包;\n" + "2. 解析捕获的数据包每层的每个字段,查看数据包的详细内容;\n" + "3. 可通过不同的需求设置了BPF过滤器,获取指定地址、端口或协议等相关条件的报文;\n" + "4. 针对应用进行流量监测,监测结果实时在流量图显示,并可设置流量预警线,当流量超过预警线时自动报警;\n" + "5. 提供了以饼状图的形式统计ARP、TCP、UDP、ICMP报文,以柱状图的形式统计IPv4、IPv6报文;\n" + "6. 可将抓取到的数据包另存为pcap文件,并能通过打开一个pcap文件对其中的数据包进行解析;\n\n" 738 | 739 | def on_action_about_clicked(self): 740 | QMessageBox.information(self, "关于", self.about) 741 | 742 | """ 743 | 退出点击事件 744 | """ 745 | 746 | def on_action_exit_clicked(self, event): 747 | self.closeEvent(event) 748 | 749 | """ 750 | 进度加载框(没用到) 751 | num: 加载数据数量 752 | """ 753 | 754 | def showDialog(self, num): 755 | progress = QProgressDialog(self) 756 | progress.setWindowTitle("请稍等") 757 | progress.setLabelText("正在加载数据...") 758 | progress.setCancelButtonText("取消") 759 | progress.setMinimumDuration(1) #进度条加载时间 760 | progress.setWindowModality(Qt.WindowModal) 761 | progress.setRange(0, num) 762 | for i in range(num): 763 | progress.setValue(i) 764 | if progress.wasCanceled(): 765 | QMessageBox.warning(self, "提示", "操作失败") 766 | break 767 | progress.setValue(num) 768 | QMessageBox.information(self, "提示", "操作成功") 769 | 770 | 771 | def start(): 772 | app = QApplication([]) 773 | ui = Ui_MainWindow() 774 | ui.setupUi() 775 | app.exec() 776 | -------------------------------------------------------------------------------- /capture_core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 抓包核心 """ 3 | import os 4 | import shutil 5 | from tempfile import NamedTemporaryFile 6 | from threading import Event, Thread 7 | from PyQt5.QtWidgets import QFileDialog, QMessageBox, QTreeWidgetItem 8 | from PyQt5.QtGui import QColor, QBrush 9 | from PyQt5.Qt import Qt 10 | 11 | from scapy.layers.inet import * 12 | from scapy.layers.inet6 import * 13 | from scapy.layers.l2 import Ether 14 | from scapy.sendrecv import sniff 15 | from scapy.utils import * 16 | from tools import * 17 | 18 | platform, netcards = get_nic_list() 19 | flush_time = 2000 20 | if platform == 'Windows': 21 | keys = list(netcards.keys()) 22 | elif platform == 'Linux': 23 | keys = list(netcards) 24 | 25 | # arp字典 26 | arp_dict = { 27 | 1: "who-has", 28 | 2: "is-at", 29 | 3: "RARP-req", 30 | 4: "RARP-rep", 31 | 5: "Dyn-RARP-req", 32 | 6: "Dyn-RAR-rep", 33 | 7: "Dyn-RARP-err", 34 | 8: "InARP-req", 35 | 9: "InARP-rep" 36 | } 37 | # icmpv6 code字典 38 | icmpv6_code = { 39 | 1: { 40 | 0: "No route to destination", 41 | 1: "Communication with destination administratively prohibited", 42 | 2: "Beyond scope of source address", 43 | 3: "Address unreachable", 44 | 4: "Port unreachable" 45 | }, 46 | 3: { 47 | 0: "hop limit exceeded in transit", 48 | 1: "fragment reassembly time exceeded" 49 | }, 50 | 4: { 51 | 0: "erroneous header field encountered", 52 | 1: "unrecognized Next Header type encountered", 53 | 2: "unrecognized IPv6 option encountered" 54 | }, 55 | } 56 | # 端口字典 57 | ports = { 58 | 80: "HTTP", 59 | 1900: "SSDP", 60 | 53: "DNS", 61 | 123: "NTP", 62 | 21: "FTP", 63 | 20: "FTP_Data", 64 | 22: "SSH" 65 | } 66 | 67 | # HTTPS解析 68 | content_type = { 69 | '14': "Change Cipher Spec", 70 | '15': "Alert Message", 71 | '16': "Handshake Protocol", 72 | '17': "Application Data" 73 | } 74 | version = {'00': "SSLv3", '01': "TLSv1.0", '02': "TLSv1.1", '03': "TLSv1.2"} 75 | 76 | # 停止抓包的线程 77 | stop_capturing_thread = Event() 78 | 79 | # 数据包背景颜色字典 80 | color_dict = { 81 | "TCP": "#e7e6ff", 82 | "TCPv6": "#e7e6ff", 83 | "UDP": "#daeeff", 84 | "UDPv6": "#daeeff", 85 | "ARP": "#faf0d7", 86 | "SSDP": "#ffe3e5", 87 | "SSDPv6": "#ffe3e5", 88 | "HTTP": "#caffbe", 89 | "HTTPv6": "#caffbe", 90 | "SSLv3": "#FFFFCC", 91 | "TLSv1.0": "#FFFFCC", 92 | "TLSv1.1": "#c797ff", 93 | "TLSv1.2": "#bfbdff", 94 | "ICMP": "#fce0ff", 95 | "ICMPv6": "#fce0ff", 96 | "NTP": "#daeeff", 97 | "NTPv6": "#daeeff", 98 | "DNS": "#CCFF99", 99 | "DNSv6": "#CCFF99" 100 | } 101 | 102 | 103 | class Core(): 104 | """ 抓包后台类 """ 105 | # 抓到的包编号从1开始 106 | packet_id = 1 107 | # 开始标志 108 | start_flag = False 109 | # 暂停标志 110 | pause_flag = False 111 | # 停止标志 112 | stop_flag = False 113 | # 保存标志 114 | save_flag = False 115 | # 窗口 116 | main_window = None 117 | # 开始时间戳 118 | start_timestamp = 0.0 119 | # 临时文件路径 120 | temp_file = None 121 | # 计数器 122 | counter = {"ipv4": 0, "ipv6": 0, "tcp": 0, "udp": 0, "icmp": 0, "arp": 0} 123 | 124 | def __init__(self, mainwindow): 125 | """ 126 | 初始化, 若不设置netcard则为捕捉所有网卡的数据包 127 | :parma mainwindow: 传入主窗口 128 | """ 129 | self.main_window = mainwindow 130 | temp = NamedTemporaryFile( 131 | suffix=".pcap", prefix=str(int(time.time())), delete=False) 132 | self.temp_file = temp.name 133 | temp.close() 134 | 135 | def process_packet(self, packet, writer): 136 | """ 137 | 处理抓到的数据包 138 | :parma packet: 需要处理分类的包 139 | """ 140 | try: 141 | # 如果暂停,则不对列表进行更新操作 142 | if not self.pause_flag and packet.name == "Ethernet": 143 | protocol = None 144 | if self.packet_id == 1: 145 | self.start_timestamp = packet.time 146 | packet_time = packet.time - self.start_timestamp 147 | # 第二层 148 | ether_type = packet.payload.name 149 | version_add = "" 150 | # IPv4 151 | if ether_type == "IP": 152 | source = packet[IP].src 153 | destination = packet[IP].dst 154 | self.counter["ipv4"] += 1 155 | # IPv6 156 | elif ether_type == "IPv6": 157 | source = packet[IPv6].src 158 | destination = packet[IPv6].dst 159 | version_add = "v6" 160 | self.counter["ipv6"] += 1 161 | # ARP 162 | elif ether_type == "ARP": 163 | self.counter["arp"] += 1 164 | protocol = ether_type 165 | source = packet[Ether].src 166 | destination = packet[Ether].dst 167 | if destination == "ff:ff:ff:ff:ff:ff": 168 | destination = "Broadcast" 169 | else: 170 | # 其他协议不处理 171 | return 172 | if ether_type != "ARP": 173 | protocol = packet.payload.payload.name 174 | sport = None 175 | dport = None 176 | if protocol == "TCP": 177 | sport = packet[TCP].sport 178 | dport = packet[TCP].dport 179 | protocol += version_add 180 | self.counter["tcp"] += 1 181 | elif protocol == "UDP": 182 | sport = packet[UDP].sport 183 | dport = packet[UDP].dport 184 | protocol += version_add 185 | self.counter["udp"] += 1 186 | elif len(protocol) >= 4 and protocol[0:4] == "ICMP": 187 | protocol = "ICMP" 188 | protocol += version_add 189 | self.counter["icmp"] += 1 190 | else: 191 | return 192 | if sport and dport: 193 | # HTTPS 194 | if sport == 443 or dport == 443: 195 | https = packet.payload.payload.payload.__bytes__( 196 | ).hex() 197 | if len(https) >= 10 and https[2:4] == '03': 198 | if https[0:2] in content_type and https[ 199 | 4:6] in version: 200 | protocol = version[https[4:6]] 201 | elif sport in ports: 202 | protocol = ports[sport] + version_add 203 | elif dport in ports: 204 | protocol = ports[dport] + version_add 205 | item = QTreeWidgetItem(self.main_window.info_tree) #数据传输 206 | # 根据协议类型不同设置颜色 207 | color = color_dict[protocol] 208 | for i in range(7): 209 | item.setBackground(i, QBrush(QColor(color))) 210 | # 添加行内容 211 | item.setData(0, Qt.DisplayRole, self.packet_id) 212 | item.setText(1, "%12.6f " % packet_time) 213 | item.setText(2, source) 214 | item.setText(3, destination) 215 | item.setText(4, protocol) 216 | item.setData(5, Qt.DisplayRole, len(packet)) 217 | item.setText(6, packet.summary()) 218 | # 设置右对齐,为了格式化后不影响排序 219 | item.setTextAlignment(1, Qt.AlignRight) 220 | self.packet_id += 1 221 | if writer: 222 | writer.write(packet) 223 | except: 224 | pass 225 | 226 | def on_click_item(self, this_id): 227 | """ 228 | 处理点击列表中的项 229 | :parma this_id: 包对应的packet_id,在packet_list里获取该packet 230 | """ 231 | try: 232 | if not this_id or this_id < 1: 233 | return 234 | previous_packet_time, packet = self.read_packet(this_id - 1) 235 | # 详细信息列表, 用于添加进GUI 236 | first_return = [] 237 | second_return = [] 238 | # 第一层: Frame 239 | first_layer = [] 240 | # on wire的长度 241 | packet_wirelen = "%d bytes (%d bits)" % (packet.wirelen, 242 | packet.wirelen << 3) 243 | # 实际抓到的长度 244 | packet_capturedlen = "%d bytes (%d bits)" % (len(packet), 245 | len(packet) << 3) 246 | frame = "Frame %d: %s on wire, %s captured" % ( 247 | this_id, packet_wirelen, packet_capturedlen) 248 | first_return.append(frame) 249 | # 抓包的时间 250 | first_layer.append( 251 | "Arrival Time: %s" % time_to_formal(packet.time)) 252 | first_layer.append("Epoch Time: %f seconds" % packet.time) 253 | delta_time = packet.time - previous_packet_time 254 | first_layer.append( 255 | "[Time delta from previous captured frame: %f seconds]" % 256 | delta_time) 257 | delta_time = packet.time - self.start_timestamp 258 | first_layer.append( 259 | "[Time since first frame: %f seconds]" % delta_time) 260 | first_layer.append("Frame Number: %d" % this_id) 261 | first_layer.append("Frame Length: %s" % packet_wirelen) 262 | first_layer.append("Capture Length: %s" % packet_capturedlen) 263 | # 添加第一层信息到二维列表中 264 | second_return.append(first_layer) 265 | first_temp, second_temp = self.get_next_layer(packet) 266 | first_return += first_temp 267 | second_return += second_temp 268 | # dump=True 将hexdump返回而不是打印 269 | except: 270 | pass 271 | return first_return, second_return, hexdump(packet, dump=True) 272 | 273 | def get_next_layer(self, packet): 274 | """ 275 | 递归处理下一层信息 276 | :parma packet: 处理来自上一层packet的payload 277 | """ 278 | # 第二层: Ethernet 279 | first_return = [] 280 | second_return = [] 281 | next_layer = [] 282 | try: 283 | protocol = packet.name 284 | packet_class = packet.__class__ #获取包的数据 285 | if protocol == "NoPayload": 286 | return first_return, second_return 287 | elif protocol == "Ethernet": 288 | ether_src = packet[packet_class].src 289 | ether_dst = packet[packet_class].dst 290 | if ether_dst == "ff:ff:ff:ff:ff:ff": 291 | ether_dst = "Broadcast (ff:ff:ff:ff:ff:ff)" 292 | ethernet = "Ethernet, Src: %s, Dst: %s" % (ether_src, 293 | ether_dst) 294 | first_return.append(ethernet) 295 | next_layer.append("Source: %s" % ether_src) 296 | next_layer.append("Destination: %s" % ether_dst) 297 | ether_type = packet.payload.name 298 | if ether_type == "IP": 299 | ether_type += "v4" 300 | ether_proto = ("Type: %s (%s)" % 301 | (ether_type, hex(packet[packet_class].type))) 302 | next_layer.append(ether_proto) 303 | # 第三层: 网络层 304 | # IPv4 305 | elif protocol == "IP" or protocol == "IP in ICMP": 306 | protocol += "v4" 307 | ip_src = packet[packet_class].src 308 | ip_dst = packet[packet_class].dst 309 | network = "Internet Protocol Version 4, Src: %s, Dst: %s" % ( 310 | ip_src, ip_dst) 311 | first_return.append(network) 312 | next_layer.append("Version: %d" % packet[packet_class].version) 313 | next_layer.append( 314 | "Header Length: %d bytes (%d)" % 315 | (packet[packet_class].ihl << 2, packet[packet_class].ihl)) 316 | next_layer.append("Differentiated Services Field: %s" % hex( 317 | packet[packet_class].tos)) 318 | next_layer.append( 319 | "Total Length: %d" % packet[packet_class].len) 320 | next_layer.append("Identification: %s (%d)" % (hex( 321 | packet[packet_class].id), packet[packet_class].id)) 322 | next_layer.append( 323 | "Flags: %d (%s)" % (packet[packet_class].flags, 324 | hex(packet[packet_class].flags.value))) 325 | next_layer.append( 326 | "Fragment offset: %d" % packet[packet_class].frag) 327 | next_layer.append( 328 | "Time to live: %d" % packet[packet_class].ttl) 329 | next_protocol = packet.payload.name 330 | if next_protocol == "IP": 331 | next_protocol += "v4" 332 | next_layer.append("Protocol: %s (%d)" % 333 | (next_protocol, packet[packet_class].proto)) 334 | ip_chksum = packet[packet_class].chksum 335 | ip_check = packet_class(raw(packet[packet_class])).chksum 336 | next_layer.append("Header checksum: %s" % hex(ip_chksum)) 337 | next_layer.append("[Header checksum status: " + "Correct]" 338 | if ip_check == ip_chksum else "Incorrect]") 339 | next_layer.append("Source: %s" % ip_src) 340 | next_layer.append("Destination: %s" % ip_dst) 341 | # IPv6 342 | elif protocol == "IPv6" or protocol == "IPv6 in ICMPv6": 343 | ipv6_src = packet[packet_class].src 344 | ipv6_dst = packet[packet_class].dst 345 | network = ("Internet Protocol Version 6, Src: %s, Dst: %s" % 346 | (ipv6_src, ipv6_dst)) 347 | first_return.append(network) 348 | next_layer.append("Version: %d" % packet[packet_class].version) 349 | next_layer.append( 350 | "Traffice Class: %s" % hex(packet[packet_class].tc)) 351 | next_layer.append( 352 | "Flow Label: %s" % hex(packet[packet_class].fl)) 353 | next_layer.append( 354 | "Payload Length: %d" % packet[packet_class].plen) 355 | next_protocol = packet.payload.name 356 | if next_protocol == "IP": 357 | next_protocol += "v4" 358 | next_layer.append("Next Header: %s (%d)" % 359 | (next_protocol, packet[packet_class].nh)) 360 | next_layer.append("Hop Limit: %d" % packet[packet_class].hlim) 361 | next_layer.append("Source: %s" % ipv6_src) 362 | next_layer.append("Destination: %s" % ipv6_dst) 363 | elif protocol == "ARP": 364 | arp_op = packet[packet_class].op 365 | network = "Address Resolution Protocol " 366 | if arp_op in arp_dict: 367 | network += "(%s)" % arp_dict[arp_op] 368 | first_return.append(network) 369 | next_layer.append( 370 | "Hardware type: %d" % packet[packet_class].hwtype) 371 | ptype = packet[packet_class].ptype 372 | temp_str = "Protocol type: %s" % hex( 373 | packet[packet_class].ptype) 374 | if ptype == 0x0800: 375 | temp_str += " (IPv4)" 376 | elif ptype == 0x86DD: 377 | temp_str += " (IPv6)" 378 | next_layer.append(temp_str) 379 | next_layer.append( 380 | "Hardware size: %d" % packet[packet_class].hwlen) 381 | next_layer.append( 382 | "Protocol size: %d" % packet[packet_class].plen) 383 | temp_str = "Opcode: %d" % arp_op 384 | if arp_op in arp_dict: 385 | temp_str += " (%s)" % arp_dict[arp_op] 386 | next_layer.append(temp_str) 387 | next_layer.append( 388 | "Sender MAC address: %s" % packet[packet_class].hwsrc) 389 | next_layer.append( 390 | "Sender IP address: %s" % packet[packet_class].psrc) 391 | next_layer.append( 392 | "Target MAC address: %s" % packet[packet_class].hwdst) 393 | next_layer.append( 394 | "Target IP address: %s" % packet[packet_class].pdst) 395 | # 第四层: 传输层 396 | elif protocol == "TCP" or protocol == "TCP in ICMP": 397 | src_port = packet[packet_class].sport 398 | dst_port = packet[packet_class].dport 399 | transport = ( 400 | "Transmission Control Protocol, Src Port: %d, Dst Port: %d" 401 | % (src_port, dst_port)) 402 | first_return.append(transport) 403 | next_layer.append("Source Port: %d" % src_port) 404 | next_layer.append("Destination Port: %d" % dst_port) 405 | next_layer.append( 406 | "Sequence number: %d" % packet[packet_class].seq) 407 | next_layer.append( 408 | "Acknowledgment number: %d" % packet[packet_class].ack) 409 | tcp_head_length = packet[packet_class].dataofs 410 | next_layer.append("Header Length: %d bytes (%d)" % 411 | (tcp_head_length << 2, tcp_head_length)) 412 | next_layer.append( 413 | "Flags: %s (%d)" % (hex(packet[packet_class].flags.value), 414 | packet[packet_class].flags)) 415 | next_layer.append( 416 | "Window size value: %d" % packet[packet_class].window) 417 | tcp_chksum = packet[packet_class].chksum 418 | tcp_check = packet_class(raw(packet[packet_class])).chksum 419 | next_layer.append("Checksum: %s" % hex(tcp_chksum)) 420 | next_layer.append("[Checksum status: " + "Correct]" 421 | if tcp_check == tcp_chksum else "Incorrect]") 422 | next_layer.append( 423 | "Urgent pointer: %d" % packet[packet_class].urgptr) 424 | options = packet[packet_class].options 425 | options_length = len(options) << 2 426 | if options_length > 0: 427 | string = "Options: (%d bytes)" % options_length 428 | for item in options: 429 | string += ", %s: %s" % (item[0], str(item[1])) 430 | next_layer.append(string) 431 | payload_length = len(packet.payload) 432 | if payload_length > 0: 433 | next_layer.append("TCP payload: %d bytes" % payload_length) 434 | elif protocol == "UDP" or protocol == "UDP in ICMP": 435 | src_port = packet[packet_class].sport 436 | dst_port = packet[packet_class].dport 437 | length = packet[packet_class].len 438 | transport = ( 439 | "User Datagram Protocol, Src Port: %d, Dst Port: %d" % 440 | (src_port, dst_port)) 441 | first_return.append(transport) 442 | next_layer.append("Source Port: %d" % src_port) 443 | next_layer.append("Destination Port: %d" % dst_port) 444 | next_layer.append("Length: %d" % length) 445 | udp_chksum = packet[packet_class].chksum 446 | udp_check = packet_class(raw(packet[packet_class])).chksum 447 | next_layer.append("Chksum: %s" % hex(udp_chksum)) 448 | next_layer.append("[Checksum status: " + "Correct]" 449 | if udp_check == udp_chksum else "Incorrect]") 450 | length = len(packet[packet_class].payload) 451 | # Have payload 452 | if length > 0: 453 | second_return.append(next_layer.copy()) 454 | next_layer.clear() 455 | payload = bytes(packet[packet_class].payload) 456 | # SSDP 457 | if src_port == 1900 or dst_port == 1900: 458 | first_return.append( 459 | "Simple Service Discovery Protocol") 460 | payload = bytes.decode(payload).split('\r\n') 461 | for string in payload: 462 | if string: 463 | next_layer.append(string) 464 | # Raw 465 | else: 466 | first_return.append("Data (%d bytes)" % length) 467 | next_layer.append("Data: %s" % payload.hex()) 468 | next_layer.append("[Length: %d]" % length) 469 | elif protocol == "ICMP" or protocol == "ICMP in ICMP": 470 | transport = "Internet Control Message Protocol" 471 | first_return.append(transport) 472 | packet_type = packet[packet_class].type 473 | temp_str = "Type: %d" % packet_type 474 | if packet_type in icmptypes: 475 | temp_str += " (%s)" % icmptypes[packet_type] 476 | next_layer.append(temp_str) 477 | packet_code = packet[packet_class].code 478 | temp_str = "Code: %d" % packet_code 479 | if packet_type in icmpcodes: 480 | if packet_code in icmpcodes[packet_type]: 481 | temp_str += " (%s)" % icmpcodes[packet_type][ 482 | packet_code] 483 | next_layer.append(temp_str) 484 | icmp_chksum = packet[packet_class].chksum 485 | icmp_check = packet_class(raw(packet[packet_class])).chksum 486 | next_layer.append("Checksum: %s" % hex(icmp_chksum)) 487 | next_layer.append("[Checksum status: " + "Correct]" if 488 | icmp_check == icmp_chksum else "Incorrect]") 489 | if packet_type == 0 or packet_type == 8 or protocol == "ICMP in ICMP": 490 | next_layer.append( 491 | "Identifier: %d (%s)" % (packet[packet_class].id, 492 | hex(packet[packet_class].id))) 493 | next_layer.append("Sequence number: %d (%s)" % 494 | (packet[packet_class].seq, 495 | hex(packet[packet_class].seq))) 496 | data_length = len(packet.payload) 497 | if data_length > 0: 498 | next_layer.append( 499 | "Data (%d bytes): %s" % 500 | (data_length, packet[packet_class].load.hex())) 501 | elif len(protocol) >= 6 and protocol[0:6] == "ICMPv6": 502 | if protocol.lower().find("option") == -1: 503 | transport = "Internet Control Message Protocol v6" 504 | first_return.append(transport) 505 | proto_type = packet[packet_class].type 506 | temp_str = "Type: %d" % proto_type 507 | if proto_type in icmp6types: 508 | temp_str += " (%s)" % icmp6types[proto_type] 509 | next_layer.append(temp_str) 510 | packet_code = packet[packet_class].code 511 | temp_str = "Code: %d" % packet_code 512 | if proto_type in icmpv6_code: 513 | if packet_code in icmpv6_code[proto_type]: 514 | temp_str += " (%s)" % icmpv6_code[proto_type][ 515 | packet_code] 516 | next_layer.append(temp_str) 517 | icmpv6_cksum = packet[packet_class].cksum 518 | icmpv6_check = packet_class(raw( 519 | packet[packet_class])).cksum 520 | next_layer.append("Checksum: %s" % hex(icmpv6_cksum)) 521 | next_layer.append("[Checksum status: " + 522 | "Correct]" if icmpv6_check == 523 | icmpv6_cksum else "Incorrect]") 524 | if proto_type == "Echo Request" or proto_type == "Echo Reply": 525 | next_layer.append("Identifier: %d (%s)" % 526 | (packet[packet_class].id, 527 | hex(packet[packet_class].id))) 528 | next_layer.append("Sequence number: %d (%s)" % 529 | (packet[packet_class].seq, 530 | hex(packet[packet_class].seq))) 531 | data_length = packet[packet_class].plen - 8 532 | if data_length > 0: 533 | next_layer.append( 534 | "Data (%d bytes): %s" % 535 | (data_length, packet[packet_class].load.hex())) 536 | elif proto_type == "Neighbor Advertisement": 537 | temp_set = "Set (1)" 538 | temp_not_set = "Not set (0)" 539 | temp_str = "Router: " 540 | if packet[packet_class].R == 1: 541 | temp_str += temp_set 542 | else: 543 | temp_str += temp_not_set 544 | next_layer.append(temp_str) 545 | temp_str = "Solicited: " 546 | if packet[packet_class].S == 1: 547 | temp_str += temp_set 548 | else: 549 | temp_str += temp_not_set 550 | next_layer.append(temp_str) 551 | temp_str = "Override: " 552 | if packet[packet_class].O == 1: 553 | temp_str += temp_set 554 | else: 555 | temp_str += temp_not_set 556 | next_layer.append(temp_str) 557 | next_layer.append( 558 | "Reserved: %d" % packet[packet_class].res) 559 | next_layer.append( 560 | "Target Address: %s" % packet[packet_class].tgt) 561 | elif proto_type == "Neighbor Solicitation": 562 | next_layer.append( 563 | "Reserved: %d" % packet[packet_class].res) 564 | next_layer.append( 565 | "Target Address: %s" % packet[packet_class].tgt) 566 | elif proto_type == "Router Solicitation": 567 | next_layer.append( 568 | "Reserved: %d" % packet[packet_class].res) 569 | elif proto_type == "Router Advertisement": 570 | temp_set = "Set (1)" 571 | temp_not_set = "Not set (0)" 572 | next_layer.append( 573 | "Cur hop limit: %d" % packet[packet_class].chlim) 574 | temp_str = "Managed address configuration: " 575 | if packet[packet_class].M == 1: 576 | temp_str += temp_set 577 | else: 578 | temp_str += temp_not_set 579 | next_layer.append(temp_str) 580 | temp_str = "Other configuration: " 581 | if packet[packet_class].O == 1: 582 | temp_str += temp_set 583 | else: 584 | temp_str += temp_not_set 585 | next_layer.append(temp_str) 586 | temp_str = "Home Agent: " 587 | if packet[packet_class].H == 1: 588 | temp_str += temp_set 589 | else: 590 | temp_str += temp_not_set 591 | next_layer.append(temp_str) 592 | temp_str = "Preference: %d" % packet[packet_class].prf 593 | next_layer.append(temp_str) 594 | temp_str = "Proxy: " 595 | if packet[packet_class].P == 1: 596 | temp_str += temp_set 597 | else: 598 | temp_str += temp_not_set 599 | next_layer.append(temp_str) 600 | next_layer.append( 601 | "Reserved: %d" % packet[packet_class].res) 602 | next_layer.append("Router lifetime (s): %d" % 603 | packet[packet_class].routerlifetime) 604 | next_layer.append("Reachable time (ms): %d" % 605 | packet[packet_class].reachabletime) 606 | next_layer.append("Retrans timer (ms): %d" % 607 | packet[packet_class].retranstimer) 608 | elif proto_type == "Destination Unreachable": 609 | next_layer.append("Length: %d (%s)" % 610 | (packet[packet_class].length, 611 | hex(packet[packet_class].length))) 612 | next_layer.append( 613 | "Unused: %d" % packet[packet_class].unused) 614 | elif proto_type == "Packet too big": 615 | next_layer.append("MTU: %d" % packet[packet_class].mtu) 616 | elif proto_type == "Parameter problem": 617 | next_layer.append("PTR: %d" % packet[packet_class].ptr) 618 | elif proto_type == "Time exceeded": 619 | next_layer.append("Length: %d (%s)" % 620 | (packet[packet_class].length, 621 | hex(packet[packet_class].length))) 622 | next_layer.append( 623 | "Unused: %d" % packet[packet_class].unused) 624 | else: 625 | # ICMPv6 Option 626 | transport = "ICMPv6 Option (" 627 | proto_type = packet[packet_class].type 628 | # Source Link-Layer or Destination Link-Layer 629 | if proto_type == 1 or proto_type == 2: 630 | address = packet[packet_class].lladdr 631 | if proto_type == 1: 632 | transport += "Source Link-Layer Address: %s)" % address 633 | proto_type = "Type: Source Link-Layer Address (1)" 634 | else: 635 | transport += "Destination Link-Layer Address: %s)" % address 636 | proto_type = "Type: Destination Link-Layer Address (2)" 637 | first_return.append(transport) 638 | next_layer.append(proto_type) 639 | length = packet[packet_class].len 640 | next_layer.append( 641 | "Length: %d (%d bytes)" % (length, length << 3)) 642 | next_layer.append("Link-Layer Address: %s" % address) 643 | # Prefix Information 644 | elif proto_type == 3: 645 | packet_prefix = packet[packet_class].prefix 646 | transport += "Prefix Information: %s)" % packet_prefix 647 | proto_type = "Type: Prefix Information (3)" 648 | first_return.append(transport) 649 | next_layer.append(proto_type) 650 | length = packet[packet_class].len 651 | next_layer.append( 652 | "Length: %d (%d bytes)" % (length, length << 3)) 653 | next_layer.append("Prefix Length: %d" % 654 | packet[packet_class].prefixlen) 655 | set_str = "Set (1)" 656 | not_set_str = "Not set (0)" 657 | next_layer.append("On-link flag (L): %s" % 658 | set_str if packet[packet_class].L == 659 | 1 else not_set_str) 660 | next_layer.append( 661 | "Autonomous address-configuration flag (A): %s" % 662 | set_str if packet[packet_class].A == 663 | 1 else not_set_str) 664 | next_layer.append("Router address flag(R): %s" % 665 | set_str if packet[packet_class].R == 666 | 1 else not_set_str) 667 | next_layer.append("Valid Lifetime: %d" % 668 | packet[packet_class].validlifetime) 669 | next_layer.append( 670 | "Preferred Lifetime: %d" % 671 | packet[packet_class].preferredlifetime) 672 | next_layer.append( 673 | "Reserverd: %d" % packet[packet_class].res2) 674 | next_layer.append("Prefix: %s" % packet_prefix) 675 | # MTU 676 | elif proto_type == 5: 677 | packet_mtu = packet[packet_class].mtu 678 | transport += "MTU: %d)" % packet_mtu 679 | proto_type = "Type: MTU (5)" 680 | first_return.append(transport) 681 | next_layer.append(proto_type) 682 | length = packet[packet_class].len 683 | next_layer.append( 684 | "Length: %d (%d bytes)" % (length, length << 3)) 685 | next_layer.append( 686 | "Reserverd: %d" % packet[packet_class].res) 687 | next_layer.append("MTU: %d" % packet_mtu) 688 | else: 689 | # 不识别,直接返回 690 | return first_return, second_return 691 | # 第五层: 应用层 692 | # TLS 693 | else: 694 | https = packet.__bytes__().hex() 695 | total_length = len(https) 696 | temp_length = 0 697 | while len(https) >= 10: 698 | if https[2:4] == '03' and https[ 699 | 0:2] in content_type and https[4:6] in version: 700 | protocol = version[https[4:6]] 701 | cont_type = content_type[https[0:2]] 702 | first_return.append("%s : %s" % (protocol, cont_type)) 703 | next_layer.append("Content Type: %s (%d)" % 704 | (cont_type, int(https[0:2], 16))) 705 | next_layer.append( 706 | "Version: %s (0x%s)" % (protocol, https[2:6])) 707 | length = int(https[6:10], 16) 708 | next_layer.append("Length: %d" % length) 709 | # 如果有数据 710 | if length > 0: 711 | this_layer_len = 10 + (length << 1) 712 | next_layer.append( 713 | "Data: %s" % https[10:this_layer_len]) 714 | temp_length += this_layer_len 715 | if total_length != temp_length: 716 | https = https[this_layer_len:] 717 | second_return.append(next_layer.copy()) 718 | next_layer.clear() 719 | else: 720 | break 721 | else: 722 | break 723 | if next_layer: 724 | second_return.append(next_layer) 725 | first_temp, second_temp = self.get_next_layer(packet.payload) 726 | first_return += first_temp 727 | second_return += second_temp 728 | except: 729 | # 未知数据包 730 | first_return.clear() 731 | second_return.clear() 732 | return first_return, second_return 733 | 734 | def flow_count(self, netcard=None): 735 | """ 736 | 刷新下载速度、上传速度和收包速度 737 | """ 738 | if netcard and platform == 'Windows': 739 | # 反转键值对 740 | my_dict = dict(zip(netcards.values(), netcards.keys())) 741 | netcard = my_dict[netcard] 742 | while not stop_capturing_thread.is_set(): 743 | recv_bytes, sent_bytes, recv_pak, sent_pak = get_formal_rate( 744 | get_rate(netcard)) 745 | if not self.pause_flag: 746 | self.main_window.comNum.setText('下载速度:' + recv_bytes) 747 | self.main_window.baudNum.setText('上传速度:' + sent_bytes) 748 | self.main_window.getSpeed.setText('收包速度:' + recv_pak) 749 | self.main_window.sendSpeed.setText('发包速度:' + sent_pak) 750 | self.main_window.comNum.setText('下载速度:0 B/s') 751 | self.main_window.baudNum.setText('上传速度:0 B/s') 752 | self.main_window.getSpeed.setText('收包速度:0 pak/s') 753 | self.main_window.sendSpeed.setText('发包速度:0 pak/s') 754 | 755 | def capture_packet(self, netcard, filters): 756 | """ 757 | 抓取数据包 758 | """ 759 | stop_capturing_thread.clear() 760 | # 第一个参数可以传入文件对象或者文件名字 761 | writer = PcapWriter(self.temp_file, append=True, sync=True) 762 | thread = Thread(target=self.flow_count, daemon=True, args=(netcard, )) 763 | thread.start() 764 | # sniff中的store=False 表示不保存在内存中,防止内存使用过高 765 | sniff( 766 | iface=netcard, 767 | prn=(lambda x: self.process_packet(x, writer)), 768 | filter=filters, 769 | stop_filter=(lambda x: stop_capturing_thread.is_set()), 770 | store=False) 771 | # 执行完成关闭writer 772 | writer.close() 773 | 774 | def start_capture(self, netcard=None, filters=None): 775 | """ 776 | 开启新线程进行抓包 777 | :parma netcard: 选择的网卡, "any"为全选 778 | :parma filters: 过滤器条件 779 | """ 780 | # 如果已开始抓包,则不能进行操作 781 | if self.start_flag: 782 | return 783 | # 如果已经停止且未保存数据包,则提示是否保存数据包 784 | if self.stop_flag: 785 | if not self.save_flag and self.packet_id > 1: 786 | resault = QMessageBox.question( 787 | None, 788 | "提示", 789 | "是否保存已抓取的数据包?", 790 | QMessageBox.Yes, 791 | QMessageBox.Cancel, 792 | ) 793 | if resault == QMessageBox.Yes: 794 | self.save_captured_to_pcap() 795 | self.stop_flag = False 796 | self.save_flag = False 797 | self.pause_flag = False 798 | self.packet_id = 1 799 | self.clean_out() 800 | temp = NamedTemporaryFile( 801 | suffix=".pcap", prefix=str(int(time.time())), delete=False) 802 | self.temp_file = temp.name 803 | temp.close() 804 | # 如果从暂停开始 805 | elif self.pause_flag: 806 | # 继续显示抓到的包显示 807 | self.pause_flag = False 808 | self.start_flag = True 809 | return 810 | # 开启新线程进行抓包 811 | thread = Thread( 812 | target=self.capture_packet, 813 | daemon=True, 814 | name="capture_packet", 815 | args=(netcard, filters)) 816 | thread.start() 817 | self.start_flag = True 818 | 819 | def pause_capture(self): 820 | """ 821 | 暂停抓包, 抓包函数仍在进行,只是不更新 822 | """ 823 | self.pause_flag = True 824 | self.start_flag = False 825 | 826 | def stop_capture(self): 827 | """ 828 | 停止抓包,关闭线程 829 | """ 830 | # 通过设置终止线程,停止抓包 831 | stop_capturing_thread.set() 832 | self.stop_flag = True 833 | self.pause_flag = False 834 | self.start_flag = False 835 | 836 | def restart_capture(self, netcard=None, filters=None): 837 | """ 838 | 重新开始抓包 839 | """ 840 | self.stop_capture() 841 | self.start_capture(netcard, filters) 842 | 843 | def save_captured_to_pcap(self): 844 | """ 845 | 将抓到的数据包保存为pcap格式的文件 846 | """ 847 | if self.packet_id == 1: 848 | QMessageBox.warning(None, "警告", "没有可保存的数据包!") 849 | return 850 | # 选择保存名称 851 | filename, _ = QFileDialog.getSaveFileName( 852 | parent=None, 853 | caption="保存文件", 854 | directory=os.getcwd(), 855 | filter="All Files (*);;Pcap Files (*.pcap)", 856 | ) 857 | if filename == "": 858 | QMessageBox.warning(None, "警告", "保存失败!") 859 | return 860 | # 如果没有设置后缀名(保险起见,默认是有后缀的) 861 | if filename.find(".pcap") == -1: 862 | # 默认文件格式为 pcap 863 | filename = filename + ".pcap" 864 | shutil.copy(self.temp_file, filename) 865 | os.chmod(filename, 0o0400 | 0o0200 | 0o0040 | 0o0004) 866 | QMessageBox.information(None, "提示", "保存成功!") 867 | self.save_flag = True 868 | 869 | def open_pcap_file(self): 870 | """ 871 | 打开pcap格式的文件 872 | """ 873 | if self.stop_flag and not self.save_flag: 874 | reply = QMessageBox.question( 875 | None, 876 | "提示", 877 | "是否保存已抓取的数据包?", 878 | QMessageBox.Yes, 879 | QMessageBox.Cancel, 880 | ) 881 | if reply == QMessageBox.Yes: 882 | self.save_captured_to_pcap() 883 | filename, _ = QFileDialog.getOpenFileName( 884 | parent=None, 885 | caption="打开文件", 886 | directory=os.getcwd(), 887 | filter="All Files (*);;Pcap Files (*.pcap)", 888 | ) 889 | if filename == "": 890 | return 891 | self.main_window.info_tree.clear() 892 | self.main_window.treeWidget.clear() 893 | self.main_window.set_hex_text("") 894 | # 如果没有设置后缀名(保险起见,默认是有后缀的) 895 | if filename.find(".pcap") == -1: 896 | # 默认文件格式为 pcap 897 | filename = filename + ".pcap" 898 | self.packet_id = 1 899 | self.main_window.info_tree.setUpdatesEnabled(False) 900 | shutil.copy(filename, self.temp_file) 901 | sniff( 902 | prn=(lambda x: self.process_packet(x, None)), 903 | store=False, 904 | offline=self.temp_file) 905 | self.main_window.info_tree.setUpdatesEnabled(True) 906 | self.stop_flag = True 907 | self.save_flag = True 908 | 909 | def clean_out(self): 910 | ''' 911 | 清除临时文件 912 | ''' 913 | try: 914 | os.remove(self.temp_file) 915 | except PermissionError: 916 | pass 917 | # 将字典中的值初始化为0 918 | self.counter = {}.fromkeys(list(self.counter.keys()), 0) 919 | 920 | def get_transport_count(self): 921 | """ 922 | 获取传输层数据包的数量 923 | """ 924 | the_keys = ['tcp', 'udp', 'icmp', 'arp'] 925 | counter_copy = self.counter.copy() 926 | return_dict = {} 927 | for key, value in counter_copy.items(): 928 | if key in the_keys: 929 | return_dict.update({key: value}) 930 | return return_dict 931 | 932 | def get_network_count(self): 933 | """ 934 | 获取网络层数据包的数量 935 | """ 936 | the_keys = ['ipv4', 'ipv6'] 937 | counter_copy = self.counter.copy() 938 | return_dict = {} 939 | for key, value in counter_copy.items(): 940 | if key in the_keys: 941 | return_dict.update({key: value}) 942 | return return_dict 943 | 944 | def read_packet(self, location): 945 | ''' 946 | 读取硬盘中的pcap数据 947 | :parma location: 数据包位置 948 | :return: 返回参数列表[上一个数据包的时间,数据包] 949 | ''' 950 | # 数据包时间是否为纳秒级 951 | nano = False 952 | # 打开文件 953 | f = open(self.temp_file, "rb") 954 | # 获取Pcap格式 magic 955 | head = f.read(24) 956 | magic = head[:4] 957 | linktype = head[20:] 958 | if magic == b"\xa1\xb2\xc3\xd4": # big endian 959 | endian = ">" 960 | nano = False 961 | elif magic == b"\xd4\xc3\xb2\xa1": # little endian 962 | endian = "<" 963 | nano = False 964 | elif magic == b"\xa1\xb2\x3c\x4d": # big endian, nanosecond-precision 965 | endian = ">" 966 | nano = True 967 | elif magic == b"\x4d\x3c\xb2\xa1": # little endian, nanosecond-precision 968 | endian = "<" 969 | nano = True 970 | else: 971 | # 不是pcap文件,弹出错误 972 | 973 | f.close() 974 | return 975 | linktype = struct.unpack(endian + "I", linktype)[0] 976 | try: 977 | LLcls = conf.l2types[linktype] 978 | except KeyError: 979 | # 未知 LinkType 980 | LLcls = conf.raw_layer 981 | sec, usec, caplen = [0, 0, 0] 982 | for _ in range(location): 983 | packet_head = f.read(16) 984 | if len(packet_head) < 16: 985 | f.close() 986 | return None 987 | sec, usec, caplen = struct.unpack(endian + "III", packet_head[:12]) 988 | # f.seek(offset=?, whence=?) 989 | # :parma offset: 偏移量 990 | # :parma whence: 开始的位置 0从头开始 1从当前位置 2从文件末尾 991 | f.seek(caplen, 1) 992 | previous_time = sec + (0.000000001 if nano else 0.000001) * usec 993 | packet_head = f.read(16) 994 | sec, usec, caplen, wirelen = struct.unpack(endian + "IIII", 995 | packet_head) 996 | rp = f.read(caplen)[:0xFFFF] 997 | if not rp: 998 | f.close() 999 | return None 1000 | try: 1001 | p = LLcls(rp) 1002 | except: 1003 | p = conf.raw_layer(rp) 1004 | p.time = sec + (0.000000001 if nano else 0.000001) * usec 1005 | p.wirelen = wirelen 1006 | f.close() 1007 | return previous_time, p 1008 | --------------------------------------------------------------------------------