緩衝區溢位 是歷史最悠久、破壞力最強的軟體弱點類型之一。雖然原理簡單,但現代作業系統與編譯器已內建多重防護機制。本文從堆疊溢位原理、Shellcode 設計、實戰攻擊範例到編譯器防禦 (/GS) 全方位剖析。
什麼是緩衝區溢位?
當資料寫入緩衝區超過其容量,多餘資料會覆蓋相鄰記憶體。依位置分類:
- 靜態儲存區溢位:全域/靜態變數區
- 堆疊溢位:函數呼叫框架、區域變數、返回位址 — 攻擊價值最高
- 堆積溢位:動態配置記憶體
攻擊靶標:存在漏洞的 victim 程式
void GetComputerName(SOCKET sck, LPSTR szComputer) {
char szBuf[512];
recv(sck, szBuf, sizeof(szBuf), 0); // 無長度檢查
LPSTR szFileName = szBuf;
while (*szFileName == '\\') szFileName++; // 略過雙反斜線
while (*szFileName != '\\' && *szFileName != '\0') {
*szComputer++ = *szFileName++; // 複製至 szComputer — 易溢位
}
*szComputer = '\0';
}
void ShowComputerName(SOCKET sck) {
char szComputer[16]; // 僅 16 字元緩衝區
GetComputerName(sck, szComputer); // 傳入未檢查長度
printf(szComputer); // 格式化字串漏洞額外贈送
}
堆疊佈局與攻擊向量分析
ShowComputerName 編譯後堆疊 (stdcall 調用慣例):
高位址
+------------------+ ← 呼叫者堆疊框架
| 參數 sck | [esp+4] (stdcall 由呼叫者清理堆疊,ret 4)
+------------------+
| 返回位址 (RA) | [esp] ← 關鍵目標
+------------------+ ← 進入 ShowComputerName 後
| 舊 EBP | [ebp] (若編譯器啟用框架指標)
+------------------+
| szComputer[16] | [esp] ← 區域變數區
| ... |
+------------------+
低位址 (堆疊向下增長)
關鍵洞察:ret 4 執行後,ESP 指向 szComputer + 24 (返回位址 + 參數 sck 被彈出)。若我們將返回位址覆蓋為 JMP ESP 指令位址,並在 szComputer + 24 放置 Shellcode,控制權即可轉移。
實戰攻擊鏈構建
1. 尋找 JMP ESP (固定位址、無 0x00/0x5C)
- EXE 基址通常較低,高位元組含 0x00 → 不適合
kernel32.dll 基址固定但 XP SP1 無 JMP ESP
user32.dll 基址固定 (0x77D4xxxx),搜尋發現 0x77D437DB = JMP ESP
2. Shellcode 設計課題與解法
| 限制 | 解決方案 |
| 不可含 0x00 (字串結束) / 0x5C (反斜線,被略過) | XOR 編碼:尋找一個 Key (0-255),使 Shellcode XOR Key 後無禁用字元 |
| Shellcode 過長難以完整放入 | 兩階段載入:StubCode (解碼器) + 階段二 Shellcode (透過 Socket 下載) |
| 無法使用絕對定址 | 全程使用相對定址 (RIP/EIP 相對) |
3. 完整攻擊載荷結構 (STUB)
+------------------+
| Padding 1 (18B) | 填滿 szComputer[16] + 對齊
+------------------+
| JMP ESP (4B) | 0x77D437DB 覆蓋返回位址
+------------------+
| Padding 2 (4B) | 填滿參數 sck 空間
+------------------+
| StubStubCode | 11 條組語:解碼 StubCode + 跳轉
+------------------+
| StubParam | API 位址表 (LoadLibrary, GetProcAddr, VirtualAlloc...)+ DLL/函數名
+------------------+
| StubCode | 階段二載入器:建立 Socket、下載攻擊主體、重定位、跳轉 AttackerEntry
+------------------+
| Padding 3 | 偽裝成合法 UNC 路徑結尾
+------------------+
4. 核心 StubStubCode (11 條組語)
xor ecx, ecx
mov cx, wXorSize ; 待解碼資料長度
lea esi, [esp + byXorOffset] ; 待解碼資料起始 (StubParam)
push esi ; 傳遞參數給 StubCode
xor_loop:
mov al, [esi]
xor al, byXorMask ; XOR 還原
mov [esi], al
inc esi
loop xor_loop
lea eax, [esp + byEntryOffset] ; StubCode 入口
call eax ; 跳轉執行
5. StubCode 功能
- 透過 StubParam 中的
LoadLibrary/GetProcAddress 解析 ws2_32.dll Socket API
VirtualAlloc 分配 RWX 記憶體 (存放下載的攻擊主體)
- 監聽 TCP 埠 (12345),接受攻擊者連線
- 將分配的記憶體基址傳給攻擊者
- 接收攻擊者傳送的經重定位 PE 映像
- 定位
AttackerEntry (RVA) 並跳轉執行
防禦機制:編譯器 /GS (Buffer Security Check)
Visual Studio .NET 2002+ 引入 /GS 編譯選項:
void ShowComputerName(SOCKET sck) {
DWORD_PTR cookie = __security_cookie; // 編譯器插入:啟動時隨機初始化
char szComputer[16];
GetComputerName(sck, szComputer);
printf(szComputer);
__security_check_cookie(cookie); // 編譯器插入:返回前驗證
}
__security_check_cookie 邏輯:
void __fastcall __security_check_cookie(DWORD_PTR cookie) {
if (cookie == __security_cookie) return; // 正常
report_failure(); // 溢位偵測 → 終止程式
}
- Cookie 置於區域變數與返回位址之間,溢位必先覆蓋 Cookie
- 隨機初始化使攻擊者難以預測/偽造 Cookie
- 缺點:僅偵測堆疊溢位,無法防禦堆積溢位;偵測後直接終止程式 (DoS)
根本原因與責任歸屬
- x86 架構:返回位址與資料共用堆疊,且堆疊向下增長 (高位址→低位址),擴大覆蓋範圍
- C 標準庫:
strcpy/gets/sprintf 等不檢查長度的函數為效率犧牲安全
- 程式設計師:未驗證輸入長度、使用不安全 API
最有效防禦:開發階段嚴格檢查緩衝區邊界、使用安全版本函數 (strcpy_s, StringCchCopy)、啟用 /GS、DEP (NX bit)、ASLR 多層防護。
- 上一篇文章: 多工 I/O 模型 (select) 實作高併發 TCP 伺服器架構設計
- 下一篇文章: Win32 序列埠控制 MFC 封裝類 CSerialPort 完整 API 參考
Copyright ©
程式碼之家 All Rights Reserved