保護與安全

緩衝區溢位(buffer overflow)

想像牆上一排嵌入式信箱,每個剛好裝得下當天的信。如果一個粗心的郵差在 5 號信箱滿了之後還一直塞信,溢出的部分不會消失——它會溢進 6 號、再進 7 號,蓋掉本該放在那裡的東西。緩衝區溢位在記憶體裡正是這麼回事:程式往一塊固定大小的儲存區(緩衝區)寫入超過它能容納的資料,多出來的位元組就溢出、覆蓋掉記憶體裡緊鄰它的任何東西。

危險的版本是經典的「堆疊破壞」。當函式執行時,CPU 把它的區域變數——包括固定大小的緩衝區——連同記帳資料一起放在堆疊上,而關鍵在於那記帳資料包含返回位址:函式結束後執行該跳回哪裡。現在假設程式把使用者提供的輸入複製進一個 64 位元組的緩衝區,卻不檢查長度,用的是像舊式 strcpy 那種東西。如果攻擊者提供 200 位元組,這次寫入就越過緩衝區、覆蓋掉被保存的返回位址。攻擊者挑選那些位元組,使返回位址現在指向他植入的程式碼(往往就在他送進來的那段輸入裡)。函式返回時,CPU 跳到攻擊者的程式碼,並以「程式的權限」執行它。這就是程式碼注入:不受信任的資料被變成了被執行的指令。

緩衝區溢位數十年來一直是破壞性最強的漏洞類別之一,也是本領域數種防禦存在的緣由。堆疊金絲雀在返回位址之前放一個祕密的守衛值,返回前先檢查它,於是「砸壞了金絲雀」的溢位會被逮到。不可執行(NX/DEP)記憶體把堆疊標記為不可執行,於是即便攻擊者把程式碼放在那裡,CPU 也拒絕去跑。位址空間配置隨機化打亂東西所在的位置,使攻擊者無法可靠地猜到要跳去的位址。誠實的真相是:這些都不是根治——攻擊者以「返回導向程式設計」等技巧反制,重用既有的可執行程式碼——這正是為什麼真正的修法是邊界檢查與記憶體安全的語言,也是為什麼溢位至今仍出現在舊的 C 與 C++ 程式碼中。

char buf[64]; strcpy(buf, input); ——若 input 有 200 位元組,strcpy 會把全部 200 都寫下去,越過 buf 達 136 位元組,覆蓋掉堆疊上被保存的返回位址。精心構造的輸入便把該返回位址導向攻擊者選定的程式碼。把 strcpy 換成有長度上限的複製(至多寫 64 位元組)就能堵住這個洞。

未檢查的輸入越過緩衝區,覆蓋掉返回位址。

那些防禦(金絲雀、NX、ASLR)提高了門檻,但沒有一個是根治——攻擊者以「返回導向程式設計」這類重用程式碼的技巧因應。真正的修法是邊界檢查與記憶體安全的語言,這也是為什麼溢位在舊的 C 與 C++ 程式碼裡始終存在。

又稱
stack smashingstack-based buffer overflowbuffer overrun緩衝區溢出堆疊破壞攻擊