Go 主机安全面试:eBPF 采集进程执行与网络连接
主机安全 Agent 经常要回答一个问题:哪个进程执行了什么命令,又连到了哪里。eBPF 很适合做这类低开销采集,但面试官通常不会只问“会不会写 eBPF”,而是会追问字段、性能、丢事件、误报和 Go 侧工程化。
岗位场景
text
主机行为
-> execve 进程执行事件
-> tcp/connect 网络外连事件
-> Go Agent 读取 ring buffer
-> 字段标准化与上下文补全
-> 本地规则 / 服务端检测
-> 告警、降噪、攻击链还原目标不是采集越多越好,而是稳定拿到能解释攻击行为的证据:进程、父进程、命令行、用户、容器上下文、目标 IP/端口和时间关系。
高频面试题
1. 为什么 HIDS/EDR 会用 eBPF 采集 exec 和 connect?
简答:eBPF 能在内核关键路径上拿到事件,比轮询
/proc更及时,也比侵入式内核模块更容易做安全约束。
关键知识点:
execve能描述“进程启动了什么命令”。connect能描述“进程主动连向哪里”。- eBPF 程序受 verifier 约束,不能随意破坏内核。
- 采集点常见选择包括 tracepoint、kprobe、LSM hook,不同内核版本可用能力不同。
Go 落地思路:
- Go Agent 负责加载 eBPF 程序、读取事件、做字段标准化。
- 内核态只做必要过滤和字段拷贝,复杂规则留给 Go 或服务端。
- 先覆盖 exec/connect 两类高价值事件,不要一开始就采满所有系统调用。
2. exec 事件至少要采哪些字段?
简答:能把一个命令放回进程树和用户上下文里的字段都要采。
建议字段:
| 字段 | 作用 |
|---|---|
pid / ppid | 还原父子进程 |
uid / gid | 判断执行身份 |
comm / argv | 判断命令和参数 |
cwd | 判断执行目录 |
container_id | 区分宿主机与容器 |
timestamp | 关联前后事件 |
Go 落地思路:
- eBPF 里采基础字段,Go 侧再补
/proc/<pid>/cmdline、进程哈希等慢字段。 - 字段标准化时保留原始值,避免规则排查时只剩加工后的结果。
- 对
argv做长度上限,避免异常命令行拖垮 Agent。
3. connect 事件怎么和进程执行事件关联?
简答:用
pid、进程启动时间和事件时间窗口关联,不能只靠进程名。
通俗理解:
text
exec: nginx worker -> /bin/sh -c curl 1.2.3.4
within 3s
connect: same pid/start_time -> 1.2.3.4:4444关键知识点:
- PID 会复用,长时间只按 PID 关联会误判。
- 进程启动时间可以降低 PID 复用带来的串案。
- DNS、代理、NAT 会让“命令里的域名”和“连接里的 IP”不完全一致。
Go 落地思路:
go
type ProcKey struct {
PID uint32
StartNS uint64
}- 本地维护一个小 TTL 缓存,保存最近进程上下文。
- 网络事件到来时按
ProcKey取上下文。 - 缓存只保留近期事件,过期就删,避免常驻内存增长。
4. eBPF 采集会不会影响主机性能?怎么控制?
简答:会,所以内核态少做事、Go 侧限流、批量上报,并监控丢事件。
性能风险:
- 每次系统调用都采集会放大 CPU 消耗。
- ring buffer 堵塞会丢事件。
- 过多字符串拷贝会增加内核态成本。
- Go 侧 JSON 编码、网络上报也可能成为瓶颈。
Go 落地思路:
- 内核态只过滤明显无用事件,例如忽略 Agent 自身 PID。
- Go 侧使用固定大小 channel,满了要计数,不要无限堆内存。
- 上报按批次发送,失败时退避重试。
- 暴露
events_total、events_lost、queue_len这类指标。
5. 如何降低 exec/connect 规则的误报?
简答:不要单点命中,至少用进程上下文、网络目标和行为序列组合判断。
例子:
| 单点信号 | 更可靠的组合 |
|---|---|
进程名是 sh | Web 进程子进程 + shell + 外连 |
| 连接高危端口 | 可疑命令 + 新进程 + 外连 |
命令包含 curl | 非交互服务账号 + 下载可执行文件 |
Go 落地思路:
- 规则结果要带命中原因,方便客户和安全运营复核。
- 对常见运维脚本做白名单,但白名单要带范围:路径、用户、父进程、主机组。
- 降噪优先做去重和聚合,不要直接丢弃原始证据。
6. eBPF 程序加载失败时怎么办?
简答:要有可解释降级,不能让 Agent 直接失明。
常见失败原因:
- 内核版本太低。
- BTF 不可用或字段布局不兼容。
- 权限不足,缺少
CAP_BPF、CAP_PERFMON或等价能力。 - verifier 拒绝程序加载。
Go 落地思路:
- 启动时输出明确错误:内核版本、加载点、verifier 日志摘要。
- 能降级时用 auditd、netlink、
/proc轮询补一部分能力。 - 降级状态要上报服务端,让规则知道当前数据质量。
7. ring buffer 丢事件后检测还能可信吗?
简答:可信度要下降,告警和排障都要看到丢事件指标。
关键知识点:
- 丢 exec 事件会导致网络事件缺少进程上下文。
- 丢 connect 事件会导致攻击链断裂。
- 丢事件不是偶发日志问题,而是检测证据缺口。
Go 落地思路:
- 单独记录丢事件计数和时间段。
- 告警里标记
data_quality: degraded。 - 当丢事件持续升高时,优先限流低价值事件,而不是让关键事件一起丢。
8. Go Agent 如何避免自己被采集规则误伤?
简答:Agent 自身行为要可识别、可过滤、可审计。
关键知识点:
- Agent 会读
/proc、打开 eBPF map、连接服务端,这些行为容易像扫描或外连。 - 不能粗暴忽略所有同名进程,攻击者可能伪装进程名。
- 过滤条件应包含进程路径、签名、启动参数、父进程和安装目录。
Go 落地思路:
- 启动后记录自己的 PID、可执行路径和 hash。
- 内核态可按 PID 跳过高频自采集,Go 侧仍保留关键审计日志。
- 检测规则里给 Agent 行为单独建画像,不和业务进程混在一起。
学习要点
- eBPF 适合做高价值、低开销的主机事件采集,但不是万能采集器。
- exec 和 connect 的核心价值在于能还原“命令执行 -> 网络外连”的行为链。
- PID 关联要考虑复用,最好带进程启动时间。
- 性能控制要从内核态、Go 队列、上报链路一起看。
- 误报治理靠上下文和行为序列,不靠单个关键字。
小练习
- 设计一个
ExecEvent和ConnectEvent的 Go 结构体,字段要能支持攻击链还原。 - 写一个 TTL 缓存,把最近 5 分钟的 exec 事件和 connect 事件按进程关联起来。
- 想一个误报场景:正常运维脚本执行
curl外连,如何用父进程、用户、路径和主机组降低误报? - 如果线上发现
events_lost持续升高,你会先看 CPU、队列、上报延迟还是规则量?为什么?
