軟體定義網絡利用控制器對網絡的控制,實作一些傳統網絡中難以實作的功能,控制器在這個網絡中扮演一個上帝的角色,上一篇文章裡已經介紹了控制器可以實作代理應答,本次要介紹的是利用控制器和python來對資料包内容進行解析,實作DNS欺騙。
原了解析
主機在進行域名解析的時候,先向DNS伺服器發送DNS Request封包,而後DNS伺服器回複一個Reply封包,主機接收此封包即可完成解析。既然控制器可以造出ICMP應答封包,也能造出DNS回複封包。
思路說明
- 先給交換機下發流表項,讓交換機把DNS封包傳給控制器處理。
- 控制器對封包進行解析,提取出域名,編号等關鍵資訊。
- 構造一個新的DNS Reply封包,通過交換機傳回給主機。
關鍵要素
要完成這樣一個過程并不簡單,雖然我們平常用抓包軟體比如Wireshark之類的看到的資料包内容非常清楚,條理分明,但是如果一個資料包到了我們手上,我們要把它研究透徹還是很複雜的,下面列出一些關鍵性的步驟:
- 資料包編碼
- 提取DNS域名
- 擷取資料包編号
代碼分析
控制器擷取到的DNS資料包資料如果不加任何修改,直接輸出就是亂碼,整個的資料包裡隻有域名是明文,我們可以先把域名提取出來,但是Ryu并沒有處理DNS封包的庫,這裡可以采用截取字元串的方法,這就出現了一個問題:域名長度不固定,沒關系,因為域名出現的位置是固定的,我們擷取了整個資料包的長度,到那個固定的位置找就可以了。
pkt_len = len(data)
domain = (data[55:pkt_len-5])
我是怎麼知道域名長度固定的呢?
用wireshark抓包,資料包的具體字段,長度wireshark都能顯示。
這樣我們算是提取出了域名,不過有關域名的事還沒完,後面會講到。
主機在送出請求的時候請求封包會有一個ID号,回複封包也必須攜帶一樣的ID才能被主機認可,這個ID在封包中位置也固定,隻是他被編碼了,是以我們看不出來它是什麼,可以從wireshark裡看一看是4個十六進制數,它的位置是
flag = data[42:44]
,是以我們首先需要對其進行解碼,但解碼我們需要知道編碼方式,是以問題就是如何擷取編碼方式,這裡推薦使用Python的chardet子產品,這是個用來檢測檔案或者字元編碼的子產品,讀者可用
pip install chardet
來安裝,然後開始解碼:
b=chardet.detect(data[:])
#print(b)
if b['encoding'] == None:
c=flag.encode("hex")
else:
flag.decode(b['encoding'])
c=flag.encode("hex")
這裡檢測的時候不一定每次ID都是有編碼的,偶爾有一次會出現沒有編碼的情況,這時就不需要解碼了,得到的變量c就是wireshark裡面的十六進制ID。
好了,域名有了,ID有了,可以開始構造資料包了,這裡推薦使用Scapy,它是一個功能強大的資料包處理程式。鑒于官網教程有限,我們可以用它發送一個DNS請求封包,看一下它的解析的應答包,照着構造一個。
a = Ether(dst=pkt_ethernet.src,src=self.hw_addr)/IP(dst=ip_dst,src=ip_src)/UDP(sport=sport,dport=dport)/DNS(opcode=,id=d,qr=,rd=,ra=,aa=,tc=,z=,ad=,cd=,rcode=,qdcount=,ancount=,nscount=,arcount=,qd=DNSQR(qname=domain),
an=DNSRR(rrname=domain,ttl=,rdata=ip_src),ns=DNSRR(rrname=domain,type=,ttl=,rdata="ns1."+domain),ar=None)
這裡面的ID值是一個十進制數,是以我們要轉化一下。這裡面我把DNS伺服器位址作為解析的目标位址,關于裡面每個字段的含義讀者有興趣可以自行查閱相關資料。
構造完畢之後用Packet-Out封包發給交換機即可。
如果僅僅按照上面的步驟,我們就會發現,解析的域名如果沒有小數點,那可以得到正确的回複,一旦我們加上小數點,運氣好的話主機會提示你傳回的域名不符。這就涉及到在封包中域名的格式了,小數點的位置上是一個數,表示後面有幾位。例如下面的域名
blog.csdn.net
在封包中格式是:
4 b l o g 4 c s d n 3 n e t 0
最後一位是結束符。是以我們提取出來的其實是
b l o g 4 c s d n 3 n e t
而中間的數字是不可見字元,是以我們還要對提取出來的域名進行一些轉化,通過ASCII碼來判斷是否是不可見字元。
for g in range(,len(domain)-):
if ord(domain[g])< or ord(domain[g])>:
domain=domain[:g]+"."+domain[g+:]
這樣域名就是正确的了。
看一下結果,域名解析已經成功。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiQ3chVEa0V3bT9CX5RXa2Fmcn9CXwczLcVmds92czlGZvwVP9EUTDZ0aRJkSwk0LcxGbpZ2LcBDM08CXlpXazRnbvZ2LcRlMMVDT2EWNvwFdu9mZvwVP3dlY1Z0ViZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zNzMDNzgTM2ETNyITM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
完整代碼已經放到github上,讀者可自行檢視。