iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0

前一篇開始進入 Pwn 之後,我們知道一個 Process 執行時,Memory 大致可以分成:

Text
Data
BSS
Heap
Stack

其中 Stack 會存放:

Local Variable
Saved Register
Return Address

而今天就要開始接觸 Pwn 中最經典的漏洞之一:

Stack Buffer Overflow

0x00 Buffer 是什麼?

先從最基本的 Buffer 開始。

假設程式裡面有:

char name[16];

代表程式準備了一塊:

16 Bytes

的空間存放資料。

可以想像成:

+----+----+----+----+----+----+----+----+
|    |    |    |    |    |    |    |    |
+----+----+----+----+----+----+----+----+
|    |    |    |    |    |    |    |    |
+----+----+----+----+----+----+----+----+

             16 Bytes

如果輸入:

shark

當然完全沒有問題。

但如果程式沒有檢查長度,我們卻塞進:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

會發生什麼?

資料不會因為 Buffer 只有 16 Bytes 就自動停下來。

它可能會繼續往後寫。

這就是:

Buffer Overflow

0x01 一個 Vulnerable Program

先來看一個簡單的程式:

#include <stdio.h>

struct user {
    char name[16];
    int is_admin;
};

int main() {
    struct user u = {0};

    printf("Name: ");
    scanf("%s", u.name);

    printf("is_admin = %d\n", u.is_admin);

    if (u.is_admin) {
        puts("Welcome, admin!");
    }

    return 0;
}

編譯:

gcc vuln.c -o vuln -g -O0

這裡最值得注意的是:

scanf("%s", u.name);

%s 並不知道:

u.name 只有 16 Bytes

所以只要我們輸入超過 16 Bytes,資料就會繼續往後寫。


0x02 正常情況

先正常執行:

./vuln

輸入:

Name: shark

可能得到:

is_admin = 0

目前記憶體大概可以想像成:

name
↓
+------------------+----------+
| shark            | is_admin |
|                  |    0     |
+------------------+----------+
     16 Bytes          4 Bytes

因為:

is_admin = 0;

所以不會進入:

if (u.is_admin)

0x03 Overflow

接著故意輸入超過 16 Bytes。

例如:

AAAAAAAAAAAAAAAAA

這裡有:

17 個 A

但 name 只有:

16 Bytes

所以前 16 個 A:

AAAAAAAAAAAAAAAA

會填滿 name。

第 17 個 A 就會繼續寫到後面的記憶體。

也就是:

+------------------+----------+
| AAAAAAAAAAAAAAAA | A....... |
+------------------+----------+
     name            is_admin

ASCII:

A = 0x41

在常見的 x86-64 Little Endian 環境下,就可能看到:

is_admin = 65

因為:

0x41 = 65

接著:

if (u.is_admin)

判斷的不是:

is_admin == 1

而是:

is_admin != 0

所以程式可能直接輸出:

Welcome, admin!

我們沒有正常修改:

is_admin

卻透過:

Buffer Overflow

改到了它。


0x04 到底發生什麼?

問題其實非常單純。

程式以為:

User Input
    ↓
name[16]

但真正發生的是:

User Input
    ↓
name[16]
    ↓
繼續寫
    ↓
其他 Memory

Memory 本身並不知道:

這 16 Bytes 是 name
後面 4 Bytes 是 is_admin

對 CPU 來說,它們就只是:

Memory Address

如果程式沒有檢查:

Input Length

資料就可能一路寫下去。


0x05 用 GDB 看看

可以用前面學過的 GDB 觀察。

gdb ./vuln

進入 main:

break main
run

可以查看兩個變數的 Address:

p/x &u.name[0]
p/x &u.is_admin

可能看到類似:

&u.name[0]  = 0x7fffffffe1a0
&u.is_admin = 0x7fffffffe1b0

兩者相差:

0x10

也就是:

16 Bytes

剛好就是:

char name[16];

的大小。

所以:

name[0]
0x...e1a0

name[15]
0x...e1af

is_admin
0x...e1b0

如果輸入超過 16 Bytes:

name
 ↓
AAAAAAAAAAAAAAAA
                ↓
              is_admin

就開始被覆蓋。

這也是 Dynamic Analysis 很重要的地方。

Source Code 只會告訴我們:

char name[16];
int is_admin;

但 GDB 可以直接讓我們看到:

它們實際在 Memory 的哪裡
資料又是怎麼被寫進去的

0x06 Stack Buffer Overflow

今天的範例只是覆蓋:

Local Variable

但事情真正危險的地方是:

Stack 上面不只有 Local Variable。

前面介紹 Stack 時看過:

High Address
+------------------+
| Return Address   |
+------------------+
| Saved RBP        |
+------------------+
| Local Variables  |
+------------------+
Low Address

如果某個 Buffer 不斷 Overflow:

Buffer
  ↓
其他 Local Variable
  ↓
Saved RBP
  ↓
Return Address

那問題就不再只是:

is_admin 被改掉

而可能變成:

Return Address 被修改

而我們之前學 Function Call 時知道:

Return Address

決定了 Function 執行完:

ret

之後要回到哪裡。

所以如果 Return Address 可以被控制……

事情就開始有趣了。


0x07 怎麼避免?

最直接的方法就是:

不要讓輸入超過 Buffer 大小。

例如剛才:

scanf("%s", u.name);

可以限制長度:

scanf("%15s", u.name);

因為:

15 Bytes Data
+
1 Byte \0
=
16 Bytes

或者使用:

fgets(u.name, sizeof(u.name), stdin);

核心概念都是:

永遠確認:

Input Length <= Buffer Size

0x08 小結

今天第一次真正接觸到:

Memory Corruption

整個問題可以簡化成:

Buffer Size = 16

User Input > 16
        ↓
Buffer 被填滿
        ↓
繼續寫入後面的 Memory
        ↓
覆蓋其他資料

今天我們只做到:

Buffer Overflow
      ↓
覆蓋 Local Variable
      ↓
改變程式狀態

但真正的 Pwn 不會只滿足於修改:

is_admin

如果 Stack Layout 是:

Buffer
  ↓
Saved RBP
  ↓
Saved RIP

那下一個問題自然就是:

到底要輸入多少 Bytes,才能剛好碰到 Return Address?

以及:

如果 Return Address 可以被覆蓋,我們能不能控制 RIP?

下一篇就來正式進入:RIP Control


上一篇
Day16|Pwn Basic
系列文
從零開始の Binary CTF:30 天系統化學習 Rev & Pwn 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言