用 eBPF 观测 SSL/TLS 明文:原理、钩点与边界
用 eBPF 观测 SSL/TLS 明文原理、钩点与边界HTTPS 在网络上看到的是密文排障、审计、零信任探针有时却需要在进程内加解密边界看到明文。eBPF 通过uprobe挂到 OpenSSL 等库的SSL_read/SSL_write可以在不改应用代码的前提下截取缓冲区内容。本文说明 TLS 握手在观测中的位置、OpenSSL API 要点、典型sslsniff思路以及合规与局限——独立成篇可直接当实践备忘。重要前提这不是「破解 TLS」或「用私钥解密抓包」而是读取库函数已经处理过的用户态缓冲区。目录问题密文链路上的盲区TLS 握手在观测里意味着什么为何钩 SSL_read / SSL_writeOpenSSL 读写 API 要点eBPF 实现思路sslsniff 类工程坑位合规、隐私与安全边界替代与互补方案免责声明问题密文链路上的盲区观测位置能看到看不到网卡 / tcpdumpIP/TCP、TLS record 密文HTTP 正文、密钥材料正常情况内核 socket仍多是密文TLS 在用户态库应用层明文OpenSSL 解密之后明文缓冲区—结论对用户态 TLSOpenSSL、BoringSSL、部分 Go crypto 等明文出现在库函数读写缓冲区的瞬间。eBPF uprobe 正是打在这一层。TLS 握手在观测里意味着什么握手成功前没有稳定的应用数据通道钩SSL_read/SSL_write主要关注握手完成后的应用数据。握手流程简化ClientHello客户端给出支持的密码套件等ServerHello服务端选定套件证书服务端出示证书含公钥等客户端验证书派生会话密钥经典客户端用服务端公钥保护 PreMasterSecret双方派生会话密钥或ECDHE 等提供前向保密即使日后私钥泄露也难解历史会话之后双方用会话密钥加解密。握手任一步失败则连接不会进入应用数据阶段。对观测的含义只钩读写 API不会自动得到私钥或破解 TLS看到的是本进程用户态已经解密/尚未加密的字节换库、换静态链接、换内核 TLSkTLS路径钩点可能失效ServerClientServerClient握手证书与密钥协商应用数据密文在网上SSL_write 入参仍是明文SSL_read 出参已是明文ClientHelloServerHello CertificateTLS records为何钩 SSL_read / SSL_write在 OpenSSL 中SSL_write应用把明文交给库由库加密后经 socket 发出SSL_read库从 socket 读密文解密后把明文拷到应用缓冲区因此函数钩入口时缓冲区含义SSL_write即将加密的明文SSL_read返回前/返回后可拿到已解密明文具体挂 entry 还是 return 要按实现选这比在send/recv上钩更贴近「应用层内容」也避开了仍是密文的 socket I/O。返回探针与非阻塞重试SSL_read更常挂在uretprobe返回路径——只有成功返回后输出缓冲区内容才可靠。非阻塞 Socket 下读操作可能因 I/O 未就绪返回错误如经SSL_get_error得到SSL_ERROR_WANT_READ此时缓冲区无效或未填充探针不得当成功明文上报。即便单次返回成功一次调用也往往只是应用层消息的一个片段短读再叠加SSL_pending等缓冲语义用户态 Agent 做短读短写重组是生产级实现的必要工作而不是可选优化。OpenSSL 读写 API 要点SSL_read / SSL_read_ex用于从已建立的 SSL 连接读数据。参数含义ssl已建立的SSL *连接buf输出缓冲区num最大读取字节数readbytes_ex实际读取长度输出SSL_read用返回值表示读到的字节数SSL_read_ex用额外参数返回更明确的长度信息。嗅探时需同时覆盖_ex变体视目标二进制导出符号而定。SSL_write / SSL_write_ex参数含义ssl目标 SSL 连接buf待写明文num长度written_ex实际写入长度实现嗅探时通常在 uprobe 里读取buf与长度写入 eBPF Map 或 ringbuf再由用户态打印/归档。eBPF 实现思路sslsniff 类典型sslsniff思路定位进程加载的libssl.so或等价路径与符号对SSL_read/SSL_write及_ex变体视目标而定挂uprobe/uretprobe在探针中用辅助函数安全读取用户缓冲区见下进阶经SSL *上下文读取底层 BIO如rbio/wbio关联的Socket FD再在用户态用getsockname/getpeername或内核侧 sock 信息补齐五元组便于全链路关联携带 PID、TID、时间戳、方向读/写、截断后的 payload及可选 FD送到用户态用户态做协议识别HTTP/1、HTTP/2 前言等、短读重组与展示钩 read/write应用OpenSSLTCP socketeBPF uproberingbuf/Map用户态 Agent读取用户态缓冲区现代内核中必须使用bpf_probe_read_user/bpf_probe_read_user_str等辅助函数。在探针里直接解引用(void *)arg_buf会被 Verifier 拒绝。较旧内核上常见的是更笼统的bpf_probe_read跨内核版本时要按目标能力选择 helper并做好 CO-RE/兼容分支。伪逻辑概念级非可编译代码on SSL_write(ctx): len min(arg_num, MAX) bpf_probe_read_user(buf, len, arg_buf) submit(pid, write, buf, len) on SSL_read return(ctx): if ret 0: # 忽略 WANT_READ 等失败/重试 bpf_probe_read_user(buf, min(ret, MAX), arg_buf) submit(pid, read, buf, len)返回探针常更适合SSL_read只有成功返回后缓冲区内容才可靠。工程坑位坑说明符号找不到静态链接、符号剥离、用的是 BoringSSL/自研栈OpenSSL 大版本符号/布局差异多版本共存容器内库路径与宿主机不同要以目标进程 maps 为准短读短写 / 重试一次调用不是完整消息WANT_READ/WANT_WRITE时勿采空缓冲需用户态重组HTTP/2 / gRPC明文是二进制帧不能当纯文本日志性能大包全量拷贝开销大应截断、采样、按 PID 过滤异步 / BIO复杂 BIO 或内存 BIO 路径可能绕开你钩的符号协程调度Go goroutine / C 协程等可能导致SSL*跨线程、栈迁移函数钩得到但 TID/连接关联变复杂kTLS部分加解密下沉内核时用户态钩可能变空合规、隐私与安全边界技术上「能钩到」≠「可以随便钩」。原则说明授权仅在自有系统、明确授权的排障/安全审计范围内使用最小化过滤 PID/容器、截断 payload、避免落盘全流量隔离探针权限极高Agent 本身需防篡改与防滥用告知涉及个人数据时遵守隐私与等保/行业要求威胁模型uprobe ≠ 中间人MitM能力eBPF uprobe中间人代理MitM需 Root / 高权限跟踪通常是否常只需控制代理与证书信任需私钥 / 导入根证书否是影响范围单主机上的目标进程/容器经代理的客户端或网段流量对终端「证书告警」无不改证书链常见未信任根时本质在已授权主机上看库内缓冲区在路径上终止并重加密TLS二者都可能触及隐私但权限形态与部署面完全不同不可互相替代也不可混为一谈。替代与互补方案方案特点应用内日志 / OpenTelemetry最干净需改代码或插桩 SDK反向代理终止 TLS明文在代理上架构上可控内核 TLS / 服务网格策略观测点随架构变化仅元数据SNI、连接五元组隐私更友好信息更少收束eBPF 观测 TLS 明文本质是在用户态密码学库的边界做动态跟踪价值在排障与受控审计难度在符号、FD 关联与协议重组风险在权限与合规。先定义谁允许看明文再写探针。拿到的明文若要落到「哪条连接、哪段协议栈」往往还要回到网络路径与sk_buff一侧的可观测设计。免责声明本文所述技术仅限在合法授权环境下用于故障排查、性能分析与安全审计。严禁用于未经授权窥探通信内容或侵犯他人隐私。读者须自行确保符合所在司法辖区与组织的法律法规及合规要求。整理自《深入理解 eBPF 与可观测性》SSL/TLS 观测相关章节并补充工程、威胁模型与合规说明。