diff --git a/example/syscall_hijack/README.md b/example/syscall_hijack/README.md new file mode 100644 index 00000000..0c06cde1 --- /dev/null +++ b/example/syscall_hijack/README.md @@ -0,0 +1,77 @@ +# syscall_hijack + +This example demonstrates how to hijack the syscall (Like openat) in the victim process. + +In this example, we use the dummy.c as the victim process and open_modify.bpf.c as the BPF program. We also use loader.c as the loader to load the BPF program. + +We hijack the open/openat syscall in the victim process and modify the file path. For example, we try to open the file "test.txt" in the victim process, but we modify the file path to "test2.txt" in the BPF program. + +Thanks to @officeyutong for the help. + +## How to run + +You should change the some path in dummy.c (Including victim_path & victim_envp) + +Open the **first** terminal and input command below. + +```console +# In the example/syscall_hijack directory +make + +sudo ./dummy +``` + +When you run the last command, you will get the pid of the victim process. +Then you should change the pid in open_modify.bpf.c(const volatile int target_pid = xxx;) + +Then you can enter **second** terminal and input command below. + +```console +# In the example/syscall_hijack directory +make loader + +cd ../.. + +sudo LD_PRELOAD=build/runtime/syscall-server/libbpftime-syscall-server.so example/syscall_hijack/loader +``` + +Then you enter any character in the first terminal, you can see the result in the second terminal. + +The output in the **first** terminal is like below. + +```console +current pid: 1091256 + +[2024-12-16 20:54:38.567] [info] [agent-transformer.cpp:39] Entering bpftime syscal transformer agent +[2024-12-16 20:54:38.567] [stderr] [debug] [agent-transformer.cpp:72] Using agent /home/sy03/.bpftime/libbpftime-agent.so +[2024-12-16 20:54:38.567] [stderr] [info] [text_segment_transformer.cpp:246] Page zero setted up.. +[2024-12-16 20:54:38.567] [stderr] [info] [text_segment_transformer.cpp:274] Rewriting executable segments.. +[2024-12-16 20:54:38.567] [stderr] [debug] [text_segment_transformer.cpp:282] Rewriting segment from 590a7712b000 to 590a7712c000 + +.... + +``` + +The output in the **second** terminal is like below. + +```console +[2024-12-16 18:46:59.215] [info] [syscall_context.cpp:83] Init bpftime syscall mocking.. +[2024-12-16 18:46:59.215] [info] [syscall_context.cpp:84] The log will be written to: ~/.bpftime/runtime.log +BPF program loaded and attached. Press Ctrl+C to exit. +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +Event: pid=1028323, uid=0, comm=, fname=, flags=0, ret=0 +... + +``` + + +## Reference + +[eBPF Development Practice: Modifying System Call Arguments with eBPF](https://eunomia.dev/tutorials/34-syscall/) \ No newline at end of file diff --git a/example/syscall_hijack/dummy.c b/example/syscall_hijack/dummy.c new file mode 100644 index 00000000..67320a40 --- /dev/null +++ b/example/syscall_hijack/dummy.c @@ -0,0 +1,30 @@ +#include +#include + +int main(int argc, char **argv) +{ + // 等待一段时间,以便外部完成对eBPF map的PID注入更新 + int pid = getpid(); + printf("current pid: %d\n", pid); + // sleep(20); + printf("pending until input\n"); + int c = getchar(); + + // 设置要执行的victim程序路径 + char *victim_path = "/home/sy03/bpftime/example/syscall_hijack/victim"; + // 设置argv + char *victim_argv[] = { "victim", NULL }; + // 设置envp,并在其中加入LD_PRELOAD、AGENT_SO和其他环境变量 + char *victim_envp[] = { + "SPDLOG_LEVEL=debug", + "BPFTIME_LOG_OUTPUT=console", + "LD_PRELOAD=/home/sy03/.bpftime/libbpftime-agent-transformer.so", + "AGENT_SO=/home/sy03/.bpftime/libbpftime-agent.so", // 新增的环境变量 + NULL + }; + + // 直接execve victim程序 + execve(victim_path, victim_argv, victim_envp); + perror("execve failed"); + return 1; +} diff --git a/example/syscall_hijack/loader.c b/example/syscall_hijack/loader.c new file mode 100644 index 00000000..1d8248d1 --- /dev/null +++ b/example/syscall_hijack/loader.c @@ -0,0 +1,92 @@ +// loader.c +#include +#include +#include +#include +#include +#include +#include "open_modify.skel.h" + +// 定义事件结构体,与 eBPF 程序中的 event 结构体一致 +struct event { + int pid; + int uid; + char comm[16]; + char fname[100]; + int flags; + int ret; +}; + +// const char *dst_file = "new_hijacked.txt"; + + +static volatile bool exiting = false; + +static void sig_handler(int sig) +{ + exiting = true; +} + +// 事件处理回调函数 +static int handle_event(void *ctx, void *data, size_t data_sz) +{ + struct event *e = data; + + printf("Event: pid=%u, uid=%u, comm=%s, fname=%s, flags=%d, ret=%d\n", + e->pid, e->uid, e->comm, e->fname, e->flags, e->ret); + + return 0; +} + +int main(int argc, char **argv) +{ + struct ring_buffer *rb = NULL; + struct open_modify_bpf *skel; + int err; + + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + skel = open_modify_bpf__open_and_load(); + if (!skel) { + fprintf(stderr, "Failed to open and load BPF skeleton\n"); + return 1; + } + + err = open_modify_bpf__attach(skel); + if (err) { + fprintf(stderr, "Failed to attach BPF skeleton\n"); + goto cleanup; + } + + + + printf("BPF program loaded and attached. Press Ctrl+C to exit.\n"); + + // ring buffer + rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, + NULL); + if (!rb) { + fprintf(stderr, "Failed to create ring buffer\n"); + err = 1; + goto cleanup; + } + + // 进入事件循环,读取 ring buffer 中的事件 + while (!exiting) { + err = ring_buffer__poll(rb, 100 /* timeout, ms */); + if (err == -EINTR) { + break; + } else if (err < 0) { + fprintf(stderr, "Error polling ring buffer: %d\n", err); + break; + } + } + + ring_buffer__free(rb); + +cleanup: + open_modify_bpf__destroy(skel); + return err < 0 ? 1 : 0; +} diff --git a/example/syscall_hijack/makefile b/example/syscall_hijack/makefile new file mode 100644 index 00000000..e48e89b6 --- /dev/null +++ b/example/syscall_hijack/makefile @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +OUTPUT := .output +CLANG ?= clang +BPFTARGET := bpf +LIBBPF_SRC := ../../third_party/libbpf/src +BPFTOOL_SRC := ../../third_party/bpftool/src +LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) +BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool) +BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \ + | sed 's/arm.*/arm/' \ + | sed 's/aarch64/arm64/' \ + | sed 's/ppc64le/powerpc/' \ + | sed 's/mips.*/mips/' \ + | sed 's/riscv64/riscv/' \ + | sed 's/loongarch64/loongarch/') +VMLINUX := ../../third_party/vmlinux/$(ARCH)/vmlinux.h + +SPDLOG_SRC := /home/sy03/bpftime/third_party/spdlog/include + +INCLUDES := -I$(OUTPUT) \ + -I$(LIBBPF_SRC)/include \ + -I$(LIBBPF_SRC)/include/uapi \ + -I$(dir $(VMLINUX)) \ + -I$(SPDLOG_SRC) + +CFLAGS := -g -Wall -O2 +CXXFLAGS := -g -Wall -O2 +LDFLAGS := -lelf -lz -lbpf -pthread + +CARGO ?= $(shell which cargo) +ifneq ($(CARGO),) + LDFLAGS += -lrt -ldl -lpthread -lm +endif + +CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-12s %s\n' "$(1)" "$(2)" + MAKEFLAGS += --no-print-directory +endif + +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +$(call allow-override,CC,$(CROSS_COMPILE)cc) +$(call allow-override,LD,$(CROSS_COMPILE)ld) + +.PHONY: all clean victim loader + +all: victim loader dummy + +clean: + $(call msg,CLEAN,Cleaning build artifacts) + $(Q)rm -rf $(OUTPUT) victim loader + +$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT): + $(call msg,MKDIR,$@) + $(Q)mkdir -p $@ + +# 构建 libbpf +$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf + $(call msg,LIBBPF,$@) + $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ + OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ + INCLUDEDIR= LIBDIR= UAPIDIR= install + +# 构建 bpftool +$(BPFTOOL): | $(BPFTOOL_OUTPUT) + $(call msg,BPFTOOL,$@) + $(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap + +# 编译 BPF 程序 +$(OUTPUT)/open_modify.bpf.o: open_modify.bpf.c open_modify.h $(LIBBPF_OBJ) $(VMLINUX) | $(OUTPUT) $(BPFTOOL) + $(call msg,COMPILING BPF,$@) + $(Q)$(CLANG) -O2 -g -target $(BPFTARGET) \ + $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \ + -c $< -o $(OUTPUT)/open_modify.tmp.bpf.o + $(Q)$(BPFTOOL) gen object $@ $(OUTPUT)/open_modify.tmp.bpf.o + +# 生成 BPF skeleton +$(OUTPUT)/open_modify.skel.h: $(OUTPUT)/open_modify.bpf.o | $(OUTPUT) $(BPFTOOL) + $(call msg,GENERATING SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +# 编译用户空间程序:victim (仅编译victim,不依赖 BPF) +victim: victim.cpp + $(call msg,COMPILING VICTIM,$@) + $(Q)$(CXX) $(CXXFLAGS) $(INCLUDES) -o $@ victim.cpp $(LDFLAGS) + +# 编译用户空间程序:loader (编译 BPF + loader) +loader: loader.c $(OUTPUT)/open_modify.skel.h $(LIBBPF_OBJ) + $(call msg,COMPILING LOADER,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -o $@ loader.c \ + -I$(OUTPUT) -L$(dir $(LIBBPF_OBJ)) -lbpf $(LDFLAGS) -lelf + +dummy: dummy.c + $(call msg,COMPILING DUMMY,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -o $@ $< $(LDFLAGS) + +# 保持中间文件 +.SECONDARY: + +.DELETE_ON_ERROR: diff --git a/example/syscall_hijack/open_modify.bpf.c b/example/syscall_hijack/open_modify.bpf.c new file mode 100644 index 00000000..f8d58d91 --- /dev/null +++ b/example/syscall_hijack/open_modify.bpf.c @@ -0,0 +1,116 @@ +#include +#include +#include "open_modify.h" + +const volatile bool targ_failed = false; +const volatile bool rewrite = true; +const volatile int target_pid = 1030515; + +const char dst_filename[] SEC(".rodata") = "new_hijacked"; + +struct args_t { + const char *fname; + int flags; +}; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10240); + __type(key, u32); + __type(value, struct args_t); +} start SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} rb SEC(".maps"); + +SEC("tracepoint/syscalls/sys_enter_open") +int tracepoint__syscalls__sys_enter_open(struct trace_event_raw_sys_enter *ctx) +{ + // spdlog::info("tracepoint__syscalls__sys_enter_open"); + u64 pid = bpf_get_current_pid_tgid() >> 32; + + /* store arg info for later lookup */ + struct args_t args = {}; + args.fname = (const char *)ctx->args[0]; // filename在args[0] + args.flags = (int)ctx->args[1]; // flags在args[1] + + if (rewrite) { + bpf_probe_write_user((char*)ctx->args[0], "1", 9); + } + + bpf_map_update_elem(&start, &pid, &args, BPF_ANY); + return 0; +} + + +SEC("tracepoint/syscalls/sys_enter_openat") +int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter *ctx) +{ + u64 pid = bpf_get_current_pid_tgid() >> 32; + if (target_pid && pid != target_pid) { + return 0; + } + + struct args_t args = {}; + args.fname = (const char *)ctx->args[1]; // openat的文件名在args[1] + args.flags = (int)ctx->args[2]; + + char dst[NAME_MAX] = {}; + if (rewrite) { + bpf_probe_write_user((char*)ctx->args[1], "1", 9); + bpf_probe_read_user(dst, 3, "1"); + bpf_printk("dst: %s\n", dst); + } + + bpf_map_update_elem(&start, &pid, &args, 0); + return 0; +} + + +static __always_inline int trace_exit(struct trace_event_raw_sys_exit *ctx) +{ + struct event *event; + struct args_t *ap; + int ret; + u32 pid = bpf_get_current_pid_tgid(); + + ap = bpf_map_lookup_elem(&start, &pid); + if (!ap) + return 0; /* missed entry */ + ret = ctx->ret; + + event = bpf_ringbuf_reserve(&rb, sizeof(*event), 0); + if (!event) + return 0; + + /* event data */ + event->pid = bpf_get_current_pid_tgid() >> 32; + event->uid = bpf_get_current_uid_gid(); + bpf_get_current_comm(&event->comm, sizeof(event->comm)); + bpf_probe_read_user_str(&event->fname, sizeof(event->fname), ap->fname); + event->flags = ap->flags; + event->ret = ret; + + /* emit event */ + bpf_ringbuf_submit(event, 0); + return 0; +cleanup: + bpf_map_delete_elem(&start, &pid); + return 0; +} + +SEC("tracepoint/syscalls/sys_exit_open") +int tracepoint__syscalls__sys_exit_open(struct trace_event_raw_sys_exit *ctx) +{ + return trace_exit(ctx); +} + +SEC("tracepoint/syscalls/sys_exit_openat") +int tracepoint__syscalls__sys_exit_openat(struct trace_event_raw_sys_exit *ctx) +{ + return trace_exit(ctx); +} + +char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/example/syscall_hijack/open_modify.h b/example/syscall_hijack/open_modify.h new file mode 100644 index 00000000..f139a212 --- /dev/null +++ b/example/syscall_hijack/open_modify.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __SYSCALL_TRACER_H +#define __SYSCALL_TRACER_H + +#define TASK_COMM_LEN 16 +#define NAME_MAX 255 +#define INVALID_UID ((uid_t) - 1) + +struct event { + /* user terminology for pid: */ + pid_t pid; + uid_t uid; + int ret; + int flags; + char comm[TASK_COMM_LEN]; + char fname[NAME_MAX]; +}; + +#endif /* __SYSCALL_TRACER_H */ \ No newline at end of file diff --git a/example/syscall_hijack/victim.cpp b/example/syscall_hijack/victim.cpp new file mode 100644 index 00000000..f1085a08 --- /dev/null +++ b/example/syscall_hijack/victim.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include + +int main() +{ + char filename[] = "my_test.txt"; + int pid = getpid(); + std::cout << "current pid: " << pid << std::endl; + + // 确保初始文件存在(可选) + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + std::cerr << "Failed to create " << filename << std::endl; + return 1; + } + close(fd); + + while (true) { + // fd = open(filename, O_WRONLY | O_APPEND); + fd = openat(AT_FDCWD, filename, O_WRONLY | O_APPEND); + if (fd == -1) { + std::cerr << "open " << filename << " failed" << std::endl; + // 如果被劫持但目标文件不存在,这里会失败,可根据需求选择退出或继续尝试 + sleep(1); + continue; + } + + const char *msg = "world\n"; + ssize_t ret = write(fd, msg, strlen(msg)); + if (ret == -1) { + std::cerr << "write to " << filename << " failed" << std::endl; + } else { + std::cout << "Wrote to " << filename << ": " << msg; + } + + close(fd); + + sleep(1); + } + + return 0; +}