本篇以 C 語言的實際範例,解釋緩衝區溢位攻擊的原理。

緩衝區溢位攻擊是一種典型的攻擊方式,雖然這種手法已經有一段的歷史,不過還是有可能因為程式設計者的疏忽,讓程式存在這樣的漏洞。


以下我們以 C 語言的程式來解釋緩衝區溢位的原理以及攻擊方式。

緩衝區溢位

程式中的緩衝區是指一塊特定的記憶體空間,程式在執行時可以將資料處存在其中,而緩衝區溢位(buffer overflow)就是指程式將資料寫入緩衝區時,超過了它的範圍,例如:

char buff[4];
buff[5] = 'a';

這裡我們宣告的 buff 字元陣列長度只有 4,是我們卻將資料寫入第六個元素的位置,超過了 buff 字元陣列的範圍。

上面那種緩衝區溢位是屬於 stack 的溢位,還有另外一種類型是 heap 的溢位:

char *ptr = (char*) malloc(4);
ptr[5] = 'a';

兩種溢位概念上差不多,只是差在些入的記憶體是在 stack 還是 heap 而已。

緩衝區溢位攻擊

#include <stdio.h>
#include <string.h>
int main() {
  char buff[10];
  int pass = 0;

  printf("Enter the password:\n");
  gets(buff);

  if(strcmp(buff, "gtwang")) {
    printf("Fail.\n");
  } else {
    printf("Pass.\n");
    pass = 1;
  }
  if(pass) {
    /* 取得更高的權限 */
    printf("You are root now.\n");
  }
  return 0;
}

使用 gcc 編譯:

gcc -o buff buff.c

編譯時會產生一些警告訊息,告知使用者 gets 是比較危險的函數,容易產生緩衝區溢位的漏洞,不過還是可以正常編譯出執行檔。

buff.c: In function ‘main’:
buff.c:8:3: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
   gets(buff);
   ^
/tmp/ccWpQlEe.o: In function `main':
buff.c:(.text+0x24): warning: the `gets' function is dangerous and should not be used.

以正常的方式執行:

./buff
Enter the password:
gtwang
Pass.
You are root now.

正常的狀況下,程式會檢查輸入的密碼是否等於 gtwang,如果密碼正確才會讓只用者取得更高的權限。

由於這個程式存在緩衝區溢位的漏洞,所以如果我們輸入的文字超過緩衝區的範圍時,結果就會不如預期:

./buff
Enter the password:
1234567891234
Fail.
You are root now.

當我們輸入的文字長度太長,超過了 buff 的記憶體範圍時,就有可能把後面的記憶體內容覆蓋掉,也就是寫到 pass 這個變數的記憶體位址,意外把原本應該要是 0pass 變數改成不是 0 的值,所以整個程式的邏輯就破壞掉了。

程式的變數在記憶體中的位置是編譯器在安排的,有時候不見得會跟程式碼的順序相同,以下是我加入兩行 printf 觀察變數的記憶體位址的程式碼:

#include <stdio.h>
#include <string.h>
int main() {
  int pass = 0;
  char buff[10];

  printf("pass: 0x%08x\n", &pass);
  printf("buff: 0x%08x\n", buff);

  printf("Enter the password:\n");
  gets(buff);

  if(strcmp(buff, "gtwang")) {
    printf("Fail.\n");
  } else {
    printf("Pass.\n");
    pass = 1;
  }
  if(pass) {
    /* 取得更高的權限 */
    printf("You are root now.\n");
  }
  return 0;
}
pass: 0x7ecdbec4
buff: 0x7ecdbeb8
Enter the password:
1234567891234
Fail.
You are root now.

這時候若我們將 passbuff 的宣告對調,變成:

char buff[10];
int pass = 0;

則輸出就會不一樣了:

pass: 0x7eaf2eb8
buff: 0x7eaf2ebc
Enter the password:
1234567891234
Fail.

所以緩衝區溢位攻擊有時候也是要看運氣,以這個例子來說 pass 的位置要剛好在 buff 之後,才會發生被覆蓋掉的狀況。

這裡只是示範緩衝區溢位的一種狀況,事實上只要是牽涉到記憶體存取的程式碼(例如指標、陣列等),都有可能因為不小心而造成緩衝區溢位,所以在使用 C/C++ 寫程式的時候,記憶體還是要小心管理。

參考資料:GeeksforGeeksThe Geek Stuff