Lack of space for ROP
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.
Possibly the simplest, but also the least likely to exist. If there is one of these, you're quite lucky.
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:
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.
Using leave; ret to stack pivot
By calling leave; ret
twice, as described, this happens:
By controlling the value popped into RBP, we can control RSP.
As before, but with a difference:
I won't bother stepping through it again - if you want that, check out the pop rsp walkthrough.
Essentially, that pops buffer
into RSP (as described previously).
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
.
Stack Pivoting
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.
Just to get the basics out of the way, as this is common to both approaches:
Using a pop rsp gadget to stack pivot
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:
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.
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.