緩沖區溢出概述
緩沖區溢出(Buffer Overflow)是一種經典的安全漏洞,當程序未對輸入長度進行檢查時,多余的數據會覆蓋相鄰內存區域,進而篡改程序控制流,達到執行任意代碼的目的。
1. 棧(Stack)與堆(Heap)
- 棧 (Stack):后進先出(LIFO)結構,用于管理函數調用。每次調用都會創建獨立棧幀,包含函數參數、返回地址、保存的寄存器(如 EBP)和局部變量。
- 堆 (Heap):用于動態分配內存,地址從低向高增長,需手動分配和釋放。堆與棧分區相互獨立。
2. 字節序(Endianness)
在多字節系統中:
- 大端序 (Big-Endian):高位字節存儲在低地址,常見于網絡協議(如 IP 頭)。
- 小端序 (Little-Endian):低位字節存儲在低地址,x86、ARM 默認采用此模式。
示例:32 位地址 0xbffffb80
在小端系統中的存儲順序:
內存地址增長 → 低地址: 0x80 0xfb 0xff 0xbf
在利用漏洞時,必須將地址按小端序寫入載荷(如 \x80\xfb\xff\xbf
),否則會跳轉到錯誤地址。
3. x86 棧幀布局與函數調用
高地址
+------------------+
| 參數 n | ← 調用者按右→左順序壓棧
| ... |
+------------------+
| 返回地址 (EIP) | ← `call` 指令自動壓棧
+------------------+
| 舊 EBP | ← `push ebp`
+------------------+ ← 新 EBP 指向此處
| 局部變量 | ← `sub esp, N`
| ... |
低地址
調用流程:
- 調用者按反序壓入參數。
call
將返回地址壓棧,并跳轉到函數入口。push ebp; mov ebp, esp; sub esp, N
設置新棧幀。leave; ret
恢復 EBP 并將返回地址彈棧到 EIP。
4. 緩沖區溢出攻擊流程
以以下易受攻擊函數為例:
void vulnerable(char *input) {
char buffer[256];
strcpy(buffer, input);
}
- 當
input
超過 256 字節,多余數據向高地址寫入,依次覆蓋:舊 EBP → 返回地址 → 參數區。 - 覆蓋返回地址后,執行
ret
時,EIP 跳轉到攻擊者指定地址。 - 若該地址指向包含 shellcode 的輸入區,即可實現任意代碼執行。
5. 構造利用載荷
./vuln $(python -c 'print "A"*268 + "\x80\xfb\xff\xbf" + "\x90"*20 + SHELLCODE')
- 偏移量:通過 GDB 找到 EIP 覆蓋點,此處為 268。
- 小端地址:
\x80\xfb\xff\xbf
對應內存地址 0xbffffb80
。 - NOP 雪橇:
\x90
填充,覆蓋返回地址與 shellcode 之間的區域,擴大跳板范圍。
6. 獲取 ESP 地址的方法
方法一:EIP 覆蓋測試
$(python -c 'print "A"*268 + "BBBB"')
,觀察 GDB 崩潰時:返回地址是否被 0x42424242
(BBBB
)替換。- 若匹配,說明偏移 268 后正好覆蓋返回地址。
方法二:ESP 跳板測試
在偏移基礎上繼續追加 NOP 和標記:
$(python -c 'print "A"*268 + "BBBB" + "C"*20')
當 EIP 被 BBBB
覆蓋后,程序崩潰時返回地址已彈棧,GDB 顯示 ESP 指向 CCCC...
區域,即 shellcode 起始處。
該地址即為跳轉目標,可按小端序寫入返回地址。
注意:多次測試時,若輸入長度變化(如從 272 → 292 字節),操作系統會為對齊在更低地址分配新的棧空間,導致 ESP 地址出現偏移差異。
7. NOP 雪橇與其作用范圍
- 目的:在返回地址到 shellcode 區間填充
\x90
,形成?NOP
?滑板,確保 EIP 落點可滑入真實 shellcode。 - 長度:通常幾十到數百字節,根據偏移范圍動態調整。
8. 壞字符測試(Bad Character Testing)
使用腳本生成連續字節串:
python -c 'print "".join([chr(i) for i in range(1,256)])'
將該字符串傳入程序,在 GDB 中查看內存(x/256x $esp
或 x/256b $esp
)。
若發現某字節丟失或被解釋異常,則需將其從 payload 中排除,直到所有字節都能正確通過。
9. 常見調用約定對比
約定 | 參數順序 | 清理者 | 示例 |
---|
cdecl | 右→左 | 調用者 | C 默認 |
stdcall | 右→左 | 被調用者 | Windows API |
fastcall | 前幾個通過寄存器 | 被調用者 | 性能優化 |
10. x86 與 x64 區別
- 寄存器寬度:x86 為 32 位,x64 為 64 位。
- 新增寄存器:x64 增加 R8–R15。
- 參數傳遞:x64 多數通過寄存器,棧負載減少。
以上內容整合了棧與堆、字節序、棧幀結構、利用流程、偏移與跳板測試、NOP 雪橇、壞字符測試及調用約定差異,建議結合 GDB 工具逐步驗證。
該文章在 2025/6/3 9:14:13 編輯過