756 lines
24 KiB
Markdown
756 lines
24 KiB
Markdown
# Execve Hook Project
|
||
|
||
本项目是一个基于 `LD_PRELOAD` 机制的系统调用拦截库,主要用于监控和控制 Linux 系统中的命令执行(`execve`)。它能够拦截命令执行请求,检查配置规则,记录执行日志,甚至通过 PTY(伪终端)捕获命令的输入输出。
|
||
|
||
## 架构概览
|
||
|
||
项目采用 **C 客户端 + Go 服务端** 的架构,通过 Unix Domain Socket 实现实时双向通信:
|
||
|
||
- **C 客户端**:作为"哑终端",负责捕获所有终端事件(键盘、鼠标、窗口大小变化)并通过 Socket 发送给服务端
|
||
- **Go 服务端**:完全控制终端输出,可以渲染 TUI、处理 AI 响应、格式化输出等
|
||
- **通信协议**:基于结构化二进制消息的现代化协议,支持多种消息类型
|
||
|
||
### 关键特性
|
||
|
||
✓ **完全服务端控制**:终端输出由 Go 服务完全接管
|
||
✓ **实时交互捕获**:鼠标点击、键盘输入、窗口调整实时同步
|
||
✓ **Raw 终端模式**:客户端使用原始模式捕获所有输入
|
||
✓ **优雅退出机制**:服务端控制连接生命周期,客户端自动清理终端状态
|
||
✓ **TUI 支持**:服务端可以使用任何 TUI 库(如 bubbletea)进行界面渲染
|
||
|
||
## 通信流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant User as 用户终端
|
||
participant Client as C客户端
|
||
participant Socket as Unix Socket
|
||
participant Server as Go服务端
|
||
participant AI as AI服务
|
||
|
||
User->>Client: 执行命令出错
|
||
Client->>Client: 进入Raw模式
|
||
Client->>Client: 启用鼠标跟踪
|
||
Client->>Socket: 发送初始化消息
|
||
Socket->>Server: 转发初始化数据
|
||
|
||
par 持续监听
|
||
loop 输入监听
|
||
User->>Client: 键盘/鼠标输入
|
||
Client->>Socket: 发送终端输入事件
|
||
Socket->>Server: 转发事件
|
||
end
|
||
|
||
loop 窗口监听
|
||
User->>Client: 调整窗口大小
|
||
Client->>Socket: 发送窗口大小更新
|
||
Socket->>Server: 转发更新
|
||
end
|
||
|
||
loop 服务端处理
|
||
Server->>AI: 请求错误分析
|
||
AI-->>Server: 返回建议
|
||
Server->>Socket: 发送响应消息
|
||
Socket->>Client: 转发响应
|
||
Client->>User: 直接输出到终端
|
||
end
|
||
end
|
||
|
||
Server->>Socket: 发送关闭消息
|
||
Socket->>Client: 转发关闭
|
||
Client->>Client: 恢复终端设置
|
||
Client->>Client: 禁用鼠标跟踪
|
||
Client->>User: 退出程序
|
||
```
|
||
|
||
### 工作模式
|
||
|
||
1. **客户端职责**(静默模式)
|
||
- 捕获所有终端输入(键盘、鼠标)
|
||
- 监听窗口大小变化(SIGWINCH)
|
||
- 将原始事件发送给服务端
|
||
- 接收服务端消息并输出到终端
|
||
- **不做任何业务逻辑处理**
|
||
|
||
2. **服务端职责**(完全控制)
|
||
- 接收客户端的所有输入事件
|
||
- 处理业务逻辑(AI 分析、日志查询等)
|
||
- 渲染 TUI 界面或格式化输出
|
||
- 控制连接生命周期
|
||
- 决定何时关闭连接
|
||
|
||
3. **终端状态管理**
|
||
- **进入时**:客户端设置 Raw 模式 + 启用鼠标跟踪
|
||
- **运行时**:保存原始 termios 设置
|
||
- **退出时**:自动恢复原始设置 + 禁用鼠标跟踪 + 输出换行符
|
||
|
||
## 核心逻辑概述
|
||
|
||
整个项目的逻辑可以分为以下几个核心模块:
|
||
|
||
### 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. Socket 通信客户端 (`src/client.c`)
|
||
|
||
现代化的 Socket 通信实现,采用多线程架构实现实时双向通信。
|
||
|
||
#### 线程架构
|
||
|
||
客户端运行 **4 个线程**:
|
||
|
||
1. **主线程**
|
||
- 连接 Socket
|
||
- 发送初始化消息(命令信息、环境变量、日志路径、终端信息)
|
||
- 等待其他线程完成
|
||
- 清理资源和恢复终端
|
||
|
||
2. **响应监听线程** (`response_listener_thread`)
|
||
- 持续监听服务端消息
|
||
- 收到 `MSG_TYPE_SERVER_RESPONSE`:直接输出到终端
|
||
- 收到 `MSG_TYPE_CLOSE`:退出循环,触发程序结束
|
||
|
||
3. **窗口监控线程** (`window_monitor_thread`)
|
||
- 监听 `SIGWINCH` 信号(窗口大小变化)
|
||
- 使用 **条件变量** (`pthread_cond_t`) 实现事件驱动等待
|
||
- 信号触发时立即唤醒,无需轮询
|
||
- 自动发送 `MSG_TYPE_WINDOW_SIZE_UPDATE` 消息
|
||
|
||
4. **输入监听线程** (`terminal_input_thread`)
|
||
- 设置终端为 **Raw 模式**(捕获所有输入)
|
||
- 启用 **鼠标跟踪**(ANSI 转义序列)
|
||
- 使用 `poll()` 监听 stdin
|
||
- 解析鼠标事件或发送原始输入
|
||
- 发送 `MSG_TYPE_TERMINAL_INPUT` / `MSG_TYPE_MOUSE_EVENT`
|
||
|
||
#### 终端模式管理
|
||
|
||
```c
|
||
// 进入 Raw 模式前保存原始设置
|
||
static struct termios g_original_termios;
|
||
tcgetattr(STDIN_FILENO, &g_original_termios);
|
||
|
||
// 设置 Raw 模式
|
||
setup_terminal_raw_mode(STDIN_FILENO);
|
||
|
||
// 启用鼠标跟踪
|
||
enable_mouse_tracking(STDOUT_FILENO);
|
||
|
||
// 退出时恢复
|
||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_original_termios);
|
||
disable_mouse_tracking(STDOUT_FILENO);
|
||
write(STDOUT_FILENO, "\r\n", 2); // 确保提示符在新行
|
||
```
|
||
|
||
#### 优雅退出机制
|
||
|
||
1. **服务端发送关闭消息**:`MSG_TYPE_CLOSE`
|
||
2. **响应线程退出**:收到关闭消息后 break
|
||
3. **主线程检测**:`pthread_join(response_thread)` 返回
|
||
4. **设置退出标志**:`g_should_exit = 1`
|
||
5. **取消其他线程**:`pthread_cancel(window_thread)`, `pthread_cancel(input_thread)`
|
||
6. **清理终端状态**:`cleanup_terminal()`
|
||
- 禁用鼠标跟踪
|
||
- 恢复原始 termios 设置
|
||
- 输出换行符
|
||
7. **关闭连接**:`close(sock)`
|
||
|
||
### 7. Socket 协议 (`src/socket_protocol.c`)
|
||
|
||
提供终端管理和消息传输的底层函数。
|
||
|
||
#### 终端管理函数
|
||
|
||
```c
|
||
// 设置 Raw 模式并保存原始设置
|
||
int setup_terminal_raw_mode(int fd);
|
||
|
||
// 恢复保存的原始终端设置
|
||
int restore_terminal_mode(int fd);
|
||
|
||
// 启用鼠标跟踪(ANSI 转义序列)
|
||
int enable_mouse_tracking(int fd);
|
||
|
||
// 禁用鼠标跟踪
|
||
int disable_mouse_tracking(int fd);
|
||
```
|
||
|
||
#### 消息传输函数
|
||
|
||
```c
|
||
// 发送结构化消息
|
||
int write_message(int sock, MessageType type, const void* payload, uint32_t payload_len);
|
||
|
||
// 读取结构化消息
|
||
int read_message(int sock, MessageType* type, void** payload, uint32_t* payload_len);
|
||
|
||
// 释放消息载荷
|
||
void free_message_payload(void* payload);
|
||
```
|
||
|
||
### 8. Go 服务端 (`go_service/internal/services/tasks/socket.go`)
|
||
|
||
#### 新的回调式 API
|
||
|
||
服务端使用回调式 `Connection` 对象处理消息:
|
||
|
||
```go
|
||
// 创建连接对象
|
||
conn := socket.NewConnection(netConn)
|
||
defer conn.Close()
|
||
|
||
// 注册消息处理器
|
||
conn.SetHandlers(socket.MessageHandlers{
|
||
OnInit: func(payload []byte) error {
|
||
// 处理初始化消息
|
||
params := parseInitPayload(payload)
|
||
// 启动 AI 分析 goroutine
|
||
go processAIRequest(conn, params)
|
||
return nil
|
||
},
|
||
|
||
OnWindowSizeUpdate: func(termInfo *socket.TerminalInfo) error {
|
||
// 更新终端尺寸
|
||
params.TerminalInfo = *termInfo
|
||
return nil
|
||
},
|
||
|
||
OnTerminalInput: func(data []byte) error {
|
||
// 处理用户输入(如需要)
|
||
return nil
|
||
},
|
||
|
||
OnMouseEvent: func(event *socket.MouseEvent) error {
|
||
// 处理鼠标事件(如需要)
|
||
return nil
|
||
},
|
||
|
||
OnKeyEvent: func(data []byte) error {
|
||
// 处理按键事件(如需要)
|
||
return nil
|
||
},
|
||
|
||
OnClose: func() error {
|
||
// 连接关闭清理
|
||
return nil
|
||
},
|
||
|
||
OnError: func(err error) {
|
||
// 错误处理
|
||
},
|
||
})
|
||
|
||
// 启动消息循环(非阻塞)
|
||
conn.Start()
|
||
|
||
// 等待连接关闭
|
||
conn.Wait()
|
||
```
|
||
|
||
#### 服务端输出控制
|
||
|
||
服务端完全控制终端输出:
|
||
|
||
```go
|
||
// 发送普通响应(会立即显示在客户端终端)
|
||
conn.SendResponse([]byte("正在分析错误...\r\n"))
|
||
|
||
// 发送格式化输出(支持 ANSI 颜色)
|
||
msg := constants.ColorGreen + "✓ 分析完成\r\n" + constants.ColorReset
|
||
conn.SendResponse([]byte(msg))
|
||
|
||
// 发送 TUI 界面更新
|
||
conn.SendResponse([]byte("\033[2J\033[H")) // 清屏
|
||
conn.SendResponse([]byte("╔════════════════╗\r\n"))
|
||
conn.SendResponse([]byte("║ AI 分析结果 ║\r\n"))
|
||
conn.SendResponse([]byte("╚════════════════╝\r\n"))
|
||
|
||
// 完成后关闭连接(客户端会自动退出)
|
||
conn.Close()
|
||
```
|
||
|
||
#### 错误上报机制
|
||
|
||
当被拦截的命令执行失败(退出码非 0 或被信号终止)时,系统会触发错误上报流程。
|
||
|
||
1. **执行与捕获**: `pty_dup.c` 中的 `handle_io` 循环负责实时捕获子进程的 `stdout` 和 `stderr`。
|
||
2. **日志记录**: 捕获到的 `stderr` 内容会被实时写入到特定的日志文件中(由 `GET_LOG_FILE` 宏定义路径)。
|
||
3. **触发上报**:
|
||
- 父进程等待子进程退出。
|
||
- 检查子进程退出状态 (`child_status`)。
|
||
- 如果 `WIFEXITED` 且退出码非 0,或者 `WIFSIGNALED`(被信号终止),则调用 `seeking_solutions`。
|
||
4. **建立连接**: `seeking_solutions` 通过 Socket 连接到 Go 服务,发送以下信息:
|
||
- **Filename**: 执行的命令名称
|
||
- **CWD**: 当前工作目录
|
||
- **Argv**: 完整的参数列表
|
||
- **Envp**: 完整的环境变量列表
|
||
- **Log Path**: 记录了错误输出的日志文件绝对路径
|
||
- **Terminal Info**: 终端尺寸、类型、termios 设置
|
||
5. **实时交互**: 连接建立后,客户端进入实时交互模式:
|
||
- 服务端完全控制输出(加载动画、AI 响应、TUI 界面等)
|
||
- 客户端捕获所有用户输入(键盘、鼠标、窗口调整)
|
||
- 服务端决定何时关闭连接
|
||
6. **优雅退出**: 服务端发送 `MSG_TYPE_CLOSE` 后,客户端自动清理终端状态并退出
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Parent as Parent Process
|
||
participant Child as Child Process (Cmd)
|
||
participant Log as Log File (Stderr)
|
||
participant Client as Socket Client
|
||
participant Server as Go Service
|
||
participant AI as AI 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->>Client: seeking_solutions()
|
||
Client->>Client: 连接 Socket
|
||
Client->>Client: 保存原始 termios
|
||
Client->>Client: 设置 Raw 模式
|
||
Client->>Client: 启用鼠标跟踪
|
||
|
||
Client->>Server: MSG_TYPE_INIT (完整上下文)
|
||
Note over Client,Server: Filename, CWD, Argv, Envp,<br/>Log Path, Terminal Info
|
||
|
||
par 启动线程
|
||
Client->>Client: 启动响应监听线程
|
||
Client->>Client: 启动窗口监控线程
|
||
Client->>Client: 启动输入监听线程
|
||
end
|
||
|
||
Server->>Server: 解析初始化消息
|
||
Server->>Log: 读取错误日志
|
||
Server->>Client: 发送初始提示
|
||
Note over Client: "检测到命令执行出错!"
|
||
|
||
Server->>AI: 请求错误分析
|
||
|
||
loop 实时交互
|
||
par 客户端监听
|
||
Client->>Server: 终端输入事件
|
||
Client->>Server: 窗口大小更新
|
||
Client->>Server: 鼠标事件
|
||
and 服务端输出
|
||
Server->>Client: 加载动画
|
||
AI-->>Server: 流式返回建议
|
||
Server->>Client: 实时输出 AI 响应
|
||
Server->>Client: 渲染 TUI 界面
|
||
end
|
||
end
|
||
|
||
Server->>Server: 分析完成
|
||
Server->>Client: MSG_TYPE_CLOSE
|
||
|
||
Client->>Client: 响应线程退出
|
||
Client->>Client: 取消其他线程
|
||
Client->>Client: cleanup_terminal()
|
||
Note over Client: - 恢复 termios<br/>- 禁用鼠标跟踪<br/>- 输出换行符
|
||
Client->>Client: 关闭连接
|
||
Client-->>Parent: 返回
|
||
end
|
||
```
|
||
|
||
#### 通信协议格式
|
||
|
||
##### 消息头(16字节)
|
||
|
||
| 字段 | 类型 | 大小 | 说明 |
|
||
| :--- | :--- | :--- | :--- |
|
||
| `magic` | `uint32_t` | 4 | 魔数 `0x42534D54` ("BSMT") |
|
||
| `type` | `uint32_t` | 4 | 消息类型(见下表) |
|
||
| `payload_len` | `uint32_t` | 4 | 载荷长度 |
|
||
| `reserved` | `uint32_t` | 4 | 保留字段 |
|
||
|
||
##### 消息类型
|
||
|
||
| 类型值 | 名称 | 方向 | 说明 |
|
||
| :--- | :--- | :--- | :--- |
|
||
| 1 | `MSG_TYPE_INIT` | C → Go | 初始化消息(包含完整上下文) |
|
||
| 2 | `MSG_TYPE_WINDOW_SIZE_UPDATE` | C → Go | 终端窗口大小变化 |
|
||
| 3 | `MSG_TYPE_SERVER_RESPONSE` | Go → C | 服务端响应(直接输出) |
|
||
| 4 | `MSG_TYPE_CLOSE` | Go → C | 关闭连接通知 |
|
||
| 5 | `MSG_TYPE_TERMINAL_INPUT` | C → Go | 终端原始输入 |
|
||
| 6 | `MSG_TYPE_TERMINAL_OUTPUT` | Go → C | 终端输出(保留) |
|
||
| 7 | `MSG_TYPE_MOUSE_EVENT` | C → Go | 鼠标事件(结构化) |
|
||
| 8 | `MSG_TYPE_KEY_EVENT` | C → Go | 键盘事件(结构化) |
|
||
|
||
##### 初始化消息载荷(MSG_TYPE_INIT)
|
||
|
||
按顺序包含以下字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| :--- | :--- | :--- |
|
||
| `filename_len` | `uint64_t` | 文件名长度 |
|
||
| `filename` | `char[]` | 文件名内容 |
|
||
| `pwd_len` | `uint64_t` | 当前工作目录长度 |
|
||
| `pwd` | `char[]` | 当前工作目录内容 |
|
||
| `argc` | `int32_t` | 参数个数 |
|
||
| `arg_len` | `uint64_t` | (循环) 参数长度 |
|
||
| `arg_str` | `char[]` | (循环) 参数内容 |
|
||
| `envc` | `int32_t` | 环境变量个数 |
|
||
| `env_len` | `uint64_t` | (循环) 环境变量长度 |
|
||
| `env_str` | `char[]` | (循环) 环境变量内容 |
|
||
| `log_path_len` | `uint64_t` | 日志路径长度 |
|
||
| `log_path` | `char[]` | 日志路径内容 |
|
||
| `term_info_fixed` | `TerminalInfoFixed` | 固定终端信息结构 |
|
||
| `term_type_len` | `uint32_t` | TERM 类型长度 |
|
||
| `term_type` | `char[]` | TERM 类型内容 |
|
||
| `shell_type_len` | `uint32_t` | SHELL 类型长度 |
|
||
| `shell_type` | `char[]` | SHELL 类型内容 |
|
||
|
||
##### 终端信息结构(TerminalInfoFixed)
|
||
|
||
```c
|
||
typedef struct {
|
||
uint32_t is_tty; // 是否为TTY
|
||
uint16_t rows; // 行数
|
||
uint16_t cols; // 列数
|
||
uint16_t x_pixel; // X像素
|
||
uint16_t y_pixel; // Y像素
|
||
uint32_t has_termios; // 是否有termios属性
|
||
uint32_t input_flags; // termios输入标志
|
||
uint32_t output_flags; // termios输出标志
|
||
uint32_t control_flags; // termios控制标志
|
||
uint32_t local_flags; // termios本地标志
|
||
} TerminalInfoFixed;
|
||
```
|
||
|
||
##### 鼠标事件载荷(MSG_TYPE_MOUSE_EVENT)
|
||
|
||
```c
|
||
typedef struct {
|
||
uint32_t event_type; // 1=按下, 2=释放, 3=移动, 4=滚轮上, 5=滚轮下
|
||
uint32_t button; // 鼠标按钮(0=左键,1=中键,2=右键)
|
||
uint32_t x; // X坐标(列)
|
||
uint32_t y; // Y坐标(行)
|
||
uint32_t modifiers; // 修饰键(Shift, Ctrl, Alt等)
|
||
} MouseEvent;
|
||
```
|
||
|
||
## 编译构建
|
||
|
||
项目使用 `Makefile` 进行管理。
|
||
|
||
### C 客户端
|
||
|
||
```bash
|
||
cd execve_hook
|
||
|
||
# 默认构建(生成 intercept.so)
|
||
make
|
||
|
||
# 开启 Hook 功能
|
||
make HOOK=1
|
||
|
||
# 开启调试模式
|
||
make DEBUG=1
|
||
|
||
# 编译测试程序
|
||
make test_socket_client
|
||
|
||
# 清理
|
||
make clean
|
||
```
|
||
|
||
### Go 服务端
|
||
|
||
```bash
|
||
cd go_service
|
||
|
||
# 编译服务端
|
||
make
|
||
|
||
# 编译测试 Socket 服务器
|
||
make test_socket_server
|
||
|
||
# 运行服务
|
||
sudo ./build/bash_go_service-amd64 daemon
|
||
```
|
||
|
||
## 测试
|
||
|
||
### 测试 Socket 通信
|
||
|
||
1. **启动 Go 测试服务器**:
|
||
```bash
|
||
cd go_service
|
||
sudo go run ./cmd/tests/test_socket_server
|
||
```
|
||
|
||
2. **运行 C 测试客户端**:
|
||
```bash
|
||
cd execve_hook
|
||
./build/test_socket_client
|
||
```
|
||
|
||
3. **测试功能**:
|
||
- 调整终端窗口大小(观察服务器接收窗口更新)
|
||
- 移动鼠标和点击(观察鼠标事件)
|
||
- 输入键盘字符(观察终端输入事件)
|
||
- 30秒后服务器自动关闭连接
|
||
- 检查客户端是否正确恢复终端状态
|
||
|
||
### 验证终端恢复
|
||
|
||
测试完成后,终端应该:
|
||
- ✓ 提示符在新的一行开始
|
||
- ✓ 正常处理换行符(\n 不再变成 \r\n)
|
||
- ✓ 鼠标跟踪已禁用
|
||
- ✓ 可以正常输入命令
|
||
|
||
## 目录结构说明
|
||
|
||
```
|
||
execve_hook/
|
||
├── src/
|
||
│ ├── execve_interceptor.c # execve 拦截主逻辑
|
||
│ ├── hook_write.c # write 系列函数拦截
|
||
│ ├── config.c # 共享内存配置加载
|
||
│ ├── pty_dup.c # 伪终端处理
|
||
│ ├── client.c # Socket 客户端(多线程)
|
||
│ ├── client.h # 客户端头文件
|
||
│ ├── socket_protocol.c # Socket 协议实现
|
||
│ ├── socket_protocol.h # Socket 协议定义
|
||
│ └── rules.c # 规则匹配逻辑
|
||
├── tests/
|
||
│ └── test_socket_client.c # Socket 通信测试程序
|
||
├── build/ # 编译输出目录
|
||
├── Makefile # 构建脚本
|
||
├── README.md # 项目文档
|
||
├── SOCKET_PROTOCOL.md # Socket 协议文档
|
||
├── SOCKET_IMPROVEMENTS.md # 改进说明
|
||
└── TERMINAL_EVENTS.md # 终端事件捕获文档
|
||
|
||
go_service/
|
||
├── cmd/
|
||
│ └── tests/
|
||
│ └── test_socket_server/ # Go 测试服务器
|
||
│ └── main.go
|
||
├── internal/
|
||
│ ├── socket/
|
||
│ │ ├── protocol.go # Socket 协议实现(Go)
|
||
│ │ └── example.go # 使用示例
|
||
│ └── services/
|
||
│ └── tasks/
|
||
│ └── socket.go # 生产环境 Socket 服务
|
||
└── ...
|
||
```
|
||
|
||
## 技术亮点
|
||
|
||
### 1. 现代化通信协议
|
||
- 结构化二进制消息(高效、类型安全)
|
||
- 魔数验证(防止协议错误)
|
||
- 可扩展的消息类型系统
|
||
|
||
### 2. 完全的服务端控制
|
||
- 客户端只负责事件捕获和转发
|
||
- 服务端完全控制输出和交互逻辑
|
||
- 支持任意 TUI 框架和渲染方式
|
||
|
||
### 3. 优雅的终端管理
|
||
- 进入时保存原始 termios 设置
|
||
- 运行时使用 Raw 模式捕获所有输入
|
||
- 退出时完全恢复终端状态
|
||
|
||
### 4. 线程安全的并发设计
|
||
- C 端:4 个独立线程处理不同职责
|
||
- Go 端:goroutine + channel 实现并发
|
||
- 互斥锁保护共享资源
|
||
|
||
### 5. 实时事件捕获
|
||
- 键盘输入(Raw 模式)
|
||
- 鼠标事件(ANSI 转义序列解析)
|
||
- 窗口大小变化(SIGWINCH 信号)
|
||
|
||
## 应用场景
|
||
|
||
### 1. AI 辅助调试
|
||
- 捕获命令执行错误
|
||
- 发送给 AI 服务分析
|
||
- 实时流式返回建议
|
||
- 服务端控制输出格式
|
||
|
||
### 2. 交互式 TUI 应用
|
||
- 服务端使用 bubbletea 等框架
|
||
- 渲染复杂的终端界面
|
||
- 响应用户鼠标和键盘操作
|
||
- 客户端只负责事件转发
|
||
|
||
### 3. 会话录制与回放
|
||
- 记录所有终端交互
|
||
- 包括输入、输出、鼠标、窗口变化
|
||
- 用于审计、培训、调试
|
||
|
||
### 4. 远程终端控制
|
||
- 本地客户端捕获事件
|
||
- 发送到远程服务器
|
||
- 服务器处理并返回输出
|
||
- 实现远程控制终端
|
||
|
||
## 性能考虑
|
||
|
||
- **窗口监听**:使用条件变量实现事件驱动,零 CPU 占用等待
|
||
- **输入监听轮询间隔**:100ms(平衡响应性和 CPU)
|
||
- **响应监听轮询间隔**:500ms(用于检测退出标志)
|
||
- **消息大小**:终端事件 20-80 字节
|
||
- **线程开销**:固定 4 个线程,不会增长
|
||
- **内存使用**:动态分配,用完即释放
|
||
- **延迟**:< 10ms(输入到转发)
|
||
|
||
## 兼容性
|
||
|
||
### 终端支持
|
||
- ✓ xterm, gnome-terminal, konsole
|
||
- ✓ iTerm2, Windows Terminal
|
||
- ✓ alacritty, kitty
|
||
- ⚠ tmux, screen(需要配置)
|
||
|
||
### 系统要求
|
||
- **Linux 内核**: 2.6+
|
||
- **Glibc**: 2.17+(pthread 支持)
|
||
- **Go**: 1.16+
|
||
|
||
## 相关文档
|
||
|
||
- [SOCKET_PROTOCOL.md](SOCKET_PROTOCOL.md) - 详细的协议规范
|
||
- [SOCKET_IMPROVEMENTS.md](SOCKET_IMPROVEMENTS.md) - 改进历史和设计决策
|
||
- [TERMINAL_EVENTS.md](TERMINAL_EVENTS.md) - 终端事件捕获详解
|