用 eBPF 观测 SSL/TLS 明文:原理、钩点与边界

用 eBPF 观测 SSL/TLS 明文:原理、钩点与边界

HTTPS 在网络上看到的是密文;排障、审计、零信任探针有时却需要在 进程内加解密边界 看到明文。eBPF 通过 uprobe 挂到 OpenSSL 等库的 SSL_read / SSL_write,可以在不改应用代码的前提下截取缓冲区内容。本文说明 TLS 握手在观测中的位置、OpenSSL API 要点、典型 sslsniff 思路,以及 合规与局限------独立成篇,可直接当实践备忘。

重要前提:这不是「破解 TLS」或「用私钥解密抓包」,而是读取 库函数已经处理过的用户态缓冲区


目录

  1. 问题:密文链路上的盲区
  2. [TLS 握手在观测里意味着什么](#TLS 握手在观测里意味着什么)
  3. [为何钩 SSL_read / SSL_write](#为何钩 SSL_read / SSL_write)
  4. [OpenSSL 读写 API 要点](#OpenSSL 读写 API 要点)
  5. [eBPF 实现思路(sslsniff 类)](#eBPF 实现思路(sslsniff 类))
  6. 工程坑位
  7. 合规、隐私与安全边界
  8. 替代与互补方案
  9. 免责声明

问题:密文链路上的盲区

观测位置 能看到 看不到
网卡 / tcpdump IP/TCP、TLS record 密文 HTTP 正文、密钥材料(正常情况)
内核 socket 仍多是密文(TLS 在用户态库) 应用层明文
OpenSSL 解密之后 明文缓冲区 ---

结论:对 用户态 TLS (OpenSSL、BoringSSL、部分 Go crypto 等),明文出现在 库函数读写缓冲区 的瞬间。eBPF uprobe 正是打在这一层。


TLS 握手在观测里意味着什么

握手成功前没有稳定的应用数据通道;钩 SSL_read/SSL_write 主要关注 握手完成后的应用数据。握手流程(简化):

  1. ClientHello:客户端给出支持的密码套件等
  2. ServerHello:服务端选定套件
  3. 证书:服务端出示证书(含公钥等)
  4. 客户端验证书
  5. 派生会话密钥
    • 经典:客户端用服务端公钥保护 PreMasterSecret,双方派生会话密钥
    • ECDHE 等:提供前向保密;即使日后私钥泄露,也难解历史会话

之后双方用会话密钥加解密。握手任一步失败则连接不会进入应用数据阶段。

对观测的含义:

  • 只钩读写 API,不会自动得到私钥或破解 TLS
  • 看到的是 本进程用户态已经解密/尚未加密 的字节
  • 换库、换静态链接、换内核 TLS(kTLS)路径,钩点可能失效

Server Client Server Client #mermaid-svg-fZfu4I12gUBxcCIp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fZfu4I12gUBxcCIp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fZfu4I12gUBxcCIp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fZfu4I12gUBxcCIp .error-icon{fill:#552222;}#mermaid-svg-fZfu4I12gUBxcCIp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fZfu4I12gUBxcCIp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fZfu4I12gUBxcCIp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fZfu4I12gUBxcCIp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fZfu4I12gUBxcCIp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fZfu4I12gUBxcCIp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fZfu4I12gUBxcCIp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fZfu4I12gUBxcCIp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fZfu4I12gUBxcCIp .marker.cross{stroke:#333333;}#mermaid-svg-fZfu4I12gUBxcCIp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fZfu4I12gUBxcCIp p{margin:0;}#mermaid-svg-fZfu4I12gUBxcCIp .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fZfu4I12gUBxcCIp text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-fZfu4I12gUBxcCIp .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fZfu4I12gUBxcCIp .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-fZfu4I12gUBxcCIp .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-fZfu4I12gUBxcCIp .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-fZfu4I12gUBxcCIp #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-fZfu4I12gUBxcCIp .sequenceNumber{fill:white;}#mermaid-svg-fZfu4I12gUBxcCIp #sequencenumber{fill:#333;}#mermaid-svg-fZfu4I12gUBxcCIp #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-fZfu4I12gUBxcCIp .messageText{fill:#333;stroke:none;}#mermaid-svg-fZfu4I12gUBxcCIp .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fZfu4I12gUBxcCIp .labelText,#mermaid-svg-fZfu4I12gUBxcCIp .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-fZfu4I12gUBxcCIp .loopText,#mermaid-svg-fZfu4I12gUBxcCIp .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-fZfu4I12gUBxcCIp .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fZfu4I12gUBxcCIp .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-fZfu4I12gUBxcCIp .noteText,#mermaid-svg-fZfu4I12gUBxcCIp .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-fZfu4I12gUBxcCIp .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fZfu4I12gUBxcCIp .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fZfu4I12gUBxcCIp .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fZfu4I12gUBxcCIp .actorPopupMenu{position:absolute;}#mermaid-svg-fZfu4I12gUBxcCIp .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-fZfu4I12gUBxcCIp .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fZfu4I12gUBxcCIp .actor-man circle,#mermaid-svg-fZfu4I12gUBxcCIp line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-fZfu4I12gUBxcCIp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 握手(证书与密钥协商) 应用数据(密文在网上) SSL_write 入参仍是明文 SSL_read 出参已是明文 ClientHello ServerHello + Certificate TLS 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 思路:

  1. 定位进程加载的 libssl.so(或等价)路径与符号
  2. SSL_read / SSL_write(及 _ex 变体,视目标而定)挂 uprobe/uretprobe
  3. 在探针中用辅助函数安全读取用户缓冲区(见下)
  4. (进阶)SSL * 上下文读取底层 BIO(如 rbio/wbio)关联的 Socket FD ,再在用户态用 getsockname / getpeername(或内核侧 sock 信息)补齐 五元组,便于全链路关联
  5. 携带 PID、TID、时间戳、方向(读/写)、截断后的 payload(及可选 FD)送到用户态
  6. 用户态做协议识别(HTTP/1、HTTP/2 前言等)、短读重组与展示

#mermaid-svg-H4UU89sd01sukzHj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-H4UU89sd01sukzHj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-H4UU89sd01sukzHj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-H4UU89sd01sukzHj .error-icon{fill:#552222;}#mermaid-svg-H4UU89sd01sukzHj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-H4UU89sd01sukzHj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-H4UU89sd01sukzHj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-H4UU89sd01sukzHj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-H4UU89sd01sukzHj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-H4UU89sd01sukzHj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-H4UU89sd01sukzHj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-H4UU89sd01sukzHj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-H4UU89sd01sukzHj .marker.cross{stroke:#333333;}#mermaid-svg-H4UU89sd01sukzHj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-H4UU89sd01sukzHj p{margin:0;}#mermaid-svg-H4UU89sd01sukzHj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-H4UU89sd01sukzHj .cluster-label text{fill:#333;}#mermaid-svg-H4UU89sd01sukzHj .cluster-label span{color:#333;}#mermaid-svg-H4UU89sd01sukzHj .cluster-label span p{background-color:transparent;}#mermaid-svg-H4UU89sd01sukzHj .label text,#mermaid-svg-H4UU89sd01sukzHj span{fill:#333;color:#333;}#mermaid-svg-H4UU89sd01sukzHj .node rect,#mermaid-svg-H4UU89sd01sukzHj .node circle,#mermaid-svg-H4UU89sd01sukzHj .node ellipse,#mermaid-svg-H4UU89sd01sukzHj .node polygon,#mermaid-svg-H4UU89sd01sukzHj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-H4UU89sd01sukzHj .rough-node .label text,#mermaid-svg-H4UU89sd01sukzHj .node .label text,#mermaid-svg-H4UU89sd01sukzHj .image-shape .label,#mermaid-svg-H4UU89sd01sukzHj .icon-shape .label{text-anchor:middle;}#mermaid-svg-H4UU89sd01sukzHj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-H4UU89sd01sukzHj .rough-node .label,#mermaid-svg-H4UU89sd01sukzHj .node .label,#mermaid-svg-H4UU89sd01sukzHj .image-shape .label,#mermaid-svg-H4UU89sd01sukzHj .icon-shape .label{text-align:center;}#mermaid-svg-H4UU89sd01sukzHj .node.clickable{cursor:pointer;}#mermaid-svg-H4UU89sd01sukzHj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-H4UU89sd01sukzHj .arrowheadPath{fill:#333333;}#mermaid-svg-H4UU89sd01sukzHj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-H4UU89sd01sukzHj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-H4UU89sd01sukzHj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H4UU89sd01sukzHj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-H4UU89sd01sukzHj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H4UU89sd01sukzHj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-H4UU89sd01sukzHj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-H4UU89sd01sukzHj .cluster text{fill:#333;}#mermaid-svg-H4UU89sd01sukzHj .cluster span{color:#333;}#mermaid-svg-H4UU89sd01sukzHj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-H4UU89sd01sukzHj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-H4UU89sd01sukzHj rect.text{fill:none;stroke-width:0;}#mermaid-svg-H4UU89sd01sukzHj .icon-shape,#mermaid-svg-H4UU89sd01sukzHj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H4UU89sd01sukzHj .icon-shape p,#mermaid-svg-H4UU89sd01sukzHj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-H4UU89sd01sukzHj .icon-shape .label rect,#mermaid-svg-H4UU89sd01sukzHj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H4UU89sd01sukzHj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-H4UU89sd01sukzHj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-H4UU89sd01sukzHj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 钩 read/write
应用
OpenSSL
TCP socket
eBPF uprobe
ringbuf/Map
用户态 Agent

读取用户态缓冲区 :现代内核中必须使用 bpf_probe_read_user / bpf_probe_read_user_str 等辅助函数。在探针里 直接解引用 (void *)arg_buf 会被 Verifier 拒绝。较旧内核上常见的是更笼统的 bpf_probe_read;跨内核版本时要按目标能力选择 helper,并做好 CO-RE/兼容分支。

伪逻辑(概念级,非可编译代码):

text 复制代码
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 观测相关章节,并补充工程、威胁模型与合规说明。

相关推荐
数据知道5 小时前
VLAN 与安全隔离:从配置错误到 VLAN Hopping 攻击
网络·安全·网络安全·智能路由器
Tsuki_tl5 小时前
UDP传输协议
网络·网络协议·udp·传输层·udp校验和·tcp与udp区别·面向数据报
Sagittarius_A*5 小时前
Open HTTP Redirect 开放重定向漏洞:URL 跳转校验缺陷与绕过技术
网络·安全·web安全·http·dvwa·burp suite
信仰8745 小时前
HCIA-华为数通基础理论与实践08
网络·笔记·华为
代码的小搬运工8 小时前
网络请求(NSURL、NSURLRequest、NSURLSessionDataTask、协议回调与 JSON 数据的基本流向)
网络·数据库·json
蓝胖的四次元口袋14 小时前
服务器网络与系统基础-面试题
服务器·网络
程序员JerrySUN15 小时前
Jetson 刷机深度解析:flash.sh vs l4t_initrd_flash.sh(含安全与磁盘加密对比)
linux·网络·arm开发·安全·系统安全
视觉AI15 小时前
VS Code Remote-SSH 连接Jetson踩坑完整解决记录(网段不通+主机密钥变更双重故障)
运维·网络·人工智能·windows·ssh·边缘计算
明志数科15 小时前
人形机器人格斗场景下的技术挑战:动力学控制、感知融合与实时决策
网络·数据库