fix replace code

This commit is contained in:
yunwei37
2023-05-31 01:03:16 +08:00
committed by 云微
parent fe3dfa9aca
commit 48fae08f08
11 changed files with 152 additions and 133 deletions

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"common.h": "c"
}
}

View File

@@ -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)
持续更新中...

View File

@@ -6,4 +6,4 @@ package.json
package.yaml
ecli
bootstrap
textreplace2
replace

View File

@@ -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)),)

View File

@@ -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'`.

View File

@@ -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];

View File

@@ -1,96 +0,0 @@
// 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

@@ -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

View File

@@ -1,10 +1,77 @@
// SPDX-License-Identifier: BSD-3-Clause
#include <argp.h>
#include <unistd.h>
#include "textreplace.skel.h"
#include "common_um.h"
#include "replace.skel.h"
#include "common.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
#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;
}

View File

@@ -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 程序。
编译:
@@ -39,3 +55,4 @@ sudo rm -r /sys/fs/bpf/textreplace
## 参考资料
- <https://github.com/pathtofile/bad-bpf>
- <https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html>

View File

@@ -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!