├── 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 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
10 |
11 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | 1574306406266
124 |
125 |
126 | 1574306406266
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | file://$PROJECT_DIR$/main_ui.py
141 | 458
142 |
143 |
144 |
145 | file://$PROJECT_DIR$/main_ui.py
146 | 416
147 |
148 |
149 |
150 | file://$PROJECT_DIR$/main_ui.py
151 | 600
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
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 |
--------------------------------------------------------------------------------