Seccomp
Introduction
seccomp(secure computing mode) 是一个 Linux kernel 下的安全机制。seccomp 允许进程单向转化为一个“安全”状态,这个状态里程序无法进行任何除了 exit()
、sigreturn()
和对目前已经打开的文件流的 read()
、write()
的 syscall
。如果程序尝试调用那些被禁止的 syscall
,那么程序就会退出并且触发 SIGKILL
、SIGSYS
。 这样 seccomp 就在没有虚拟化资源的情况下把程序与系统资源进行了隔离。
Usage
首先安装 seccomp 库
sudo apt install libseccomp-dev libseccomp2 seccomp
使用 seccomp 的方式有两种
- 是早期的使用方式,利用系统调用 prctl 进行所有设置
- 现在可以直接使用seccomp_init,seccomp_rule_add,seccomp_load来设置过滤规则
seccomp
设置 seccomp 不可撤销
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
这样设置后,即便后续程序能够继续调用 prctl,也不能对 seccomp 的规则进行修改
设置模式
-
严格模式,限制程序只能使用前面提到的四个
syscall
:prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
-
自定义模式,可以通过 filter 进行限制
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
prog 是一个结构体,其中可以配置 filter,后文介绍
filter
每个 filter
对应一条规则
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
filter
的编写稍微有点复杂,分为 BPF_STMT(code, k)
BPF_JUMP(code, k, jt, jf)
两个宏,一个是声明,一个是跳转。
code
部分由多个常数相加之后传入,代表了一个操作的类型(和汇编类似)
#define BPF_CLASS(code) ((code) & 0x07) //首先指定操作的类别
#define BPF_LD 0x00 //将值cp进寄存器
#define BPF_LDX 0x01
#define BPF_ST 0x02
#define BPF_STX 0x03
#define BPF_ALU 0x04
#define BPF_JMP 0x05
#define BPF_RET 0x06
#define BPF_MISC 0x07
/* ld/ldx fields */
#define BPF_SIZE(code) ((code) & 0x18) //在ld时指定操作数的大小
#define BPF_W 0x00
#define BPF_H 0x08
#define BPF_B 0x10
#define BPF_MODE(code) ((code) & 0xe0) //操作数类型
#define BPF_IMM 0x00
#define BPF_ABS 0x20
#define BPF_IND 0x40
#define BPF_MEM 0x60
#define BPF_LEN 0x80
#define BPF_MSH 0xa0
/* alu/jmp fields */
#define BPF_OP(code) ((code) & 0xf0) //当操作码类型为ALU时,指定具体运算符
#define BPF_ADD 0x00 //到底执行什么操作可以看filter.h里面的定义
#define BPF_SUB 0x10
#define BPF_MUL 0x20
#define BPF_DIV 0x30
#define BPF_OR 0x40
#define BPF_AND 0x50
#define BPF_LSH 0x60
#define BPF_RSH 0x70
#define BPF_NEG 0x80
#define BPF_MOD 0x90
#define BPF_XOR 0xa0
#define BPF_JA 0x00 //当操作码类型是JMP时指定跳转类型
#define BPF_JEQ 0x10
#define BPF_JGT 0x20
#define BPF_JGE 0x30
#define BPF_JSET 0x40
#define BPF_SRC(code) ((code) & 0x08)
#define BPF_K 0x00 //常数
#define BPF_X 0x08
甚至可以把 syscall
的调用号存入寄存器,以及在不同的 filter
之间跳转,从而实现复杂的 seccomp
逻辑
对 seccomp
感兴趣的同学可以移步博客,里面有详细的介绍
prog
这个结构体用来存放 filter
的数量以及 filter
指针
struct sock_fprog {
unsigned short len; /* Number of BPF instructions */
struct sock_filter *filter; /* Pointer to array of
BPF instructions */
};
example
#include <stdio.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
int main(){
struct sock_filter filter[] = { //规则更改在此处完成
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL), //规则只有一条,即禁止所有系统调用
};
struct sock_fprog prog = { //这是固定写法
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),//规则条数
.filter = filter, //规则entrys
};
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0); //必要的,设置NO_NEW_PRIVS
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);//过滤模式,重点就是第三个参数,过滤规则
printf("test");
}
编译
gcc test.c -o test -lseccomp
调用 sys_write
,弹出 bad system call
seccomp-bpf
这种方法进一步简化了 seccomp filter
的编写步骤与难度
filter
scmp_filter_ctx ctx;
init
ctx = seccomp_init(SCMP_ACT_ALLOW);
设置默认的处理方式,上面这种设置就是默认允许所有的 syscall
- SCMP_ACT_ALLOW
- SCMP_ACT_KILL
add filter
int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...);
action
可以自行设置,一般 SCMP_ACT_KILL 终止进程即可;
/*
* seccomp actions
*/
/**
* Kill the process
*/
#define SCMP_ACT_KILL 0x00000000U
/**
* Throw a SIGSYS signal
*/
#define SCMP_ACT_TRAP 0x00030000U
/**
* Return the specified error code
*/
#define SCMP_ACT_ERRNO(x) (0x00050000U | ((x) & 0x0000ffffU))
/**
* Notify a tracing process with the specified value
*/
#define SCMP_ACT_TRACE(x) (0x7ff00000U | ((x) & 0x0000ffffU))
/**
* Allow the syscall to be executed after the action has been logged
*/
#define SCMP_ACT_LOG 0x7ffc0000U
/**
* Allow the syscall to be executed
*/
#define SCMP_ACT_ALLOW 0x7fff0000U
syscall
一般填要 ban
的系统调用即可
SCMP_SYS(execve)
SCMP_SYS(read)
当然,seccomp-bpf
也支持复杂的逻辑,详见博客
load filter
上面将所有规则添加之后,进行加载即可
seccomp_load(ctx);
example
#include <stdio.h>
#include <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);
write(1,"i will give you a shell\n",24);
system("/bin/sh");
return 0;
}
编译 gcc test.c -o test -lseccomp
可以看到,程序并没有弹出 shell,因为 execve
系统调用被禁止了。
seccomp-tools
一个用来检测 Linux 沙盒的工具,通过它也能很好看出 filter
之间的逻辑
https://github.com/david942j/seccomp-tools
用法示例:
做 pwn 题目时,我们可以根据 seccomp 的规则,进行相应的绕过操作。
- ban
execve
,利用 orw(open read write) 来读取 flag - ban
execve、write
,编写ac自动机来猜测flag(既然无法打印,那么利用系统的错误输出等方法,可以猜测出flag)
References
https://blog.csdn.net/ATOOHOO/article/details/88957596
https://blog.betamao.me/2019/01/23/Linux%E6%B2%99%E7%AE%B1%E4%B9%8Bseccomp/
https://blog.jingwei.site/2018/10/31/seccomp-de-shi-yong/
https://veritas501.space/2018/05/05/seccomp%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/