National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 78 24°C PM2.5 18
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 13

記憶體漏洞

緩衝區溢位的原理與防護機制
Interaction 35 minute(s)

Learning Objectives

  • 描述堆疊框架的基本結構,並說明區域變數與回傳位址的相對位置
  • 由緩衝區大小計算寫入超過長度時會覆蓋到哪些資料
  • 說明 Stack Canary、NX、ASLR 各自能解決與不能解決的問題
  • 將不安全的複製改為有長度限制的版本,並說明記憶體安全語言的作用

堆疊框架的結構

作業系統軌介紹過,函式被呼叫時,系統會在堆疊上配置一塊空間,稱為堆疊框架(stack frame)。框架裡有函式的區域變數,也有函式結束時需要的控制資訊:保存的框架指標與回傳位址。回傳位址記錄函式結束後要回到程式的哪個位置。

資料與控制資訊相鄰存放,這是本課主題的根源。C 語言的陣列沒有內建的長度檢查,下列函式把使用者的輸入複製到 8 位元組的緩衝區:

void check_user(const char *input) {
    int  is_admin = 0;
    char buf[8];
    strcpy(buf, input);          // 複製到遇見結尾的 NUL 為止,不檢查 buf 的大小
    if (is_admin) grant_admin();
    puts(buf);
}

C 字串以一個值為 0 的位元組(NUL)結尾,所以 buf[8] 最多放 7 個字元。若輸入更長,strcpy 會繼續往高位址寫入,覆蓋相鄰的資料。這類弱點稱為緩衝區溢位(buffer overflow),寫入發生在堆疊上時稱為堆疊緩衝區溢位(CWE-121)。在 C 標準中,這屬於未定義行為,實際結果取決於編譯器與環境,可能沒有明顯症狀,也可能造成程式停止或資料毀損。

本課的模擬環境。 下方的記憶體完全由瀏覽器內的模型算出,不是真的記憶體,沒有真實位址,也不執行機器碼。輸入內容固定由程式產生(A、B、C…),只能調整長度。本課只說明原理與防護,不涉及任何攻擊的實作方式。未經授權測試他人的系統可能觸犯法律(第 1 課)。

互動練習:寫入長度與覆寫範圍

調整輸入長度後按「執行模擬程式」。藍色是寫在 buf 內的位元組,紅色是被改變的相鄰資料,黃色是被寫入但數值沒有改變的位元組。\0 代表結尾的 NUL,· 代表尚未初始化的內容。

溢位造成的影響

剛才的練習顯示了兩種層次的影響:

  • 相鄰變數被改變:is_admin 從 0 變成非 0,程式的判斷隨之改變。這種情形不需要程式停止,也不一定有明顯症狀,影響的是程式邏輯
  • 控制資訊被改變:回傳位址或框架指標毀損後,函式無法正確返回。模擬程式在此停止;真實程式可能異常終止,在沒有任何防護的環境裡,這也是歷史上攻擊者試圖取得程式控制權的途徑。本課不討論這些手法的細節,重點放在如何預防

溢位也可能發生在堆積(heap)與全域資料,讀取超過範圍同樣是問題。Heartbleed(CVE-2014-0160)即屬於讀取超過範圍的弱點,可洩漏行程記憶體中的敏感資訊。

模型中的變數順序是固定的。真實的框架由編譯器決定,C 語言標準沒有規定區域變數在記憶體中的排列方式,會受到最佳化與對齊的影響,所以不能假設某個變數一定相鄰於緩衝區。

防護機制分成幾層:最根本的作法是不寫出範圍(邊界檢查、安全函式、記憶體安全語言);其次是在寫出範圍時降低後果(Stack Canary、NX、ASLR)。第二類機制都是緩解措施:CWE-121 指出編譯器的偵測機制與 ASLR 都不是完整的解法,Microsoft 的文件也說明 DEP 並非針對所有攻擊的完整防護。

Stack Canary、NX 與 ASLR

機制 作用方式 無法解決的問題
Stack Canary 編譯器在控制資訊之前放一個檢查值,函式返回前比對;不符時印出錯誤訊息並結束程式(GCC 的 -fstack-protector 系列) 只在函式返回時檢查;寫入沒有到達檢查值時不會被偵測;程式被中止,服務仍然中斷
NX(No-eXecute,又稱 DEP) 由處理器與作業系統將堆疊等資料區標為不可執行 不阻止覆寫,只限制資料區不能被當成指令執行
ASLR 每次啟動時將堆疊、函式庫等區域放在隨機的位置 不阻止覆寫,只讓位置難以預測;記憶體內容洩漏等弱點可以使它失效

這三者都不會讓溢位消失。它們假設錯誤可能發生,並提高利用的難度或把程式提早中止,屬於縱深防禦。CWE-121 也指出,Canary 只能偵測特定類型的溢位,偵測後通常是結束程式,這本身仍是一種服務中斷。

在 GCC 中,-fstack-protector-strong 會保護更多函式,例如含有區域陣列定義的函式。編譯選項是否預設開啟,會因編譯器版本與作業系統發行版本而異,專案應明確設定並確認。

啟用這項保護的編譯器通常也會調整變數的排列,把陣列移到靠近檢查值的位置,讓溢位不容易碰到其他變數。以本課的 check_user 為例,在 Compiler Explorer 以 x86-64 的 GCC 13.3、14.2 與 Clang 18.1、19.1 編譯:關閉保護時,buf 的位址比 is_admin 低 8 位元組,寫出 buf 會碰到 is_admin;加上 -fstack-protector-strong 後,is_admin 移到 buf 的低位址方向,寫出 buf 碰不到它。其他編譯器與版本可能不同。所以下方練習的版面是教學模型,用來說明「寫入沒有到達檢查值時不會被偵測」這項限制,真實程式不一定出現這個版面。結構內部的溢位、相鄰的陣列仍然不受這層保護。

互動練習:防護機制的效果與限制

開啟或關閉防護後按「執行模擬程式」。開啟 Stack Canary 時,版面會多出 8 位元組的檢查值,所以回傳位址的位置也跟著移動。模型的變數順序固定不變,真實的編譯器啟用保護時通常會調整順序(見上一段)。

有長度限制的複製與記憶體安全語言

最直接的修補是在寫入時限制長度。常見的函式行為並不相同:

寫法 長輸入時的行為
strcpy(buf, input) 不檢查長度,寫出範圍
strncpy(buf, input, sizeof(buf)) 不寫出範圍,但計數用完時不會加上結尾的 NUL,之後當成字串讀取會超出範圍
strncpy(buf, input, sizeof(buf) - 1) 後設定 buf[sizeof(buf) - 1] = '\0' 不寫出範圍,字串有正確的結尾
snprintf(buf, sizeof(buf), "%s", input) 最多寫入 sizeof(buf) - 1 個字元並加上結尾的 NUL;回傳值可用來判斷內容是否被截斷

長度參數是另一個容易出錯的地方:傳入的數字必須是目的地的大小,不是輸入的長度。memcpy(buf, input, strlen(input)) 依輸入長度複製,等於沒有限制。截斷本身也可能造成邏輯問題,例如路徑或帳號被截短後指向其他對象,程式應該檢查並拒絕過長的輸入。

記憶體安全語言從語言層面處理這類問題:執行時檢查索引範圍,或由編譯器保證存取的合法性。美國 CISA、NSA 與多國資安機關的公開文件建議軟體廠商規劃採用記憶體安全語言,例如 C#、Go、Java、Python、Rust、Swift。文件同時指出這不是絕對的保護:這些語言通常仍保留不安全的操作與對既有函式庫的呼叫。索引超出範圍時程式仍會丟出例外或中止,可用性問題需要另外處理。

既有的 C 與 C++ 程式還可以搭配編譯器警告、靜態分析、執行期檢查工具(例如 AddressSanitizer)與模糊測試,在開發階段找出這類錯誤。

互動練習:修補不安全的複製

選一種複製方式。「執行模擬程式」使用目前的長度,「測試這個版本」同時以 5 字元與 24 字元的輸入檢查,只有兩者都正常才算通過。

互動練習:在教學核心修改 C 程式

下方是真的 C 程式,在作業系統軌的教學核心上執行,輸入是程式內固定的字串。教學核心依宣告順序放置全域變數,所以 buf 與 is_admin 相鄰;真實的編譯器不一定如此。按「編譯並開始」後再按「跑到結束」。

重點整理

  • 堆疊框架中,區域變數與回傳位址等控制資訊相鄰存放,C 語言的陣列沒有內建的長度檢查
  • 寫入超過緩衝區長度屬於未定義行為,可能改變相鄰變數,也可能毀損控制資訊
  • 真實的變數順序由編譯器決定,模型只用來說明原理
  • Stack Canary 偵測檢查值是否改變,寫入沒有到達檢查值時不會被偵測;編譯器通常會把陣列移到靠近檢查值的位置,但結構內部與相鄰陣列仍不受保護;偵測後程式中止,服務仍然中斷
  • NX 與 ASLR 不阻止覆寫,只提高利用的難度;三者都是緩解措施,不是修補
  • 修補應限制寫入長度並確保字串結尾;strncpy 不保證有結尾的 NUL,長度參數必須是目的地的大小
  • 記憶體安全語言從語言層面處理這類問題,但不代表沒有其他弱點

下一課討論作業系統層級的權限,以及機密資訊的管理方式。

小測驗

1.

char buf[8] 搭配 strcpy(buf, input),輸入恰好 8 個字元時會發生什麼?

2.

程式開啟了 Stack Canary。下列哪一種情形可能不會被它偵測到?

3.

ASLR 開啟後,下列敘述何者正確?

4.

將 strcpy 改成 strncpy(buf, input, sizeof(buf)) 後,還需要處理什麼?

5.

關於 NX(不可執行)的敘述,下列何者正確?

第 1 次作答
這一課完成了!
Next lesson:權限與最小授權
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。