python嗅探工具详解(带tkinter界面)
必备基础知识
IP数据包格式
TCP报文格式
struct模块
在Python中,一切皆对象,基本数据类型也不列外
C语言的数组int a[3] = {1, 2, 4};
,存储的是真正的值
Python的列表lyst = [1, 2, 4]
,存储的是元素的指针
这就造成了列表元素的不连续存储,在Python中列表中的数据可能不会被存储为连续的字节块
为了处理它们,将python值转换为C结构很重要,即将它们打包成连续的数据字节struct.pack()
,或者将一个连续的字节块分解成Python对象struct.upack()
struct模块执行Python值和以Pythoncbytes
表示的C结构体之间的转换,这可以用于处理存储在文件中或来自网络连接以及其他源的二进制数据;
struct.pack(format,v1,v2,... )
返回一个字节流对象,其中包含根据格式字符串format打包的v1,v2,…的值。参数必须与格式所需的值完全匹配。
struct.unpack(format, string)
解包,将字节流转化为python对象
示例
import struct
b = (120, 127, -110)
byte = struct.pack('!ihb', *b)
print(byte)
str = struct.unpack('!ihb',byte)
print(str)
ctypes模块
是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
结构体和联合中的位域
结构体和联合中是可以包含位域字段的。位域只能用于整型字段,位长度通过 _fields_
中的第三个参数指定:
>>> class Int(Structure):
... _fields_ = [("first_16", c_int, 16),
... ("second_16", c_int, 16)]
...
>>> print(Int.first_16)
<Field type=c_long, ofs=0:0, bits=16>
>>> print(Int.second_16)
<Field type=c_long, ofs=0:16, bits=16>
>>>
若干函数
from_buffer_copy
(source [,偏移量] )
此方法创建一个ctypes实例,从必须可读的源对象缓冲区中复制缓冲区 。可选的offset 参数以字节为单位指定到源缓冲区的偏移量;默认值为零。如果源缓冲区不够大,ValueError
则会引发a。
发起一个审计事件 ctypes.cdata/buffer
带有参数pointer
,size
,offset
。
socket.inet_ntoa(packed_ip)
将字节流转换32位打包的IPV4地址为IP地址的标准点号分隔字符串表示。
socket.recvfrom(bufsize [,标志])
从套接字接收数据。返回值是一对(字符串,地址),其中字符串是代表接收到的数据的字符串,而地址是发送数据的套接字的地址此方法bufsize仅从套接字接收最大字节数。
异常处理
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
- 如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。
- 如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印默认的出错信息)。
- 如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。
打包成exe文件
python中pyinstaller模块
PyInstaller 工具的命令语法如下:
pyinstaller 选项 Python 源文件
运行打包完后,会在pyinstaller的根目录下的dist文件夹下存放相应的exe文件,该文件可以跨平台使用
-h,–help | 查看该模块的帮助信息 |
---|---|
-F,-onefile | 产生单个的可执行文件 |
-D,–onedir | 产生一个目录(包含多个文件)作为可执行程序 |
-a,–ascii | 不包含 Unicode 字符集支持 |
-d,–debug | 产生 debug 版本的可执行文件 |
-w,–windowed,–noconsolc | 指定程序运行时不显示命令行窗口(仅对 Windows 有效) |
-c,–nowindowed,–console | 指定使用命令行窗口运行程序(仅对 Windows 有效) |
-o DIR,–out=DIR | 指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件 |
-p DIR,–path=DIR | 设置 Python 导入模块的路径(和设置 PYTHONPATH 环境变量的作用相似)。也可使用路径分隔符(Windows 使用分号,Linux 使用冒号)来分隔多个路径 |
-n NAME,–name=NAME | 指定项目(产生的 spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字 |
在表 1 中列出的只是 PyInstaller 模块所支持的常用选项,如果需要了解 PyInstaller 选项的详细信息,则可通过 pyinstaller -h 来查看。
源码
基本框架:
定义模块
创建tkinter主窗口
class IP(Structure):
构造类似于c的结构体,对IP数据包进行解析
函数`_new_`将类实例化
函数`_init_`初始化该实例的变量和参数
strat()函数 创建套接字、绑定端口、设置IP头部、开启混杂模式、为show()函数创建线程
show()函数 接收数据、解析IP数据包、打印结果
stop()函数 关闭混杂模式,关闭套接字
tkinter触发按钮设置
import socket
import os,sys
import struct
from ctypes import *
import time
import datetime as dt
import tkinter as tk
import threading
from tkinter import messagebox
#创建tkinter主窗口
window = tk.Tk()
window.title('嗅探工具:')
window.geometry('800x600')
#本地监听
l = tk.Label(window, text='请您输入以太网卡ip:', width=50, height=3)
l.pack(side='top')
e = tk.Entry(window, show=None)
e.pack(side='top')
var = tk.StringVar() #定义一个字符串变量
#IP头定义
class IP(Structure):
_fields_ = [
('ihl', c_ubyte, 4),
('version', c_ubyte, 4),
('tos', c_ubyte),
('len', c_ushort),
('id', c_ushort),
('offset', c_ushort),
('ttl', c_ubyte),
('protocol_num', c_ubyte),
('sum', c_ushort),
('src', c_ulong),
('dst', c_ulong),
("src_port", c_ushort),
("dst_port", c_ushort)
]
def __new__(self, socket_buffer=None):
return self.from_buffer_copy(socket_buffer) #实例化类
def __init__(self, socket_buffer=None):
self.protocol_map = {1:"ICMP", 6:"TCP", 17:"UDP"} #创建一个字典,协议字段与协议名称对应
self.src_address = socket.inet_ntoa(struct.pack("<L",self.src))
#inet_ntoa()函数将字节流转化为点分十进制的字符串,专用于IPv4地址转换
#将c_ulong类型的src(源地址)转为小端的long类型数据,返回源地址的字节流格式
self.dst_address = socket.inet_ntoa(struct.pack("<L",self.dst))
#协议判断
try:
self.protocol = self.protocol_map[self.protocol_num] #将协议号与协议名对应
except:
self.protocol = str(self.protocol_num) #若字典中没有,则直接输出协议号
#Windows下嗅探所有数据包,Linux下嗅探ICMP数据包
def strat():
var = e.get()
if os.name == "nt": #判断系统是否为window
socket_protocol = socket.IPPROTO_IP #设置协议为ip协议
else:
socket_protocol = socket.IPPROTO_ICMP
global sniffer
#创建一个原始套接字
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
try:
sniffer.bind((var, 0)) #套接字绑定地址,0默认所有端口
except:
tk.messagebox.showerror(title='错误', message='socket连接错误') #若绑定失败则弹窗解释
#设置ip头部
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
#Windows下要打开混杂模式
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
#设置开启混杂模式,socket.SIO_RCVALL默认接收所有数据,socket.RCVALL_ON开启
show_th=threading.Thread(target=show) #创建一个线程,执行函数为show()
show_th.setDaemon(True)
show_th.start()
def show():
window.title('抓包中') #更改界面标题
while True:
#读取数据包
raw_buffer = sniffer.recvfrom(65535)[0] #获取数据包,接收最大字节数为65565
#读取前20字节
ip_header = IP(raw_buffer[0:24])
#输出协议和双方通信的IP地址
now_time = dt.datetime.now().strftime('%T') #获取系统当前时间
result = 'Protocol: '+str(ip_header.protocol)+' '+str(ip_header.src_address)+' : '+str(ip_header.src_port)+' -> '+str(ip_header.dst_address)+' : '+str(ip_header.dst_port)+' size:'+str(ip_header.len)+' time:'+str(now_time)+'\n' #设置输出的字符串
t.insert('end',result) #将每条输出插入到界面
time.sleep(1)
def stop():
window.title('已停止')
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) #关闭混杂模式,第一个参数是接收所有数据,第二个对应关闭
sniffer.close() #关闭套接字
b_1 = tk.Button(window, text='确定', width=15, height=2, command=strat).pack(side='top')
t = tk.Text(window,width=100)
t.pack(side='top')
b_2 = tk.Button(window, text='停止', width=15, height=2, command=stop).pack()
window.mainloop()
注:运行代码时要以管理员身份运行,否则系统不允许获取网卡信息。
运行结果: