核心功能是将内核态与用户态做严格隔离,安全计算模式–seccomp,主要用于限制用户进程能够使用的系统调用;

模式

1
SECCOMP_SET_MODE_STRICT

它是 Linux 内核安全计算模块 (seccomp) 中的一个操作指令,用于将调用线程的系统调用权限限制到极致。启用此模式后,线程只被允许调用 read、write、_exit 和 sigreturn 这四个系统调用,任何其他系统调用都会导致线程被 SIGKILL 信号立即终止;

1
SECCOMP_SET_MODE_FILTER

filter模式的核心是允许开发者使用 BPF(Berkeley Packet Filter) 程序来定义哪些系统调用可以被执行、以何种方式被执行,或是直接被拒绝;

BPF

BPF有一套伪汇编,用于编写过滤规则,定长指令,总共64位,如下结构所示:

1
2
3
4
5
6
struct sock_filter {
__u16 code;
__u8 jt; //jump true
__u8 jf; //jump false
__u32 k; //generic multiuse field
}

code在BPF.h中拥有宏定义:linux/include/uapi/linux/bpf.h at master · torvalds/linux · GitHub

编写好的filter结构以数组方式挨着排放,是一个队列,可以继续添加,类似访问控制列表。然后用以下结构打包:

1
2
3
4
struct sock_fprog {
unsigned short len; //块数
struct sock_filter __user * filter;
}

一般使用下面两个最常用的宏来进行BPF的编写:

1
2
BPF_STMT(code, k)  //用于操作BPF的寄存器
BPF_JUMP(code, k, jt, jf) //用于跳转

有个头文件能够便捷快速的去设置BPF规则,用这个网站:outflux.net/teach-seccomp/step-3/seccomp-bpf.h

里面定义了一些BPF宏,快速地去满足一些常见的PWN的要求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define VALIDATE_ARCHITECTURE \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)

#define EXAMINE_SYSCALL \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)

#define ALLOW_SYSCALL(name) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)

#define KILL_PROCESS \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)

使用上面的4个宏定义可以快速地去设置白名单的系统调用规则的过滤,具体过滤如下:

1
2
3
4
5
6
7
8
9
10
11
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
KILL_PROCESS,
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};

这样一来就设置好了只允许该进程使用read和write的系统调用;

prctl激活

设置模式使用到系统调用prctl:

1
2
3
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0); //使seccomp对所有用户(包括root)生效
prctl(PR_SET_SECCOMP,SECCOMP_MODE_STRICT); //设为严格模式
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog); //设为过滤模式,prog则为sock_fprog结构体

绕过

限制了system以后就无法提权,大部分时候(白名单)是通过shellcode或者ROP来写一个ORW,即open,openv,read,readv,write,writev;

如果是黑名单过滤,那么可以找功能相似的其他调用号;

设置了seccomp的程序可以通过工具seccomp-tools来获取过滤规则:

1
seccomp-tools dump ./process

未检查架构

同样也是黑名单;

32位和64位的系统调用号不同,如果没有检查架构,那么就可以钻空子;

可以利用retq指令修改cs寄存器的值,cs=0x23表示32位,cs=0x33表示64位:

1
2
3
mov DWORD [rsp+4], 0x23
retf
//跨段返回,先pop rip,再pop cs

比如在64位下禁用了59号,也就是execve,但在32位下是另一个数字;

未检查大小

同样也是黑名单;

linux有个机制叫做x32-abi模式;有个掩码是__X32_SYSCALL_BIT,默认为0x40000000;

当系统调用号或上这个掩码的时候,意思是使用x32-abi模式来进行系统调用;

当禁用59号,那么或上掩码,没有检查调用号大小,就能正常执行系统调用,从而绕过;