【技術分享】ARM 架構—探究繞過NX的一種方式Ret2ZP
一、前言
ARM 指令集架構,常用于嵌入式設備和智能手機中,是從RISC衍生而來的。并且ARM處理器幾乎出現在所有的流行智能手機中,包括IOS、Android、Window Phone和黑莓操作系統,并且在嵌入式設備中經常出現,如電視機、路由器、智能網關等。ARM和x86指令集架構先比,ARM由于起精簡指令集,具有高效,低耗能等優點,可以去確保在嵌入式系統上提供出色的性能。
二、緩沖區溢出原理
緩沖區是用于保存數據的臨時內存空間。緩沖區溢出通常發生在寫入緩沖區的數據大于緩沖區大小時,由于邊界檢查不足,緩沖區溢出并寫入相鄰的內存位置,這些位置可能是一些重要的數據或返回地址等。
并且局部變量一般來說在程序中是充當緩沖變量或者緩沖區。
在緩沖區溢出中,我們的主要目的是用一些手段來修改返回地址,通過控制鏈接寄存器(LR)來控制程序執行流。
一旦我們控制了返回地址,返回地址將賦值給PC寄存器,劫持了PC,你就掌控了一切。
三、ARM和X86對緩沖區溢出利用的區別
當一個程序開啟NX保護之后,X86 架構下,首先想到的是Ret2Libc 來繞過NX ,篇幅有限,這里Ret2Libc就不展開說了。
但是在ARM架構中,Ret 到 Libc 是無法做到的,因為在ARM處理器中,函數的參數是通過寄存器傳遞的,而不是如X86,函數的參數通過堆棧傳遞。因此在ARM實現和x86 中Ret2Libc一樣的效果,我們需要將參數放入到寄存器中。
這里使用到了Ret2ZP技術(Return To Zero Prevention)
舉個例子,我們在棧溢出利用中,一般的思路是構造gadgets,來執行system(“/bin/sh”) 來獲取shell。但是在構造gadgets, 需要將 “/bin/sh” 傳遞到system 函數執行時調用的寄存器R0中。
這里在 Ret2ZP 中常用的在libc 中的gadgets 有以下這些,是實際利用時,并不是每個都有用,選在實際環境中可以使用的就行。
erand48

lrand48

seed48

四、環境搭建
這里使用的的Raspbian 虛擬機,下載安裝方法搜索引擎有很詳細的文章。這里說明一下我搭建過程遇到的問題。
首先是在Raspbian 虛擬機中 下載gdb , 建議手動編譯。因為apt 下載的gdb 是版本小于8.1。在實際gdb 調試會出現如下圖所示問題

解決方案:這是gdb 因 ARM 程序內存損壞而造成的錯誤,最好的解決方式是安裝gdb-8.1版本。https://github.com/hugsy/gef/issues/206
編譯完之后。運行gdb 會出現如下錯誤
Python Exception No module named gdb: /usr/local/bin/gdb: warning: Could not load the Python gdb module from `/home/pi/gdb-8.2/=/usr/share/gdb/python'.Limited Python support is available from the _gdb module.Suggest passing --data-directory=/path/to/gdb/data-directory.
解決方案:這是因為在編譯的的時候需要指定編譯環境是python3.5,默認是2.5,因此需要 ./configure —prefix=/usr —with-system-readline —with-python=/usr/bin/python3.5m 。
在編譯的過程中還有可能會出現如下圖所示的缺少庫文件 “/usr/bin/ld: cannot find -lreadline “

解決方案:sudo apt-get install libreadline6-dev
然后就是安裝gef 插件了。QAQ
五、緩沖區溢出實例
1)漏洞代碼
這里用一個最簡單的代碼來學習Ret2ZP技術,并且緩沖區溢出點特別明確,有strcpy函數將輸入的大于buf分配內存大小的參數傳入到固定大小的buf緩沖區,從而造成緩沖區溢出。
漏洞代碼
#include #include
void do_echo(char* buffer){char buf[10];strcpy(buf,buffer);}int main(int argc, char **argv){ do_echo(argv[1]);return(0);}
關閉ALSR保護
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
編譯代碼
pi@raspberrypi:~$ gcc -fno-stack-protector echo_arm1.c -o echo_arm
查看編譯后文件的保護情況。

2) 確定溢出點并計算偏移
使用gdb 打開文件調試,并且查看文件中函數的匯編代碼,如下圖所示。

利用pattern.py 生成字符串

這里在do_echo 函數的0x00010468處打斷點。
運行程序并將pattern生成的字符串輸入可以看到在執行到0x00010468處,R11寄存器中值是”a5Aa”,接下來計算出棧溢出的偏移地址

經過計算,buf緩沖區溢出到返回地址需要16個字節,也就是說劫持PC寄存器需要16個padding。

3) 構造ROP Chain
這里利用seed48代碼片段,當然也可以用我上面說的lrand48。這里使用seed48比lrand48 的gadgets 要復雜一點點。

可以看到R11寄存器后的值

查找system函數地址 0xb6eab154

查找“/bin/sh” 所在的地址 0xb6f91944

構造ROP chain 如下圖所示,根據 ARM 的特性,system 函數需要的參數需要R0寄存器傳入到 system。因此需要將 “/bin/sh” 字符串的地址放入到 R0 寄存器中,這里 gadgets1 首先將棧上 “/bin/sh” 的地址傳入到R4寄存器中,由于在 gadgets2 中 R0=R4+6 ,因此這個時候需要0xb6f91944 減 6 ,然后在PC寄存器中放入 gadgets ,在執行 gadgets2 的過程中,會把“/bin/sh” 正確的地址傳入到 R0,然后輸入 padding(“DDDD” ) 傳入到 R4 , 再執行system 函數從而獲取shell 。

這里在構造的 payload 的時候,需要注意大小端的問題,這里文件是小端的,在小端字節序中,最低有效字節存儲在最低地址。
AAAABBBBCCCCDDDD\x88\x30\xea\xb6\x3e\x19\xf9\xb6\x84\x30\xea\xb6DDDD\x54\xb1\xea\xb6
將構造的payload 發送過去
r `printf "AAAABBBBCCCCDDDD\x88\x30\xea\xb6\x3e\x19\xf9\xb6\x84\x30\xea\xb6DDDD\x54\xb1\xea\xb6"`
可以看到棧上成功覆蓋到我們構造的payload

繼續往下執行就可以拿到shell

六、總結
Ret2ZP 技術,和Ret2lib的原理相差不大,但是由于ARM處理器的特性,函數執行的過程中,需要從R0~R3寄存器中獲取參數,因此在構造ROP的時候,需要多考慮一點是將函數參數的值傳入到R0~R3 寄存器中。