seccomp 技术笔记

发布于 2020-07-31  3328 次阅读


Seccomp

Introduction

seccomp(secure computing mode) 是一个 Linux kernel 下的安全机制。seccomp 允许进程单向转化为一个“安全”状态,这个状态里程序无法进行任何除了 exit()sigreturn() 和对目前已经打开的文件流的 read()write()syscall。如果程序尝试调用那些被禁止的 syscall,那么程序就会退出并且触发 SIGKILLSIGSYS。 这样 seccomp 就在没有虚拟化资源的情况下把程序与系统资源进行了隔离。

Usage

首先安装 seccomp 库
sudo apt install libseccomp-dev libseccomp2 seccomp

使用 seccomp 的方式有两种

  1. 是早期的使用方式,利用系统调用 prctl 进行所有设置
  2. 现在可以直接使用seccomp_init,seccomp_rule_add,seccomp_load来设置过滤规则

seccomp

设置 seccomp 不可撤销

prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);

这样设置后,即便后续程序能够继续调用 prctl,也不能对 seccomp 的规则进行修改

设置模式
  1. 严格模式,限制程序只能使用前面提到的四个 syscall:

    prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);

  2. 自定义模式,可以通过 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 感兴趣的同学可以移步博客,里面有详细的介绍

Linux沙箱之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

file

调用 sys_write,弹出 bad system call

seccomp-bpf

这种方法进一步简化了 seccomp filter 的编写步骤与难度

filter

scmp_filter_ctx ctx;

init

ctx = seccomp_init(SCMP_ACT_ALLOW);

设置默认的处理方式,上面这种设置就是默认允许所有的 syscall

  1. SCMP_ACT_ALLOW
  2. 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 也支持复杂的逻辑,详见博客

seccomp学习笔记

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

file

可以看到,程序并没有弹出 shell,因为 execve 系统调用被禁止了。

seccomp-tools

一个用来检测 Linux 沙盒的工具,通过它也能很好看出 filter 之间的逻辑

https://github.com/david942j/seccomp-tools

用法示例:

file

做 pwn 题目时,我们可以根据 seccomp 的规则,进行相应的绕过操作。

  1. ban execve,利用 orw(open read write) 来读取 flag
  2. 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/

https://en.wikipedia.org/wiki/Seccomp


CTFer|NOIPer|CSGO|摸鱼|菜鸡