Buffer Overflow 攻擊原理

Stack 基礎知識

在撰寫程式時,特別是 C 語言程式,經常需要一塊暫存的記憶體空間,這通常被稱為 buffer。獲取 buffer 的方式主要有兩種:

  1. 透過 malloc() 從 heap 取得記憶體。
  2. 在 stack 上宣告陣列,例如 char array[16]

Memory Layout

一般 C 程式的記憶體配置如上圖所示,主要區塊包括:

  • .text:存放機器碼
  • .data:存放已初始化的全域變數
  • .bss:存放尚未初始化的全域變數
  • heap:主要用來存放需長期使用的資料
  • stack:主要用於函式呼叫時的暫存資料

Stack 的運作

本文以下部分將使用 x86 組合語言來解析 stack 的運作。

在 C 語言中,每個函式執行時都會建立自己的 stack frame,用於儲存區域變數與其他相關資訊。為了確保函式執行完畢後能夠回到正確的位置,程式需要保存當前的執行狀態,包括程式計數器(Program Counter, PC)以及 stack frame 起始位址(%rbp 暫存器)。

以下是一個簡單的範例:

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

void copy(char *str) {
   char buffer[16];
   strcpy(buffer, str);
   printf("%s\n", buffer);
}

int main (int argc, char **argv) {
   copy(argv[1]);
   return 0;
}

這段程式碼的主要行為是將傳入的字串複製到 copy() 函式內的 buffer,然後印出其內容。接著我們來分析 main() 呼叫 copy() 時,stack 的行為:

main:
  # ... 此時 argv 位於 %rdi

  # 根據規格,第一個參數必須放在 %rdi
  # 將 %rax 64 位元的資料複製到 %rdi
  movq    %rax, %rdi

  # 1. 呼叫 copy() 同時把 return address (PC) 推到 stack
  call    copy(char*)

  # 結束 main()
  leave
  ret

copy(char*):
  # function prologue
  # 2. 記錄 main() 的 stack frame 起始位址
  pushq   %rbp

  # 把 %rbp 拿來存 copy() 的 stack frame 起始位址
  movq    %rsp, %rbp

  # 3. 在 stack 上空出一些空間,額外的空間是為了記憶體對齊,實際上只需要 24 bytes
  subq    $32, %rsp

  # 4. 放 str
  movq    %rdi, -24(%rbp)
  # 5. 剩下空間是 buffer 的,這裡沒有指令是因為 buffer 不需初始化
  
  # 準備呼叫 strcpy(),把 buffer 位址放到第一個參數(%rdi  leaq    -16(%rbp), %rdi
  
  # 把 str 放到第二個參數(%rsi),注意 str 存的已經是記憶體位址了所以不使用 leaq 複製位址的指令
  movq    -24(%rbp), %rsi

  # 呼叫 strcpy()
  call    strcpy

  # 呼叫 puts()
  leaq    -16(%rbp), %rdi
  call    puts

  # 結束 copy() 並恢復 main() 的 PC 與 %rbp
  leave
  ret

根據以上指令,在 call() 回傳前的 stack 狀態如下圖所示:

Call Stack

什麼是 Buffer Overflow 攻擊?

Buffer Overflow(緩衝區溢位) 指的是將超過 buffer 容量的資料寫入,導致 buffer 後方的記憶體被覆寫。在上述範例中,若傳入 copy() 的字串長度超過 16 bytes,可能會覆寫 main()%rbp 及 return address。

這帶來一個危險的可能性:如果我們能夠修改 return address,當函式回傳時,PC 就會跳轉至我們控制的記憶體區域。進一步地,若該記憶體區段包含我們設計的指令,就能夠執行惡意程式碼,這種技術即為 Buffer Overflow 攻擊。

典型攻擊方式如下:

  1. 將惡意指令(shellcode)以 buffer overflow 的方式存入 buffer 與其後方的記憶體區段。
  2. 覆寫 return address,讓 PC 指向 shellcode。
  3. 當函式回傳時,惡意程式碼被執行。

由於惡意程式碼通常會先開啟 shell,後續攻擊者可透過 shell 進行進一步的操作,所以這段惡意程式碼又被稱為 shellcode

Buffer Overflow

本文省略了諸多攻擊細節,例如如何找到 buffer 的記憶體位址、如何觸發系統呼叫等,若有興趣深入了解,請參閱參考資料。

現代防禦手段

為了防範緩衝區溢位攻擊,現代系統採取了多種安全措施,以下介紹幾種常見的防禦機制。

Stack Canary

Stack Canary 是一種檢測緩衝區溢位的技術,在 return address 前方插入一段特殊值(canary)。函式回傳前會檢查該值是否被修改,若被更動則程式會立即終止,避免執行惡意程式碼。

Canary(金絲雀)這個名稱源於早期礦工會攜帶金絲雀進入礦坑,以偵測有毒氣體。若金絲雀死亡,表示礦坑內可能存在危險,礦工便會立即撤離。在軟體開發領域,canary 也扮演類似的「試毒」角色,常見於軟體的版本號中。

Write XOR Execute(W^X)

此技術強制記憶體區塊只能是可寫(Write)或可執行(Execute),但不能同時具備這兩種屬性。這樣一來就算攻擊者能夠寫入惡意程式碼,也無法執行,從而降低攻擊成功的可能性。

位址空間配置隨機化(ASLR)

位址空間配置隨機化(Address-Space Layout Randomization, ASLR)透過隨機化程式記憶體區域(如 .text、stack、heap 等),使攻擊者難以預測緩衝區的位址,從而增加發動攻擊的難度。


參考資料

  1. Smashing the Stack in the 21st Century :: Jon Gjengset
  2. 緩衝區溢位 - 維基百科,自由的百科全書
  3. 位址空間組態隨機載入 - 維基百科,自由的百科全書