From 48fae08f08874daa24d4bc52a213aa875123b79a Mon Sep 17 00:00:00 2001 From: yunwei37 <1067852565@qq.com> Date: Wed, 31 May 2023 01:03:16 +0800 Subject: [PATCH] fix replace code --- .vscode/settings.json | 5 + README.md | 2 +- src/27-replace/.gitignore | 2 +- src/27-replace/Makefile | 2 +- src/27-replace/README.md | 27 +++++- src/27-replace/common.h | 4 + src/27-replace/common_um.h | 96 ------------------- .../{textreplace.bpf.c => replace.bpf.c} | 37 ++++--- src/27-replace/{textreplace.c => replace.c} | 81 ++++++++++++++-- src/28-detach/README.md | 23 ++++- src/28-detach/textreplace2.bpf.c | 6 +- 11 files changed, 152 insertions(+), 133 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 src/27-replace/common_um.h rename src/27-replace/{textreplace.bpf.c => replace.bpf.c} (92%) rename src/27-replace/{textreplace.c => replace.c} (76%) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aafbd4e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "common.h": "c" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 208b556..dc08444 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Android: - [使用 bpf_send_signal 发送信号终止进程](src/25-signal/README.md) - [使用 eBPF 添加 sudo 用户](src/26-sudo/README.md) - [使用 eBPF 替换任意程序读取或写入的文本](src/27-replace/README.md) -- [使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序](src/28-detach/README.md) +- [BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序](src/28-detach/README.md) 持续更新中... diff --git a/src/27-replace/.gitignore b/src/27-replace/.gitignore index 81acd4b..d630f18 100644 --- a/src/27-replace/.gitignore +++ b/src/27-replace/.gitignore @@ -6,4 +6,4 @@ package.json package.yaml ecli bootstrap -textreplace2 +replace diff --git a/src/27-replace/Makefile b/src/27-replace/Makefile index ecfd9e1..e696bfd 100644 --- a/src/27-replace/Makefile +++ b/src/27-replace/Makefile @@ -24,7 +24,7 @@ INCLUDES := -I$(OUTPUT) -I../../libbpf/include/uapi -I$(dir $(VMLINUX)) CFLAGS := -g -Wall ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) -APPS = textreplace2 # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall +APPS = replace # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall CARGO ?= $(shell which cargo) ifeq ($(strip $(CARGO)),) diff --git a/src/27-replace/README.md b/src/27-replace/README.md index 43424e9..1558626 100644 --- a/src/27-replace/README.md +++ b/src/27-replace/README.md @@ -1,3 +1,26 @@ -# replace +# 使用 eBPF 替换任意程序读取或写入的文本 -TODO +```sh +sudo ./replace --filename /path/to/file --input foo --replace bar +``` + +This program replaces all text matching `input` in the file with the `replace` text. +This has a number of uses, for example: + +To hide kernel module `joydev` from tools such as `lsmod`: + +```bash +./replace -f /proc/modules -i 'joydev' -r 'cryptd' +``` + +Spoof the MAC address of the `eth0` interface: + +```bash +./replace -f /sys/class/net/eth0/address -i '00:15:5d:01:ca:05' -r '00:00:00:00:00:00' +``` + +Malware conducting anti-sandbox checks might check the MAC address to look for signs it is +running inside a Virtual Machine or Sandbox, and not on a 'real' machine. + +**NOTE:** Both `input` and `replace` must be the same length, to avoid adding NULL characters to the +middle of a block of text. To enter a newline from a bash prompt, use `$'\n'`, e.g. `--replace $'text\n'`. diff --git a/src/27-replace/common.h b/src/27-replace/common.h index 4686d92..1fda5c3 100644 --- a/src/27-replace/common.h +++ b/src/27-replace/common.h @@ -16,6 +16,10 @@ // Simple message structure to get events from eBPF Programs // in the kernel to user spcae #define TASK_COMM_LEN 16 +#define LOCAL_BUFF_SIZE 64 +#define loop_size 64 +#define text_len_max 20 + struct event { int pid; char comm[TASK_COMM_LEN]; diff --git a/src/27-replace/common_um.h b/src/27-replace/common_um.h deleted file mode 100644 index 06267aa..0000000 --- a/src/27-replace/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/27-replace/textreplace.bpf.c b/src/27-replace/replace.bpf.c similarity index 92% rename from src/27-replace/textreplace.bpf.c rename to src/27-replace/replace.bpf.c index 648829d..019b6cf 100644 --- a/src/27-replace/textreplace.bpf.c +++ b/src/27-replace/replace.bpf.c @@ -58,15 +58,13 @@ struct { const volatile int target_ppid = 0; // These store the name of the file to replace text in -const int filename_len_max = 50; const volatile int filename_len = 0; -const volatile char filename[filename_len_max]; +const volatile char filename[50]; // These store the text to find and replace in the file -const unsigned int text_len_max = 20; const volatile unsigned int text_len = 0; -const volatile char text_find[filename_len_max]; -const volatile char text_replace[filename_len_max]; +const volatile char text_find[FILENAME_LEN_MAX]; +const volatile char text_replace[FILENAME_LEN_MAX]; SEC("tp/syscalls/sys_exit_close") int handle_close_exit(struct trace_event_raw_sys_exit *ctx) @@ -102,7 +100,7 @@ int handle_openat_enter(struct trace_event_raw_sys_enter *ctx) } // Get filename from arguments - char check_filename[filename_len_max]; + char check_filename[FILENAME_LEN_MAX]; bpf_probe_read_user(&check_filename, filename_len, (char*)ctx->args[1]); // Check filename is our target @@ -188,17 +186,15 @@ int find_possible_addrs(struct trace_event_raw_sys_exit *ctx) return 0; } long int buff_size = ctx->ret; - long int read_size = buff_size; + unsigned long int read_size = buff_size; bpf_printk("[TEXT_REPLACE] PID %d | read_size %lu | buff_addr 0x%lx\n", pid, read_size, buff_addr); // 64 may be to large for loop - const unsigned int local_buff_size = 32; - const unsigned int loop_size = 32; - char local_buff[local_buff_size] = { 0x00 }; + char local_buff[LOCAL_BUFF_SIZE] = { 0x00 }; - if (read_size > (local_buff_size+1)) { + if (read_size > (LOCAL_BUFF_SIZE+1)) { // Need to loop :-( - read_size = local_buff_size; + read_size = LOCAL_BUFF_SIZE; } // Read the data returned in chunks, and note every instance @@ -209,7 +205,7 @@ int find_possible_addrs(struct trace_event_raw_sys_exit *ctx) for (unsigned int i = 0; i < loop_size; i++) { // Read in chunks from buffer bpf_probe_read(&local_buff, read_size, (void*)buff_addr); - for (unsigned int j = 0; j < local_buff_size; j++) { + for (unsigned int j = 0; j < LOCAL_BUFF_SIZE; j++) { // Look for the first char of our 'to find' text if (local_buff[j] == text_find[0]) { name_addr = buff_addr+j; @@ -220,7 +216,7 @@ int find_possible_addrs(struct trace_event_raw_sys_exit *ctx) } } - buff_addr += local_buff_size; + buff_addr += LOCAL_BUFF_SIZE; } // Tail-call into 'check_possible_addrs' to loop over possible addresses @@ -267,12 +263,13 @@ int check_possible_addresses(struct trace_event_raw_sys_exit *ctx) { break; } bpf_probe_read_user(&name, text_len_max, (char*)name_addr); - for (j = 0; j < text_len_max; j++) { - if (name[j] != text_find[j]) { - break; - } - } - if (j >= name_len) { + // for (j = 0; j < text_len_max; j++) { + // if (name[j] != text_find[j]) { + // break; + // } + // } + // we can use bpf_strncmp here, but it's not available in the kernel version older + if (bpf_strncmp(name, text_len_max, (const char *)text_find) == 0) { // *********** // We've found out text! // Add location to map to be overwritten diff --git a/src/27-replace/textreplace.c b/src/27-replace/replace.c similarity index 76% rename from src/27-replace/textreplace.c rename to src/27-replace/replace.c index f6621d0..4e139e1 100644 --- a/src/27-replace/textreplace.c +++ b/src/27-replace/replace.c @@ -1,10 +1,77 @@ // SPDX-License-Identifier: BSD-3-Clause #include #include -#include "textreplace.skel.h" -#include "common_um.h" +#include "replace.skel.h" #include "common.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; +} + // Setup Argument stuff #define filename_len_max 50 #define text_len_max 20 @@ -98,7 +165,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz) int main(int argc, char **argv) { struct ring_buffer *rb = NULL; - struct textreplace_bpf *skel; + struct replace_bpf *skel; int err; // Parse command line arguments @@ -121,7 +188,7 @@ int main(int argc, char **argv) } // Open BPF application - skel = textreplace_bpf__open(); + skel = replace_bpf__open(); if (!skel) { fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno)); return 1; @@ -137,7 +204,7 @@ int main(int argc, char **argv) skel->rodata->text_len = strlen(env.input); // Verify and load program - err = textreplace_bpf__load(skel); + err = replace_bpf__load(skel); if (err) { fprintf(stderr, "Failed to load and verify BPF skeleton\n"); goto cleanup; @@ -168,7 +235,7 @@ int main(int argc, char **argv) } // Attach tracepoint handler - err = textreplace_bpf__attach( skel); + err = replace_bpf__attach( skel); if (err) { fprintf(stderr, "Failed to attach BPF program: %s\n", strerror(errno)); goto cleanup; @@ -197,6 +264,6 @@ int main(int argc, char **argv) } cleanup: - textreplace_bpf__destroy( skel); + replace_bpf__destroy( skel); return -err; } diff --git a/src/28-detach/README.md b/src/28-detach/README.md index a5625f1..720fe41 100644 --- a/src/28-detach/README.md +++ b/src/28-detach/README.md @@ -1,6 +1,22 @@ -# 后台运行 eBPF 程序 +# 在用户态应用退出后运行 eBPF 程序:eBPF 程序的生命周期 -通过使用 `--detach` 运行程序,用户空间加载器可以退出,而不会停止 eBPF 程序。 +通过使用 detach 的方式运行 eBPF 程序,用户空间加载器可以退出,而不会停止 eBPF 程序。 + +## eBPF 程序的生命周期 + +首先,我们需要了解一些关键的概念,如 BPF 对象(包括程序,地图和调试信息),文件描述符 (FD),引用计数(refcnt)等。在 eBPF 系统中,用户空间通过文件描述符访问 BPF 对象,而每个对象都有一个引用计数。当一个对象被创建时,其引用计数初始为1。如果该对象不再被使用(即没有其他程序或文件描述符引用它),它的引用计数将降至0,并在 RCU 宽限期后被内存清理。 + +接下来,我们需要了解 eBPF 程序的生命周期。首先,当你创建一个 BPF 程序,并将它连接到某个“钩子”(例如网络接口,系统调用等),它的引用计数会增加。然后,即使原始创建和加载该程序的用户空间进程退出,只要 BPF 程序的引用计数大于 0,它就会保持活动状态。然而,这个过程中有一个重要的点是:不是所有的钩子都是相等的。有些钩子是全局的,比如 XDP、tc's clsact 和 cgroup-based 钩子。这些全局钩子会一直保持 BPF 程序的活动状态,直到这些对象自身消失。而有些钩子是局部的,只在拥有它们的进程存活期间运行。 + +对于 BPF 对象(程序或映射)的生命周期管理,另一个关键的操作是“分离”(detach)。这个操作会阻止已附加程序的任何未来执行。然后,对于需要替换 BPF 程序的情况,你可以使用替换(replace)操作。这是一个复杂的过程,因为你需要确保在替换过程中,不会丢失正在处理的事件,而且新旧程序可能在不同的 CPU 上同时运行。 + +最后,除了通过文件描述符和引用计数来管理 BPF 对象的生命周期,还有一个叫做 BPFFS 的方法,也就是“BPF 文件系统”。用户空间进程可以在 BPFFS 中“固定”(pin)一个 BPF 程序或映射,这将增加对象的引用计数,使得即使 BPF 程序未附加到任何地方或 BPF 映射未被任何程序使用,该 BPF 对象也将保持活动状态。 + +所以,当我们谈论在后台运行 eBPF 程序时,我们需要清楚这个过程的含义。在某些情况下,即使用户空间进程已经退出,我们可能还希望 BPF 程序保持运行。这就需要我们正确地管理 BPF 对象的生命周期 + +## 运行 + +这里还是采用了上一个的字符串替换的应用,来体现对应可能的安全风险。通过使用 `--detach` 运行程序,用户空间加载器可以退出,而不会停止 eBPF 程序。 编译: @@ -38,4 +54,5 @@ sudo rm -r /sys/fs/bpf/textreplace ## 参考资料 -- \ No newline at end of file +- +- diff --git a/src/28-detach/textreplace2.bpf.c b/src/28-detach/textreplace2.bpf.c index f7caf8e..6778d7c 100644 --- a/src/28-detach/textreplace2.bpf.c +++ b/src/28-detach/textreplace2.bpf.c @@ -201,8 +201,8 @@ int BPF_PROG(find_possible_addrs, struct pt_regs *regs, long ret) long int read_size = buff_size; bpf_printk("[TEXT_REPLACE] PID %d | read_size %lu | buff_addr 0x%lx\n", pid, read_size, buff_addr); - const unsigned int local_buff_size = 64; - const unsigned int loop_size = 64; + const unsigned int local_buff_size = 32; + const unsigned int loop_size = 32; char local_buff[local_buff_size] = { 0x00 }; if (read_size > (local_buff_size+1)) { @@ -295,6 +295,8 @@ int BPF_PROG(check_possible_addresses, struct pt_regs *regs, long ret) break; } } + // for newer kernels, maybe use bpf_strncmp + // if (bpf_strncmp(pFind->text, TEXT_LEN_MAX, name) == 0) { if (j >= name_len) { // *********** // We've found out text!