過去的幾個月我一直在周游以指導人們如何對嵌入式設備進行漏洞利用,單單幻燈片已經不足以承載足夠的信息,所以我將所有的都寫下來以便與知識的消化。接下來的內容是 第一部分,介紹了一些嵌入式設備端的軟件。
我決定首先介紹軟件 因為很多漏洞都發生在軟件端,從二進制可執行程序到驅動程序。第二部分會介紹硬件層,教授 JTAG 是如何工作的,如何利用修改硬件繞過密碼保護或提取可入侵目標設備的機密信息。
目錄
1 使用Binwalk提取固件
2 學習目標設備的匯編
3 GPL
4 漏洞利用
5 DVRFv0.3 socket_bof 解決方案
6 引用參考
1 使用Binwalk提取固件
當你能夠得到你有的嵌入式設備的二進制固件的時候,或許你想看看里面有什么。幸運的是有個開源的工具 Binwalk,可分析目標的二進制文件中的 Magicbytes,看這里。
為了更加直觀的表現,這里使用Binwalk提取DVRFv0.3
通過 binwalk -e filename提取二進制文件的內容:
binwalk_dvrf3.png
binwalk 顯示已知的結構以及在二進制文件中的偏移量
Offset_binwalk_arrow.png
在vim中使用 xxd 顯示TRX和gzip偏移量,確實和Binwalk提供的相匹配(使用vim打開DVRFv0.3,在命令模式下輸入%!xxd)
gzip_trx_offset_arrows.png
Binwalk交叉參考出TRX結構
trx_magic_bytes_git.png
Binwalk交叉參考出gzip結構的
gzip_github.png
根據Binwalk發現的內容,使用vim和xxd交叉參考出SquashFS結構的偏移量
squashfs_dvrf_arrows.png
binwalk 交叉參考出SquashFS結構
2學習目標設備的匯編
如果對目標設備使用的匯編不熟悉,可以使用C語言和反匯編器快速學習。我認為,下面的內容是學習一門新匯編最先要去看的部分。
參數傳遞
函數的進入和返回
堆棧的使用
函數的調用
條件分支
參數傳遞
有個簡單的C語言程序, 傳遞2個int 參數給另一個函數并且返回二者之和。
當你編譯完成C程序后你想對生成的可執行文件進行反匯編
Note : 使用自己熟悉的反匯編工具, 這邊使用的是Radare2
雖然我們可以看到圖形話的視圖,不過也可以按g 然后按 a 查看函數 pass_args_to_me
為了理解了當傳遞的參數數量多于參數寄存器數量的情況。比如在MIPS中寄存器參數使用$a0 - $a3 ,所以修改上面的代碼,增加傳遞的參數使得參數數量大于4
將編譯后的可執行文件使用radare2進行反匯編,查看生成的匯編
可以看到當參數個數大于4時,多余的參數會被壓入棧中
函數的進入 調用 返回
需要注意的是:在 MIPS和ARM處理器中的返回地址寄存器。當一個跳轉鏈接指令在MIPS中執行的時候,返回地址寄存器的地址是當前指令指針+8 bytes,8bytes的偏移是因為pipeling,因為PC+4 會在跳轉發生前執行。讓我們編譯一個在返回main函數前調用2個或更多函數的程序。
記住 發生函數調用(JAL)會把 $PC+8 保存到$ra寄存器中,但是如果被調用函數還會調用其他函數時,$ra寄存器會被覆蓋,調用者的地址會丟失。為了防止這種情況,返回地址首先被保存到函數入口的棧上.所以我們可以看到所有的函數會將返回地址保存到棧上,除了 函數call_two,因為call_two()沒有調用其他函數。
只是對函數入口進行分析我們就可以判斷這個函數是否調用了其他函數。當試圖找內存堆棧溢出漏洞的時候這個技巧很有用
條件分支
分析一個新架構的時候,最重要的事情之一是處理器怎么處理條件分支。像之前一樣我們使用c和radare2進行分析。
下面的程序會傳入一個命令行參數,類型為int, 并判斷是否小于5
查看編譯器會產生怎樣的匯編來滿足條件
可以看到當比較結果為小于的時候,使用了 slti。 學習一種新的匯編語言的時候,由于大量的比較運算符和類型,條件判斷會花費大部分的時間,參考c語言中的表達, 確保你分析了所有生成條件分支的方法。例如 :在MIPS架構中,有時既可以使用有符號立即數,又可以使用無符號立即數,這可能會被濫用。
現在你看了上面一些例子,你掌握那些技巧后, 就可以在只有編譯器和反匯編器的情況下學習任何處理器的架構和匯編。不這樣的話,那就只能不幸的用更艱苦的方式學習,查看處理器的開發手冊,甚至涉及自己的匯編器,模擬器,反匯編器。
3GPL
如果你審計的設備使用了開源軟件,那么軟件應該是遵循GPL授權的。那么如果開發者使用代碼并編譯的話,源碼必須公開,不公開就違反了GPL協議。
很多路由器和小型設備使用Linxu(或者FressRTOS), Busybox,和其他授權GPL協議開源軟件,所以開始反匯編之前可以在Google搜索一小段供應商的或者產品的源代碼。下面是一些我搜索到的示例源代碼庫.
4漏洞利用
這一部分假定讀者有利用內存漏洞的基礎知識。如果沒有,可以查看底部的 SmashtheStack, SmashtheStack是我開始學習x86漏洞利用的地方。
如果你在審計的是MIPS架構的嵌入式Linux設備, 那么很有可能在分析目標二進制文件的時候看到的是一下內容
如你所見,棧區和堆區被標記為可執行,所以不必擔心NX(Not execute), 雖然棧上是可執行的,但是為了讓代碼執行,ROP(return-oriented Programming)也是必要的。你也會發現 ASLR在大部分設備上都不會生效,所以不必要尋找首先泄露漏洞的信息
譯者注 :NX bit 是 一些CPU內存管路單元的特性,允許特定內存頁可執行或不可執行。more of NX
模擬
一旦使用Binwalk提取固件,為了分析崩潰你會想模擬運行二進制文件。我個人使用的靜態編譯版本的QEMU,可以使用 chroot,在提取的固件環境中加載程序。于是漏洞利用者可以使用與目標設備同一套的libc庫,改變得僅僅只是libc的地址。而有時使用QEMU
模擬一陣套系統也是必要的,因為主機也會不支持二進制文件使用的IO操作而導致崩潰。
如果你使用的是基于debian的Linux發行版, 你可以通過 apt-get安裝QEMU
sudo apt-get install qemu-user-static qemu-system-*
安裝完成QEMU,需要將qemu的可執行文件拷貝到提取的固件的根目錄處。例如我們在你DVRFv0.3中使用MIPS little Endian的模擬器。
cp `which qemu-mipsel-static` ./
這邊我們使用可以被攻破的二進制文件
/pwnable/lntro/stack_bof_01 ,并為之寫一段利用代碼。然后將payload作為程序參數 ,看看會發生什么。
二進制的源代碼:
我們有一個簡單的棧溢出漏洞了,目的是執行 dat_shell函數。但我們分析ELF文件的時候可以看到如下:
Entry point address: 0x00400630
因為Payload中不能有NULL字符,于是得依靠部分覆蓋來執行,因為是小端格式,我們可以覆蓋最低的3個字節,最高位置NULL, 在大端機不適用。
為了演示模擬環境的功能, 我會編寫payload并展示怎么找到模擬環境中加載庫的地址。
gdb遠程調試附加進程
可以看到 CP置為了 A8gA ,可以算出偏移量為 204, 即$RA在208字節上,在這邊我們只會覆蓋4字節中的3字節。
再次嘗試,使$RA寄存器為 0x42424242
我們想要跳過修改$gp的指令,它會導致程序崩潰, 我以我們跳轉到 0x0040095c
我們也可以打斷點來確認是否跳轉到了函數正確的偏移地址
所以構建ROP 鏈的時候你所要做的就是替換libc的地址(可以通過 cat /proc/[pid]/maps獲得).你需要的是libc的基址。如果構造的ROP鏈在QEMU中可以正常運行那么99%可以在真實設備上運行。
5DVRFv0.3 socket_bof 解決方案
當設計DVRF項目的實驗的時候,我想納入我所見過的大部分的常見漏洞類型。最常見的是棧溢出漏洞,如果不熟悉匯編的話會有點挑戰。
下面的漏洞利用代碼花了大概8個小時,因為自己人在學MIPS匯編 ,這段代碼是在QEMU上完成編寫的。
因為棧上可執行,且庫文件沒有地址偏移,所以我們可以對ROP鏈進行硬編碼,但是ROP的本質是將$SP的值想想一個可以調用的寄存器。我認為硬編碼棧地址不夠可靠,我更加喜歡使用偏移量來代替,下面是socket_bof的內存映射,
地址0x2ab3e000是 libc可執行塊的基址, 當測試實際設備的時候,在QEMU上編寫的exploit代碼中,這是唯一需要更改的地方。
整個ROP 鏈 都是使用Radare2的 /R功能來完成的,比如,我想要查找 mmove t9, a1 來作為 ROP 的最后一小部分,我們可以按照如下方式來尋找:
Note : 一開始我是準備自己編寫shellcode的,不過了解到了一個項目 Bowcaster已經給出了。所以這邊展示流程,我根據下面的C語言代碼來進行模塊化.
如果我們查看 Bowcaster Reverse_TCP 的shellcode,會發現上面的C代碼和Bowcaster的Shellocde是一致的
首先設置 Socket ( syscall Value 4183)
連接socket (syscall value 4170)
調用dup2 (syscall value 4063)
執行 sh (syscall value 4011)
我們可以通過radare2 反匯編C語言程序來驗證syscall。
我們可以看到 C中調用socket()函數就是 syscall 4183,其他的系統調用號也可以這樣來進行查看。
注意shellcode在QEMU的用戶模式下不是100%成功的, 表現在于你會看到一個TCP的反向連接,但是不會彈出shell而是一段錯誤信息。而這段Shellocde在實際設備上能夠正常運行。
更加簡單的方式去分析運行中的是使用qira(QEMU Interactive Runtime Analyse),下面的圖片展示了Qira是如何在不需要斷點的情況下分析二進制文件
qira_shellcode.jpg
基于web的Qira 輸出顯示了所有的指令和系統調用
所以為了編寫漏洞利用代碼而重新造輪子是沒有必要的, 而設計自己的shellcode 和shellcode的編碼器則是進行漏洞利用的很好的鍛煉方式。當決定自己設計之前確保使用過所有可用的工具。如果已經存在的shellcode 正好適用目標設備,那么拿來用沒有什么錯,但是確認要對網上找來的shellcode進行代碼審計。
6引用參考
審核編輯:湯梓紅
評論
查看更多