diff --git a/.vscode/settings.json b/.vscode/settings.json index e8731cb..3fc51a4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,8 @@ "*.json": "jsonc", "string.h": "c", "shm.h": "c", - "unistd.h": "c" + "unistd.h": "c", + "logging.h": "c", + "fcntl.h": "c" } } \ No newline at end of file diff --git a/Makefile b/Makefile index da84008..940fef5 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,32 @@ CC = gcc CFLAGS = -shared -fPIC -Wall -Wextra -Werror -O2 -fno-strict-aliasing -fPIC -fno-omit-frame-pointer -fno-stack-protector -Wl,-z,relro,-z,now LDFLAGS = -ldl -ljson-c -TARGET = intercept.so -SRC = execve_intercept.c +TARGET_NAME = intercept.so +BUILD_DIR = build +SRC_DIR = src +SRC = $(wildcard $(SRC_DIR)/*.c) +OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC)) +TARGET = $(BUILD_DIR)/$(TARGET_NAME) # 如果需要开启 debug,只需执行 make DEBUG=1 ifeq ($(DEBUG),1) - CFLAGS += -DDEBUG + CFLAGS += -DDEBUG -g # Add -g for debugging symbols endif all: $(TARGET) -$(TARGET): $(SRC) +$(BUILD_DIR)/$(TARGET_NAME): $(OBJ) + @mkdir -p $(BUILD_DIR) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + clean: - rm -f $(TARGET) + rm -rf $(BUILD_DIR) debug: $(MAKE) DEBUG=1 - \ No newline at end of file + +rebuild: clean all \ No newline at end of file diff --git a/build/config.o b/build/config.o new file mode 100644 index 0000000..d6eed6a Binary files /dev/null and b/build/config.o differ diff --git a/build/debug.o b/build/debug.o new file mode 100644 index 0000000..7699e81 Binary files /dev/null and b/build/debug.o differ diff --git a/build/execve_interceptor.o b/build/execve_interceptor.o new file mode 100644 index 0000000..a96107f Binary files /dev/null and b/build/execve_interceptor.o differ diff --git a/build/init_cleanup.o b/build/init_cleanup.o new file mode 100644 index 0000000..7ec0007 Binary files /dev/null and b/build/init_cleanup.o differ diff --git a/build/intercept.so b/build/intercept.so new file mode 100755 index 0000000..723b94c Binary files /dev/null and b/build/intercept.so differ diff --git a/build/logging.o b/build/logging.o new file mode 100644 index 0000000..71d4ce6 Binary files /dev/null and b/build/logging.o differ diff --git a/build/rules.o b/build/rules.o new file mode 100644 index 0000000..c011107 Binary files /dev/null and b/build/rules.o differ diff --git a/build/utils.o b/build/utils.o new file mode 100644 index 0000000..9da22ca Binary files /dev/null and b/build/utils.o differ diff --git a/execve_intercept.c b/execve_intercept.c deleted file mode 100644 index 04ba7ef..0000000 --- a/execve_intercept.c +++ /dev/null @@ -1,515 +0,0 @@ -#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); - // } - // 注意:这里不删除共享内存段,因为可能被其他进程使用。 - // 如果需要删除,需要一个明确的机制来判断是否是最后一个使用者。 - // 例如,可以创建一个单独的工具来管理共享内存的生命周期。 -} diff --git a/intercept.so b/intercept.so deleted file mode 100755 index f9bebb3..0000000 Binary files a/intercept.so and /dev/null differ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..7470bf5 --- /dev/null +++ b/src/config.c @@ -0,0 +1,144 @@ +#include "config.h" +#include "debug.h" +#include +#include +#include +#include +#include +#include + +// Global variables (defined in execve_interceptor.c) +extern ConfigData *shared_config; +extern int shm_id; +extern time_t last_modified_time; + +// Load configuration to shared memory +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; // Feature not enabled, don't load rules + } + + 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); + + // Parse args parameter + 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; // Limit to MAX_ARGS parameters + + 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); + + // Copy the temporary configuration to shared memory + memcpy(shared_config, &temp_config, sizeof(ConfigData)); + + DEBUG_LOG("Loaded %d rules to shared memory", shared_config->rule_count); + return 0; +} + +// Check if the configuration file has been modified +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; +} + +// Load or reload configuration to shared memory if needed +void load_config_if_needed() { + if (shared_config == NULL) { + // First load, create shared memory + 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; + } + // Read config file on first load + 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); + // Initialize an empty configuration + 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."); + } +} \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..73cbd3a --- /dev/null +++ b/src/config.h @@ -0,0 +1,10 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "exec_hook.h" + +int load_config_to_shm(); +int config_file_modified(); +void load_config_if_needed(); + +#endif // CONFIG_H \ No newline at end of file diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..cc73b52 --- /dev/null +++ b/src/debug.c @@ -0,0 +1,11 @@ +#ifdef DEBUG +#include +#include +#include + +void print_stacktrace() { + void *buffer[100]; + int size = backtrace(buffer, 100); + backtrace_symbols_fd(buffer, size, STDERR_FILENO); +} +#endif \ No newline at end of file diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..90c88eb --- /dev/null +++ b/src/debug.h @@ -0,0 +1,15 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#ifdef DEBUG +#include + +void print_stacktrace(); +#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 + +#endif // DEBUG_H \ No newline at end of file diff --git a/src/exec_hook.h b/src/exec_hook.h new file mode 100644 index 0000000..1fd00d5 --- /dev/null +++ b/src/exec_hook.h @@ -0,0 +1,40 @@ +#ifndef EXEC_HOOK_H +#define EXEC_HOOK_H + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "struct.h" + +#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 + +// Global variable, pointing to the configuration data in shared memory +extern ConfigData *shared_config; +extern int shm_id; +extern time_t last_modified_time; +// extern int is_initialized; + +#endif // EXEC_HOOK_H \ No newline at end of file diff --git a/src/execve_interceptor.c b/src/execve_interceptor.c new file mode 100644 index 0000000..325176a --- /dev/null +++ b/src/execve_interceptor.c @@ -0,0 +1,106 @@ +#include "execve_interceptor.h" + +#include +#include +#include +#include +#include + +#include "config.h" +#include "debug.h" +#include "init_cleanup.h" +#include "logging.h" +#include "rules.h" +#include "utils.h" + +// Global variables (declared in exec_hook.h and defined here) +ConfigData *shared_config = NULL; +int shm_id = -1; +time_t last_modified_time = 0; +// int is_initialized = 0; + +// Original pointer +orig_execve_type orig_execve = NULL; + +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 configuration (only if needed) + load_config_if_needed(); + + // Intercept only when execve is called from a shell terminal + if (!is_terminal_shell()) { + DEBUG_LOG("Not a terminal shell, bypassing interception."); + return orig_execve(filename, argv, envp); + } + + // Current configuration information + DEBUG_LOG("Current Config rule count : %d", shared_config->rule_count); + + // If shared memory was not successfully loaded, execute directly + if (shared_config == NULL) { + DEBUG_LOG("Shared memory not initialized, bypassing interception."); + return orig_execve(filename, argv, envp); + } + + // If the feature is disabled, execute directly + 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]; + } + + // Special handling for commands executed via shell.posix + // Execute directly, without rule matching and output redirection + 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; + } + } + + // Duplicate stdout and stderr to the log file + duplicate_output_to_log(); + + return orig_execve(filename, argv, envp); +} \ No newline at end of file diff --git a/src/execve_interceptor.h b/src/execve_interceptor.h new file mode 100644 index 0000000..5225ee1 --- /dev/null +++ b/src/execve_interceptor.h @@ -0,0 +1,14 @@ +#ifndef EXECVE_INTERCEPTOR_H +#define EXECVE_INTERCEPTOR_H + +#include "exec_hook.h" + +// Original execve type +typedef int (*orig_execve_type)(const char *filename, char *const argv[], + char *const envp[]); + +extern orig_execve_type orig_execve; + +int execve(const char *filename, char *const argv[], char *const envp[]); + +#endif // EXECVE_INTERCEPTOR_H \ No newline at end of file diff --git a/src/init_cleanup.c b/src/init_cleanup.c new file mode 100644 index 0000000..5958a7b --- /dev/null +++ b/src/init_cleanup.c @@ -0,0 +1,43 @@ +#include "init_cleanup.h" +#include "exec_hook.h" +#include "debug.h" +#include +#include +#include + +// Global variables (defined in execve_interceptor.c) +extern ConfigData *shared_config; +extern int shm_id; + +// // Constructor, executed when the library is loaded +// __attribute__((constructor)) static void initialize() { +// if (is_initialized) return; +// is_initialized = 1; +// DEBUG_LOG("Initializing execve_intercept library."); +// load_config_if_needed(); +// // orig_execve is initialized in the execve function itself +// } + +// Destructor, executed when the library is unloaded +__attribute__((destructor)) static void cleanup_shared_memory() { + DEBUG_LOG("execve_intercept library unloaded."); + // Log output paths + 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."); + // Detach shared memory segment + if (shmdt(shared_config) == -1) { + perror("shmdt failed"); + } + shared_config = NULL; + } +#ifdef DEBUG + print_stacktrace(); +#endif + // Note: We don't delete the shared memory segment here, as it might be + // used by other processes. A separate mechanism would be needed to manage + // the lifecycle of the shared memory if deletion is required. +} \ No newline at end of file diff --git a/src/init_cleanup.h b/src/init_cleanup.h new file mode 100644 index 0000000..aa07e6a --- /dev/null +++ b/src/init_cleanup.h @@ -0,0 +1,7 @@ +#ifndef INIT_CLEANUP_H +#define INIT_CLEANUP_H + +// void initialize(); +__attribute__((destructor)) static void cleanup_shared_memory(); + +#endif // INIT_CLEANUP_H \ No newline at end of file diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000..2a3e764 --- /dev/null +++ b/src/logging.c @@ -0,0 +1,100 @@ +#include "logging.h" +#include "exec_hook.h" +#include "debug.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// Write log +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); +} + +// Duplicate stdout/stderr to log file, while preserving terminal colors +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) { // Child process + close(master_fd); + + // Connect slave_fd to standard input/output/error + dup2(slave_fd, STDIN_FILENO); + dup2(slave_fd, STDOUT_FILENO); + dup2(slave_fd, STDERR_FILENO); + close(slave_fd); + + // Child process does not output, only keeps the environment for execve + return; // Will be replaced by the original execve call + } + + // Parent process (controller): read from master_fd and write to stdout + log + close(slave_fd); + + // Ignore child process exit signal + signal(SIGCHLD, SIG_IGN); + + char buffer[1024]; + ssize_t n; + int has_error = 0; + + while ((n = read(master_fd, buffer, sizeof(buffer))) > 0) { + // Check for errors + if (memmem(buffer, n, "error", 5) || memmem(buffer, n, "Error", 5) || + memmem(buffer, n, "ERROR", 5)) { + has_error = 1; + } + + // Write to log + 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); +} \ No newline at end of file diff --git a/src/logging.h b/src/logging.h new file mode 100644 index 0000000..7daf1b0 --- /dev/null +++ b/src/logging.h @@ -0,0 +1,7 @@ +#ifndef LOGGING_H +#define LOGGING_H + +void write_log(const char *filename, char *const argv[]); +void duplicate_output_to_log(); + +#endif // LOGGING_H \ No newline at end of file diff --git a/src/rules.c b/src/rules.c new file mode 100644 index 0000000..a7ae738 --- /dev/null +++ b/src/rules.c @@ -0,0 +1,23 @@ +#include "rules.h" +#include "debug.h" +#include + +// Check if args match +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; // No args constraint, so it matches + } + + for (int i = 0; i < rule->arg_count; i++) { + int found = 0; + for (int j = 1; argv[j] != NULL; j++) { // Skip argv[0] (command itself) + if (strcmp(argv[j], rule->args[i]) == 0) { + found = 1; + break; + } + } + if (!found) return 0; // If any argument doesn't match, the rule doesn't apply + } + return 1; +} \ No newline at end of file diff --git a/src/rules.h b/src/rules.h new file mode 100644 index 0000000..72d42d2 --- /dev/null +++ b/src/rules.h @@ -0,0 +1,8 @@ +#ifndef RULES_H +#define RULES_H + +#include "exec_hook.h" + +int args_match(char *const argv[], Rule *rule); + +#endif // RULES_H \ No newline at end of file diff --git a/src/struct.h b/src/struct.h new file mode 100644 index 0000000..cf33a9e --- /dev/null +++ b/src/struct.h @@ -0,0 +1,22 @@ +#ifndef STRUCT_H +#define STRUCT_H + +#define _GNU_SOURCE +#define MAX_RULES 100 +#define MAX_ARGS 10 + +typedef struct { + char cmd[256]; + char type[32]; + char msg[1024]; + char args[MAX_ARGS][256]; + int arg_count; +} Rule; + +typedef struct { + bool enabled; + Rule rules[MAX_RULES]; + int rule_count; +} ConfigData; + +#endif // STRUCT_H \ No newline at end of file diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..371d150 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,34 @@ +#include "utils.h" +#include "debug.h" +#include +#include +#include +#include + +// Determine if the parent process is a terminal shell (bash, zsh, fish, etc.) +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; // Remove newline + 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; +} + +// Check if a string is an ANSI escape sequence +int is_ansi_escape_sequence(const char *str) { + // Common format for ANSI escape sequences is ESC followed by '[' + return str[0] == '\033' && str[1] == '['; +} \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..718b690 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,7 @@ +#ifndef UTILS_H +#define UTILS_H + +int is_terminal_shell(); +int is_ansi_escape_sequence(const char *str); + +#endif // UTILS_H \ No newline at end of file diff --git a/test_bash.sh b/test_bash.sh index 0ab6f05..eeff015 100755 --- a/test_bash.sh +++ b/test_bash.sh @@ -35,5 +35,5 @@ echo -e "${CYAN}=============================================${RESET}" HOOK_EXEC_PATH=/tmp/exec_hook/intercept.so rm -rf $HOOK_EXEC_PATH -cp ./intercept.so $HOOK_EXEC_PATH +cp ./build/intercept.so $HOOK_EXEC_PATH LD_PRELOAD=$HOOK_EXEC_PATH bash