You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

特定进程Syscall劫持需求:非内核表/非LD_PRELOAD方案咨询

针对单个进程的特定Syscall管控方案

以下两种方案完全符合你的需求:仅针对单个进程、管控指定Syscall、不修改内核Syscall表、不使用LD_PRELOAD、不篡改进程的0x0f 0x05指令。

方案1:eBPF 内核态钩子

核心原理

eBPF是内核提供的安全可扩展机制,允许在不修改内核源码或加载恶意模块的前提下,在内核态注入钩子逻辑。通过PID过滤,可精准仅针对目标进程生效,且只需要挂载你关心的read/write/connect等Syscall钩子。

实现步骤

  1. 选择钩子类型:
    • 用tracepoint:内核预定义的Syscall入口/返回跟踪点(如syscalls/sys_enter_read),稳定性高,无需关心Syscall号的架构差异。
    • 用kprobe/kretprobe:直接挂钩Syscall的内核实现函数(如sys_read),灵活性更强。
  2. PID过滤逻辑:在钩子代码中通过bpf_get_current_pid_tgid()获取当前进程PID,与目标PID对比,仅匹配时执行管控逻辑。
  3. 管控操作:
    • 读取Syscall参数(如read的文件描述符、缓冲区地址)。
    • 修改Syscall参数或返回值(如限制read的读取长度)。
    • 通过eBPF Map或Perf Buffer将事件通知到用户态的控制进程,由控制进程决定后续行为。

示例代码(基于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的执行。

实现步骤

  1. 附加目标进程:通过ptrace附加到目标进程(仅用于设置Seccomp规则,不篡改进程指令)。
  2. 设置Seccomp规则:在目标进程上下文执行prctl,配置过滤规则:仅对read/write/connect触发用户通知,其余Syscall默认允许。
  3. 处理通知:控制进程通过监听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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 14:48:10