503 lines
16 KiB
C
503 lines
16 KiB
C
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <errno.h> // 添加 errno 相关定义
|
|
#include <fcntl.h>
|
|
#include <fcntl.h> // 添加 fcntl 相关定义
|
|
#include <json-c/json.h>
|
|
#include <signal.h> // 添加 SIGCHLD 相关定义
|
|
#include <stdbool.h> // 引入 bool 类型
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ipc.h>
|
|
#include <sys/select.h> // 添加 select 相关定义
|
|
#include <sys/shm.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <pty.h>
|
|
#include <signal.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;
|
|
// 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");
|
|
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();
|
|
|
|
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;
|
|
}
|
|
// if (log_fd != -1) {
|
|
// DEBUG_LOG("Closing log file descriptor.");
|
|
// close(log_fd);
|
|
// }
|
|
// 注意:这里不删除共享内存段,因为可能被其他进程使用。
|
|
// 如果需要删除,需要一个明确的机制来判断是否是最后一个使用者。
|
|
// 例如,可以创建一个单独的工具来管理共享内存的生命周期。
|
|
}
|