背景:詭異的堆疊回溯
/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45) [0xb2c4b0e5]
/lib/i386-linux-gnu/libc.so.6(__stack_chk_fail+0x...) [0x...]
/ramdisk/xxxxxx(xxxxxxxxxxxx+0x444) [0x84b9224]
出現 __fortify_fail、__stack_chk_fail 即為 GCC Stack Smashing Protector (SSP) 偵測到堆疊緩衝區溢位並主動終止程式。
編譯選項:-fstack-protector vs -fstack-protector-all
| 選項 | 保護範圍 | 效能影響 |
-fstack-protector | 含字元陣列、聯合、結構體等「易受攻擊」區域變數的函式 | 較小 |
-fstack-protector-all | 所有函式 | 較大 |
原理:Canary 值(保護數)
- 函式序言:在儲存基址指標 (EBP) 與返回位址 之間,於堆疊放置一個隨機 Canary 值(通常 8 位元組)。
- 函式結尾:檢查 Canary 是否被改寫。若異常 → 呼叫
__stack_chk_fail → 觸發 __fortify_fail → abort()。
反組譯對比實證
原始碼:
char a; int i;
memcpy(&a, "ss", 2); // 溢位寫入 a (1 byte) 寫 2 bytes
memcpy(&i, "sssss", 4); // 正常寫入 i (4 bytes)
未加保護:無額外指令。
加 -fstack-protector:序言插入 Canary 存入、結尾插入比較跳轉指令。
關鍵發現:a 位址較高(接近堆疊頂),溢位會覆蓋 Canary 被偵測;i 位址較低,溢位可能跳過 Canary 直接破壞 EBP/返回位址 而不被偵測。
實際崩潰堆疊驗證
#0 __kernel_vsyscall
#1 raise
#2 abort
#3 __fortify_fail
#4 __stack_chk_fail
#5 main <-- 這裡觸發檢查失敗
局限性與補充防禦
- 無法防禦:跳過 Canary 的溢位、非區域變數溢位、堆積溢位、格式化字串漏洞。
- 互補方案:
-D_FORTIFY_SOURCE=2:編譯期/執行期緩衝區檢查(strcpy、sprintf 等)。
- AddressSanitizer (
-fsanitize=address):完整記憶體錯誤偵測(堆積/堆疊/Use-after-free)。
- 編譯器警告
-Warray-bounds、靜態分析工具。
修正流程
- 定位
__stack_chk_fail 呼叫點(上例為 main+0x...)。
- 檢視該函式區域變數陣列大小與寫入長度。
- 修正緩衝區尺寸或改用安全函式(
strncpy、snprintf、memcpy_s)。
總結
-fstack-protector 以極低成本大幅提高堆疊溢位偵測率,是 C/C++ 專案必開的編譯選項。但它非銀彈,需配合 _FORTIFY_SOURCE、Sanitizer、程式碼審查構建多層防禦體系。
- 上一篇文章: C 語言字串反轉完全指南:從整數陣列到字串的演算法優化之路
- 下一篇文章: C 語言 printf 格式化輸出技巧:%02hhX 完美解決位元組十六進制列印難題
Copyright ©
程式碼之家 All Rights Reserved