棧溢位漏洞及棧溢位攻擊
1. 棧溢位的原因
棧溢位(stack-based buffer overflows)算是安全界常見的漏洞。一方面因為程式設計師的疏忽,使用了
strcpy、sprintf 等不安全的函式,增加了棧溢位漏洞的可能。另一方面,因為棧上儲存了函式的返回地址等資訊,因此如果攻擊者能任意覆蓋棧上的資料,通常情況下就意味著他能修改程式的執行流程,從而造成更大的破壞。這種攻擊方法就是棧溢位攻擊(stack
smashing attacks)。
棧是從高地址到低地址增長的。
棧溢位攻擊的原因是由於程式中缺少錯誤檢測,另外對緩衝區的潛在操作(比如字串的複製)都是從記憶體低址到高址,而函式呼叫的返回地址往往就在緩衝區的上方(當前棧底),這為我們覆蓋返回地址提供了條件
#include <stdio.h>
#include <string.h>
int bof(FILE *badfile){
char buffer[20];
fread(buffer, sizeof(char), 100, badfile);
return 1;
}
int main(){
FILE *badfile;
badfile = fopen("badfile", "r");
bof(badfile);
printf("Returned Properly\n");
fclose(badfile);
return 0;
}
DEMO的邏輯很簡單,就是從badfile檔案中讀取最長100位元組的資料,然而buffer的長度只有20位元組,所以這裡是有可能發現棧溢位的。
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $LC0, 4(%esp)
movl $LC1, (%esp)
call _fopen
movl %eax, 28(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call _bof
movl $LC2, (%esp)
call _puts
movl 28(%esp), %eax
movl %eax, (%esp)
call _fclose
movl $0, %eax
leave
ret
_bof:
pushl %ebp
movl %esp, %ebp
subl $56, %esp
movl 8(%ebp), %eax
movl %eax, 12(%esp)
movl $100, 8(%esp)
movl $1, 4(%esp)
leal -28(%ebp), %eax
movl %eax, (%esp)
call _fread
movl $1, %eax
leave
ret
我們只關注從main進入bof以及bof執行完畢後返回main這個過程。
- 在呼叫從call __fopen開始看,在進入__bof前,badfile地址已經入棧。
- call _bof語句的作用則是把下一條指令(movl $LC2, (%esp))入棧,也就是_bof執行完畢後的返回地址
- 在進入_bof後,第一時間把ebp入棧,ebp是當前棧底,用於恢復esp的。
從分佈圖可以看到,系統實際分配給buffer的長度是28位元組,接下來就是舊的棧底地址,bof返回地址和badfile地址。因此當badfile的內容長度是低於28位元組的情況下,程式依然可以正常執行。但當badfile的內容長度超出28位元組,就會直接把old EBP和ret address覆蓋掉,這就達到了修改返回地址的目的了。
2. 棧溢位漏洞的防護:
stack smashing attacks並不是無敵的,其對抗技術就是DEP(Data
Execution Prevention )和ASLR(Address Space Layout Radomization),通過這兩種技術的保護下Stack
smashing attacks一定程度上揭制。
DEP: 把stack 段設定為 不可執行。
另一種針對stack smashing 的防護技術就是 : stack canary.
2.1 stack canary
要檢測對函式棧的破壞,需要修改函式棧的組織,在緩衝區和控制資訊(如 EBP 等)間插入一個 canary word。這樣,當緩衝區被溢位時,在返回地址被覆蓋之前 canary word 會首先被覆蓋。通過檢查 canary word 的值是否被修改,就可以判斷是否發生了溢位攻擊。
3. GCC 中使用 stack canary 技術的堆疊保護:
Stack Guard 是第一個使用 Canaries 探測的堆疊保護實現,它於 1997 年作為 GCC 的一個擴充套件釋出。最初版本的 Stack Guard 使用 0x00000000 作為 canary word。儘管很多人建議把 Stack Guard 納入 GCC,作為 GCC 的一部分來提供堆疊保護。但實際上,GCC 3.x 沒有實現任何的堆疊保護。直到 GCC 4.1 堆疊保護才被加入,並且 GCC4.1 所採用的堆疊保護實現並非 Stack Guard,而是 Stack-smashing Protection(SSP,又稱 ProPolice)。
SSP 在 Stack Guard 的基礎上進行了改進和提高。它是由 IBM 的工程師 Hiroaki Rtoh 開發並維護的。與 Stack Guard 相比,SSP 保護函式返回地址的同時還保護了棧中的 EBP 等資訊。此外,SSP 還有意將區域性變數中的陣列放在函式棧的高地址,而將其他變數放在低地址。這樣就使得通過溢位一個數組來修改其他變數(比如一個函式指標)變得更為困難。
GCC與 stack 保護相關的編譯選項:
-fstack-protector:
啟用堆疊保護,不過只為區域性變數中含有 char 陣列的函式插入保護程式碼。
-fstack-protector-all:
啟用堆疊保護,為所有函式插入保護程式碼。
-fno-stack-protector:
禁用堆疊保護。