深入理解ARP攻擊
ARP(地址解析協議)是一種把物理地址地址轉換成邏輯地址的通訊協議,它屬于TCP/IP協議棧中的數據鏈路層協議。下圖是Wiki上的一副截圖
所以它只有兩層數據包頭——以太網頭、ARP頭(TCP協議有三層,以太網頭、IP頭、TCP頭;HTTP協議有四層,以太網頭、IP頭、TCP頭、HTTP頭)。
- 以太網頭包括6bytes的目標地址,6bytes的源地址,2bytes數據幀類型(對于ARP來說它是0x0806)
- ARP頭部包括2bytes的物理地址類型(0x0001以太網,代表MAC地址),2bytes邏輯地址類型(0x8000 IP地址);6bytes表示硬件地址長度(MAC地址為固定值6),4bytes表示邏輯地址長度(IP地址為固定值4);1bytes操作碼(1表示請求,2表示回復)
- 后面是可以變長的發送者物理地址、邏輯地址;目標硬件地址、邏輯地址。
我一直使用物理地址、邏輯地址這兩個術語;ARP是一種地址解析協議所以它可以實現任何兩種地址之間的轉換。只不過我們常用的是MAC地址轉換成IP地址。
首先用tcpdump抓ARP包。(tcpdump -i en0 -xx -vvvv -n -e arp,希望你還記得這個最常用的選項。e選項表示輸出以太網頭部,-xx表示輸出以太網16進制頭部)
- ffff ffff ffff(6bytes),廣播地址。說明ARP是通過廣播的方式發送ARP請求的
- 60f8 1dac 8904(6bytes),本機MAC地址。
- 0806,表示ARP數據包
- 0001固定值表示物理地址是MAC地址
- 0800固定值表示邏輯地址是IP地址
- 06表示物理地址6bytes
- 04表示邏輯地址4bytes
- 0001表示是ARP請求
- 60f8 1dac 8904表示發送方物理地址,這里是發送方MAC地址
- c0a8 1fe9 表示發送方邏輯地址,這里是發送方的IP地址(192.168.31.233)
- 0000 0000 0000表示接收方物理地址,這里是接收方的MAC地址。全0表示廣播地址
- c0a8 1f01表示接收方的邏輯地址,這里是想要查詢的IP地址
第二條數據包ARP回應,大家可以自行解讀。
ARP攻擊的原理
通過上面的數據包分析不難看出,ARP協議是非常天真的協議——發送ARP請求->解讀ARP回應。PC1喊一句——誰有IP地址192.168.31.1,快點把MAC地址發給我;如果PC2擁有這個IP地址則回應ARP請求。但是這只是理想情況,事實上一個沒有擁有IP地址192.168.31.1的機器也是可以發送回應包的。如果PC1信以為真那么就會拿到錯誤的“IP地址<->MAC地址”關系。這就是ARP攻擊。
從應用層面看機器之間通訊是基于IP地址的,但是操作系統在發送數據包的時候會在IP頭部加上以太網頭。這是由于以太網規范所決定的——數據包在網絡上傳送使用的地址都是物理地址。錯誤“IP<->MAC映射”會導致“數據包不可達”——通俗的說“連不上對方”。如果這個IP地址剛好是網關,那就意味著——“斷網”。
實施ARP攻擊需要解決三個問題
- 探測到ARP請求
- 發送ARP回應數據包
- 請求者“應用”我們的回應包
探測ARP請求
以太網是“共享傳輸介質”,所以任何在網絡上傳遞的數據包都可以被所有網卡探測到。為了減輕網卡的壓力網卡設計的時候會只選擇讀取和自己MAC地址匹配的數據包(以太網數據包頭目標地址=自己MAC地址)所以操作系統是無法讀取“所有數據包”的。當然,這個開關是可以被關閉的——這就是混雜模式。在這種模式下所有的數據包都會被網卡接受并且傳遞到操作系統的TCP/IP協議棧。
ARP請求本身就是廣播數據包所以我們打不打開"混雜模式"都可以彈出到ARP請求。我一般習慣開啟混雜模式。
實現網絡探測的庫基本上都是脫胎于libpcap(包括tcpdump),這是一個非常歷史悠久的庫,我們直接上代碼。
pcap_open_live第一個參數是網卡名字(比如:eth0),65545表示最大捕獲65545bytes的數據包(其實我們永遠到不了這個數字,一般1460或者1500就可以了),1表示開啟混雜模式,0表示一直抓取數據;errbuf錯誤消息如果函數調用失敗會把錯誤信息寫入到這個字符串中。
pcap_loop第一個參數是pcap_open_live的返回值,它是一個結構體;第二個參數是抓取次數,-1表示一直抓取;on_pcaket是一個函數,每當抓取到數據包都會調用該函數(回調);最后一個參數是一個指針把共享數據傳遞到回調函數里面。
這個就是on_pcaket的原型(第一個參數是pcap_loop的最后一個參數),pkthdr表示抓取到數據包頭指針;packet是數據包。
我們可以在on_packet里面直接判斷packet是否為ARP請求
packet表示完整的數據包,所以它的開頭一定是以太網數據包頭。我們直接看結構體
就是以太網頭的三個字段。
跳過以太網頭一定是ARP頭部,所以我們定義了指針arp_hdr。
發送ARP回應數據包
socket的封裝提供了TCP和UDP兩種接口,而我們要構造的ARP數據包不屬于這兩種所以只能通過RAW類型的socket來寫入自己構造的數據包。我們使用libetnet來完成這個工作。
第一個參數告訴libnet,我們要直接構造數據鏈路層數據包——自己填充以太網頭和ARP頭;第二個參數是要發送數據包的網卡,第三個參數是錯誤處理。
構造一個ARPOP_REPLY類型的數據包(回應);mac_addr是自己的MAC地址;tpa是請求arp的目標地址(對于我們來說是源地址——即便我們不是192.168.31.1也是可以填寫這個字段,系統不會校驗這部分數據);后面的數據都可以通過以太網頭和arp頭填充。
最后加上以太網頭
然后就可以發送了
請求者“應用”的回應包
PC1發送ARP請求,如果被我們的程序探測到并且我們“編造”一個ARP回應那么對于PC1來說它會收到兩次ARP回應。一條是正確的ARP回應,一條是我們編造的ARP回應。
對于Windows來說它會選擇最后到達的回應應用;對于Linux來說它會選擇第一個到達的回應應用。(所以一些簡單的ARP攻擊Linux是天生免疫的,此處你可以盡情吐槽一下Windows的弱智)
所以如果我們要攻擊Windows只需要sleep(1)再發送回應就行了;對于Linux來說就比較困難了——我們很難保證自己的數據包會優先到達。但是正所謂道高一次魔高一丈,我們不能欺騙Linux主機但是可以冒充Linux主機啊。簡單來說就是:當網關發送ARP請求探測PC的時候我們可以冒充PC回應網關的請求,這也會到導致——斷網。
盡情發揮吧
我只能提供一個簡單的Demo,它會回應整個網絡內所有的ARP請求(除了自己)。你可以加上一些判斷語句實現“精準打擊”只針對某個機器(比如知道它的IP地址或者MAC地址)。還可以發揮更多想象~~~
小心開車
ARP的防護辦法其實很簡單,以360的流量防火墻為例。它啟動的時候會首先拿到當前網關的MAC地址,通過修改系統底層所有的ARP回應數據包都不會被真正的應用,這樣就實現了“防”(你可以用MAC+IP地址綁定的方式固定網關,但是顯的不夠高大上)。
當收到ARP回應的時候360會比較自己保存的網關MAC地址,如果發現MAC地址發生了變化就認為發現了ARP攻擊。最最致命的——無論你怎么偽造你都會暴漏自己的mac地址(回應ARP的時候頭部以太網頭的源地址是你自己的MAC地址)這是由于網卡本身的限制,如果mac地址不是自己的數據包是“發不出去的”。
完整代碼
https://gist.github.com/fireflyc/796f55d54be39e629a2a1fcb2607a33b
總結
除了做壞事這種框架還可以做很多事情。它們的思路基本上都是一致的——探測數據包,通過應用處理,返回數據包。比如我們可以用DPDK(一種高性能的數據平面開發工具包,徹底無視Linux和TCP/IP協議棧)來探測數據包,通過移植TCP/IP協議到用戶空間來繞過操作系統(kernel bypass)。
經過多年的努力我們已經解決了C10k的問題,現在又有人提出來C10M問題。基本上很多人的觀點都基于kernel空間的TCP/IP是沒有辦法做到這一點的(受限于打開文件數量、維護每個連接的開銷、操作系統處理網卡的方式),解決辦法正是這種——kernel bypass的思路。
已經有人做了相關的工作比如libuinet、dpdk-ans,甚至還有人基于dpdk + dpdk-ans(用戶空間TCP/IP協議棧) 移植了nginx(dpdk-nginx)。陳碩老師有一篇《C1000k 新思路:用戶態TCP/IP 協議棧》大家也可以學習一下。
【本文是51CTO專欄作者邢森的原創文章,轉載請聯系作者本人獲取授權】