使用Linux進行緩沖區溢出實驗的配置記錄
在基礎的軟件安全實驗中,緩沖區溢出是一個基礎而又經典的問題。最基本的緩沖區溢出即通過合理的構造輸入數據,使得輸入數據量超過原始緩沖區的大小,從而覆蓋數據輸入緩沖區之外的數據,達到諸如修改函數返回地址等目的。但隨著操作系統和編譯器針對緩沖區溢出問題引入防護機制,初學者想要由簡入繁的學習和實踐緩沖區溢出的原理變得困難。在 Linux 環境下,用戶可以通過設置編譯和系統環境來去除某些防護措施,從而方便的完成某些簡單的緩沖區溢出實驗。
1.關閉SSP( Stack Smashing Protector )
實現原理
SSP 是 gcc 提供的針對棧上緩沖區溢出提供檢查的機制。典型的緩沖區溢出攻擊會通過構造輸入數據覆蓋緩沖區外的數據,實現一定的溢出效果,如修改函數返回地址等。啟用 SSP 機制後,在編譯器生成的代碼中,對於那些存在緩沖區的函數,函數開始時會在對應棧幀中入棧一個隨機值( canary ),在函數調用結束準備返回時,編譯器生成的代碼會檢查上述引入的隨機值是否發生了變化,若發生變化則說明出現了緩沖區溢出,控制流會轉移至對應的處理函數處。
以下列代碼為例進行說明
void simpleCopy( char *src )
{
char buff[10];
strcpy( buff , src );
}
上述代碼將緩沖區 src 中的數據簡單的拷貝至 buff 數組中,而沒有考慮 src 中的數據量是否超出了 buff 數組的容納能力。
使用 gdb 查看得到 simpleCopy 的匯編如圖所示。
在開啟了 SSP 機制的 gcc 生成的代碼中,函數開始數據處理之前,將 gs:0x14 處的值存放在了 %ebp - 12 處,在函數返回時,對 %ebp - 12 處的值進行檢查,若此時值不等於 gs:0x14 處的值,則說明存在數據的覆蓋和修改,程序會轉入 __stack_chk_fail 執行。通過 simpleCopy("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")調用函數的結果如下圖所示。上述 simpleCopy 函數中,緩沖區 buff 首地址為 %ebp - 22 , 而引入的 canary 存放在 %ebp - 12 處,當緩沖區存在數據溢出時,會修改 canary 的值,從而在函數返回時被檢測到。
選項設置
gcc的編譯設置中,默認設置SSP機制是有效狀態,用戶可在編譯時加入 -fno-stack-protector 參數禁用SSP機制。
-fstack-protector //編譯時啟用SSP機制 -fno-stack-protector //編譯時禁用SSP機制
2.關閉DEP( Data Execution Prevention )
在某些棧緩沖區數據溢出實踐中,會將構造好的機器指令序列通過輸入寫入位於棧上的輸入緩沖區中,同時,構造數據覆蓋位於數據緩沖區外的函數調用的返回地址,將其值修改為緩沖區中構造好的機器指令序列的地址,這樣當函數調用返回時,程序控制流會跳轉至輸入緩沖區中的構造指令序列,從而實現執行流的修改和劫持。而現代編譯器中,為防止用戶通過棧上的緩沖區構造數據,使用了DEP機制,即限制內存的屬性,使得某些可寫內存不可執行( 如棧 )或可執行內存不可寫( 如.text段),從而達到防護的效果。
使用 gcc 進行編譯時,可通過 -z execstack 參數,使得最終生成的程序中棧內存段具備可執行權限。
-z execstack //設置棧內存段具備可執行權限
在程序運行時,可通過查看 cat /proc/pid/maps 文件查看 pid 所對應的進程的內存映射情況,其中包括對進程的段的屬性描述。
3.關閉ASLR( address space layout randomization )
在基礎棧緩沖區溢出實踐中,一個重要的步驟為定位某些目標的內存映射地址,如最簡單的 shellcode 需定位註入棧上的構造指令序列的地址( 從而修改函數返回地址指向該指令序列),ret2libc 方法則需要攻擊者定位位於內存中的標準庫函數的內存映射地址等。ASLR ,即內存布局隨機化機制由操作系統實現,其主要被劃分為映像隨機化、棧隨機化和堆隨機化這幾類,分別針對程序的加載基地址、棧基址和堆基址進行隨機化。
設計原理
已經編譯完成的程序的各個段在內存中的加載基位地址是固定的,也就是說程序運行時的內存映射情況是固定的,攻擊者可以通過多次調試運行程序,獲得他們所需的目標地址。在編譯好的 ELF 文件中,段頭部表描述了程序需加載入內存的各個段的基地址,可通過 readelf -h filename 對其進行查看。對於運行中的程序,可以通過 cat /proc/pid/maps 查看 pid 對應的進程的內存映射情況,包括棧、堆的基地址等。
objdump -h hello //查看可執行文件hello的段頭部表 cat /proc/pid/maps //pid為進程號,查看pid對應進程的內存映射情況
(1)通過 objdump -h hello 查看該 ELF 文件的.text 加載基地址為 0x08048370,而.rodata段的加載基地址為 0x08048558。
(2)通過 cat /proc/$$/maps 查看 shell 的內存映射情況(64位Ubuntu),可以看到 shell 進程的 stack 段的內存範圍以及其屬性為可讀可寫但不可執行,p 表示該虛擬內存段為私有的( private ),s 表示該虛擬內存段為共享的( shared )。
在過去的編譯環境中,程序的數據段包括.text、.bss、.rodata 段加載進內存的基地址在編譯時即已確定,程序運行時進程中 stack 和 heap 的起始地址也總是固定的,這就使得攻擊者可以較容易的定位內存中目標的地址,從而進行攻擊嘗試。在引入 ALSR 機制後,對於程序的某個特定的段( .text 、stack 、heap ),操作系統會在加載時自其原始的起始基地址處加入一個隨機大小的“填充”,對應的程序數據總是自“填充”區域後開始,由於每次使用的“填充”的大小並不固定,同一程序多次運行時的內存布局會發生變化,從而使得攻擊者較難通過固定的地址去訪問其所需要的目標。
選項設置
用戶可通過cat /proc/sys/kernel/randomize_va_space 查看當前 ALSR 機制的運行情況。其中,為 0 表示 ALSR 機制未啟用,為 1 表示 ALSR 機制會隨機化 stack、vdso和 mmap 的起始基地址,為 2 表示除對上述目標進行隨機化外還會對堆基地址進行隨機化。用戶可通過 echo 0 > /proc/sys/kernel/randomize_va_space 設置關閉 ALSR。
cat /proc/sys/kernel/randomize_va_space //查看 ALSR 機制的運行情況 echo 0 > /proc/sys/kernel/randomize_va_space //設置關閉 ALSR 機制,需要 root 用戶進行操作
上述對 /proc/sys/kernel/randomize_va_space 的操作為針對系統的全局設置,需要 root 權限且存在弊端,用戶可通過以下命令將當前終端 /bin/bash 的 ALSR 機制關閉,則通過該 shell 運行的程序均不會啟動 ALSR 機制。該終端關閉後上述設置即失效。
setarch `uname -m` -R /bin/bash
4.通過 gdb 獲得目標地址
基礎的緩沖區溢出實踐通常需要確定運行狀態下程序中的某些地址,如需要確定輸入緩沖區的起始地址從而獲得註入緩沖區中的機器指令的地址等。在 Linux 環境下,可通過 gdb 對程序進行動態調試,從而獲得程序運行狀態下的信息( 關閉 ALSR 機制後效果更好 ),基礎的 gdb 操作可參見筆者的文章Linux下編輯、編譯、調試命令總結——gcc和gdb描述。使用 gdb 可以方便的獲取程序動態運行狀態下的信息,但通過 gdb 動態調試獲取的諸如緩沖區的起始地址等信息可能與程序實際運行時的信息並不相同,從而影響緩沖區溢出實踐的效果。關於保證通過 gdb 動態調試程序獲得的局部變量的地址與直接運行該程序時的地址一致的問題,筆者另外通過博文針對 Linux 環境下 gdb 動態調試獲取的局部變量地址與直接運行程序時不一致問題的解決方案進行描述。
5.總結
為了完成基礎的緩沖區溢出實踐,用戶可通過 gcc 的編譯選項 -fno-stack-protector 關閉 SSP 保護機制,通過 -z execstack 選項使得生成的可執行程序的棧可執行,通過 setarch `uname -m` -R /bin/bash 設置關閉當前終端中運行程序的 ALSR 機制,並通過 gdb 動態調試獲取某些所需要的程序局部變量的信息。
6.參考資料
1.Stack Smashing Protector - OSDev Wiki
2.protect against buffer overflow exploits
3.Address space layout randomization - Wiki
使用Linux進行緩沖區溢出實驗的配置記錄