#define _GNU_SOURCE #include #include #include // 添加 errno 相关定义 #include #include #include #include // 添加 SIGCHLD 相关定义 #include // 引入 bool 类型 #include #include #include #include #include // 添加 select 相关定义 #include #include #include #include #ifdef DEBUG // getpid要用到 #include #include void print_stacktrace() { void *buffer[100]; int size = backtrace(buffer, 100); // 注意这里是 void ** 和 int 参数 backtrace_symbols_fd(buffer, size, STDERR_FILENO); } #define DEBUG_LOG(fmt, ...) \ fprintf(stderr, "[DEBUG][PID %d] %s:%d:%s(): " fmt "\n", getpid(), \ __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; // static int is_initialized = 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] == '['; } // // 保存原始的 write 函数指针 // static ssize_t (*original_write)(int fd, const void *buf, size_t count) = // NULL; // // 保存日志文件描述符 // static int log_fd = -1; // ssize_t write(int fd, const void *buf, size_t count) { // ssize_t result = -1; // result = original_write(fd, buf, count); // // 如果原始 write 成功,则将相同的内容写入日志文件 // if (result > 0 && log_fd != -1) { // ssize_t log_result = original_write(log_fd, buf, count); // if (log_result == -1) { // fprintf(stderr, "Error writing to log file: %s\n", // strerror(errno)); // // 注意:这里不应该影响原始 write 的返回值 // } // } // return result; // } typedef int (*orig_execve_type)(const char *filename, char *const argv[], char *const envp[]); // 原始指针 static orig_execve_type orig_execve = NULL; // 判断父进程是否为终端 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; DEBUG_LOG("Checking if config file has been modified: %s", CONFIG_FILE); 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) { // 首次加载,创建共享内存 DEBUG_LOG("Creating shared memory for config data"); 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; } // 首次加载时读取配置文件 DEBUG_LOG("Loading config file for the first time"); 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."); } } // 复制 stdout/stderr 到日志文件,同时保留终端颜色 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 master_fd, slave_fd; if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) { perror("openpty failed"); close(log_fd); return; } pid_t pid = fork(); if (pid == -1) { perror("fork failed"); close(log_fd); close(master_fd); close(slave_fd); return; } if (pid == 0) { // 子进程 close(master_fd); // 连接 slave_fd 到标准输入输出错误 dup2(slave_fd, STDIN_FILENO); dup2(slave_fd, STDOUT_FILENO); dup2(slave_fd, STDERR_FILENO); close(slave_fd); // 子进程不做输出,只保留环境等待 execve return; // 留给你调用 execve } // 父进程(主控):读取 master_fd 并写入 stdout + 日志 close(slave_fd); // 忽略子进程退出信号 signal(SIGCHLD, SIG_IGN); char buffer[1024]; ssize_t n; int has_error = 0; while ((n = read(master_fd, buffer, sizeof(buffer))) > 0) { // 检查错误 if (memmem(buffer, n, "error", 5) || memmem(buffer, n, "Error", 5) || memmem(buffer, n, "ERROR", 5)) { has_error = 1; } // // 输出到终端 // 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(master_fd); close(log_fd); } int execve(const char *filename, char *const argv[], char *const envp[]) { // if (!is_initialized) { // initialize(); // } DEBUG_LOG("Intercepted execve for: %s", filename); DEBUG_LOG("argv[0] = %s", argv[0]); orig_execve = (orig_execve_type)dlsym(RTLD_NEXT, "execve"); if (orig_execve == NULL) { fprintf(stderr, "Error in dlsym(\"execve\"): %s\n", dlerror()); exit(EXIT_FAILURE); } // 加载配置(仅在需要时) load_config_if_needed(); // 仅在 shell 终端调用 execve 时拦截 if (!is_terminal_shell()) { DEBUG_LOG("Not a terminal shell, bypassing interception."); return orig_execve(filename, argv, envp); } // 当前配置信息 DEBUG_LOG("Current Config rule count : %d", shared_config->rule_count); // 如果共享内存未成功加载,则直接执行 if (shared_config == NULL) { DEBUG_LOG("Shared memory not initialized, bypassing interception."); return orig_execve(filename, argv, envp); } // 如果功能被禁用,则直接执行 if (!shared_config->enabled) { DEBUG_LOG("Not enabled."); 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) { 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"); exit(EXIT_FAILURE); // 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); exit(EXIT_FAILURE); // return -1; } break; } } // 复制 stdout 和 stderr 到日志文件 duplicate_output_to_log(); return orig_execve(filename, argv, envp); } // // 构造函数,在库被加载时执行 // __attribute__((constructor)) static void initialize() { // if (is_initialized) return; // is_initialized = 1; // DEBUG_LOG("Initializing execve_intercept library."); // // // 获取原始的 write 函数 // // original_write = dlsym(RTLD_NEXT, "write"); // // if (original_write == NULL) { // // fprintf(stderr, "Error in dlsym(\"write\"): %s\n", dlerror()); // // exit(EXIT_FAILURE); // // } // // // 打开日志文件,以追加模式打开,如果不存在则创建 // // log_fd = open(LOG_OUT_FILE, O_WRONLY | O_CREAT | O_APPEND, 0644); // // if (log_fd == -1) { // // fprintf(stderr, "Error opening log file \"%s\": %s\n", // LOG_OUT_FILE, // // strerror(errno)); // // exit(EXIT_FAILURE); // // } // load_config_if_needed(); // orig_execve = (orig_execve_type)dlsym(RTLD_NEXT, "execve"); // if (orig_execve == NULL) { // fprintf(stderr, "Error in dlsym(\"execve\"): %s\n", dlerror()); // exit(EXIT_FAILURE); // } // } // 在库卸载时分离和删除共享内存 __attribute__((destructor)) static void cleanup_shared_memory() { DEBUG_LOG("execve_intercept library unloaded."); // log输出路径 DEBUG_LOG("Log file: %s", LOG_FILE); DEBUG_LOG("Log out file: %s", LOG_OUT_FILE); DEBUG_LOG("Config file: %s", CONFIG_FILE); DEBUG_LOG("Shared memory ID: %d", shm_id); if (shared_config != NULL) { DEBUG_LOG("Cleaning up shared memory."); // 解除共享内存映射 if (shmdt(shared_config) == -1) { perror("shmdt failed"); } shared_config = NULL; } #ifdef DEBUG print_stacktrace(); #endif // if (log_fd != -1) { // DEBUG_LOG("Closing log file descriptor."); // close(log_fd); // } // 注意:这里不删除共享内存段,因为可能被其他进程使用。 // 如果需要删除,需要一个明确的机制来判断是否是最后一个使用者。 // 例如,可以创建一个单独的工具来管理共享内存的生命周期。 }