緩衝區溢出也是大家耳熟能詳的一種軟體漏洞了,我們經常會看到「某某軟體系統存在緩衝區溢出漏洞,攻擊者可能依據此漏洞獲得系統權限,請儘快下載安裝最新版本……」這樣的新聞。
其實,緩衝區溢出的基本原理並不複雜。緩衝區就是作業系統為函數執行專門劃分出的一段內存,包括棧(自動變量)、堆(動態內存)和靜態數據區(全局或靜態)。其中緩衝區溢出發生在棧裡,棧存放了函數的參數、返回地址、EBP(EBP是當前函數的存取指針,即存儲或者讀取數時的指針基地址,可以看成一個標準的函數起始代碼)和局部變量。結構如下:
當函數中對局部變量的賦值超過了為其分配的存儲空間,超出的部分就會覆蓋棧裡其他部分的數據,也就是發生了緩衝區溢出。如下圖所示:
在上圖中,原來函數的返回地址數據被其他數據覆蓋了,函數無法找到正確的返回地址,就會發生分段錯誤(Segmentation fault)。舉個簡單的代碼例子:
#include "stdafx.h"
#include <string.h>
char name[] = "This is a buffer overflow test.";
int _tmain(int argc, _TCHAR* argv[])
{
char buffer[8];
strcpy(buffer, name);
printf("%s\n",buffer);
return 0;
}
代碼裡只給buffer數組分配了8個字節的長度,但拷貝到buffer裡的name長度超過了8個字節,執行這段代碼,就會發生緩衝區溢出。雖然name的值輸出了,但程序結束時會崩潰。
如果用VS調試,會直接提示發生了緩衝區溢出的錯誤。
如果緩衝區溢出只是導致程序執行出錯,那麼看起來危害還不是那麼大。但如果溢出到返回地址的數據是另一段函數或代碼的入口地址,那麼這段函數或代碼就會被執行,這就是緩衝區溢出漏洞真正的危害所在。如下圖。
緩衝區溢出攻擊就是利用了上述原理,通過精心構造溢出數據,將函數返回地址修改為其他函數或代碼的地址,從而達到運行的目的。而且被攻擊的程序如果有管理員權限,那麼新指向的函數或代碼也會繼承其權限,這就是緩衝區溢出攻擊容易獲得系統最高權限的原因。在實際應用中,攻擊者一般將是返回地址指向一個shell程序,這樣通過shell就可以執行任意的操作了。
但實際上攻擊者是沒辦法確定新指向函數或代碼地址的,因為作業系統每次加載程序到進程空間的位置都是無法預測的,棧的位置實際是不固定的,因此通過直接修改函數返回地址的方法是不可行。攻擊者需要其他方法來確保程序能執行到正確的地址,這裡面最常用的就是藉助跳板。大體方法是這樣:通過構造溢出數據,將棧裡的函數參數修改成要執行的程序代碼(如shell),返回地址修改為系統特殊的指令jmpesp的地址,而jmpesp就是跳轉到棧寄存器。被攻擊程序執行後,首先將執行jmpesp命令,而jmpesp命令會使程序跳轉回esp所在位置,而這時esp位置就是函數參數位置(具體原因請參閱堆棧及寄存器相關知識),而函數參數已經被shell代碼所覆蓋,這樣shell就被執行了。如下圖:
緩衝區溢出的根本原因是運行的程序的代碼和數據在計算機內存中是一樣的,系統無法分別兩者,這就使通過數據修改代碼成為可能。而緩衝區溢出的直接原因是程序中沒有仔細檢查用戶輸入的參數的值,造成了數據的溢出。因此,對於別人的系統和程序,防範緩衝區溢出的方法就是勤打補丁和升級,堵塞漏洞。而對於自己開發的系統,有編譯器的邊界檢查、指針完整性檢查等方法。但我認為最好的方法是在代碼層次實施完善的參數及數據檢查,以及良好的編寫規範(凸顯了程序代碼編寫規範上一篇的重要性),這些都可以有效地防範緩衝區溢出攻擊。