代码 replace.bpf.c
// 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 "replace.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} rb SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, size_t);
__type(value, unsigned int);
} map_fds SEC(".maps");
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");
#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");
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, 5);
__type(key, __u32);
__type(value, __u32);
} map_prog_array SEC(".maps");
const volatile int target_ppid = 0;
const volatile int filename_len = 0;
const volatile char filename[50];
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)
{
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;
}
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;
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;
}
}
char check_filename[FILENAME_LEN_MAX];
bpf_probe_read_user(&check_filename, filename_len, (char*)ctx->args[1]);
for (int i = 0; i < filename_len; i++) {
if (filename[i] != check_filename[i]) {
return 0;
}
}
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)
{
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;
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)
{
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;
}
unsigned int map_fd = *pfd;
unsigned int fd = (unsigned int)ctx->args[0];
if (map_fd != fd) {
return 0;
}
long unsigned int buff_addr = ctx->args[1];
bpf_map_update_elem(&map_buff_addrs, &pid_tgid, &buff_addr, BPF_ANY);
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)
{
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;
}
if (ctx->ret <= 0) {
return 0;
}
long int buff_size = ctx->ret;
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);
char local_buff[LOCAL_BUFF_SIZE] = { 0x00 };
if (read_size > (LOCAL_BUFF_SIZE+1)) {
read_size = LOCAL_BUFF_SIZE;
}
unsigned int tofind_counter = 0;
for (unsigned int i = 0; i < loop_size; i++) {
bpf_probe_read(&local_buff, read_size, (void*)buff_addr);
for (unsigned int j = 0; j < LOCAL_BUFF_SIZE; j++) {
if (local_buff[j] == text_find[0]) {
name_addr = buff_addr+j;
bpf_map_update_elem(&map_name_addrs, &tofind_counter, &name_addr, BPF_ANY);
tofind_counter++;
}
}
buff_addr += LOCAL_BUFF_SIZE;
}
bpf_printk("[TEXT_REPLACE] PID %d | tofind_counter %d \n", pid, tofind_counter);
bpf_tail_call(ctx, &map_prog_array, PROG_01);
return 0;
}
size_t my_strlen(const char *str) {
size_t len = 0;
while (str[len] != '\0') {
len++;
}
return len;
}
static inline int my_bpf_strncmp(const char *s1, size_t len, const char *s2)
{
for (size_t i = 0; i < 3; i++)
{
if (s1[i] != s2[i])
return 1;
}
return 0;
}
SEC("tp/syscalls/sys_exit_read")
int check_possible_addresses(struct trace_event_raw_sys_exit *ctx) {
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;
}
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);
int key = 0;
for (j = 0; j < text_len_max && text_find[j] != '\0' && name[j] != '\0'; j++) {
if (name[j] != text_find[j]) {
key = 1;
break;
}
}
if (key==0){
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);
bpf_printk("[TEXT_REPLACE] PID %d | [*] name: %s\n", pid, name);
}
if (match_counter > 0) {
bpf_printk("[TEXT_REPLACE] PID %d | match_counter %d \n", pid, match_counter);
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) {
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;
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;
}
long ret = bpf_probe_write_user((void*)name_addr, (void*)text_replace, text_len);
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);
bpf_map_delete_elem(&map_to_replace_addrs, &match_counter);
}
return 0;
}
// Map 保存 openat 系统调用中的文件描述符
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, size_t);
__type(value, unsigned int);
} map_fds SEC(".maps");
// Map 保存 read 系统调用中的缓冲区地址
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");
SEC("tp/syscalls/sys_exit_close")
int handle_close_exit(struct trace_event_raw_sys_exit *ctx)
{
size_t pid_tgid = bpf_get_current_pid_tgid();
int pid = pid_tgid >> 32;
// 没在Map中找到说明不是我们要关注的进程,就放行
unsigned int* check = bpf_map_lookup_elem(&map_fds, &pid_tgid);
if (check == 0) {
return 0;
}
bpf_map_delete_elem(&map_fds, &pid_tgid);
bpf_map_delete_elem(&map_buff_addrs, &pid_tgid);
return 0;
}
该 eBPF 程序在系统调用退出时执行,很适合做收尾、清理数据工作的, map_fds
中存储着 进程ID 对 文件描述符 的映射, map_buff_addrs
中存储着 进程ID 对 数据缓冲区地址 的映射,该进程打开文件-读取文件 完成后就会触发 handle_close_exit
该 eBPF 程序,清除该进程相关信息(文件描述符/文件内容)。
#define FILENAME_LEN_MAX 50
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;
// 如果指定了目标进程ID,就只关注该进程,否则就关注所有进程
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;
}
}
// 从参数中获取文件名
char check_filename[FILENAME_LEN_MAX];
bpf_probe_read_user(&check_filename, filename_len, (char*)ctx->args[1]);
// 检查文件名是不是我们的目标
for (int i = 0; i < filename_len; i++) {
if (filename[i] != check_filename[i]) {
return 0;
}
}
// 将这个我们关注的进程ID(键)存储在 map_fds 映射中,值暂时赋值为0
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;
}
handle_openat_enter
该 eBPF 程序是在某系统调用(进程打开文件)发生时执行,作用就是记录哪个进程打开了我们的目标文件
SEC("tp/syscalls/sys_exit_openat")
int handle_openat_exit(struct trace_event_raw_sys_exit *ctx)
{
size_t pid_tgid = bpf_get_current_pid_tgid();
// 没在Map中找到说明不是我们要关注的进程,就放行
unsigned int* check = bpf_map_lookup_elem(&map_fds, &pid_tgid);
if (check == 0) {
return 0;
}
int pid = pid_tgid >> 32;
// 将映射值设置为返回的文件描述符
unsigned int fd = (unsigned int)ctx->ret;
bpf_map_update_elem(&map_fds, &pid_tgid, &fd, BPF_ANY);
return 0;
}
handle_openat_exit
该 eBPF 程序在进程执行完系统调用 openat 后,将返回的文件描述符更新到一个名为 map_fds
的 eBPF Map 中,以进程的 PID 作为键。如果在 Map 中找不到对应的 PID,则说明该进程不是我们关注的进程,直接返回。如果找到了,则将返回的文件描述符更新到 Map 中。
SEC("tp/syscalls/sys_enter_read")
int handle_read_enter(struct trace_event_raw_sys_enter *ctx)
{
// 没在Map中找到说明不是我们要关注的进程,就放行
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;
}
// 检查上面查找获取的文件描述符是不是正确的
unsigned int map_fd = *pfd;
unsigned int fd = (unsigned int)ctx->args[0];
if (map_fd != fd) {
return 0;
}
// 进程ID为键,将缓冲区地址为值存储在 Map 中
long unsigned int buff_addr = ctx->args[1];
bpf_map_update_elem(&map_buff_addrs, &pid_tgid, &buff_addr, BPF_ANY);
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;
}
handle_read_enter
该 eBPF 程序是在某系统调用(进程读取文件)发生时执行。在系统调用 read 中,数据会被读取到应用程序提供的缓冲区中,缓冲区地址表示文件读取的数据存储的内存地址
#define LOCAL_BUFF_SIZE 64
#define loop_size 64
#define FILENAME_LEN_MAX 50
const volatile char text_find[FILENAME_LEN_MAX];
SEC("tp/syscalls/sys_exit_read")
int find_possible_addrs(struct trace_event_raw_sys_exit *ctx)
{
// 检查是不是我们关注的进程(是的话在handle_read_enter中存储了对应的缓冲区地址)
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;
}
// 从读取系统调用返回的数据量
if (ctx->ret <= 0) {
return 0;
}
long int buff_size = ctx->ret;
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);
char local_buff[LOCAL_BUFF_SIZE] = { 0x00 };
// 将读取每一块的数据大小限制在LOCAL_BUFF_SIZE内
if (read_size > (LOCAL_BUFF_SIZE+1)) {
read_size = LOCAL_BUFF_SIZE;
}
// 读取以块为单位返回的数据,并记下"查找"文本的第一个字符的每个实例
unsigned int tofind_counter = 0;
// 第一层循环指定了迭代次数。loop_size是一个常量,定义了外部循环的次数
for (unsigned int i = 0; i < loop_size; i++) {
// 从缓冲区读取数据块,读取大小为read_size
bpf_probe_read(&local_buff, read_size, (void*)buff_addr);
// 内部循环,遍历local_buff数组中的每个字符
for (unsigned int j = 0; j < LOCAL_BUFF_SIZE; j++) {
// 检查local_buff数组中的每个字符是否与要查找的文本的第一个字符相匹配
if (local_buff[j] == text_find[0]) {
// 如果匹配成功,将当前字符的地址记录下来,并将该地址更新到一个eBPF映射中,以便后续处理
name_addr = buff_addr+j;
bpf_map_update_elem(&map_name_addrs, &tofind_counter, &name_addr,BPF_ANY);
tofind_counter++;
}
}
// 下一次迭代中读取下一个数据块
buff_addr += LOCAL_BUFF_SIZE;
}
bpf_printk("[TEXT_REPLACE] PID %d | tofind_counter %d \n", pid,tofind_counter);
// 尾调用 check_possible_addrs 以循环访问可能的地址
// map_prog_array 是编号与eBPF 程序的指针的映射
// 其中 PROG_01 对应着 check_possible_addresses
bpf_tail_call(ctx, &map_prog_array, PROG_01);
return 0;
}
find_possible_addrs
该 eBPF 程序是在某系统调用(进程读取文件)结束时执行。从系统调用返回的数据分块,并循环(第一层循环)读取每一块数据块,在某个数据块中再次循环(第二层循环)搜索要查找的文本的第一个字符的位置,并记录该位置保存在 Map 中
const volatile unsigned int text_len = 0;
#define text_len_max 20
#define MAX_POSSIBLE_ADDRS 500
SEC("tp/syscalls/sys_exit_read")
int check_possible_addresses(struct trace_event_raw_sys_exit *ctx) {
// 检查是不是我们关注的进程(是的话在handle_read_enter中存储了对应的缓冲区地址)
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;
}
// 仔细检查每一个可能的位置,检查它是否真的与要查找的文本匹配
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);
int key = 0;
for (j = 0; j < text_len_max && text_find[j] != '\0' && name[j] != '\0';j++) {
if (name[j] != text_find[j]) {
key = 1;
break;
}
}
if (key==0){
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);
}
// 找到至少一个与要查找的文本的匹配项,就跳到 PROG_02 对应的eBPF程序中覆盖文本
if (match_counter > 0) {
bpf_tail_call(ctx, &map_prog_array, PROG_02);
}
return 0;
}
overwrite_addresses
该 eBPF 程序是在某系统调用(进程读取文件)结束时执行,也就是上一个eBPF 程序最后进行尾调用跳转的地方。和上一个 eBPF 程序相辅相成,上一个程序细致的匹配某一个字符片段和要查找的文本是否匹配,本程序则是将那个字符片段替换成我们想要替换的字符串。
SEC("tp/syscalls/sys_exit_read")
int overwrite_addresses(struct trace_event_raw_sys_exit *ctx) {
// 检查是不是我们关注的进程(是的话在handle_read_enter中存储了对应的缓冲区地址)
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;
// 在每个地址上循环以将查找的文本替换
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;
}
long ret = bpf_probe_write_user((void*)name_addr, (void*)text_replace,text_len);
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);
bpf_map_delete_elem(&map_to_replace_addrs, &match_counter);
}
return 0;
}
overwrite_addresses
该 eBPF 程序是在某系统调用(进程读取文件)结束时执行,也就是上一个eBPF 程序昀后进行尾调用跳转的地方。和上一个 eBPF 程序相辅相成,上一个程序细致的匹配某一个字符片段和要查找的文本是否匹配,本程序则是将那个字符片段替换成我们想要替换的字符串
我们执行命令
./replace -f /proc/net/tcp -i"0100007F" -r "08080808"
任何读取
/proc/net/tcp
文件的返回内容中的0100007F
替换成08080808
netstat
命令也是读取这个文件用户态代码,这就实现了一个网络伪造的功能
和上面的几个用户态代码其实是一模一样的,特别标准化,这也是用 libbpf-bootstrap
脚手架的好处