如何使用seccomp或seccomp-bpf实现系统调用Hook并修改调用返回结果?
Hey there, let's break down how to use seccomp-bpf to hook syscalls like read and tweak their behavior—whether that's altering return values or modifying process state. Seccomp-bpf is ideal for this because it lets you attach custom filters to a process's syscall flow, giving you granular control over what happens when specific syscalls are triggered.
核心原理
Seccomp has multiple modes, and we'll use the filter mode with BPF (Berkeley Packet Filter) programs. The key trick here is using SECCOMP_RET_TRACE: this action sends a SIGSYS signal to the process, allowing a tracer (like a ptrace-based monitor) to intercept the syscall, modify its outcome, or adjust the process's state. For simple fixed return values, you can even skip the tracer and use SECCOMP_RET_ERRNO or SECCOMP_RET_DATA directly.
实现步骤
1. 初始化Seccomp上下文
First, set up a seccomp filter context with a default action (we'll allow all syscalls except the one we want to hook):
#include <seccomp.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/prctl.h> int main() { // 开启NO_NEW_PRIVS,非root进程也能加载seccomp过滤器 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { perror("prctl"); exit(EXIT_FAILURE); } scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); // 默认允许所有syscall if (ctx == NULL) { perror("seccomp_init"); exit(EXIT_FAILURE); }
2. 添加针对read的Hook规则
We'll configure the filter to trigger a trace when read is called. This tells seccomp to hand control to a tracer process:
// 针对read系统调用(SCMP_SYS宏自动适配不同架构的syscall编号) if (seccomp_rule_add(ctx, SCMP_ACT_TRACE(0), SCMP_SYS(read), 0) < 0) { perror("seccomp_rule_add"); seccomp_release(ctx); exit(EXIT_FAILURE); }
The 0 in SCMP_ACT_TRACE(0) is custom data that gets passed with the SIGSYS signal, so your tracer can identify which rule was triggered.
3. 加载过滤器并清理
if (seccomp_load(ctx) < 0) { perror("seccomp_load"); seccomp_release(ctx); exit(EXIT_FAILURE); } seccomp_release(ctx); // 测试:触发read调用 char buf[10]; ssize_t ret = read(STDIN_FILENO, buf, sizeof(buf)); printf("read returned: %zd\n", ret); return 0; }
4. 编写Tracer程序(用ptrace)
To modify the read syscall's outcome, we need a tracer that attaches to the target process, intercepts SIGSYS, and adjusts registers or process memory:
#include <sys/ptrace.h> #include <sys/wait.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/user.h> #include <seccomp.h> int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <pid>\n", argv[0]); exit(EXIT_FAILURE); } pid_t pid = atoi(argv[1]); struct user_regs_struct regs; // 附加到目标进程 if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { perror("ptrace_attach"); exit(EXIT_FAILURE); } waitpid(pid, NULL, 0); while (1) { int status; waitpid(pid, &status, 0); // 进程退出或终止时停止追踪 if (WIFEXITED(status) || WIFSIGNALED(status)) break; // 处理SIGSYS信号(seccomp触发的trace) if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSYS) { ptrace(PTRACE_GETREGS, pid, NULL, ®s); // 确认是read syscall触发的(orig_rax存储原始syscall编号) if (regs.orig_rax == SCMP_SYS(read)) { // 修改返回值为100(代替实际读取的字节数) regs.rax = 100; ptrace(PTRACE_SETREGS, pid, NULL, ®s); // 可选:修改进程内存(比如修改read的buf参数) // char fake_data[] = "hooked!"; // ptrace(PTRACE_POKEDATA, pid, regs.rdi, *(long*)fake_data); } // 让目标进程继续执行 ptrace(PTRACE_CONT, pid, NULL, NULL); } else { // 处理其他信号 ptrace(PTRACE_CONT, pid, NULL, NULL); } } ptrace(PTRACE_DETACH, pid, NULL, NULL); return 0; }
关键注意事项
- 架构兼容性: Syscall numbers vary across architectures (e.g.,
readis 0 on x86_64, 63 on ARM64). Use theSCMP_SYS()macro to avoid hardcoding values. - Permissions: Non-root processes need
PR_SET_NO_NEW_PRIVSenabled to load seccomp filters. Root processes can skip this. - Performance: Using
SECCOMP_RET_TRACEwithptraceadds overhead (since it switches between processes). For fixed return values, useSECCOMP_RET_ERRNOdirectly (e.g.,seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(read), 0)to makereadreturn-1). - Process State Modification: Use
ptrace(PTRACE_POKEDATA)orptrace(PTRACE_POKETEXT)to write to the target process's memory, or adjust register values to modify syscall parameters.
内容的提问来源于stack exchange,提问作者Avi Feder

