HTB Satellite Hijack - Pez1181/CTF GitHub Wiki

๐Ÿ›ฐ๏ธ [Hard] SatelliteHijack

๐Ÿ”’ Challenge Type: Reversing
๐Ÿ”‘ Vulnerability: Encrypted shellcode & XOR validation
๐Ÿ›ก๏ธ Protection Bypassed: memfrob obfuscation, runtime decryption, XOR-based flag check


๐Ÿ“ฆ Files Provided

  • satellite: PIE-enabled ELF executable that echoes user input
  • library.so: Shared object, stripped and crashes on execution
  • shellcode.bin: Extracted from library.so's .rodata post-decryption

๐Ÿ” Recon & Vulnerability Analysis

๐Ÿงช Step 1: Initial Testing

Running satellite shows it simply echoes back input โ€” no visible vulnerability.

Using ltrace, we spot this call:

send_satellite_message(...)

This is dynamically resolved from library.so, confirming that real logic is offloaded into the shared object.


๐Ÿ”“ Step 2: Backdoor Discovery

Reversing send_satellite_message() in library.so reveals:

strncpy(local_28,"TBU`QSPE`FOWJSPONFOU", 0x15);
for (...) local_28[i] -= 1;
getenv(local_28); // becomes SAT_PROJECTENVIRONMENT

So: setting the environment variable SAT_PROJECTENVIRONMENT triggers a hidden payload!

We run:

SAT_PROJECTENVIRONMENT=1 ./satellite

Still no output โ€” but under the hood, it jumps to FUN_001023e3().


๐Ÿ”ฅ Step 3: Shellcode Execution

Inside FUN_001023e3() we find:

  • Allocates RWX memory via mmap()
  • Copies .rodata block into memory
  • Calls memfrob() (XOR with 0x2A)
  • Then executes the result

So: encrypted shellcode is sitting in .rodata, and executed only if the env var is set.

We dump this decrypted shellcode using Ghidra or runtime memory dumping tools.


๐Ÿงฉ Step 4: XOR-Encoded Flag Check

Disassembling the shellcode shows:

mov rax, 0x37593076307b356c
mov rdx, 0x3a7c3e753f665666
...
check[i] = <encrypted bytes>
if ((input[i] ^ check[i]) != i) fail;

This means:

To solve: XOR each encrypted byte with its index to recover the correct flag character.

From runtime or static analysis, we extract the encrypted check buffer:

buf = bytearray(b"l5{0v0Y7fVf?u>|:O!|Lx!o$j,;f")

โœ… Final Script

This fully reconstructs the flag:

# Final working solution โ€” reconstructs the flag from memory-extracted check buffer

buf = bytearray(b"l5{0v0Y7fVf?u>|:O!|Lx!o$j,;f")

# XOR each byte with its index
for i in range(len(buf)):
    buf[i] ^= i

# Format and print flag
flag = b"HTB{" + buf + b"}"
print(flag.decode())

๐Ÿ Output

HTB{S4t3llite_D0wn_S4t3ll1t3_DOWNNN!}

๐Ÿง  Final Notes

This was a beautifully layered challenge:

  • Used obfuscated data in .rodata (memfrob)
  • XOR trick required reversing a validation function
  • Required combining static reverse engineering with runtime environment manipulation

Every layer peeled back revealed a new one โ€” true to its name. ๐Ÿง…

HTB{S4t3llite_D0wn_S4t3ll1t3_DOWNNN!}