234 lines
8.6 KiB
Markdown
234 lines
8.6 KiB
Markdown
# Execve Hook Project
|
||
|
||
本项目是一个基于 `LD_PRELOAD` 机制的系统调用拦截库,主要用于监控和控制 Linux 系统中的命令执行(`execve`)。它能够拦截命令执行请求,检查配置规则,记录执行日志,甚至通过 PTY(伪终端)捕获命令的输入输出。
|
||
|
||
## 核心逻辑概述
|
||
|
||
整个 C 代码库的逻辑可以分为以下几个核心模块:
|
||
|
||
### 1. `execve` 拦截器 (`src/execve_interceptor.c`)
|
||
|
||
这是项目的核心入口。
|
||
|
||
- **拦截机制**: 通过 `LD_PRELOAD` 预加载动态库,覆盖系统默认的 `execve` 函数。
|
||
- **主要流程**:
|
||
1. **环境清理**: 在调用前移除 `LD_PRELOAD` 环境变量,防止对子进程造成非预期的递归影响。
|
||
2. **配置加载**: 调用 `config.c` 从共享内存中加载配置。
|
||
3. **规则匹配**: 使用 `rules.c` 检查当前执行的命令是否命中特定规则。
|
||
4. **行为决策**: 根据规则决定是否需要拦截、记录日志或通过 PTY 执行。
|
||
5. **执行原函数**: 最终调用原始的 `execve` 系统调用执行目标程序。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant User as User/Shell
|
||
participant Hook as execve_interceptor
|
||
participant Config as Config (Shm)
|
||
participant Rules as Rules Engine
|
||
participant PTY as PTY/IO Handler
|
||
participant Sys as Original execve
|
||
|
||
User->>Hook: execve(filename, argv, envp)
|
||
Hook->>Hook: Remove LD_PRELOAD
|
||
Hook->>Hook: dlsym(RTLD_NEXT, "execve")
|
||
|
||
Hook->>Config: load_config()
|
||
Config-->>Hook: ConfigData*
|
||
|
||
alt Not Terminal Shell or Config Disabled
|
||
Hook->>Sys: orig_execve(...)
|
||
end
|
||
|
||
loop Check Rules
|
||
Hook->>Rules: args_match(argv, rule)
|
||
Rules-->>Hook: Match Result
|
||
end
|
||
|
||
alt Rule Type: SKIP
|
||
Hook->>Sys: orig_execve(...)
|
||
else Rule Type: WARN
|
||
Hook->>User: Print Warning
|
||
User->>Hook: Confirm (Y/N)
|
||
alt No
|
||
Hook->>User: Exit
|
||
end
|
||
else Rule Type: ERROR
|
||
Hook->>User: Print Error
|
||
Hook->>User: Exit
|
||
else Rule Type: LOG/DEFAULT (Match Found)
|
||
Hook->>PTY: dupIO(filename, argv, envp)
|
||
PTY->>PTY: forkpty()
|
||
par Child Process
|
||
PTY->>Sys: orig_execve(...)
|
||
and Parent Process
|
||
PTY->>PTY: handle_io() (Capture Output)
|
||
PTY->>User: Forward Output
|
||
end
|
||
else No Match
|
||
Hook->>Sys: orig_execve(...)
|
||
end
|
||
```
|
||
|
||
### 2. 配置管理 (`src/config.c`)
|
||
|
||
- **共享内存**: 配置数据不直接从文件读取,而是从共享内存(Shared Memory)中加载。
|
||
- **机制**: 这意味着有一个外部进程(通常是配套的 Go 服务)负责解析配置文件(如 JSON/YAML)并将结构化数据写入共享内存,C 代码只需高效读取即可。
|
||
|
||
### 3. 规则引擎 (`src/rules.c`)
|
||
|
||
- **功能**: 负责解析和匹配命令执行规则。
|
||
- **匹配逻辑**: 检查命令名称(`filename`)以及参数列表(`argv`)是否符合预定义的条件。
|
||
|
||
### 4. I/O 捕获与 PTY (`src/pty_dup.c`)
|
||
|
||
- **目的**: 为了完整记录交互式命令的输入输出(Session Recording)。
|
||
- **实现**:
|
||
- 使用 `forkpty` 创建一个新的伪终端会话。
|
||
- 在子进程中执行目标命令。
|
||
- 父进程负责在主终端和伪终端之间转发数据(`stdin`, `stdout`, `stderr`)。
|
||
- 单独处理 `stderr` 管道以区分标准输出和错误输出。
|
||
- 处理信号(如 `SIGINT`, `SIGCHLD`)以确保子进程正确退出。
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[dupIO Called] --> B{forkpty}
|
||
B -- Error --> C[Exit]
|
||
B -- Child Process --> D[Setup Signals]
|
||
D --> E[Close Pipe Read End]
|
||
E --> F[Dup2 Stderr -> Pipe Write End]
|
||
F --> G[Return to execve_interceptor]
|
||
G --> H[Call orig_execve]
|
||
|
||
B -- Parent Process --> I[Close Pipe Write End]
|
||
I --> J[Get Log File Paths]
|
||
J --> K[handle_io Loop]
|
||
|
||
subgraph IO_Loop [IO Handling Loop]
|
||
L{Select/Poll}
|
||
L -- Stdin Data --> M[Write to Master PTY]
|
||
L -- Master PTY Data --> N[Read Output]
|
||
N --> O[Write to Stdout]
|
||
N --> P[Write to Log File]
|
||
L -- Stderr Pipe Data --> Q[Read Stderr]
|
||
Q --> R[Write to Stderr]
|
||
Q --> S[Write to Log File]
|
||
end
|
||
|
||
K --> IO_Loop
|
||
IO_Loop -- Child Exit --> T[Check Exit Status]
|
||
T -- Error Code --> U[Send Params to Socket]
|
||
T -- Success --> V[Exit Parent]
|
||
```
|
||
|
||
### 5. Write 调用拦截 (`src/hook_write.c`)
|
||
|
||
这是一个独立的模块(编译为 `hook_write.so`),用于更细粒度的输出捕获。
|
||
|
||
- **拦截对象**: `write`, `writev`, `fwrite`, `puts`, `printf` 等标准输出函数。
|
||
- **功能**: 将进程写入 `stdout` 或 `stderr` 的内容同时写入到一个指定的日志文件中。
|
||
- **用途**: 即使不使用 PTY,也能捕获程序的输出内容。
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[Application] -->|Calls write/printf| B(Hook Function)
|
||
B --> C{Log FD Open?}
|
||
C -- Yes --> D[Write to Log File]
|
||
C -- No --> E[Skip Log]
|
||
D --> F[Call Original Function]
|
||
E --> F
|
||
F --> G[Output to Terminal]
|
||
```
|
||
|
||
### 6. 进程间通信与错误上报 (`src/client.c`)
|
||
|
||
- **方式**: Unix Domain Socket (`/etc/exec_hook/exec.sock`)。
|
||
- **作用**: 将拦截到的执行信息(文件名、参数、环境变量、日志路径等)发送给后端服务(Go Service)。
|
||
- **流程**: 在 `execve` 拦截阶段,如果需要上报,会通过此模块连接 Socket 并发送数据。
|
||
|
||
#### 错误上报机制
|
||
|
||
当被拦截的命令执行失败(退出码非 0 或被信号终止)时,系统会触发错误上报流程。
|
||
|
||
1. **执行与捕获**: `pty_dup.c` 中的 `handle_io` 循环负责实时捕获子进程的 `stdout` 和 `stderr`。
|
||
2. **日志记录**: 捕获到的 `stderr` 内容会被实时写入到特定的日志文件中(由 `GET_LOG_FILE` 宏定义路径)。
|
||
3. **触发上报**:
|
||
- 父进程等待子进程退出。
|
||
- 检查子进程退出状态 (`child_status`)。
|
||
- 如果 `WIFEXITED` 且退出码非 0,或者 `WIFSIGNALED`(被信号终止),则调用 `send_exec_params`。
|
||
4. **发送数据**: `send_exec_params` 将以下信息打包发送给 Go 服务:
|
||
- **Filename**: 执行的命令名称。
|
||
- **CWD**: 当前工作目录。
|
||
- **Argv**: 完整的参数列表。
|
||
- **Envp**: 完整的环境变量列表。
|
||
- **Log Path**: 记录了错误输出的日志文件绝对路径。
|
||
5. **等待响应**: 客户端等待服务端返回处理结果(如 AI 分析建议),并将其打印到终端。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Parent as Parent Process
|
||
participant Child as Child Process (Cmd)
|
||
participant Log as Log File (Stderr)
|
||
participant Socket as Unix Socket
|
||
participant Server as Go Service
|
||
|
||
Parent->>Child: forkpty() & exec()
|
||
loop Execution
|
||
Child->>Parent: Stderr Output (Pipe)
|
||
Parent->>Log: Write Stderr Content
|
||
end
|
||
Child-->>Parent: Exit (Status != 0)
|
||
|
||
Parent->>Parent: Check Exit Code
|
||
|
||
alt Exit Code != 0
|
||
Parent->>Socket: Connect
|
||
Parent->>Socket: Send Filename
|
||
Parent->>Socket: Send CWD
|
||
Parent->>Socket: Send Argv & Envp
|
||
Parent->>Socket: Send Log Path (Error Log)
|
||
|
||
Socket->>Server: Forward Request
|
||
Server->>Server: Analyze Error (Read Log)
|
||
Server-->>Socket: Response (Suggestion)
|
||
Socket-->>Parent: Receive Response
|
||
Parent->>Parent: Print Suggestion
|
||
end
|
||
```
|
||
|
||
#### 通信协议格式
|
||
|
||
数据通过 Socket 顺序发送,格式如下(均为二进制流):
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| :--- | :--- | :--- |
|
||
| `filename_len` | `size_t` | 文件名长度 |
|
||
| `filename` | `char[]` | 文件名内容 |
|
||
| `pwd_len` | `size_t` | 当前工作目录长度 |
|
||
| `pwd` | `char[]` | 当前工作目录内容 |
|
||
| `argc` | `int` | 参数个数 |
|
||
| `arg_len` | `size_t` | (循环) 参数长度 |
|
||
| `arg_str` | `char[]` | (循环) 参数内容 |
|
||
| `envc` | `int` | 环境变量个数 |
|
||
| `env_len` | `size_t` | (循环) 环境变量长度 |
|
||
| `env_str` | `char[]` | (循环) 环境变量内容 |
|
||
| `log_path_len` | `size_t` | 日志路径长度 |
|
||
| `log_path` | `char[]` | 日志路径内容 |
|
||
|
||
## 编译构建
|
||
|
||
项目使用 `Makefile` 进行管理。
|
||
|
||
- **默认构建**: `make` (生成 `intercept.so`)
|
||
- **开启 Hook 功能**: `make HOOK=1` (定义 `HOOK` 宏,启用拦截逻辑)
|
||
- **清理**: `make clean`
|
||
|
||
## 目录结构说明
|
||
|
||
- `src/`: 源代码目录
|
||
- `execve_interceptor.c`: `execve` 拦截主逻辑
|
||
- `hook_write.c`: `write` 系列函数拦截逻辑
|
||
- `config.c`: 共享内存配置加载
|
||
- `pty_dup.c`: 伪终端处理
|
||
- `client.c`: Socket 通信客户端
|
||
- `rules.c`: 规则匹配逻辑
|
||
- `build/`: 编译输出目录
|