HackTheBox Pwn Challenge - Evil Corp (Medium)

Published on: 4/18/2026

Summary: We turned our assembly tester off because a big mistake from our new C developer. Do you think there are other mistakes he made ?


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:
    • SupportMsg at 0x10000 (RW, 0x4b0 bytes, MAP_FIXED)
    • AssemblyTestPage at 0x11000 (RWX, 0x800 bytes, MAP_FIXED)
  • Login(): Wide-char login with hardcoded credentials (eliot / 4007). Password read via fgetws(rsp, 0x12c, stdin) into a 0x150-byte stack frame — stack buffer overflow.
  • ContactSupport(): Reads 0x1000 wchars, converts via wcharToChar16() 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:

  1. ContactSupport char16 overflow: wcharToChar16 writes 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.

  2. 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

  1. Login with eliot / 4007
  2. ContactSupport → send 0x800 'A' padding wchars + shellcode encoded as wchars → shellcode lands at 0x11000 (RWX page)
  3. Logout (option 3)
  4. 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

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}
Table of Contents