Canary:(取名自地下煤礦的金絲雀,因為它能夠比曠工更早的發現煤氣洩漏,有預警的作用)
,是一種用於對抗棧溢位攻擊
的技術,即SSP安全機制
,有時候也叫做Stack cookies
。
Canary的值是棧
上的一個亂數
,在程式啟動時隨機生成並且儲存在比函數返回地址
更低的位置。(看過棧溢位文章的應該都知道,我們棧溢位就是為了覆蓋返回地址),由於我們覆蓋到返回地址,那麼不可避免的會覆蓋到這個亂數(Cannary),程式在函數返回前,檢查下Cannary是否被覆蓋從而判斷是否被棧溢位,從而達到保護棧的目的。
#預設情況下是會開啟Canary保護的
#禁用棧保護
gcc -fno-stack-protector cannary.c -o cannary
#開啟區域性保護(只保護區域性變數中含有char陣列的函數)
gcc -fstack-protector cannary.c -o cannary
#開啟全域性保護
gcc -fstack-protector-all cannary.c -o cannary
#命令速記
stack-protector(棧保護)
-f(開啟)
-fno(關閉)
-all(全部)
Cannary通常可以分為3中型別,terminator、random、random XOR
,具體的實現有StackGuard、StackShield、Propoliced等
。
大多數緩衝區溢位攻擊都基於某些字串操作,比如說strcpy
,這些操作基本上以字串終止符結束\x00
。
終止符金絲雀包含 NULL(0x00)、CR(0x0d)、LF(0x0a)和 EOF(0xff)
這四個字元,這四個字元應終止大多數位符串操作,從而使溢位嘗試無害。
這可以防止使用 strcpy() 和其他方法的攻擊,這些方法在複製空字元時返回。
繞過方法:
攻擊者可以使用其已知值覆蓋金絲雀,並使用特製值覆蓋返回地址,從而繞過這種型別的保護,從而導致程式碼執行。
當使用非字串函數來複制緩衝區並且緩衝區內容和緩衝區長度都受攻擊者控制時,可能會發生這種情況。
隨機金絲雀是在程式執行時隨機選擇的。
使用此方法,攻擊者無法在程式啟動之前通過搜尋可執行映像來了解金絲雀值。隨機值取自 /dev/urandom(如果可用),如果不支援 /dev/urandom,則通過hash一天中的時間來建立。這種隨機性足以阻止大多數預測嘗試。
繞過方法:
需要配合資訊洩漏漏洞
如果應用程式中存在可用於讀取金絲雀值的資訊洩漏漏洞,則可以繞過這種保護。
隨機互斥或金絲雀是使用全部或部分控制資料(影格指標+返回地址等)進行互斥或加干擾的隨機金絲雀。
這樣,一旦金絲雀或控制資料被破壞,金絲雀值就是錯誤的,它將導致程式立即終止。
洩露記憶體中的canary,如通過格式化字串漏洞列印出來
one-by-one爆破,但是一般是多執行緒的程式,產生新執行緒後canary不變才行。最高位為00。
劫持_stack_chk_fail函數,canary驗證失敗會進行該函數,__stack_chk_fail 函數是一個普通的延遲繫結函數,可以通過修改 GOT 表劫持這個函數。
覆蓋執行緒區域性儲存TLS中的canary,溢位尺寸比較大可以用。同時修改棧上的canary和TLS中的canary.
No-eXecute(NX)表示不可執行
,其原理是將資料所在的記憶體頁標識為不可執行。
相當於Windows平臺上的DEP(系統記憶體保護),NX的實現由軟體和硬體共同完成
VirtualAlloc
、VirtualProtect
等mmap
、mprotect
等if ${readelf} -l "${1}" 2>/dev/null | grep -q 'GNU_STACK'; then
if ${readelf} -l "${1}" 2>/dev/null | grep 'GNU_STACK' | grep -q 'RWE'; then
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
else
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' nx="yes"' '"nx":"yes",'
fi
else
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
fi
#判斷是否有GNU_STACK,繼續判斷是否有RWE許可權,否則就是開啟了NX保護.
繞過方式:
在Linx中,當裝載器將程式載入進記憶體空間後,將程式的.text節
標記為可執行,而其餘的資料段.data、.bss、.rodata
以及堆疊
均為不可執行。
所以:無法用傳統的修改GOT表來執行shellcode.
繞過的正確方法:
攻擊者可以通過程式碼重用來執行攻擊(ret2libc)。
PIE(Position-Independent Executable
) 中文解釋為地址無關可執行檔案。
也叫做ASLR(地址空間隨機化
)全稱是叫(Address Space Layout Randomization
)。
這個搞過逆向的應該都不會陌生,比如搞過Windows逆向或者是iOS/macOS逆向的,我們在逆一個程式的時候。用動態偵錯程式去偵錯他的時候,他的裝載地址或入口點地址,每次重啟後都是會變化的,
所以每次都需要在IDA中計算出基地址,然後再加上模組的基址才能在偵錯程式中找到真實的記憶體地址,在iOS中則每次都要image list -o -f
來找裝載的地址。
ASLR cat /proc/sys/kernel/randomize_va_space
有三種情況:
0:表示全部關閉
1:表示部分開啟
2:表示完全開啟(在部分開啟的基礎上增加了heap的隨機化)
ASLR | Executable | PLT | Heap | Stack | Shared Libraries |
---|---|---|---|---|---|
0 | × | × | × | × | × |
1 | × | × | × | √ | √ |
2 | × | × | √ | √ | √ |
2+pie | √ | √ | √ | √ | √ |
PIE 位置無關可執行檔案,在應用層的編譯器上實現,通過將程式編譯為位置無關程式碼PIC,使程式載入到任意位置,就像是一個特殊的共用庫。PIE會一定程度上影響效能。
sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
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
#助記
-pie (開啟)
-no-pie(關閉)
if ${readelf} -h "${1}" 2>/dev/null | grep -q 'Type:[[:space:]]*EXEC'; then
echo_message '\033[31mNo PIE \033[m ' 'No PIE,' ' pie="no"' '"pie":"no",'
elif ${readelf} -h "${1}" 2>/dev/null | grep -q 'Type:[[:space:]]*DYN'; then
if ${readelf} -d "${1}" 2>/dev/null | grep -q 'DEBUG'; then
echo_message '\033[32mPIE enabled \033[m ' 'PIE enabled,' ' pie="yes"' '"pie":"yes",'
else
echo_message '\033[33mDSO \033[m ' 'DSO,' ' pie="dso"' '"pie":"dso",'
fi
elif ${readelf} -h "${1}" 2>/dev/null | grep -q 'Type:[[:space:]]*REL'; then
echo_message '\033[33mREL \033[m ' 'REL,' ' pie="rel"' '"pie":"rel",'
else
echo_message '\033[33mNot an ELF file\033[m ' 'Not an ELF file,' ' pie="not_elf"' '"pie":"not_elf",'
fi
FORTIFY_SOURCE(原始碼增強),這個其實有點類似與Windows中用新版Visual Studio
進行開發的時候,當你用一些危險函數比如strcpy、sprintf、strcat
,編譯器會提示你用xx_s
加強版函數。
FORTIFY_SOURCE本質上一種檢查和替換機制,對GCC和glibc的一個安全修補程式。
目前支援memcpy
, memmove
, memset
, strcpy
, strncpy
, strcat
, strncat
,sprintf
, vsprintf
, snprintf
, vsnprintf
, gets
等。
#預設Ubuntu16.04下是關閉的,測試發現Ubuntu18.04是開啟的
gcc -D_FORTIFY_SOURCE=1 僅僅只在編譯時進行檢查(尤其是#include <string.h>這種檔案頭)
gcc -D_FORTIFY_SOURCE=2 程式執行時也會進行檢查(如果檢查到緩衝區溢位,就會終止程式)
#助記
FORTIFY_SOURCE(程式碼增強)
-D 1(開啟緩衝區溢位攻擊檢查)
-D 2(開啟緩衝區溢位以及格式化字串攻擊檢查) ,通過陣列大小來判斷替換`strcpy、memcpy、memset`等函數名,來防止緩衝區溢位。
RELRO全稱:Relocation Read-Only
(重定位表唯讀),之前文章有介紹過動態連結(GOT表、PLT表)需要延遲繫結。
在啟用延時繫結時,符號的解析只發生在第一次使用的時候,該過程是通過PLT表進行的,解析完成後,相應的GOT表條目才會修改為正確的函數地址。因此,在延遲繫結的情況下,.got.plt
必須是可寫的。攻擊者就可以通過篡改地址
劫持程式。
RELRO就是為了解決延遲繫結的安全問題的,之前可以看到我們很輕鬆的用偵錯程式修改了GOT表,達到了劫持效果,RELRO會把這些延遲繫結表設定成唯讀,防止來修改。
RELRO一般有兩種形式:
Ubuntu 16.04
開始預設的GCC設定,幾乎所有的二進位制檔案都至少有部分RELRO。這樣僅僅只能防止全域性變數上的緩衝區溢位從而覆蓋到GOT。
(.dynamic、.got
)初始化時候標記為唯讀。
使整個GOT唯讀,從而無法被覆蓋,但是這樣會大大增加程式的啟動時間,因為需要在啟動之前解析所有的符號。
也就是延遲繫結被禁止了,所有的匯入符號在開始時候被解析,.got.plt
段會被完全初始化為目標函數的最終地址
,並且被mprotect
標記為唯讀
,但是.got.plt
會被合併到.got
,也就看不到這些段了,對效能會造成影響。
#關閉RELRO
-z norelro 禁用relro
#開啟RELRO
-z lazy 開啟Partial RELRO(部分RELRO)
-z now FULL PARTIAL(完全開啟RELRO)
#助記
-z (RELRO)
norelro (關閉RELRO)
lazy (開啟部分RELRO)
now (完全開啟RELRO)
最後感謝大家的閱讀,本菜雞也是剛學,文章中如有錯誤請及時指出。
大家也可以來群裡罵我哈哈哈,群裡有PWN、RE、WEB大佬,歡迎交流
參考文章 :
http://www.manongzj.com/blog/27-gvafmooopa.html (PWN保護機制詳解)
https://www.cnblogs.com/ttxs69/p/pwn_canary.html (PWN之Canary學習)
https://lzeroyuee.cn/2021/02/23/Linux-Pwn-安全機制/ (Linux Pwn - 安全機制)
https://www.redhat.com/en/blog/security-technologies-stack-smashing-protection-stackguard (安全技術:堆疊粉碎保護 (StackGuard))
https://clibre.io/blog/por-secciones/hardening/item/413-proteccion-de-ejecutables-fortify-source (可執行檔案保護:FORTIFY_SOURCE)
《CTF權威指南(PWN篇)》
本文來自部落格園,作者:VxerLee,轉載請註明原文連結:https://www.cnblogs.com/VxerLee/p/16393508.html 專注逆向、網路安全 ——VxerLee