天天看點

棧保護機制

一、CANNARY(棧溢出保護)

​ 棧溢出保護是一種用緩沖區溢出攻擊環節手段,當函數存在緩沖區溢出漏洞時,攻擊者可以覆寫棧上的傳回位址來讓shellcode能夠得到執行。當啟用棧保護後,函數開始執行的時候就會向往棧裡插入cookie資訊,當函數真正傳回的時候回驗證cookie資訊是否合法,若果不合法就會停止程式運作。攻擊者在覆寫傳回位址的時候往往也會将cookie資訊給覆寫掉,導緻棧保護檢查失敗,而阻止shellcode的執行,在Linux中将cookie資訊稱為canary。

當開啟棧溢出時不可以覆寫最後的canary内容,但是可以對canary前面進行溢出

GCC用法:

gcc -o test test.c // 預設情況下,不開啟Canary保護
gcc -fno-stack-protector -o test test.c //禁用棧保護
gcc -fstack-protector -o test test.c //啟用堆棧保護,不過隻為局部變量中含有 char 數組的函數插入保護代碼
gcc -fstack-protector-all -o test test.c //啟用堆棧保護,為所有函數插入保護代碼
-fno-stack-protector /-fstack-protector / -fstack-protector-all (關閉 / 開啟 / 全開啟)
           

二、FORTIFY

​ fortify是輕微的檢查,用于檢查是否存在緩沖區溢出的錯誤。适用于程式采用大量的字元串或者記憶體操作函數,如:

>>> memcpy():
	描述:void *memcpy(void *str1, const void *str2, size_t n)
    	 從存儲區str2複制n個字元到存儲區str1
  參數:str1 -- 指向用于存儲複制内容的目标數組,類型強制轉換為 void* 指針
    	 str2 -- 指向要複制的資料源,類型強制轉換為 void* 指針
    	 n -- 要被複制的位元組數
  傳回值:該函數傳回一個指向目标存儲區 str1 的指針
---------------------------------------------------------------------------------------
>>> memset():
  描述:void *memset(void *str, int c, size_t n)
    	 複制字元 c(一個無符号字元)到參數 str 所指向的字元串的前 n 個字元
  參數:str -- 指向要填充的記憶體塊
    	 c -- 要被設定的值。該值以 int 形式傳遞,但是函數在填充記憶體塊時是使用該值的無符号字元形式
    	 n -- 要被設定為該值的位元組數
  傳回值:該值傳回一個指向存儲區 str 的指針
---------------------------------------------------------------------------------------
>>> strcpy():
  描述:char *strcpy(char *dest, const char *src)
    	 把 src 所指向的字元串複制到 dest,容易出現溢出
  參數:dest -- 指向用于存儲複制内容的目标數組
    	 src -- 要複制的字元串
  傳回值:該函數傳回一個指向最終的目标字元串 dest 的指針
-------------------------------------------------------------------------------------->>> stpcpy():
  描述:extern char *stpcpy(char *dest,char *src)
    	 把src所指由NULL借宿的字元串複制到dest所指的數組中
  說明:src和dest所指記憶體區域不可以重疊且dest必須有足夠的空間來容納src的字元串傳回指向dest結尾處字元(NULL)的指針	 
  傳回值:
---------------------------------------------------------------------------------------    >>> strncpy():
  描述:char *strncpy(char *dest, const char *src, size_t n)
    	 把 src 所指向的字元串複制到 dest,最多複制 n 個字元。當 src 的長度小于 n 時,dest 的剩餘部分将用空位元組填充
  參數:dest -- 指向用于存儲複制内容的目标數組
    	 src -- 要複制的字元串
    	 n -- 要從源中複制的字元數
  傳回值:該函數傳回最終複制的字元串
--------------------------------------------------------------------------------------->>> strcat():
  描述:char *strcat(char *dest, const char *src)
    	 把 src 所指向的字元串追加到 dest 所指向的字元串的結尾
  參數:dest -- 指向目标數組,該數組包含了一個 C 字元串,且足夠容納追加後的字元串
    	 src -- 指向要追加的字元串,該字元串不會覆寫目标字元串
  傳回值:
--------------------------------------------------------------------------------------->>> strncat():
  描述:char *strncat(char *dest, const char *src, size_t n)
    	 把 src 所指向的字元串追加到 dest 所指向的字元串的結尾,直到 n 字元長度為止
  參數:dest -- 指向目标數組,該數組包含了一個 C 字元串,且足夠容納追加後的字元串,包括額外的空字元
    	 src -- 要追加的字元串
    	 n -- 要追加的最大字元數
  傳回值:該函數傳回一個指向最終的目标字元串 dest 的指針
--------------------------------------------------------------------------------------->>> sprintf():PHP
  描述:sprintf(format,arg1,arg2,arg++)
    	 arg1、arg2、++ 參數将被插入到主字元串中的百分号(%)符号處。該函數是逐漸執行的。在第一個 % 符号處,插入 arg1,在第二個 % 符号處,插入 arg2,依此類推
  參數:format -- 必需。規定字元串以及如何格式化其中的變量
    	 arg1 -- 必需。規定插到 format 字元串中第一個 % 符号處的參
    	 arg2 -- 可選。規定插到 format 字元串中第二個 % 符号處的參數
    	 arg++ -- 可選。規定插到 format 字元串中第三、四等等 % 符号處的參數
  傳回值:傳回已格式化的字元串
--------------------------------------------------------------------------------------->>> snprintf():
  描述:int snprintf ( char * str, size_t size, const char * format, ... )
    	 設将可變參數(...)按照 format 格式化成字元串,并将字元串複制到 str 中,size 為要寫入的字元的最大數目,超過 size 會被截斷
  參數:str -- 目标字元串
    	 size -- 拷貝位元組數(Bytes)如果格式化後的字元串長度大于 size
    	 format -- 格式化成字元串
  傳回值:如果格式化後的字元串長度小于等于 size,則會把字元串全部複制到 str 中,并給其後添加一個字元串結束符 \0。 如果格式化後的字元串長度大于 size,超過 size 的部分會被截斷,隻将其中的 (size-1) 個字元複制到 str 中,并給其後添加一個字元串結束符 \0,傳回值為欲寫入的字元串長度
--------------------------------------------------------------------------------------->>> vsprintf():PHP
  描述:vsprintf(format,argarray) 
    	 與 sprintf() 不同,vsprintf() 中的參數位于數組中。數組元素将被插入到主字元串中的百分号(%)符号處。該函數是逐漸執行的
  參數:format -- 必需。規定字元串以及如何格式化其中的變量
    	 argarray -- 必需。帶有參數的一個數組,這些參數會被插到 format 字元串中的 % 符号處
  傳回值:以格式化字元串的形式傳回數組值
--------------------------------------------------------------------------------------->>> vsnprintf():
  描述:int vsnprintf (char * s, size_t n, const char * format, va_list arg )
    	 将格式化資料從可變參數清單寫入大小緩沖區
如果在printf上使用格式,則使用相同的文本組成字元串,但使用由arg辨別的變量參數清單中的元素而不是附加的函數參數,并将結果内容作為C字元串存儲在s指向的緩沖區中 (以n為最大緩沖區容量來填充)。如果結果字元串的長度超過了n-1個字元,則剩餘的字元将被丢棄并且不被存儲,而是被計算為函數傳回的值。在内部,函數從arg辨別的清單中檢索參數,就好像va_arg被使用了一樣,是以arg的狀态很可能被調用所改變。在任何情況下,arg都應該在調用之前的某個時刻由va_start初始化,并且在調用之後的某個時刻,預計會由va_end釋放
  參數:s -- 指向存儲結果C字元串的緩沖區的指針,緩沖區應至少有n個字元的大小
    	 n -- 在緩沖區中使用的最大位元組數,生成的字元串的長度至多為n-1,為額外的終止空字元留下空,size_t是一個無符号整數類型
    	 format -- 包含格式字元串的C字元串,其格式字元串與printf中的格式相同
     	 arg -- 辨別使用va_start初始化的變量參數清單的值
  傳回值:如果n足夠大,則會寫入的字元數,不包括終止空字元。如果發生編碼錯誤,則傳回負數。注意,隻有當這個傳回值是非負值且小于n時,字元串才被完全寫入
--------------------------------------------------------------------------------------->>> gets():
  描述:char *gets(char *str)
    	 從标準輸入 stdin 讀取一行,并把它存儲在 str 所指向的字元串中。當讀取到換行符時,或者到達檔案末尾時,它會停止,具體視情況而定
  參數:str -- 這是指向一個字元數組的指針,該數組存儲了 C 字元串
  傳回值:如果成功,該函數傳回 str。如果發生錯誤或者到達檔案末尾時還未讀取任何字元,則傳回 NULL 	
           

GCC用法:

gcc -D_FORTIFY_SOURCE=1  僅僅隻在編譯時進行檢查(尤其是#include <string.h>這種檔案頭)
gcc -D_FORTIFY_SOURCE=2  程式執行時也會進行檢查(如果檢查到緩沖區溢出,就會終止程式)
           

在-D_FORTIFY_SOURCE=2時,通過對數組大小來判斷替換strcpy、memcpy、memset等函數名,進而達到防止緩沖區溢出的作用

三、NX(DEP)

​ NX即No-eXecute(不可執行)的意思,NX(DEP)的基本原理是将資料所在記憶體表示為不可執行,當程式溢出成功轉入shellcode時,程式會嘗試在資料頁面上執行指令,此時CPU就會抛出異常,而不去執行惡意代碼,主要防止在資料區溢出

​ 正常在棧溢出時通過跳轉指令跳轉至shellcode,但是NX開啟後CPU會對資料區域進行檢查,當發現正常程式不執行,并跳轉至其他位址後會抛出異常,接下來不會繼續執行shellcode,而是去轉入異常處理,處理後會禁止shellcode繼續執行

GCC 用法:

gcc -o test test.c // 預設情況下,開啟NX保護
gcc -z execstack -o test test.c // 禁用NX保護
gcc -z noexecstack -o test test.c // 開啟NX保護
-z execstack / -z noexecstack (關閉 / 開啟)
           

四、PIE(ASLR)

​ 一般情況下NX(Windows平台上稱為DEP)和位址空間分布随機化(ASLR)會同時工作。記憶體位址随機化機制有三種情況

0 - 表示關閉程序位址随機化
1 - 表示将mmap的基位址,棧基位址和.so位址随機化
2 - 表示在1的基礎上增加heap的位址随機化
           

可以防止Ret2libc方式針對DEP的攻擊。ASLR和DEP配合使用,能有效阻止攻擊者在堆棧上運作惡意代碼

Linux下關閉PIE的指令:

sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
           

GCC用法:

gcc -o test test.c // 預設情況下,不開啟PIE
gcc -fpie -pie -o test test.c // 開啟PIE,此時強度為1
gcc -fPIE -pie -o test test.c // 開啟PIE,此時為最高強度2
gcc -fpic -o test test.c // 開啟PIC,此時強度為1,不會開啟PIE
gcc -fPIC -o test test.c // 開啟PIC,此時為最高強度2,不會開啟PIE
-no-pie / -pie (關閉 / 開啟)
           

gcc中的-fPIC選項是針對某些特殊機型做了特殊處理,比如适合動态連結并能避免超出GOT大小限制之類的錯誤

五、RELRO

​ 在Linux系統安全領域資料可以寫的存儲區就會是攻擊的目标,尤其是存儲函數指針的區域。是以在安全防護的角度應盡量減少可寫的存儲區域

RELRO會設定符号重定向表格為隻讀或者程式啟動時就解析并綁定所有動态符号,進而減少對GOT表的攻擊。如果RELRO為Partial RELRO,就說明對GOT表具有寫權限

GCC用法:

gcc -o test test.c // 預設情況下,是Partial RELRO
gcc -z norelro -o test test.c // 關閉,即No RELRO
gcc -z lazy -o test test.c // 部分開啟,即Partial RELRO
gcc -z now -o test test.c // 全部開啟
-z norelro / -z lazy / -z now (關閉 / 部分開啟 / 完全開啟)
           

繼續閱讀