From d914136ce45575a71dc218e58e79f523eb8238b7 Mon Sep 17 00:00:00 2001 From: "QCQCQC@Ubuntu" <1220204124@zust.edu.cn> Date: Wed, 26 Mar 2025 10:18:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=85=E6=8B=A6=E6=88=AA=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E8=B0=83=E7=94=A8=EF=BC=8C=E4=BB=85=E5=9C=A8=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=E5=90=AF=E5=8A=A8=E5=92=8C=E5=8F=98=E5=8C=96=E6=97=B6?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- execve_intercept.c | 155 +++++++++++++++++++++++++++++++++++---------- intercept.so | Bin 16896 -> 21440 bytes logs/execve.log | 55 ++++++++++++++++ 3 files changed, 176 insertions(+), 34 deletions(-) diff --git a/execve_intercept.c b/execve_intercept.c index 281fa13..6f7d069 100644 --- a/execve_intercept.c +++ b/execve_intercept.c @@ -1,11 +1,12 @@ #define _GNU_SOURCE #include +#include #include #include -#include #include +#include #include -#include +#include #define CONFIG_FILE "./config/execve_rules.json" #define LOG_FILE "./logs/execve.log" @@ -24,10 +25,8 @@ typedef struct { int arg_count; } Rule; -typedef int (*orig_execve_type)(const char *filename, char *const argv[], char *const envp[]); - // 加载配置规则 -Rule* load_rules(int *rule_count) { +Rule *load_rules(int *rule_count) { json_object *root = json_object_from_file(CONFIG_FILE); if (!root) { fprintf(stderr, "Failed to parse JSON from %s\n", CONFIG_FILE); @@ -57,13 +56,16 @@ Rule* load_rules(int *rule_count) { // 解析 args 参数 rules[i].arg_count = 0; - if (json_object_object_get_ex(rule, "args", &args) && json_object_get_type(args) == json_type_array) { + if (json_object_object_get_ex(rule, "args", &args) && + json_object_get_type(args) == json_type_array) { int args_len = json_object_array_length(args); - rules[i].arg_count = args_len < 10 ? args_len : 10; // 限制最多 10 个参数 + rules[i].arg_count = + args_len < 10 ? args_len : 10; // 限制最多 10 个参数 for (int j = 0; j < rules[i].arg_count; j++) { json_object *arg_item = json_object_array_get_idx(args, j); - strncpy(rules[i].args[j], json_object_get_string(arg_item), 255); + strncpy(rules[i].args[j], json_object_get_string(arg_item), + 255); } } } @@ -80,13 +82,13 @@ int args_match(char *const argv[], Rule *rule) { for (int i = 0; i < rule->arg_count; i++) { int found = 0; - for (int j = 1; argv[j] != NULL; j++) { // 跳过 argv[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; // 只要有一个参数没有匹配,则不符合规则 + if (!found) return 0; // 只要有一个参数没有匹配,则不符合规则 } return 1; } @@ -108,54 +110,139 @@ void write_log(const char *filename, char *const argv[]) { fclose(log); } +typedef int (*orig_execve_type)(const char *filename, char *const argv[], + char *const envp[]); + +static Rule *rules = NULL; +static int rule_count = 0; +static time_t last_modified_time = 0; + +// 判断父进程是否为终端 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) { + return 0; + } + return file_stat.st_mtime != last_modified_time; +} + +// 加载或重新加载规则 +void load_rules_if_needed() { + if (!rules || config_file_modified()) { + json_object *root = json_object_from_file(CONFIG_FILE); + if (!root) { + fprintf(stderr, "Failed to parse JSON from %s\n", CONFIG_FILE); + return; + } + + if (rules) { + free(rules); + } + + rule_count = json_object_array_length(root); + rules = malloc(sizeof(Rule) * rule_count); + if (!rules) { + json_object_put(root); + rule_count = 0; + return; + } + + for (int i = 0; i < rule_count; i++) { + json_object *rule = json_object_array_get_idx(root, i); + json_object *cmd, *type, *msg, *args; + + json_object_object_get_ex(rule, "cmd", &cmd); + json_object_object_get_ex(rule, "type", &type); + json_object_object_get_ex(rule, "msg", &msg); + + strncpy(rules[i].cmd, json_object_get_string(cmd), 255); + strncpy(rules[i].type, json_object_get_string(type), 31); + strncpy(rules[i].msg, json_object_get_string(msg), 1023); + + // 解析 args 参数 + rules[i].arg_count = 0; + if (json_object_object_get_ex(rule, "args", &args) && + json_object_get_type(args) == json_type_array) { + int args_len = json_object_array_length(args); + rules[i].arg_count = args_len < 10 ? args_len : 10; + + for (int j = 0; j < rules[i].arg_count; j++) { + json_object *arg_item = json_object_array_get_idx(args, j); + strncpy(rules[i].args[j], json_object_get_string(arg_item), + 255); + } + } + } + + json_object_put(root); + struct stat file_stat; + if (stat(CONFIG_FILE, &file_stat) == 0) { + last_modified_time = file_stat.st_mtime; + } + } +} + int execve(const char *filename, char *const argv[], char *const envp[]) { - int rule_count; - Rule *rules = load_rules(&rule_count); - if (!rules) { - return -1; + // 仅在 shell 终端调用 execve 时拦截 + if (!is_terminal_shell()) { + orig_execve_type orig_execve = + (orig_execve_type)dlsym(RTLD_NEXT, "execve"); + return orig_execve(filename, argv, envp); } - const char *basename = argv[0]; // 直接使用 argv[0] + // 加载规则(仅在需要时) + load_rules_if_needed(); - // 处理 command-not-found 情况 + const char *basename = argv[0]; if (strcmp(filename, COMMAND_NOT_FOUND) == 0 && argv[2]) { - basename = argv[2]; // 解析出真正要执行的命令 + basename = argv[2]; } - // 检查规则匹配 for (int i = 0; i < rule_count; i++) { - if (strcmp(basename, rules[i].cmd) == 0 && args_match(argv, &rules[i])) { + if (strcmp(basename, rules[i].cmd) == 0 && + args_match(argv, &rules[i])) { if (strcmp(rules[i].type, "warn") == 0) { - printf(ANSI_COLOR_YELLOW "[Warning] %s\n" ANSI_COLOR_RESET, rules[i].msg); + printf(ANSI_COLOR_YELLOW "[Warning] %s\n" ANSI_COLOR_RESET, + rules[i].msg); printf("按下 'Y' 继续执行, 或按任意键取消: "); - char input = getchar(); if (input != 'Y' && input != 'y') { printf("\nExecution cancelled.\n"); - free(rules); return -1; } printf("\nContinuing execution...\n"); } else if (strcmp(rules[i].type, "error") == 0) { - printf(ANSI_COLOR_RED "[Error] %s" ANSI_COLOR_RESET "\n", rules[i].msg); - free(rules); + printf(ANSI_COLOR_RED "[Error] %s" ANSI_COLOR_RESET "\n", + rules[i].msg); return -1; } break; } } - // 记录日志 write_log(filename, argv); - - // 调用原始 execve orig_execve_type orig_execve = (orig_execve_type)dlsym(RTLD_NEXT, "execve"); - if (!orig_execve) { - fprintf(stderr, "Error: %s\n", dlerror()); - free(rules); - return -1; - } - - free(rules); return orig_execve(filename, argv, envp); } diff --git a/intercept.so b/intercept.so index 2e9b4a85f7ec67cb41d25e0b00c6861c30c162f2..4fdbf1b135ee468cfd536e4bcbd8da8833f7b5d3 100755 GIT binary patch literal 21440 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 Ji-Cn@{|RW?6?*^x literal 16896 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 ZiJULXQg?TW$fS