Evil Corp - Pwn (Medium)
Challenge Info
- Category: Pwn
- Difficulty: Medium (697 solves)
- Description: N/A
- Binary: ELF 64-bit PIE, dynamically linked, not stripped
Binary Analysis
Protections
- PIE: Enabled
- NX: Enabled
- Canary: Disabled
- RELRO: Partial
Key Functions
Setup(): Initializes locale (UTF-8), unbuffered I/O, and two mmap regions at fixed addresses:SupportMsgat 0x10000 (RW, 0x4b0 bytes, MAP_FIXED)AssemblyTestPageat 0x11000 (RWX, 0x800 bytes, MAP_FIXED)
Login(): Wide-char login with hardcoded credentials (eliot/4007). Password read viafgetws(rsp, 0x12c, stdin)into a 0x150-byte stack frame — stack buffer overflow.ContactSupport(): Reads 0x1000 wchars, converts viawcharToChar16()to SupportMsg. Since char16 output can reach 0x2000 bytes from 0x10000, it overflows into the RWX page at 0x11000.GetOpt(): Menu with 4 options. Option 2 = ContactSupport, Option 3 = Logout.
Program Flow
main → Setup → loop { WelcomeMsg → Login (if not logged in) → ShowNotifications → GetOpt }
Vulnerability
Two vulnerabilities chained:
ContactSupport char16 overflow:
wcharToChar16writes up to 0x1000 char16 values (0x2000 bytes) starting at SupportMsg (0x10000). With 0x800 wchars of padding, the output reaches 0x11000 (AssemblyTestPage, RWX), allowing shellcode injection.Login password buffer overflow:
fgetws(rsp, 0x12c, stdin)reads up to 300 wchars (1200 bytes) into a 0x150-byte frame. Return address at offset 0x158 (wchar[86]).
Exploit Strategy
- Login with
eliot/4007 - ContactSupport → send 0x800 'A' padding wchars + shellcode encoded as wchars → shellcode lands at 0x11000 (RWX page)
- Logout (option 3)
- Login again → overflow password buffer:
- 86 wchars padding +
chr(0x11000)(return addr low 4 bytes) + null byte (high 4 bytes) + padding to fill fgetws buffer (299 total wchars) - Using
r.send()without newline — fgetws stops after reading 299 chars (n-1) - The null byte (
\x00) as UTF-8 produces wchar 0x00000000 for the return address high dword
- 86 wchars padding +
Key Technique: wchar_t → char16_t Shellcode Encoding
Each 2-byte pair of shellcode is encoded as one Unicode character:
val = shellcode[i] | (shellcode[i+1] << 8) # e.g., 0x48 0x31 → U+3148
The wcharToChar16 function stores only the low 16 bits, preserving the exact shellcode bytes in little-endian.
Key Technique: Null Byte in fgetws
To set return address high 4 bytes to 0x00000000, a raw \x00 byte is sent in the UTF-8 stream. glibc's fgetws processes null bytes as wchar 0x0000 (does NOT stop — only stops at newline or n-1 chars).
Shellcode
execve("/bin/sh", NULL, NULL) - 28 bytes
48 31 f6 56 48 bf 2f 62 69 6e 2f 2f 73 68 57 48
89 e7 48 31 d2 48 31 c0 b0 3b 0f 05
Flag
HTB{45c11_15_N07_4L0000n3}