Controlling all registers at once
A sigreturn is a special type of syscall. The purpose of sigreturn is to return from the signal handler and to clean up the stack frame after a signal has been unblocked.
What this involves is storing all the register values on the stack. Once the signal is unblocked, all the values are popped back in (RSP points to the bottom of the sigreturn frame, this collection of register values).
By leveraging a sigreturn
, we can control all register values at once - amazing! Yet this is also a drawback - we can't pick-and-choose registers, so if we don't have a stack leak it'll be hard to set registers like RSP to a workable value. Nevertheless, this is a super powerful technique - especially with limited gadgets.
As with the syscalls, I made the binary using the pwntools ELF features:
It's quite simple - a read
syscall, followed by a pop rax; ret
gadget. You can't control RDI/RSI/RDX, which you need to pop a shell, so you'll have to use SROP.
Once again, I added /bin/sh
to the binary:
First let's plonk down the available gadgets and their location, as well as the location of /bin/sh
.
From here, I suggest you try the payload yourself. The padding (as you can see in the assembly) is 8
bytes until RIP, then you'll need to trigger a sigreturn
, followed by the values of the registers.
The triggering of a sigreturn
is easy - sigreturn is syscall 0xf
(15
), so we just pop that into RAX and call syscall
:
Now the syscall looks at the location of RSP for the register values; we'll have to fake them. They have to be in a specific order, but luckily for us pwntools has a cool feature called a SigreturnFrame()
that handles the order for us.
Now we just need to decide what the register values should be. We want to trigger an execve()
syscall, so we'll set the registers to the values we need for that:
However, in order to trigger this we also have to control RIP and point it back at the syscall
gadget, so the execve actually executes:
We then append it to the payload and send.