iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

上一篇看到了 Stack Buffer Overflow——buf 只有 32 Bytes,read 卻能讀 200 Bytes,多出來的資料會覆蓋 Stack 上其他東西。

但知道漏洞存在不代表能直接利用,還有兩個問題要先解決:

  1. 要輸入多少 Byte 才能蓋到 Return Address?
  2. 蓋到之後,RIP 真的會被我們控制嗎?

今天用 GDB + pwndbg 走一次這個過程,目標就是 確認我們可以控制 RIP


0x00 準備 Vulnerable Program

#include <stdio.h>
#include <unistd.h>

void vuln()
{
    char buf[32];

    puts("Input:");

    read(0, buf, 200);
}

int main()
{
    vuln();

    return 0;
}

先關掉保護機制,專心看 Stack Overflow 本身。Binary Protection 之後再介紹。

gcc chall.c -o chall -fno-stack-protector -no-pie

0x01 先讓它 Crash

執行 ./chall,輸入 AAAA,程式正常結束。但如果塞一大串 A 進去:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

就會看到 Segmentation fault。程式掛了,但到底壞在哪?開 GDB 看看。


0x02 用 GDB / pwndbg 打開

gdb ./chall

裝了 pwndbg 的話,prompt 會變成 pwndbg>。先 disassemble vuln():

push   rbp
mov    rbp,rsp
sub    rsp,0x20

...

call   read

leave
ret

sub rsp, 0x20 在 Stack 上配置 32 Bytes,對應 char buf[32]。


0x03 Stack Layout

前面介紹過,進 Function 之後 Stack 大概長這樣:

Higher Address
+------------------+
| Saved RIP        |   ← ret 時 CPU 從這裡取出 Return Address
+------------------+
| Saved RBP        |
+------------------+
| buf[32]          |
+------------------+
Lower Address

往 buf 塞超過 32 Bytes,多出來的會先蓋掉 Saved RBP,再蓋到 Saved RIP。Saved RIP 一旦被改掉,ret 的時候 CPU 就不會回 main(),而是跳去我們寫進去的位置。


0x04 問題是:Offset 多少?

看 Stack Layout,buf 32 Bytes 加上 Saved RBP 8 Bytes,直覺猜 Offset 是 40。

不過實際上不要靠猜。Compiler 的 alignment、padding 都可能影響 Stack Layout,用 cyclic pattern 確認比較保險。


0x05 Cyclic Pattern

如果全部輸入 AAAA...,Crash 的時候看到 0x4141414141414141,沒辦法判斷這是第幾個 Byte 蓋上去的。

cyclic pattern 的設計是讓每個位置的 4 Bytes 都不一樣:

aaaabaaacaaadaaaeaaafaaagaaah...

這樣看到某個值就能反查它在 Input 裡的位置。

在 pwndbg 裡產生 100 Bytes 的 pattern:

pwndbg> cyclic 100

然後 run,把 pattern 貼進去,程式會 Crash。


0x06 找 Offset

Crash 之後用 info registers 或 pwndbg 的顯示看 Register。也可以用 telescope $rsp 看 Stack。

假設看到 Return Address 附近出現了 0x6161616b6161616a 這種值,明顯不是正常 Address,而是我們輸入的 cyclic pattern。

接著問 pwndbg 這段 pattern 在第幾個 Byte:

pwndbg> cyclic -l 0x6161616b
40

Offset = 40。也就是 Input 的第 0~39 Byte 填滿 buf 和 Saved RBP,第 40 Byte 開始蓋到 Saved RIP。

跟前面猜的一樣,但這個值每題都不同,不能直接假設 40。


0x07 驗證 RIP Control

知道 Offset 之後,可以精準構造 Payload:40 個 A 當 padding,後面接 8 個 B。如果 Saved RIP 變成 0x4242424242424242,就確認了我們能控制 Return Address。

python3 -c 'import sys; sys.stdout.buffer.write(b"A"*40 + b"B"*8)' > payload

GDB 裡執行:

pwndbg> run < payload

程式 Crash,觀察 Return Address 附近確實出現 0x4242424242424242。Saved RIP 被我們蓋掉了。


0x08 Crash ≠ Exploit

讓程式 Segfault 只能說明有 Memory Corruption,但不代表能利用。Crash 之後要繼續確認:我們到底控制了什麼?是 RIP?RSP?某個 Function Pointer?還是只是蓋到了不重要的地方?

今天確認的是能控制 Saved RIP,也就是 ret 之後程式跳去哪裡由我們決定。


0x09 Rev 和 Pwn 的思考差異

同一行 read(0, buf, 200),Reverse 看到的是「把 Input 讀進 buf」而已。

Pwn 會想說:buf 多大?read 能讀多少?超過的部分蓋到哪?Saved RIP 離 buf 多遠?角度不一樣,一個在理解行為,一個在找怎麼打。


0x0A 小結

今天的流程:

  1. 找到 Stack Buffer Overflow
  2. 用 cyclic pattern 輸入,讓程式 Crash
  3. 從 Crash 的 Register / Stack 找到 pattern 的位置
  4. 用 cyclic -l 算出 Offset = 40
  5. 構造 padding + 目標值,驗證 Saved RIP 被覆蓋

Payload 的結構就是 Padding (40 Bytes) + New Return Address (8 Bytes)。目前塞的 0x4242424242424242 沒有實際意義,真正打的時候這裡會放 win() 的位址、ROP Gadget、或 libc 裡的東西。

Return Address 可以由我們決定了,接下來的問題是:要讓程式 Return 到哪?

不過每次手動複製 pattern、算 Offset、拼 payload 實在太煩了。Day19 來用 pwntools 把這些全部自動化。


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

尚未有邦友留言

立即登入留言