mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 02:04:30 +08:00
fix replace code
This commit is contained in:
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"common.h": "c"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
持续更新中...
|
||||
|
||||
|
||||
2
src/27-replace/.gitignore
vendored
2
src/27-replace/.gitignore
vendored
@@ -6,4 +6,4 @@ package.json
|
||||
package.yaml
|
||||
ecli
|
||||
bootstrap
|
||||
textreplace2
|
||||
replace
|
||||
|
||||
@@ -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)),)
|
||||
|
||||
@@ -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'`.
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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!
|
||||
|
||||
Reference in New Issue
Block a user