Skip to content

Commit 84bb589

Browse files
author
lipeng hao
committed
monitor bash readline
1 parent 49dbbf4 commit 84bb589

File tree

3 files changed

+308
-0
lines changed

3 files changed

+308
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2+
# Bash readline 命令监控示例
3+
4+
APPS = bash_monitor
5+
include ../Makefile.common
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2+
// 使用 eBPF uretprobe 监控 Bash readline 命令输入
3+
// Hook bash 的 readline 函数捕获交互式命令
4+
5+
#include "vmlinux.h"
6+
#include <bpf/bpf_helpers.h>
7+
#include <bpf/bpf_tracing.h>
8+
#include <bpf/bpf_core_read.h>
9+
10+
char LICENSE[] SEC("license") = "Dual BSD/GPL";
11+
12+
#define MAX_COMMAND_LEN 256
13+
#define MAX_COMM_SIZE 16
14+
15+
// Bash 命令事件结构体
16+
struct bash_event {
17+
__u32 pid; // Bash 进程 ID
18+
__u32 ppid; // 父进程 ID
19+
__u32 uid; // 用户 ID
20+
char comm[MAX_COMM_SIZE]; // 进程名(应为 "bash")
21+
char command[MAX_COMMAND_LEN]; // 用户输入的命令
22+
};
23+
24+
// Ring Buffer:传递事件到用户空间
25+
struct {
26+
__uint(type, BPF_MAP_TYPE_RINGBUF);
27+
__uint(max_entries, 256 * 1024);
28+
} events SEC(".maps");
29+
30+
// 目标 PID 过滤(0 表示监控所有 bash 进程)
31+
struct {
32+
__uint(type, BPF_MAP_TYPE_ARRAY);
33+
__uint(max_entries, 1);
34+
__type(key, __u32);
35+
__type(value, __u32);
36+
} target_pid_map SEC(".maps");
37+
38+
// 检查当前进程是否是目标进程的后代(最多向上查找 10 层)
39+
static __always_inline bool is_descendant_of_target(__u32 target_pid)
40+
{
41+
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
42+
43+
// 检查自己是否是目标进程
44+
__u32 pid = bpf_get_current_pid_tgid() >> 32;
45+
if (pid == target_pid)
46+
return true;
47+
48+
// 向上遍历进程树
49+
#pragma unroll
50+
for (int i = 0; i < 10; i++) {
51+
__u32 ppid = BPF_CORE_READ(task, real_parent, tgid);
52+
53+
// 找到目标进程
54+
if (ppid == target_pid)
55+
return true;
56+
57+
// 到达 init 进程,停止遍历
58+
if (ppid == 0 || ppid == 1)
59+
return false;
60+
61+
// 继续向上遍历
62+
task = BPF_CORE_READ(task, real_parent);
63+
if (!task)
64+
return false;
65+
}
66+
return false;
67+
}
68+
69+
// uretprobe: 捕获 bash readline 返回值
70+
SEC("uretprobe//usr/bin/bash:readline")
71+
int BPF_URETPROBE(bash_readline, const void *ret)
72+
{
73+
struct bash_event *e;
74+
struct task_struct *task;
75+
__u32 pid, ppid, uid;
76+
__u32 key = 0;
77+
78+
// 如果返回值为空,跳过(用户按 Ctrl+D 等情况)
79+
if (!ret)
80+
return 0;
81+
82+
// 验证是 bash 进程
83+
char comm[MAX_COMM_SIZE];
84+
bpf_get_current_comm(&comm, sizeof(comm));
85+
if (comm[0] != 'b' || comm[1] != 'a' || comm[2] != 's' ||
86+
comm[3] != 'h' || comm[4] != 0)
87+
return 0;
88+
89+
// 获取进程信息
90+
pid = bpf_get_current_pid_tgid() >> 32;
91+
uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
92+
task = (struct task_struct *)bpf_get_current_task();
93+
ppid = BPF_CORE_READ(task, real_parent, tgid);
94+
95+
// 检查目标 PID 过滤
96+
__u32 *target = bpf_map_lookup_elem(&target_pid_map, &key);
97+
if (target && *target != 0) {
98+
if (!is_descendant_of_target(*target))
99+
return 0;
100+
}
101+
102+
// 分配事件
103+
e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
104+
if (!e)
105+
return 0;
106+
107+
// 填充事件数据
108+
e->pid = pid;
109+
e->ppid = ppid;
110+
e->uid = uid;
111+
__builtin_memcpy(e->comm, comm, sizeof(e->comm));
112+
113+
// 读取命令字符串(readline 返回值)
114+
bpf_probe_read_user_str(e->command, sizeof(e->command), ret);
115+
116+
bpf_ringbuf_submit(e, 0);
117+
return 0;
118+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2+
// Bash readline 命令监控 - 用户空间程序
3+
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <string.h>
7+
#include <signal.h>
8+
#include <unistd.h>
9+
#include <time.h>
10+
#include <getopt.h>
11+
#include <bpf/libbpf.h>
12+
#include "bash_monitor.skel.h"
13+
14+
#define MAX_COMMAND_LEN 256
15+
#define MAX_COMM_SIZE 16
16+
17+
// 与内核程序一致的结构体定义
18+
struct bash_event {
19+
__u32 pid;
20+
__u32 ppid;
21+
__u32 uid;
22+
char comm[MAX_COMM_SIZE];
23+
char command[MAX_COMMAND_LEN];
24+
};
25+
26+
static volatile sig_atomic_t exiting = 0;
27+
static int bash_count = 0;
28+
29+
static void sig_handler(int sig)
30+
{
31+
exiting = 1;
32+
}
33+
34+
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
35+
{
36+
if (level == LIBBPF_DEBUG)
37+
return 0;
38+
return vfprintf(stderr, format, args);
39+
}
40+
41+
// 获取当前时间戳
42+
static void get_timestamp(char *buf, size_t len)
43+
{
44+
time_t now = time(NULL);
45+
struct tm *tm_info = localtime(&now);
46+
strftime(buf, len, "%H:%M:%S", tm_info);
47+
}
48+
49+
// 处理 bash 事件
50+
static int handle_event(void *ctx, void *data, size_t data_sz)
51+
{
52+
const struct bash_event *e = data;
53+
char timestamp[32];
54+
55+
// 跳过空命令(用户只按回车)
56+
if (e->command[0] == '\0')
57+
return 0;
58+
59+
bash_count++;
60+
get_timestamp(timestamp, sizeof(timestamp));
61+
62+
printf("[%s] PID:%-6u PPID:%-6u UID:%-5u | %s\n",
63+
timestamp, e->pid, e->ppid, e->uid, e->command);
64+
65+
return 0;
66+
}
67+
68+
static void usage(const char *prog)
69+
{
70+
printf("Usage: %s [OPTIONS]\n", prog);
71+
printf("\n");
72+
printf("监控 Bash 交互式命令输入 (readline)\n");
73+
printf("\n");
74+
printf("Options:\n");
75+
printf(" -p PID 只监控指定 PID 及其子进程的 Bash\n");
76+
printf(" -h 显示帮助信息\n");
77+
printf("\n");
78+
printf("Examples:\n");
79+
printf(" %s # 监控所有 Bash 进程\n", prog);
80+
printf(" %s -p 1234 # 只监控 PID 1234 及其子进程的 Bash\n", prog);
81+
printf("\n");
82+
printf("注意:\n");
83+
printf(" - 仅捕获交互式 Bash 的命令输入\n");
84+
printf(" - Shell 内置命令(如 cd)也会被捕获\n");
85+
printf(" - 需要 /usr/bin/bash 存在且有 readline 符号\n");
86+
}
87+
88+
int main(int argc, char **argv)
89+
{
90+
struct bash_monitor_bpf *skel;
91+
struct ring_buffer *rb = NULL;
92+
int err;
93+
__u32 target_pid = 0;
94+
int opt;
95+
96+
// 解析命令行参数
97+
while ((opt = getopt(argc, argv, "p:h")) != -1) {
98+
switch (opt) {
99+
case 'p':
100+
target_pid = atoi(optarg);
101+
break;
102+
case 'h':
103+
default:
104+
usage(argv[0]);
105+
return opt == 'h' ? 0 : 1;
106+
}
107+
}
108+
109+
// 检查 bash 是否存在
110+
if (access("/usr/bin/bash", F_OK) != 0) {
111+
if (access("/bin/bash", F_OK) != 0) {
112+
fprintf(stderr, "错误: 未找到 bash 二进制文件\n");
113+
return 1;
114+
}
115+
}
116+
117+
signal(SIGINT, sig_handler);
118+
signal(SIGTERM, sig_handler);
119+
libbpf_set_print(libbpf_print_fn);
120+
121+
// 打开并加载 BPF 程序
122+
skel = bash_monitor_bpf__open_and_load();
123+
if (!skel) {
124+
fprintf(stderr, "加载 BPF 程序失败\n");
125+
fprintf(stderr, "提示: 确保 /usr/bin/bash 有 readline 符号\n");
126+
return 1;
127+
}
128+
129+
// 设置目标 PID 过滤
130+
if (target_pid > 0) {
131+
__u32 key = 0;
132+
err = bpf_map__update_elem(skel->maps.target_pid_map,
133+
&key, sizeof(key),
134+
&target_pid, sizeof(target_pid), 0);
135+
if (err) {
136+
fprintf(stderr, "设置目标 PID 失败: %d\n", err);
137+
goto cleanup;
138+
}
139+
}
140+
141+
// 附加 BPF 程序
142+
err = bash_monitor_bpf__attach(skel);
143+
if (err) {
144+
fprintf(stderr, "附加 BPF 程序失败: %d\n", err);
145+
fprintf(stderr, "提示: 可能是 bash 没有 readline 符号\n");
146+
goto cleanup;
147+
}
148+
149+
// 创建 Ring Buffer
150+
rb = ring_buffer__new(bpf_map__fd(skel->maps.events), handle_event, NULL, NULL);
151+
if (!rb) {
152+
fprintf(stderr, "创建 ring buffer 失败\n");
153+
err = -1;
154+
goto cleanup;
155+
}
156+
157+
printf("========================================\n");
158+
printf("Bash readline 命令监控 (uretprobe)\n");
159+
printf("========================================\n");
160+
if (target_pid > 0) {
161+
printf("目标 PID: %u (包含子进程)\n", target_pid);
162+
} else {
163+
printf("目标 PID: 所有 Bash 进程\n");
164+
}
165+
printf("========================================\n");
166+
printf("监控中... (Ctrl+C 退出)\n\n");
167+
168+
// 主循环
169+
while (!exiting) {
170+
err = ring_buffer__poll(rb, 100);
171+
if (err < 0 && err != -EINTR) {
172+
fprintf(stderr, "轮询错误: %d\n", err);
173+
break;
174+
}
175+
}
176+
177+
printf("\n========================================\n");
178+
printf("监控结束,共捕获 %d 条 Bash 命令\n", bash_count);
179+
printf("========================================\n");
180+
181+
cleanup:
182+
ring_buffer__free(rb);
183+
bash_monitor_bpf__destroy(skel);
184+
return err < 0 ? -err : 0;
185+
}

0 commit comments

Comments
 (0)