iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0

上一篇我們把 Shellcode 塞進 Stack,讓 RIP 跳過去執行。乍看之下好像已經天下無敵——只要有 Buffer Overflow 就能拿 Shell。

但如果真的這麼簡單,現代的 Stack Buffer Overflow 應該都可以直接塞 Shellcode 打穿才對。實際上不行,因為現代 Binary 通常都有各種 Exploit Mitigation,專門增加利用難度。

今天來看 Pwn 題裡最常遇到的幾個保護機制:NX、Stack Canary、PIE、ASLR、RELRO,以及每次拿到 Binary 幾乎第一個會跑的 checksec。


0x00 checksec

拿到 Pwn 題的 Binary 時,我通常先跑:

file chall
pwn checksec chall

pwndbg 裡面也可以直接打 checksec。

為了對比,先故意把大部分保護關掉來編譯:

gcc chall.c -o chall \
    -fno-stack-protector \
    -z execstack \
    -no-pie \
    -Wl,-z,norelro

跑 pwn checksec chall 會看到類似:

Arch:       amd64-64-little
RELRO:      No RELRO
Stack:      No canary found
NX:         NX disabled
PIE:        No PIE

這幾行資訊會直接影響我們的 Exploit Strategy,可以先簡單整理:

Protection 主要保護什麼
Canary Stack Overflow
NX Code Execution
PIE Binary Address
ASLR Memory Address
RELRO GOT

接下來一個一個看。


0x01 NX

先從昨天留下的問題開始。上一篇我們把 Shellcode 放進 Stack,讓 RIP 跳過去執行。但這有一個前提:Stack 必須可以執行。

Memory Page 通常有不同的 Permission:R(Read)、W(Write)、X(Execute)。一般來說:

.text  → R-X
.data  → RW-
Stack  → RW-

Stack 是 RW-,可以讀寫但不能執行。這就是 NX(No-eXecute)。

當 NX 開啟時,就算我們成功把 Shellcode 塞進 Stack、也控制了 RIP,CPU 跳到 Stack 上一看——這個 Memory Page 沒有 Execute Permission,直接 Segmentation Fault。

注意 NX 不是防止 Buffer Overflow,而是防止你執行放在 Stack 上的 Code。我們仍然可以 Overflow、仍然可以控制 RIP,只是不能再讓 RIP 跳到 Stack 上的 Shellcode。


0x02 NX 開了怎麼辦?

既然自己放進去的 Code 不能執行,那可不可以不要執行自己的 Code,改用 Binary 裡原本就存在、而且本來就有 Execute Permission 的 Code?像 .text、libc 這些 Memory 本來就是 R-X。

如果我們能把裡面已有的 Instruction 拿來拼湊,就不需要自己注入 Code 了。這就是之後會碰到的 ROP(Return-Oriented Programming)。演進的脈絡很清楚:

NX Disabled → Stack Shellcode
NX Enabled  → 不能跑 Stack Shellcode → Reuse Existing Code → ROP

0x03 Stack Canary

名字來自以前礦工帶金絲雀進礦坑——如果空氣有毒,金絲雀會先出事,礦工就知道該撤了。

Stack Canary 的概念差不多。原本的 Stack Layout:

+------------------+
|    Saved RIP     |
+------------------+
|    Saved RBP     |
+------------------+
|      buffer      |
+------------------+

加入 Canary 之後:

+------------------+
|    Saved RIP     |
+------------------+
|    Saved RBP     |
+------------------+
|      Canary      |  ← 夾在中間
+------------------+
|      buffer      |
+------------------+

從 buffer Overflow 到 Saved RIP,中間勢必會經過 Canary。Function 進入時 Compiler 會在 Stack 放一個 Canary Value,Return 前再檢查:如果 Canary 被改了,代表 Stack 被動過,程式直接終止。

把 Canary 打開編譯試試:

gcc chall.c -o chall_canary -fstack-protector-all

丟一堆 A 進去:

python3 -c 'print("A"*100)' | ./chall_canary

會看到 *** stack smashing detected ***,因為我們的 AAAA... 不只蓋了 buffer,也把 Canary 改掉了。


0x04 有 Canary 就不能 Overflow 嗎?

當然不是。Protection 的意思是增加 Exploit 難度,不是 100% 無敵。

如果有辦法先 Leak 出 Canary 的值,Payload 就可以變成:

Padding + 正確的 Canary + Saved RBP + New RIP

Function 檢查 Canary 時發現值沒變,照常 Return——但 Saved RIP 已經被我們改掉了。所以之後 Pwn 會常常看到「先 Leak 再 Exploit」的思路,Canary 是其中一個原因。


0x05 PIE

前面做 ret2win 的時候,我們很理所當然地把 win() 的位址寫死:

payload = b"A" * 40
payload += p64(0x401176)

為什麼可以寫死?因為 Binary 的 Address 是固定的——main 永遠在 0x4011a0、win 永遠在 0x401176,每次執行都一樣。

PIE(Position Independent Executable)改變了這件事。PIE 讓 Binary 可以被載入到不同的 Base Address,搭配 ASLR 之後,每次執行的 Base 都不一樣:

Run 1: Base = 0x555555554000
Run 2: Base = 0x564f9ad02000

win() 的 Offset(例如 0x1176)通常不變,但 Base 每次都換,所以完整 Address 也跟著變。這時候再硬寫 p64(0x401176) 就沒用了。


0x06 PIE 和 ASLR 的差別

這兩個很容易搞混,簡單理解:

  • ASLR(Address Space Layout Randomization):作業系統層面,Randomize Stack、Heap、libc、mmap 等區域的位址。Linux 下 cat /proc/sys/kernel/randomize_va_space 通常是 2,代表已開啟。
  • PIE:讓 Executable 本身也可以跟著 Randomize。沒有 PIE 的話,即使 ASLR 開著,Main Binary 的 .text 還是固定位址。

所以 ASLR + PIE 通常要一起看。

要繞過 PIE 的思路跟 Canary 類似——如果有辦法 Leak 出 Binary 裡某個已知 Offset 的 Address,就能反推 PIE Base,再算出目標 Function 的位址:

Leaked main = 0x5555555551a0
main offset = 0x11a0
PIE Base    = 0x5555555551a0 - 0x11a0 = 0x555555554000
win         = 0x555555554000 + 0x1176

0x07 RELRO

要理解 RELRO(RELocation Read-Only),需要回想 ELF 裡的 GOT 和 PLT。Dynamic Linking 時,像 puts("Hello") 這種呼叫,程式需要在 Runtime 找到 libc 裡 puts() 的真正位址,而 GOT(Global Offset Table)就負責保存這些位址。

如果攻擊者能做到 Arbitrary Write,把 puts@GOT 從指向 puts() 改成指向 system(),那下次程式呼叫 puts("/bin/sh") 時實際上就會變成 system("/bin/sh")。這就是 GOT Overwrite。

checksec 通常會看到三種狀態:

RELRO GOT 的狀態
No RELRO Writable
Partial RELRO 部分仍可寫
Full RELRO Read-Only

Full RELRO 會在程式啟動時就完成所有 Symbol 的 Relocation,之後把 GOT 改成 Read-Only,GOT Overwrite 就被擋掉了。不過 Full RELRO 不影響讀取 GOT——很多 Pwn 題還是會透過 puts@GOT 來 Leak libc Address,只是不能直接改寫。


0x08 Protection 全開長什麼樣?

把所有保護打開重新編譯:

gcc chall.c -o chall_protected \
    -fstack-protector-all \
    -fPIE -pie \
    -Wl,-z,relro,-z,now \
    -Wl,-z,noexecstack

跑 pwn checksec chall_protected:

Arch:       amd64-64-little
RELRO:      Full RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        PIE enabled

同一個 read(0, buf, 200) 漏洞還在,但原本直接 Overflow → 跳 Shellcode 的路徑已經被層層擋住:Canary 不讓你直接碰 Saved RIP、NX 不讓你跑 Stack 上的 Code、PIE + ASLR 讓 Address 不再固定、Full RELRO 擋掉 GOT Overwrite。


0x09 Protection 不是修漏洞

這個觀念很重要。即使 Canary、NX、PIE、Full RELRO 全開,read(0, buf, 200) 對一個 32 Bytes 的 buffer 來說仍然是 Buffer Overflow。Protection 做的是讓 Exploitation 變困難,不是消除漏洞本身。真正的修法還是 read(0, buf, sizeof(buf))。Protection 是 Defense in Depth,不是漏洞修補。


0x0A 拿到題目怎麼看 checksec?

假設 checksec 結果是:

RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE

可以快速推斷:No Canary 代表 Stack Overflow 可以直接碰 RIP;NX Enabled 代表不適合 Stack Shellcode;No PIE 代表 Binary Address 固定。

如果 Binary 裡有 win(),直接想 ret2win。沒有 win() 但 NX Enabled,方向就可能轉向 ROP。

checksec 不是在告訴我們「這題很安全」,而是在告訴我們哪些 Exploit Technique 還可能用、哪些路被擋住了。


0x0B Protection 和 Exploit Technique 的對應

目前學過的東西可以開始串起來了。拿到 Stack Buffer Overflow、確認能控制 RIP 之後,根據 Protection 選方向:

  • No NX → Shellcode
  • NX Enabled → ret2win / ROP / ret2libc
  • PIE Enabled → 需要先 Leak Binary Address
  • Canary Enabled → 需要先 Leak Canary
  • ASLR + PIE + libc Randomization → Leak → Calculate Base → Exploit

你會發現 Information Leak 在 Pwn 裡越來越重要,因為很多 Protection 的核心概念就是讓攻擊者不知道某些關鍵資訊。


0x0C 小結

今天把五個最常見的 Binary Protection 過了一遍:

  • NX:Data Page 不能執行 Code
  • Canary:偵測 Stack Overflow 是否碰到 Control Data
  • PIE:Binary 不再固定載入同一個 Address
  • ASLR:Randomize Process Memory Layout
  • RELRO:保護 GOT 等 Relocation 資料

之後拿到 Pwn 題的流程可以慢慢固定:file → checksec → 執行程式 → Reverse → 找漏洞 → 確認能控制什麼 → 根據 Protection 選 Exploit Technique。

前幾天我們一路從 Stack Buffer Overflow、RIP Control、pwntools、ret2win 走到 Shellcode,今天終於遇到第一個真正的大問題:NX Enabled,Stack 上的 Shellcode 不能直接跑了。

既然不能執行自己的 Code,那就來執行別人的 Code。下一篇進入 Pwn 裡非常經典的世界——ROP(Return-Oriented Programming),看看怎麼把 Binary 裡原本存在的 Instruction 拆成一小段一小段,拼出我們想要的程式流程。


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

尚未有邦友留言

立即登入留言