大纲
usage: ceccomp <asm|disasm|emu|trace|probe|version|help> [FILE] [-q|--quiet]
[-f|--format FMT] [-a|--arch ARCH] [-p|--pid PID] [-s|--seize]
[-o|--output FILE] [-c|--color WHEN] ...
概念
内核使用BPF过滤器来限制系统调用规则,并使用 seccomp 和 prctl 两个系统调用来安装过滤器。以下是一个以十六进制表示的限制 execve 系统调用的简单过滤器:
1: 20 00 00 00 00 00 00 00 $A = $syscall_nr 2: 15 00 00 01 3b 00 00 00 if ($A != execve) goto 4 3: 06 00 00 00 00 00 00 00 return KILL 4: 06 00 00 00 00 00 ff 7f return ALLOW
以上十六进制的部分就是内核收到的过滤器,而 ceccomp 负责把它拿来反汇编为人类可读的文本。 例如左侧的 行号 和右侧的 伪代码 。
|
Important
|
之后我会使用 TEXT 作为BPF过滤器人类可读文本(伪代码)的缩写, 使用 RAW 作为BPF过滤器原始格式的缩写,请记住这个约定。 |
描述
ceccomp 有5个主要的功能,它基本上是C版本的 seccomp-tools ,然而, 有一些不同的地方你需要知道,它们会在在每个子命令的章节中被注明。
asm - 汇编
ceccomp asm [-c WHEN] [-a ARCH] [-f FMT] [TEXT]
将 TEXT 汇编为 RAW 。适用于将手写的过滤器规则嵌入到C代码中, 或希望观察一些 TEXT 对应的原始字节码。
- WHEN
-
决定了
ceccomp何时输出有颜色的文本。当设置为 auto 时,ceccomp会在输出目标是一个“tty”时打印颜色。可以是 auto 、 never 或者 always 。 默认值是 auto 。 - ARCH
-
可以设置为任何 libseccomp 支持的架构。它将被用于决定系统调用名称对应的系统调用号。 例如,在 x86_64 上,就像上面的基本示例,你可以写
"execve"而不是数字59指代系统调用号。如果不设置这个参数,则通过uname提取当前系统的架构。 你的系统上的默认值是 x86_64 。
|
Note
|
从 4.0 版本 开始考虑端序。如果目标架构 ARCH 的端序与机器端序不同,则会在输出前反转过滤器(CODE 和 K)。 |
- FMT
-
决定了
ceccomp如何输出二进制格式的BPF字节码。可以是 hexfmt 、 hexline 或者 raw 。你可以在 ceccomp 示例 节中找到示例输出。默认值是 hexline 。 - TEXT
-
一个可选的文件名,其中存放了需要被汇编的 TEXT 。不设置则从 标准输入 中读取。
-被视作 标准输入。
|
Important
|
4.0 版本 大幅修改了汇编的语法,请查阅下面的语法参考! |
查看 TEXT 语法参考 一节可以找到如何手写规则。一些示例会在 ceccomp 示例 一节中展示。
| 命令 | 差别 |
|---|---|
|
使用它自己的语法汇编,有点像脚本;可以汇编会被内核拒绝的错误 TEXT |
|
你可以直接拿着 |
disasm - 反汇编
ceccomp disasm [-c WHEN] [-a ARCH] [RAW]
反汇编 RAW 为 TEXT 。适用于当你无法使用 trace 看到过滤器时,必须手动提取过滤器, 然后检查其含义。
- WHEN
-
参数描述可以在 asm - 汇编 一节中找到。
disasm可能会打印更多有颜色的文本,包括针对 TEXT 的语法高亮。 - ARCH
-
可以设置为任何 libseccomp 支持的架构。它将被用于决定 RAW 中的系统调用号如何被翻译为系统调用名。 例如,在 x86_64 上,在比较系统调用号时,数字
0x3b将被翻译为execve,可以看上面的基本示例。 你的系统上的默认值是 x86_64 。 - RAW
-
包含原始 BPF 代码的二进制文件。如果不设置则将 标准输入 作为输入。
-被视作 标准输入。这个文件是与架构有关的,所以它在不同架构之间可能不是通用的。
|
Note
|
从 4.0 版本 开始考虑端序。如果目标架构 ARCH 的端序与机器端序不同,则会在解码前反转过滤器(CODE 和 K)。 |
|
Note
|
当且仅当在某一行的架构可以被确定时,ceccomp 才会尝试用那个架构解析那个系统调用号。 如果是外部架构(不等于你设置的架构),它会被附加到系统调用名前。你可能会注意到在一些情况下, seccomp-tools 能解析一些系统调用名,而 ceccomp 不能,这可能是因为此时架构不能被确定。 |
| 命令 | 差别 |
|---|---|
|
用它自己的语法反汇编;永远不会检查 RAW 是否合法 |
|
用 ceccomp 语法反汇编,并且默认将 标准输入 作为输入;严格检查架构, 并且永远打印外部架构名 |
emu - 模拟
ceccomp emu [-c WHEN] [-a ARCH] [-q] TEXT SYSCALL_NAME/SYSCALL_NR [ARGS[0] ARGS[1] ... ARGS[5] PC]
按照 TEXT 中描述的规则,模拟从 PC 调用 syscall(SYSCALL_NR, ARGS[0], ARGS[1], …, ARGS[5]) 的结果。适用于在不实际运行程序或不想手动检查规则时,查看触发系统调用的结果。 这个子命令适合用来自动检测一个过滤器。
- WHEN
-
参数描述可以在 asm - 汇编 一节中找到。
emu可能会打印更多有颜色的文本,包括针对 TEXT 的语法高亮以及跳过的伪代码。 - SYSCALL_NAME/SYSCALL_NR
-
如果你设置了 SYSCALL_NAME (比如
execve),那么它会基于 ARCH 先被翻译为对应的 SYSCALL_NR 。或者你可以直接设置 SYSCALL_NR (例如59)。然后会测试这个系统调用号经过 BPF 过滤器处理后的输出并打印出来。这个参数是必填的。 - ARGS[0-5] 和 PC
-
当调用系统调用时对应寄存器的值。例如,在 x86_64 上,它们分别对应了
rdi、rsi、rdx、r10、r8、r9和rip。它们的默认值都是0。 - ARCH
-
参数描述可以在 asm - 汇编 一节中找到。
- TEXT
-
一个文件名,其中存放了需要被测试的 TEXT 规则。注意文件名 不能 被忽略,因为 ceccomp 无法判断一个位置参数是系统调用号还是文件名。使用
-可以指代 标准输入。 - -q, --quiet
-
只打印过滤器的模拟结果。例如,最后一行模拟的伪代码是
return KILL,那么将会打印KILL。
| 命令 | 差别 |
|---|---|
|
用 RAW 作为输入 |
|
用 TEXT 作为输入,并默认将 标准输入 作为输入;可以设置 PC |
trace - 运行时捕获过滤器
ceccomp trace [-c WHEN] [-o FILE] PROGRAM [program-args]
[-c WHEN] -p PID [-s]
使用第一行的命令可以利用调试在 PROGRAM 运行中加载过滤器时动态捕获过滤器; 第二行的命令可以从 PID 对应的进程中提取出 seccomp 过滤器,或通过调试 PID 捕获后续的 seccomp 过滤器;一旦捕获到了过滤器, 将会以 TEXT 的格式将它打印出来。你可以从两个格式中选择一个使用。 适用于运行一个程序是捕获BPF过滤器最简单的方式或者一个安装了 seccomp 过滤器的程序正在等待输入。
- WHEN
-
参数描述可以在 asm - 汇编 一节中找到。
trace可能会打印更多有颜色的文本,包括针对 TEXT 的语法高亮。 - FILE
-
当 PROGRAM 会产生很多输出到 标准错误 时可能很有用。
ceccomp允许用户关闭 标准输入 和 标准输出 来限制 PROGRAM 的输入和输出,所以 当运行 PROGRAM 时ceccomp使用 标准错误 来打印消息。如果你想在别的文件中看见 TEXT 的话请设置 FILE 。-被视作 标准输出。 - PROGRAM
-
设置为你想运行的程序,并且 program-args 将作为它的参数, 就像运行 shell 命令
exec PROGRAM program-args。 - PID
-
设置为你想检查的 pid。 PID 和 PROGRAM 相冲突;你只能在一条命令中动态运行一个程序, 或者检查一个 pid。没有
-s标志,trace pid 会尝试使用ptrace(PTRACE_SECCOMP_GET_FILTER)从 PID 中提取 seccomp 过滤器,这个操作在一些系统上可能不可用。 - -s, --seize
-
只适用于 TRACE PID 模式。 设置这个标志将会覆盖 trace pid 的行为为像 trace prog 模式一样把调试器挂到 PID 上并持续跟踪 seccomp 过滤器的加载。这个标志引入于 4.0 版本。
- -q, --quiet
-
设置这个标志将会在检测到进程分叉、退出或加载 seccomp 过滤器时抑制多余的 [INFO] 输出。 这个标志引入于 4.0 版本。
|
Note
|
要想从 PID 中提取过滤器,你需要 CAP_SYS_ADMIN (没有 -s 标志),同时还可能需要 CAP_SYS_PTRACE ,最简单的获取它们的方法是用 sudo 运行 ceccomp 。
|
|
Note
|
从 3.1 版本 开始引入了多进程支持,并且当被调试进程 fork/resolve/exit 时,将会打印一条额外的 INFO 信息。你可以使用像 ceccomp trace -q PROG 2>/dev/null 这样的命令丢弃它。
|
| 命令 | 差别 |
|---|---|
|
可以设置输出格式;每一个过滤器可以输出到不同的文件;当 PROGRAM
加载了 LIMIT 个过滤器后就杀死程序;将 PROGRAM 包装在 |
|
所有过滤器被输出到同一个文件;永远不会杀死 PROGRAM ; PROGRAM 是直接被执行的,
所以不需要 |
probe - 快速测试常见的系统调用
ceccomp probe [-c WHEN] [-o FILE] [-q] PROGRAM [program-args]
以 program-args 为参数运行 PROGRAM 来捕获 第一个 seccomp 过滤器, 然后杀死所有子进程。适用于快速测试一个程序的规则并检测潜在的 seccomp 规则问题。
所有参数描述都可以在 trace - 运行时捕获过滤器 一节中找到。
这个子命令的输出是一系列常见的系统调用的模拟结果,例如 execve 、 open 等。 如果过滤器本身并不能阻拦系统调用,那你一眼就能看出来。
这个子命令的典型输出如下所示,更多完整的实例可以在 ceccomp 示例 一节中找到。
open -> ALLOW read -> ALLOW write -> ALLOW execve -> KILL execveat -> KILL mmap -> ALLOW mprotect -> ALLOW openat -> ALLOW sendfile -> ALLOW ptrace -> ERRNO(1) fork -> ALLOW
|
Note
|
seccomp-tools 没有等价的子命令。
|
TEXT 语法参考
|
Important
|
4.0 版本 提高了词法分析器代码的可读性,伴随着大幅修改的语法。前缀为 # 的行现在是注释了。同时 行号 被替换为 标签,现在词法分析器根据标签定义来决定跳转到哪里,而不是根据 TEXT 文件中的行号。
|
这里有正确的 TEXT 格式,以类 EBNF 语法描述:https://github.com/dbgbgtf1/Ceccomp/issues/17#issuecomment-3610531705。 没兴趣了解 EBNF?请继续阅读以下的例子。
其余未描述到的BPF操作都被内核禁止了。
注释与标签
ceccomp disasm 展示了很多东西,但对于 asm 来说有些是可选的。
#Label CODE JT JF K #--------------------------------- L0001: 0x06 0x00 0x00 0x7fff0000 return ALLOW #---------------------------------
任何在 # 之后的文本将被 asm 丢弃,就像一些脚本语言一样。
允许空行。
标签声明是一个从行首开始并以 : 结尾的标识符,例如 L0001。标识符是一个以字母为首,中间只包含字母、数字和下划线 _ 的字符串。标签只在它是 goto 的目标时才是必要的,由 disasm 添加的多余的标签知识为了可读性。例如在 if ($A == 0) goto somewhere 中,somewhere 是一个标签并且必须在这行伪代码后声明。标签声明可以单独占据一行,也可以放置在伪代码的前面。
由 disasm 生成的 CODE、JT、JF 和 K 值会被 asm 丢弃,asm 之解析 K 之后的有效伪代码。
|
Note
|
ceccomp disasm 和 seccomp-tools disasm 的输出之间有很多细微的差别, 以下是一个典型的输出示例。同时有些伪代码是不同的,所以不要盲目将 seccomp-tools 的输出管道给 ceccomp。
|
line CODE JT JF K ================================= 0000: 0x06 0x00 0x00 0x7fff0000 return ALLOW
赋值
A 可以直接赋值为 seccomp 属性。由于内核限制, X 不能直接赋值为 seccomp 属性。
$A = $arch $A = $syscall_nr
要给 A 赋值为这些64位长的字段,必须使用 low_ 或者 high_ 的前缀。
$A = $low_pc $A = $high_pc $A = $low_args[0] $A = $high_args[0] ... $A = $low_args[5] $A = $high_args[5]
一个特殊的属性是 sizeof(struct seccomp_data) ,它可以直接赋值给 A 或 X 。
$A = $scmp_data_len $X = $scmp_data_len
临时内存是32位的,要想访问它们,你可以使用十六进制或者十进制的索引。 A 和 X 都是可赋值的。给 A 或 X 赋值为立即数接受任意格式的数字,只要你使用 "0x" 或者 "0b" 等前缀正确表达数字是几进制的。
$X = $mem[0] $A = $mem[0xf] $A = $mem[15] # both hex and dec index are OK $A = 0 $X = 0x3b $A = 0b1111 $A = 0333
你还可以将 X 赋值给 A 或者反过来。将 X 或 A 赋值给临时内存当然可以。
$A = $X $X = $A $mem[3] = $X $mem[0x4] = $A
数学运算
你可以以多种方式操作 A 。
$A += 30 $A -= 4 $A *= 9 $A /= 1 $A &= 7 $A >>= 6
右值也可以是 X 。
$A &= $X $A |= $X $A ^= $X $A <<= $X
想要对 A 取反可以这么做。
$A = -$A
当…时向下跳转
无条件跳转:
goto L3
当…跳转:
if ($A == execve) goto L3 if ($A != 1234) goto L4 if ($A & $X) goto L5 if !($A & 7) goto L6 if ($A <= $X) goto L7
当条件为真时跳转到…,条件为假时跳转到…:
if ($A > $X) goto L3, else goto L4 if ($A >= 4567) goto L5, else goto L6
只有 在做条件判断时,你才能将数字替换为系统调用号或架构名。在以上的例子中, 0x3b 被 execve 替换。所有系统调用名将会以你设置的架构解析为系统调用号。 如果你希望解析外部架构(不等于你设置的架构)的系统调用名, 请在前面附加架构名和一个点。例如,你设置的架构是 x86_64,并且你正在写 aarch64 架构的规则,请这样写:
if ($A == aarch64.read) goto 5
注意当你手动使用 -a aarch64 将架构设置为 aarch64 时, 你可以在伪代码中忽略 aarch64. 。
返回码
返回寄存器 A 的值:
return $A
或者返回一个立即数,多余的字段放在 () 里。 TRACE 、 TRAP 和 ERRNO 接受一个额外的字段,如果没有 () ,它们将被视为 行为(0) 。
return 0x13371337 return KILL return KILL_PROCESS return TRAP(123) return ERRNO(0) return TRACE return TRACE(3) return LOG return NOTIFY
简短的例子
下面的 TEXT 对与 asm 来说是正确的,这段 TEXT 阻止了 amd64 的 execve 和 execveat 系统调用:
$A = $syscall_nr if ($A == execve) goto forbid if ($A == execveat) goto forbid return ALLOW forbid: return KILL
限制
为了更好的性能,Ceccomp asm 对 TEXT 有一些限制。
-
由于 TEXT 是个文本文件,
'\0'不能出现在 TEXT 中。 -
一行必须短于 384 字节。
-
一个 TEXT 文件必须短于 4096 行。
-
一个 TEXT 文家必须小于 1 MiB。
并且对于 asm 和 disasm 来说,有效的伪代码(能被编码或解码为 BPF 的)必须少于等于 1024 条,这是内核规定的。
一个关于 ceccomp asm 有趣的知识:任何在 TEXT 文件中基本的 ANSI 颜色,例如 \x1b[31m,会在处理时被丢弃。
ceccomp 示例
asm 示例
disasm 示例
emu 示例
trace 示例
运行程序:
如果设置了 -o FILE :
PID 模式:
跟踪 PID 模式:
zsh下PID模式可以使用补全:
probe 示例
仓库
在 https://github.com/dbgbgtf1/Ceccomp 可以找到源代码。 欢迎提交 Pull Requests 和 Issues !
Copyright © 2025-现在,基于 GPLv3 或更新版本分发。