特定进程Syscall劫持需求:非内核表/非LD_PRELOAD方案咨询
针对单个进程的特定Syscall管控方案
以下两种方案完全符合你的需求:仅针对单个进程、管控指定Syscall、不修改内核Syscall表、不使用LD_PRELOAD、不篡改进程的0x0f 0x05指令。
方案1:eBPF 内核态钩子
核心原理
eBPF是内核提供的安全可扩展机制,允许在不修改内核源码或加载恶意模块的前提下,在内核态注入钩子逻辑。通过PID过滤,可精准仅针对目标进程生效,且只需要挂载你关心的read/write/connect等Syscall钩子。
实现步骤
- 选择钩子类型:
- 用
tracepoint:内核预定义的Syscall入口/返回跟踪点(如syscalls/sys_enter_read),稳定性高,无需关心Syscall号的架构差异。 - 用
kprobe/kretprobe:直接挂钩Syscall的内核实现函数(如sys_read),灵活性更强。
- 用
- PID过滤逻辑:在钩子代码中通过
bpf_get_current_pid_tgid()获取当前进程PID,与目标PID对比,仅匹配时执行管控逻辑。 - 管控操作:
- 读取Syscall参数(如
read的文件描述符、缓冲区地址)。 - 修改Syscall参数或返回值(如限制
read的读取长度)。 - 通过eBPF Map或Perf Buffer将事件通知到用户态的控制进程,由控制进程决定后续行为。
- 读取Syscall参数(如
示例代码(基于BCC工具链)
#include <uapi/linux/ptrace.h> #include <linux/sched.h> // 存储目标PID的哈希表,用户态控制进程可动态添加/移除目标 BPF_HASH(target_pids, u32, u32); // 挂钩sys_read的入口 TRACEPOINT_PROBE(syscalls, sys_enter_read) { u32 pid = bpf_get_current_pid_tgid() >> 32; u32 *is_target = target_pids.lookup(&pid); if (!is_target) return 0; // 获取read的参数:fd, buf, count struct pt_regs *regs = (struct pt_regs *)args->regs; int fd = regs->di; size_t count = regs->dx; // 发送事件到用户态,供控制进程处理 bpf_trace_printk("Target PID %d called read(fd=%d, count=%zu)\n", pid, fd, count); // 可选:修改参数,比如限制最大读取长度为1024字节 // if (count > 1024) regs->dx = 1024; return 0; }
优势与注意事项
- 优势:安全(内核验证eBPF程序合法性)、精准(仅针对目标PID)、灵活(支持复杂管控逻辑)。
- 注意事项:需要内核版本4.15+,开启
CONFIG_BPF/CONFIG_BPF_SYSCALL等配置;用户态控制进程可通过bpftool或自定义程序与eBPF程序交互。
方案2:Seccomp 用户态通知机制
核心原理
Seccomp是内核原生的进程Syscall过滤机制,通过SECCOMP_RET_USER_NOTIF规则,可让目标进程在触发指定Syscall时,将请求转发给用户态的控制进程,由控制进程决定允许、拒绝或修改Syscall的执行。
实现步骤
- 附加目标进程:通过
ptrace附加到目标进程(仅用于设置Seccomp规则,不篡改进程指令)。 - 设置Seccomp规则:在目标进程上下文执行
prctl,配置过滤规则:仅对read/write/connect触发用户通知,其余Syscall默认允许。 - 处理通知:控制进程通过监听Seccomp通知FD,接收目标进程的Syscall请求,检查参数后返回处理结果(允许/拒绝/修改返回值)。
示例代码(控制进程)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/prctl.h> #include <linux/seccomp.h> #include <linux/filter.h> #include <sys/ptrace.h> #include <sys/wait.h> #include <poll.h> int setup_seccomp(pid_t target_pid) { // 附加目标进程 if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == -1) return -1; waitpid(target_pid, NULL, 0); // 构建Seccomp过滤规则 struct sock_filter filter[] = { BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 0, 3), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 2), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_connect, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), }; struct sock_fprog prog = { .len = sizeof(filter)/sizeof(filter[0]), .filter = filter, }; // 在目标进程中设置Seccomp并创建通知监听FD int listener_fd = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog); if (listener_fd == -1) { ptrace(PTRACE_DETACH, target_pid, NULL, NULL); return -1; } // 分离目标进程,恢复其运行 ptrace(PTRACE_DETACH, target_pid, NULL, NULL); return listener_fd; } void handle_notifications(int listener_fd) { struct pollfd pfd = {.fd = listener_fd, .events = POLLIN}; while (1) { poll(&pfd, 1, -1); if (!(pfd.revents & POLLIN)) continue; struct seccomp_notif req; struct seccomp_notif_resp resp; socklen_t req_len = sizeof(req); if (recvfrom(listener_fd, &req, sizeof(req), 0, NULL, &req_len) == -1) continue; // 根据Syscall类型处理 switch (req.data.nr) { case __NR_read: printf("Target PID %d read(fd=%d)\n", req.pid, (int)req.data.args[0]); resp.id = req.id; resp.error = 0; resp.val = 0; sendto(listener_fd, &resp, sizeof(resp), 0, NULL, 0); break; case __NR_connect: printf("Target PID %d connect(fd=%d, addr=%p)\n", req.pid, (int)req.data.args[0], (void*)req.data.args[1]); // 拒绝connect调用,返回EACCES resp.id = req.id; resp.error = -EACCES; resp.val = 0; sendto(listener_fd, &resp, sizeof(resp), 0, NULL, 0); break; default: resp.id = req.id; resp.error = 0; resp.val = 0; sendto(listener_fd, &resp, sizeof(resp), 0, NULL, 0); } } } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s <target-pid>\n", argv[0]); exit(1); } pid_t target_pid = atoi(argv[1]); int listener_fd = setup_seccomp(target_pid); if (listener_fd == -1) { perror("setup_seccomp"); exit(1); } handle_notifications(listener_fd); return 0; }
优势与注意事项
- 优势:基于内核原生机制,无需额外工具链,逻辑简洁,适合简单的允许/拒绝管控。
- 注意事项:需要内核版本4.14+;目标进程需允许被ptrace附加(可通过
prctl(PR_SET_PTRACER, ...)调整权限)。
内容的提问来源于stack exchange,提问作者R-Rothrock
相关产品推荐
相关产品推荐

