seccomp沙箱及常见绕过
基本概念
PR_SET_SECCOMP
是 Linux 内核提供的一种机制,用于限制进程可以执行的系统调用,从而增强系统的安全性。PR_SET_SECCOMP
机制可以通过使用 prctl()
系统调用来设置,具体来说,可以通过 PR_SET_SECCOMP
命令设置进程的 seccomp 过滤器,或通过 PR_SET_NO_NEW_PRIVS
命令设置进程的 no_new_privs
标志。
seccomp 过滤器可以通过编写 BPF(Berkeley Packet Filter)程序来实现,BPF 程序可以过滤进程所发起的系统调用,只允许特定的系统调用通过,从而限制进程的行为。seccomp 过滤器只能在进程启动时设置,并且一旦设置,就不能修改,这样可以防止攻击者通过注入代码来修改过滤器。
PR_SET_NO_NEW_PRIVS
标志可以用于禁止进程获取更高的权限,即使进程拥有特权级别的用户或进程权限。这可以防止进程通过提升权限来攻击系统,从而增强系统的安全性。
一般使用 seccomp 有两种方法,一种是用 prctl
,另一种是用 seccomp
。
例
1 |
|
上述代码执行时会报错退出。原因是 seccomp 阻止了程序通过 execve 来执行 syscall。
沙箱开启
使用prctl
创建沙箱
我们可以借助工具seccomp-toolsl
来编写沙箱规则。
首先编写沙箱规则,这里我们保存在文件rule
中了。cs:
1 |
|
运行命令将沙箱规则转换为可被PR_SET_SECCOMP
识别的规则。
1 |
|
将生成的规则应用到C程序中,这里使用prctl
系统调用来设置沙箱规则。
1 |
|
编译后通过 seccomp-tools dump
命令可以看到程序中有了 seccomp 规则(ptctl
系统调用需要 root 权限因此需要加 sudo)。
1 |
|
运行程序发现 execve
系统调用无法正常执行。
1 |
|
使用 seccomp 创建沙箱
seccomp
相当于对prctl做了一个封装
如果是使用 seccomp
系统调用添加规则,那么首先需要安装 seccomp
库的开发包:
1 |
|
前面的代码可以写作如下形式:
1 |
|
其中添加规则的函数 seccomp_arch_add
定义如下:
1 |
|
其中参数解释如下:
ctx
:过滤器上下文,用于存储过滤规则。action
:当规则匹配时的操作,可以是以下值之一。SCMP_ACT_ALLOW
:允许系统调用。SCMP_ACT_KILL
:杀死进程。SCMP_ACT_ERRNO
:返回错误码并允许系统调用,用法为SCMP_ACT_ERRNO(返回值)
,这样该系统调用如果满足条件则直接返回定义的返回值而不进行系统调用。在某些题目中通常用来劫持特定系统调用返回特殊值,比如劫持open
系统调用返回 0 即标准输入。
syscall
:要限制的系统调用号。arg_cnt
:要匹配的参数数量,如果没有参数需要匹配,则arg_cnt
应该为 0 。...
:可变参数列表,用于指定要匹配的参数值。对于每个参数,需要指定一个scmp_arg_cmp
结构体,这个结构体包含了参数的比较方式和比较值。scmp_arg_cmp
结构体定义如下:
1 |
|
arg
:要比较的参数序号,从0开始。op
:比较方式,可以是以下值之一:SCMP_CMP_NE
:不等于SCMP_CMP_EQ
:等于SCMP_CMP_LT
:小于SCMP_CMP_LE
:小于等于SCMP_CMP_GT
:大于SCMP_CMP_GE
:大于等于SCMP_CMP_MASKED_EQ
:按位与运算后等于(比较值为掩码)。
datum_a
:用来与参数进行比较的值。
例如下面的代码添加的规则是规定 read
必须从标准输入读取不超过 BUF_SIZE
的内容到 buf
中。
1 |
|
绕过方法
orw
orw
32位(55字节)
1
2
3
4
5shellcode=''
shellcode+=shellcraft.open('./flag')
shellcode+=shellcraft.read('eax','esp',0x100)
shellcode+=shellcraft.write(1,'esp',0x100)
shellcode=asm(shellcode)orw(56字节)
1 |
|
- orw(43字节)
1 |
|
- 64位(66字节)
1 |
|
- 某些题目还会禁用 SYS_open,需要用 SYS_openat 代替。
1 |
|
是否可以进行rop
往往在动态链接程序我们通过库函数进行orw
静态链接程序我们就通过系统调用进行orw
shellcode进行orw
pwntabe_orw
- 查保护
几乎没有保护,所以我们可以尝试shellcode。
1 |
|
- 分析程序
程序提升让我们输入 shellcode 并且将 shellcode 指令当作函数调用执行。
我们直接尝试shellcode,但是我们传统的通过execve
获取shell的方式并不可行。
这是因为这里所考察的并不是那种简单shellcode,通过题目名称我们可以知道这里我考察的是orw。
而考察orw的题目一般都是沙箱题目。
1 |
|
我们通过seccomp-tools这个工具来检查沙箱禁止了哪些系统调用。
这里使用了白名单机制即允许指定的系统调用执行,其它全部禁止。
可以看到我们orw中所用到的open
、read
、write
三个函数都在白名单上,所以我们可以直接进行orw。
1 |
|
- exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#!/usr/bin/env python3
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
shellcode=shellcraft.open("/home/orw/flag")
#上面open打开的文件标识符存放在eax中,所以我们从eax将flag读到栈中
shellcode+=shellcraft.read("eax","esp",100)
shellcode+=shellcraft.write(1,"esp",100)
shellcode=asm(shellcode)
r()
sl(shellcode)
ia()
使用相似功能调用号
有的题目除了禁用 execve
系统调用外,还可能会禁用 open
等系统调用。对于这种情况我们可以使用可以代替被禁用的系统调用的其他系统调用。
1 |
|
注意,libc 中的 open
函数底层实现也是 openat
系统调用。
1 |
|
例题:2024YLCTF orw
- 思路
沙箱禁止了常规的系统调用,我们通过openat和sendfile来进行绕过。
- exp
1 |
|
2024NewStar Easy_Shellcode
使用4字节系统调用
例如下面这种情况,虽然所有可例用的系统调用号都被禁了,但是由于没有判断 sys_number >= 0x40000000
的情况,因此可以使用 0x40000000|sys_number
来绕过。这里 sys_number
是 64 位的系统调用号。
1 |
|
正常应该有下面这个判断:
1 |
|
使用32位shellcode
根据下面两条性质绕过沙箱:
x86 架构的 CPU 根据 CS 段寄存器(段选择子)对应的段描述符中的属性确定当前访问的指令是 32 位还是 64 位。
linux 程序在 32 位和 64 位下的系统调用号不同。
缺少对当前架构的判断
示例程序:
1 |
|
对应是沙箱规则如下,其中缺少对当前架构的判断。
1 |
|
由于沙箱采取白名单机制,我们无法使用 open
或者 openat
系统调用获取 flag
文件句柄,但是 64 位下的 fstat
调用号和 32 位下的 open
调用号相同,因此我们可以切换到 32 位下调用 open
系统调用。不过需要注意:
rdi
寄存器需要指向 shellcode 的地址。- shellcode 的地址需要小于 0x100000000 。
rsp
需要小于 0x100000000 。
exp:
1 |
|
侧信道
示例代码如下:
1 |
|
例如下面这种情况,由于是采用白名单过滤系统调用,因此所有与输出有关的系统调用都被禁了(有的题目是关闭了输出流),也就是说我们无法输出 flag 。虽然无法输出,但是我们可以将读出的 flag 的某一特定字节与给定字节比较,从而逐字节爆破 flag 。
1 |
|
这里有一个判断进程是否退出的技巧:p.recv(timeout=1)
。如果进程已经结束会触发异常,而进程未结束但没有输出导致超时则接收数据长度为 0 ,并不会触发异常。
exp:
1 |
|
使用close绕过fd参数检查
示例代码如下:
1 |
|
沙箱规则如下:
1 |
|
主要是下面这几条规则:
- 可以是
exit_group
系统调用。 - 不能是
open
。 - 如果是
read
则fd
只能为 0 。 - 不能是
execve
。
open
可以用 openat
代替,read
要想读文件则需要将 stdin
关闭。
exp:
1 |
|
DASCTF2024 金秋十月赛 sixbytes
查看发现程序禁止了标准输出
1 |
|
ptrace绕过seccomp
KCTF 2024 第8题-星门
后言
参考:[原创]KCTF 2024 第8题-星门-WriteUp-Ptrace绕过Seccomp
参考:看雪pwn探索篇