基于python+scapy设计协议编辑器,基于Tkinter的Python GUI界面设计。实现了MAC、ARP、IP 、TCP、UDP协议的编辑与发送,并且支持生成协议字段的默认值,支持用户输入协议字段值,发包前对协议字段的合理性进行检查,发包前自动计算并显示校验和,支持单次发包、多次发包,支持连续发包,连续发包时可随时停止和计算并显示数据包发送速度的功能。
注:
程序源代码:https://download.csdn.net/download/wmrem/10439779
运行前需要先安装第三方库scapy,安装方法:https://blog.csdn.net/wmrem/article/details/80004819
…
…
send_packet_button['text'] = '发送'
1.构造数据包,发送数据包——“/"运算符数据包的拼装,send()用于3层发包,可直接发送IP数据包,自动加以太帧;只发送以太帧使用sendp()。
2.计算校验和——IP包的校验和默认值为None,在IP包被发送时,其校验和被被自动计算并填充在IP包中
以下方式可以在发包前计算IP包的校验和,直接用packet.show2()命令也可以显示校验和。raw(packet)将数据包的内容转换为字节。
3.查看数据包的字段值(使用ls()),查看某层协议数据包对象支持的函数(使用help())
4.ls()列出scapy支持的所有数据包,协议;lsc()列出scapy支持的全部命令;conf列出scapy的当前配置。
1.创建IP包编辑器的界面
# 创建协议字段编辑区
def create_protocol_editor(root, field_names):
"""
创建协议字段编辑区
:param root: 协议编辑区
:param field_names: 协议字段名列表
:return: 协议字段编辑框列表
"""
entries = []
for field in field_names:
row = Frame(root)
label = Label(row, width=15, text=field, anchor='e')
entry = Entry(row, font=('Courier', '12', 'bold'), state='normal') # 设置编辑框为等宽字体
row.pack(side=TOP, fill=X, padx=5, pady=5)
label.pack(side=LEFT)
entry.pack(side=RIGHT, expand=YES, fill=X)
entries.append(entry)
return entries
def create_ip_sender():
"""
创建IP包编辑器
:return: None
"""
# IP帧编辑区
ip_fields = 'IP协议的版本:', '首部长度(5-15):', '区分服务:', '总长度:', '标识:', '标志(0-2)DF,MF:', \
'片偏移:', '生存时间:', '协议(数据部分):', '首部校验和:', '源IP地址:', '目的IP地址:'
entries = create_protocol_editor(protocol_editor_panedwindow, ip_fields)
send_packet_button, reset_button, default_packet_button = create_bottom_buttons(protocol_editor_panedwindow)
# 为"回车键"的Press事件编写事件响应代码,发送ARP包
tk.bind('', (lambda event: send_ip_packet(entries, send_packet_button))) # 代表回车键
# 为"发送"按钮的单击事件编写事件响应代码,发送ARP包
send_packet_button.bind('', (
lambda event: send_ip_packet(entries, send_packet_button))) # 代表鼠标左键单击
# 为"清空"按钮的单击事件编写事件响应代码,清空协议字段编辑框
reset_button.bind('', (lambda event: clear_protocol_editor(entries)))
# 为"默认值"按钮的单击事件编写事件响应代码,在协议字段编辑框填入ARP包字段的默认值
default_packet_button.bind('', (lambda event: create_default_ip_packet(entries)))
2.在协议字段编辑框中填入默认IP包的字段值,填入前需要先清空当前值。
def clear_protocol_editor(entries):
"""
清空协议编辑器的当前值
:param entries: 协议字段编辑框列表
:return: None
"""
for entry in entries:
# 如果有只读Entry,也要清空它的当前值
state = entry['state']
entry['state'] = 'normal'
entry.delete(0, END)
entry['state'] = state
# 当前网卡的默认网关
default_gateway = [a for a in os.popen('route print').readlines() if ' 0.0.0.0 ' in a][0].split()[-3]
def create_default_ip_packet(entries):
"""
在协议字段编辑框中填入默认IP包的字段值
:param entries: 协议字段编辑框列表
:return: None
"""
clear_protocol_editor(entries)
default_ip_packet = IP()
entries[0].insert(0, int(default_ip_packet.version))
entries[1].insert(0, 5)
entries[3].insert(0, 20)
entries[2].insert(0, hex(default_ip_packet.tos))
entries[4].insert(0, int(default_ip_packet.id))
entries[5].insert(0, int(default_ip_packet.flags))
entries[6].insert(0, int(default_ip_packet.frag))
entries[7].insert(0, int(default_ip_packet.ttl))
entries[8].insert(0, int(default_ip_packet.proto))
entries[9]['state'] = NORMAL # 可操作
entries[9].insert(0, "单机发送时自动计算")
entries[9]['state'] = DISABLED # 不可操作
# 目标IP地址设成本地默认网关
entries[11].insert(0, default_gateway)
default_ip_packet = IP(dst=entries[11].get())#可以省略
entries[10].insert(0, default_ip_packet.src)
3.发送IP包
def send_ip_packet(entries, send_packet_button):
"""
发IP包
:param entries:
:param send_packet_button:
:return:
"""
if send_packet_button['text'] == '发送':
ip_version = int(entries[0].get())
ip_ihl = int(entries[1].get())
ip_tos = int(entries[2].get(), 16)
ip_len = int(entries[3].get())
ip_id = int(entries[4].get())
ip_flags = int(entries[5].get())
ip_frag = int(entries[6].get())
ip_ttl = int(entries[7].get())
ip_proto = int(entries[8].get())
ip_src = entries[10].get()
ip_dst = entries[11].get()
# ip_options = entries[12].get()
packet_to_send = IP(version=ip_version, ihl=ip_ihl, tos=ip_tos, len=ip_len, id=ip_id,
frag=ip_frag, flags=ip_flags, ttl=ip_ttl, proto=ip_proto, src=ip_src, dst=ip_dst)
packet_to_send = IP(raw(packet_to_send))
entries[9]['state'] = NORMAL # 重新激活
entries[9].delete(0, END)
entries[9].insert(0, hex(packet_to_send.chksum))
entries[9]['state'] = DISABLED # 不可操作
# 开一个线程用于连续发送数据包
t = threading.Thread(target=send_packet, args=(packet_to_send,))
t.setDaemon(True)
t.start()
# 使协议导航树不可用
toggle_protocols_tree_state()
send_packet_button['text'] = '停止'
else:
# 终止数据包发送线程
stop_sending.set()
# 恢复协议导航树可用
toggle_protocols_tree_state()
send_packet_button['text'] = '发送'
4.线程中运行的用于发送数据包的函数,可以计算发包速度
def send_packet(packet_to_send):
"""
在我们给出的发包程序中,如果电脑速度太快,send_packet函数中的send(...)函数执行前后,
datetetime返回的begin_time和end_time可能是相同的,结果会报除零错误,所以,send_packet函数应该做修改
用于发送数据包的线程函数,持续发送数据包
:type packet_to_send: 待发送的数据包
"""
# print(packet.show(dump=True))
# 对发送的数据包次数进行计数,用于计算发送速度
n = 0
stop_sending.clear()
# 待发送数据包的长度(用于计算发送速度)
packet_size = len(packet_to_send)
# 推导数据包的协议类型
proto_names = ['TCP', 'UDP', 'ICMP', 'IP', 'ARP', 'Ether', 'Unknown']
packet_proto = ''
for pn in proto_names:
if pn in packet_to_send:
packet_proto = pn
break
# 开始发送时间点
begin_time = datetime.now()
while not stop_sending.is_set():
if isinstance(packet_to_send, Ether):
sendp(packet_to_send, verbose=0) # verbose=0,不在控制回显'Sent 1 packets'.
else:
send(packet_to_send, verbose=0)
n += 1
end_time = datetime.now()
total_bytes = packet_size * n
#修改
total_time = (end_time - begin_time).total_seconds()
if total_time == 0:
total_time = 2.23E-308 # 当begin_time和end_time相等时,将total_time设为IEEE 745标准中规定的最小浮点数
bytes_per_second = total_bytes / total_time / 1024
# bytes_per_second = total_bytes / ((end_time - begin_time).total_seconds()) / 1024
status_bar.set('已经发送了%d个%s数据包, 已经发送了%d个字节,发送速率: %0.2fK字节/秒',
n, packet_proto, total_bytes, bytes_per_second)