execve_hook/execve_intercept.c

416 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <json-c/json.h>
#include <stdbool.h> // 引入 bool 类型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#ifdef DEBUG
#define DEBUG_LOG(fmt, ...) \
fprintf(stderr, "[DEBUG] %s:%d:%s(): " fmt "\n", __FILE__, __LINE__, \
__func__, ##__VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...) ((void)0)
#endif
#define CONFIG_FILE "/tmp/exec_hook/config/execve_rules.json"
#define LOG_FILE "/tmp/exec_hook/logs/execve.log"
#define LOG_OUT_FILE "/tmp/exec_hook/logs/execve_out.log"
#define COMMAND_NOT_FOUND "/usr/lib/command-not-found"
#define ANSI_COLOR_RED "\033[31m"
#define ANSI_COLOR_YELLOW "\033[33m"
#define ANSI_COLOR_RESET "\033[0m"
#define SHM_KEY 12345 // 用于标识共享内存的键值,需要确保唯一性
#define MAX_RULES 100 // 假设最大规则数量
#define MAX_ARGS 10 // 支持最多 10 个参数
typedef struct {
char cmd[256];
char type[32];
char msg[1024];
char args[MAX_ARGS][256]; // 支持最多 MAX_ARGS 个参数
int arg_count;
} Rule;
typedef struct {
bool enabled;
Rule rules[MAX_RULES];
int rule_count;
} ConfigData;
// 全局变量,指向共享内存中的配置数据
static ConfigData *shared_config = NULL;
static int shm_id = -1;
static time_t last_modified_time = 0;
// 加载配置到共享内存
int load_config_to_shm() {
DEBUG_LOG("Loading configuration from %s to shared memory", CONFIG_FILE);
json_object *root = json_object_from_file(CONFIG_FILE);
if (!root) {
DEBUG_LOG("Failed to parse config file from %s", CONFIG_FILE);
return -1;
}
ConfigData temp_config;
temp_config.enabled = false;
temp_config.rule_count = 0;
json_object *enabled_obj;
if (json_object_object_get_ex(root, "enabled", &enabled_obj)) {
temp_config.enabled = json_object_get_boolean(enabled_obj);
}
if (!temp_config.enabled) {
json_object_put(root);
return 0; // 功能未启用,不加载规则
}
json_object *rules_array_obj;
if (json_object_object_get_ex(root, "rules", &rules_array_obj) &&
json_object_get_type(rules_array_obj) == json_type_array) {
int rules_len = json_object_array_length(rules_array_obj);
temp_config.rule_count = rules_len < MAX_RULES ? rules_len : MAX_RULES;
for (int i = 0; i < temp_config.rule_count; i++) {
json_object *rule_obj =
json_object_array_get_idx(rules_array_obj, i);
json_object *cmd, *type, *msg, *args;
json_object_object_get_ex(rule_obj, "cmd", &cmd);
json_object_object_get_ex(rule_obj, "type", &type);
json_object_object_get_ex(rule_obj, "msg", &msg);
if (cmd)
strncpy(temp_config.rules[i].cmd, json_object_get_string(cmd),
sizeof(temp_config.rules[i].cmd) - 1);
if (type)
strncpy(temp_config.rules[i].type, json_object_get_string(type),
sizeof(temp_config.rules[i].type) - 1);
if (msg)
strncpy(temp_config.rules[i].msg, json_object_get_string(msg),
sizeof(temp_config.rules[i].msg) - 1);
// 解析 args 参数
temp_config.rules[i].arg_count = 0;
if (json_object_object_get_ex(rule_obj, "args", &args) &&
json_object_get_type(args) == json_type_array) {
int args_len = json_object_array_length(args);
temp_config.rules[i].arg_count =
args_len < MAX_ARGS ? args_len
: MAX_ARGS; // 限制最多 MAX_ARGS 个参数
for (int j = 0; j < temp_config.rules[i].arg_count; j++) {
json_object *arg_item = json_object_array_get_idx(args, j);
if (arg_item) {
strncpy(temp_config.rules[i].args[j],
json_object_get_string(arg_item),
sizeof(temp_config.rules[i].args[j]) - 1);
}
}
}
}
}
json_object_put(root);
// 将临时配置复制到共享内存
memcpy(shared_config, &temp_config, sizeof(ConfigData));
DEBUG_LOG("Loaded %d rules to shared memory", shared_config->rule_count);
return 0;
}
// 检查 args 是否匹配
int args_match(char *const argv[], Rule *rule) {
DEBUG_LOG("Matching args for rule with cmd: %s", rule->cmd);
if (rule->arg_count == 0) {
return 1; // 没有 args 约束,则直接匹配
}
for (int i = 0; i < rule->arg_count; i++) {
int found = 0;
for (int j = 1; argv[j] != NULL; j++) { // 跳过 argv[0] (命令本身)
if (strcmp(argv[j], rule->args[i]) == 0) {
found = 1;
break;
}
}
if (!found) return 0; // 只要有一个参数没有匹配,则不符合规则
}
return 1;
}
// 写入日志
void write_log(const char *filename, char *const argv[]) {
DEBUG_LOG("Writing exec log for command: %s", filename);
time_t now;
time(&now);
FILE *log = fopen(LOG_FILE, "a");
if (!log) return;
fprintf(log, "[%s] Command: %s\n", ctime(&now), filename);
for (int i = 0; argv[i]; i++) {
fprintf(log, "arg[%d]: %s\n", i, argv[i]);
}
fclose(log);
}
// 判断字符是否为 ANSI 转义序列
int is_ansi_escape_sequence(const char *str) {
// ANSI 转义序列的常见格式是以 ESC 开头,后跟 '[', 'm' 等
return str[0] == '\033' && str[1] == '[';
}
// 复制标准输出和错误输出到日志文件
void duplicate_output_to_log() {
DEBUG_LOG("Duplicating stdout/stderr to log file: %s", LOG_OUT_FILE);
int log_fd = open(LOG_OUT_FILE, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (log_fd == -1) {
perror("Failed to open log file");
return;
}
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
perror("Failed to create pipe");
close(log_fd);
return;
}
pid_t pid = fork();
if (pid == -1) {
perror("Failed to fork");
close(log_fd);
close(pipe_fds[0]);
close(pipe_fds[1]);
return;
}
if (pid == 0) { // 子进程:读取 pipe并写入日志
close(pipe_fds[1]); // 关闭写端
char buffer[1024];
ssize_t n;
int has_error = 0;
while ((n = read(pipe_fds[0], buffer, sizeof(buffer))) > 0) {
// 检查buffer中是否包含错误信息
if (strstr(buffer, "error") || strstr(buffer, "Error") ||
strstr(buffer, "ERROR")) {
has_error = 1;
}
// 输出到终端时保留颜色
if (isatty(STDOUT_FILENO)) {
if (write(STDOUT_FILENO, buffer, n) == -1) {
perror("Failed to write to stdout");
}
}
if (write(log_fd, buffer, n) == -1) {
perror("Failed to write to log file");
}
}
if (has_error) {
printf("\n检测到命令执行出错,已经上报北冥论坛~ \n");
fflush(stdout); // 确保提示文字被输出
}
close(pipe_fds[0]);
close(log_fd);
_exit(0);
} else { // 父进程:写入 pipe
close(pipe_fds[0]); // 关闭读端
dup2(pipe_fds[1], STDOUT_FILENO);
dup2(pipe_fds[1], STDERR_FILENO);
close(pipe_fds[1]);
close(log_fd);
}
}
typedef int (*orig_execve_type)(const char *filename, char *const argv[],
char *const envp[]);
// 判断父进程是否为终端 shell (bash, zsh, fish 等)
int is_terminal_shell() {
pid_t ppid = getppid();
char path[64], proc_name[256];
FILE *file;
snprintf(path, sizeof(path), "/proc/%d/comm", ppid);
file = fopen(path, "r");
if (!file) return 0;
if (fgets(proc_name, sizeof(proc_name), file)) {
proc_name[strcspn(proc_name, "\n")] = 0; // 去除换行符
if (strcmp(proc_name, "bash") == 0 || strcmp(proc_name, "zsh") == 0 ||
strcmp(proc_name, "fish") == 0 || strcmp(proc_name, "sh") == 0) {
fclose(file);
return 1;
}
}
fclose(file);
return 0;
}
// 检查配置文件是否已修改
int config_file_modified() {
struct stat file_stat;
if (stat(CONFIG_FILE, &file_stat) != 0) {
DEBUG_LOG("Cannot get stat for FILE: %s", CONFIG_FILE);
return 0;
}
int isChanged = file_stat.st_mtime != last_modified_time;
if (isChanged != 0) {
DEBUG_LOG("Updating last_modified_time to: %ld", file_stat.st_mtime);
last_modified_time = file_stat.st_mtime;
return 1;
}
return 0;
}
// 加载或重新加载配置到共享内存
void load_config_if_needed() {
if (shared_config == NULL) {
// 首次加载,创建共享内存
shm_id = shmget(SHM_KEY, sizeof(ConfigData), IPC_CREAT | 0644);
if (shm_id == -1) {
perror("shmget failed");
return;
}
shared_config = (ConfigData *)shmat(shm_id, NULL, 0);
if (shared_config == (void *)-1) {
perror("shmat failed");
shared_config = NULL;
return;
}
// 首次加载时读取配置文件
struct stat file_stat;
if (stat(CONFIG_FILE, &file_stat) == 0) {
last_modified_time = file_stat.st_mtime;
load_config_to_shm();
} else {
DEBUG_LOG("Cannot get stat for FILE: %s", CONFIG_FILE);
// 初始化一个空的配置
shared_config->enabled = false;
shared_config->rule_count = 0;
}
} else if (config_file_modified()) {
DEBUG_LOG("Config file has been modified.");
load_config_to_shm();
} else {
DEBUG_LOG("Config file has not been modified, skipping reload.");
}
}
int execve(const char *filename, char *const argv[], char *const envp[]) {
DEBUG_LOG("Intercepted execve for: %s", filename);
DEBUG_LOG("argv[0] = %s", argv[0]);
// 仅在 shell 终端调用 execve 时拦截
if (!is_terminal_shell()) {
DEBUG_LOG("Not a terminal shell, bypassing interception.");
orig_execve_type orig_execve =
(orig_execve_type)dlsym(RTLD_NEXT, "execve");
return orig_execve(filename, argv, envp);
}
// 加载配置(仅在需要时)
load_config_if_needed();
// 当前配置信息
DEBUG_LOG("Current Config rule count : %d", shared_config->rule_count);
// 如果共享内存未成功加载,则直接执行
if (shared_config == NULL) {
DEBUG_LOG("Shared memory not initialized, bypassing interception.");
orig_execve_type orig_execve =
(orig_execve_type)dlsym(RTLD_NEXT, "execve");
return orig_execve(filename, argv, envp);
}
// 如果功能被禁用,则直接执行
if (!shared_config->enabled) {
DEBUG_LOG("Not enabled.");
orig_execve_type orig_execve =
(orig_execve_type)dlsym(RTLD_NEXT, "execve");
return orig_execve(filename, argv, envp);
}
write_log(filename, argv);
const char *basename = argv[0];
if (strcmp(filename, COMMAND_NOT_FOUND) == 0 && argv[2]) {
basename = argv[2];
}
// 特殊处理以 shell.posix
// 方式执行的命令,直接执行,不进行规则匹配和输出重定向
if (argv[1] != NULL && strcmp(argv[1], "shell.posix") == 0) {
orig_execve_type orig_execve =
(orig_execve_type)dlsym(RTLD_NEXT, "execve");
return orig_execve(filename, argv, envp);
}
for (int i = 0; i < shared_config->rule_count; i++) {
if (strcmp(basename, shared_config->rules[i].cmd) == 0 &&
args_match(argv, &shared_config->rules[i])) {
DEBUG_LOG("Rule matched: %s (type: %s)",
shared_config->rules[i].cmd,
shared_config->rules[i].type);
if (strcmp(shared_config->rules[i].type, "warn") == 0) {
printf(ANSI_COLOR_YELLOW "[Warning] %s\n" ANSI_COLOR_RESET,
shared_config->rules[i].msg);
printf("按下 'Y' 继续执行, 或按任意键取消: ");
char input = getchar();
if (input != 'Y' && input != 'y') {
printf("\nExecution cancelled.\n");
return -1;
}
printf("\nContinuing execution...\n");
} else if (strcmp(shared_config->rules[i].type, "error") == 0) {
printf(ANSI_COLOR_RED "[Error] %s" ANSI_COLOR_RESET "\n",
shared_config->rules[i].msg);
return -1;
}
break;
}
}
// 复制 stdout 和 stderr 到日志文件
duplicate_output_to_log();
orig_execve_type orig_execve = (orig_execve_type)dlsym(RTLD_NEXT, "execve");
return orig_execve(filename, argv, envp);
}
// 在库卸载时分离和删除共享内存
__attribute__((destructor)) static void cleanup_shared_memory() {
DEBUG_LOG("Cleaning up shared memory.");
if (shared_config != NULL) {
if (shmdt(shared_config) == -1) {
perror("shmdt failed");
}
shared_config = NULL;
}
// 注意:这里不删除共享内存段,因为可能被其他进程使用。
// 如果需要删除,需要一个明确的机制来判断是否是最后一个使用者。
// 例如,可以创建一个单独的工具来管理共享内存的生命周期。
}