ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ecapture utils 目录实战:为 BoringSSL/GnuTLS/OpenSSL 自动生成 eBPF 结构体偏移头文件

2026/9/14 9:35:35 拓冰建站 浏览量
ecapture utils 目录实战:为 BoringSSL/GnuTLS/OpenSSL 自动生成 eBPF 结构体偏移头文件 ecapture utils 目录实战为 BoringSSL/GnuTLS/OpenSSL 自动生成 eBPF 结构体偏移头文件【免费下载链接】ecaptureCapturing SSL/TLS plaintext without a CA certificate using eBPF. Supported on Linux/Android kernels for amd64/arm64.项目地址: https://gitcode.com/GitHub_Trending/ec/ecaptureecapture 的核心能力之一是无需 CA 证书即可捕获 SSL/TLS 明文其底层依赖 eBPF 探针从目标进程内存中按字节偏移读取 TLS 密钥。这些偏移值因库版本、CPU 架构而异且会随上游库的结构体演进而漂移。utils/目录正是为了解决这个问题而构建的偏移生成工具集本文完整讲解它的目录结构、驱动脚本流程、基于 C17 类型特征的自动版本探测设计以及boringssl_const.h/boringssl_masterkey.h两个自适应头文件如何把版本差异收敛为纯 C 预处理器条件使新版本适配基本无需手工改动。读完本文你将能够独立运行这些脚本重新生成kern/下的偏移头文件并理解如何为零成本适配下一个 Android 版本做准备。工具集的作用与目录结构utils/包含一组脚本与 C 源码用于生成 eBPF 兼容的 C 头文件kern/boringssl_a_XX_kern.cAndroid BoringSSL 13–16、kern/boringssl_na_kern.c非 Android 上游 BoringSSL、kern/gnutls_*.c、kern/openssl_*.c等。每个头文件都以#define常量定义 TLS 关键字段在目标库内部结构体中的字节偏移eBPF 探针在运行时用这些常量配合bpf_probe_read_user从进程用户态内存中读出密钥。utils/ ├── boringssl-offset.c # C17 偏移探测工具Android BoringSSL 专用 ├── boringssl_android_offset.sh # 驱动脚本Android 13–16 BoringSSL ├── boringssl_non_android_offset.sh # 驱动脚本非 Android上游BoringSSL ├── gnutls_offset.c / gnutls_offset.sh ├── openssl_*_offset.c / openssl_offset_*.sh └── README.md # 本工具集说明文档设计上的关键点在于这些头文件不是手写的而是编译期探测 运行时输出自动生成的产物。工具针对目标库自身的头文件编译offsetof(struct_name, field)程序把结果打印成#define再由 shell 脚本包装成带 include guard 的最终头文件。这样做的直接收益是偏移值与某一特定库版本 特定 CPU 架构严格对应不依赖人工对照头文件计算。快速开始所有脚本必须在项目根目录即包含go.mod的目录下执行。脚本自身会检查这一点例如 boringssl_android_offset.sh 开头即有if [[ ! -f go.mod ]]; then echo Run the script from the project root directory exit 1 fi常用命令如下# Android BoringSSLandroid13 – android16 bash utils/boringssl_android_offset.sh # 非 Android上游BoringSSL bash utils/boringssl_non_android_offset.sh # GnuTLS bash utils/gnutls_offset.sh # OpenSSL选择对应版本的脚本 bash utils/openssl_offset_3.5.sh几个行为细节均可在脚本源码中确认输出位置生成文件统一写入kern/目录。幂等跳过若目标头文件已存在脚本会打印Skip ${header_file}并跳过该版本见 boringssl_android_offset.sh。想强制重新生成时删除对应kern/*.c文件再运行即可。依赖源码克隆脚本会把目标库克隆到deps/下BoringSSL Android 版为deps/boringssl上游版为deps/boringssl_non_androidGnuTLS 为deps/gnutls克隆与 checkout 都在该子目录内完成不会污染主仓库工作树。平台要求脚本需要编译原生 C/C 代码并直接运行必须在 Linux / Android 构建主机上执行x86_64 内核 ≥ 4.18aarch64 ≥ 5.5不支持 macOS。如果你的开发机是 macOS可通过 SSH 在远程 Linux 构建机上执行ssh userlinux-build-host cd repo-root # 仓库根目录含 go.mod bash utils/boringssl_android_offset.sh驱动脚本深读boringssl_android_offset.shAndroid 版驱动脚本的完整流程如下boringssl_android_offset.sh │ ├─ git clone / checkout android13-release … android16-release │ ├─ compile boringssl-offset.c (C17, against boringssl headers) │ ├─ run ./offset → emits #define 常量 feature-flag 宏 │ └─ wrap output → kern/boringssl_a_XX_kern.c逐步骤对照源码boringssl_android_offset.sh拉取源码仓库地址为 Android 平台的external/boringsslAOSP 仓库。git fetch --tags失败时仅告警并继续使用本地已缓存的分支#L23因此离线环境也可以工作。版本注册表sslVerMap#L25-L29declare -A sslVerMap() sslVerMap[1]13 # android13-release sslVerMap[2]14 # android14-release sslVerMap[3]15 # android15-release sslVerMap[4]16 # android16-release循环内以android${val}-release作为 git tag 名 checkout输出文件为kern/boringssl_a_${val}_kern.c。编译探测工具#L51-L52g -stdc17 -Wno-write-strings -Wno-invalid-offsetof \ -I include/ -I . -I ./src/ offset.c -o offset-I include/ -I . -I ./src/正是针对目标库自身头文件编译的体现——offsetof的结果由这一特定版本、这一架构下的真实结构体布局决定。包装输出#L54-L64在工具输出前后加上 include guardECAPTURE_BORINGSSL_A_XX_KERN_H并追加三个#include#include boringssl_const.h #include boringssl_masterkey.h #include openssl.h即每版本一个偏移头文件 共享的自适应逻辑头文件的组合模式。添加新 Android 版本在sslVerMap中加一行即可sslVerMap[5]17 # android17-release然后重跑脚本。大多数情况下无需其他改动——原因在下文的设计思路一节。非 Android BoringSSL 与 GnuTLS / OpenSSL 驱动脚本boringssl_non_android_offset.sh 拉取上游google/boringssl仓库到deps/boringssl_non_android默认sslVerMap[0]master#L26-L27生成单一文件kern/boringssl_na_kern.c同样包装 includeboringssl_const.h、boringssl_masterkey.h、openssl.h。gnutls_offset.sh 克隆 GnuTLS 仓库按 tag如 3.8.0–3.8.9checkout执行bootstrapconfigure后用clang编译gnutls_offset.c生成kern/gnutls_X_Y_Z_kern.c并 include gnutls.h 与 gnutls_masterkey.h。openssl_offset_*.sh系列openssl_offset_1.0.2.sh、openssl_offset_1.1.1.sh、openssl_offset_3.5.sh 等按 OpenSSL 大版本各一个脚本模式完全一致checkout 对应 tag → 编译utils/openssl_*_offset.c→ 包装输出kern/openssl_X_Y_Z_kern.c。可以看出utils/里的四组脚本共享同一个工程范式差异只在目标仓库地址、tag 列表和编译参数上。boringssl-offset.c基于编译期探测的偏移工具输出形态工具输出的就是一组#define例如// ssl_st-session #define SSL_ST_SESSION 0x58 // ssl_session_st-ssl_version #define SSL_SESSION_ST_SSL_VERSION 0x4这些定义被原样粘贴进生成的 kern 头文件在 eBPF 编译阶段被 kern/boringssl_masterkey.h 消费。以仓库中已生成的两个真实产物为例kern/boringssl_a_15_kern.c#define SSL_ST_VERSION 0x10、#define SSL_SESSION_ST_SECRET_LENGTH 0xakern/boringssl_a_16_kern.c无SSL_ST_VERSION取而代之的是#define SSL_SESSION_ST_SSL_VERSION 0x4与#define SSL_SESSION_ST_SECRET_LENGTH 0xFF#L25-L27以及#define BSSL__SSL3_STATE_VERSION 0xd0#L72-L73两个版本的差异不是脚本知道 Android 16 改了结构体而是编译期探测的结果——这正是本工具的核心设计。设计思路C17 类型特征自动版本探测早期版本的工具使用固定字段列表并依赖 shell 脚本里的sed后处理来应对被删除/重命名的字段。这种方式的缺陷是每出一个改动了结构体的 Android 新版本都要新增一条版本专用的sed规则维护成本线性增长且容易漏配。当前 boringssl-offset.c 改用C17 类型特征std::void_t SFINAE在编译期探测字段是否可访问再用**偏模板特化partial template specialisation**选择输出路径字段是否存在 ┌─── 存在 ──→ 输出 offsetof(T, field) 的值 类型特征探测 ────┤ └─── 不存在 ──→ 输出替代宏 / 哨兵值三类要素在源码中的对应位置1. 类型特征探针boringssl-offset.c#L46-L64——每个探针回答T 上是否存在某公有字段// ssl_st::version (present in Android ≤ 15, removed in Android 16) template typename T, typename void struct ssl_st_has_version : std::false_type {}; template typename T struct ssl_st_has_versionT, std::void_tdecltype(std::declvalT().version) : std::true_type {};2. 成对的 emitter主模板 T,true偏特化boringssl-offset.c#L97-L110template typename T, bool Present struct emit_ssl_st_version { static void emit() { // Presentfalse字段不存在 → 输出替代宏兼作 feature flag format(ssl_session_st, ssl_version, offsetof(ssl_session_st, ssl_version)); } }; template typename T struct emit_ssl_st_versionT, true { // offsetof(T, version) 是依赖表达式仅在 T 确定后实例化时才被检查 static void emit() { format(ssl_st, version, offsetof(T, version)); } };3.main()中的调用boringssl-offset.c#L156emit_ssl_st_versionssl_st, ssl_st_has_versionssl_st::value::emit();其中两条关键设计原则值得特别强调不用if constexpr。在非模板函数中if constexpr的两个分支仍然都会被编译器做语法与语义检查offsetof(T, 已被删除的字段)即使在未取用的分支里也是硬编译错误。用偏模板特化。emit_fooT, true::emit()体内的offsetof(T, field)是依赖表达式依赖模板参数T只有该偏特化被实际实例化时编译器才会检查它——即只有选中Presenttrue分支时才会编译。这保证了对不存在字段的 offsetof 永远不会被执行到。feature-flag 宏是探测的副产品。当某字段不存在时工具输出的替代宏本身就是一个版本特性标志供下游头文件判断不存在的字段输出的宏消费方ssl_st::versionSSL_SESSION_ST_SSL_VERSIONboringssl_const.h、boringssl_masterkey.hssl_session_st::secret_lengthSSL_SESSION_ST_SECRET_LENGTH 0xFF哨兵值boringssl_masterkey.hSSL3_STATE::version存在时BSSL__SSL3_STATE_VERSIONboringssl_masterkey.h已跟踪的字段变化Android 版本变化工具行为≤ 15ssl_st::version存在输出SSL_ST_VERSION16ssl_st::version被删除输出SSL_SESSION_ST_SSL_VERSIONfeature flag≤ 15ssl_session_st::secret_length存在输出SSL_SESSION_ST_SECRET_LENGTH 偏移值16ssl_session_st::secret_length被删除输出SSL_SESSION_ST_SECRET_LENGTH 0xFF哨兵≤ 15SSL3_STATE::version不存在不输出任何内容16SSL3_STATE::version新增输出BSSL__SSL3_STATE_VERSIONfeature flag对照 kern/boringssl_a_15_kern.c 与 kern/boringssl_a_16_kern.c 两个已生成文件即可逐条验证这张表。自适应头文件boringssl_const.h与boringssl_masterkey.h这两个头文件把所有版本特定逻辑实现为纯 C 预处理器条件编译。只要偏移工具输出了正确的 feature flag支持新 Android 版本时它们无需任何手工修改。boringssl_const.hTLS 1.3 密钥偏移的统一公式该头文件boringssl_const.h通过#ifdef SSL_SESSION_ST_SSL_VERSIONAndroid 16 feature flag在两套偏移计算公式间切换覆盖SSL_HANDSHAKE结构体中的 TLS 1.3 密钥字段Android ≤ 15Android 16密钥存储类型private uint8_t secret_[48]私有 raw 数组public InplaceVectoruint8_t,48公有字段步长48 字节49 字节SSL_HANDSHAKE_FIELD_STEPSSL_HANDSHAKE_SECRET_基址roundup(MAX_VERSION2, 8) 8MAX_VERSION 2SSL_HANDSHAKE_HASH_LEN_独立的size_t hash_len_字段secret.size_位于SECRET_ 48对应源码即 #L33-L57 的#ifdef/#else两个分支。由于InplaceVectoruint8_t,N的内存布局是[storage_(N) | size_(1B)]且对齐为 1Android 16 分支里secret.storage_紧跟max_version之后、每个向量恰好占 49 字节于是 6 个下游字段偏移EARLY_TRAFFIC_SECRET_等见 #L63-L68可以统一写成单条共享公式#define SSL_HANDSHAKE_EARLY_TRAFFIC_SECRET_ (SSL_HANDSHAKE_SECRET_ SSL_HANDSHAKE_FIELD_STEP * 1) #define SSL_HANDSHAKE_CLIENT_TRAFFICE_SECRET_ (SSL_HANDSHAKE_SECRET_ SSL_HANDSHAKE_FIELD_STEP * 2) /* … 其余 4 个字段同理 … */无需为每个字段维护两套常量杜绝了重复与漂移。boringssl_masterkey.hTLS 版本探测的三级策略boringssl_masterkey.h 的probe_ssl_master_keyuprobe 用两个 feature flag 保证在所有 Android 版本上正确读出协议版本#ifndef SSL_SESSION_ST_SSL_VERSION#L216-L225— Android ≤ 15从ssl_st.version读取 TLS 版本#ifdef BSSL__SSL3_STATE_VERSION#L236-L246— Android 16在 TLS 1.2 / 1.3 分支判断之前从SSL3_STATE.version偏移0xd0读取版本从而确保 TLS 1.3 连接被正确识别#ifdef SSL_SESSION_ST_SSL_VERSION#L332-L339— 在 TLS 1.2 分支内部从ssl_session_st.ssl_version补读版本用于准确上报。此外还有哨兵值路径#L341-L354当SSL_SESSION_ST_SECRET_LENGTH 0xFFAndroid 16 删除了该字段时master secret 长度直接取最大值BORINGSSL_SSL_MAX_MASTER_KEY_LENGTH48否则读取真实长度字段。Android 16 的背景Android 16 对 BoringSSL 做了一次较大重构具体影响如下变化影响ssl_st.version字段删除无法再从 SSL 对象直接读取协议版本ssl_session_st.secret_length字段删除master secret 长度需用最大值48替代ssl_session_st.secret类型从uint8_t[]改为InplaceVectoruint8_t,48偏移不变storage_仍在首位但 TLS 1.3 所有握手密钥字段同样改为 InplaceVectorSSL_HANDSHAKETLS 1.3 密钥从private uint8_t[]改为public InplaceVector字段步长从 48 变为 49整体基址前移 8 字节SSL3_STATE.version新增替代被删除的ssl_st.version用于读取协议版本这正是上面已跟踪的字段变化表的来源——工具、boringssl_const.h、boringssl_masterkey.h三者围绕这一组结构体变化形成了闭环。支持未来 Android 版本的完整步骤若新 Android 版本删除或改动了某个结构体字段按以下五步扩展全程不需要sed规则工具本身不包含任何硬编码的版本号判断在 boringssl-offset.c 中添加类型特征探针template typename T, typename void struct foo_has_new_field : std::false_type {}; template typename T struct foo_has_new_fieldT, std::void_tdecltype(std::declvalT().new_field) : std::true_type {};添加一对 emitter主模板 T,true偏特化template typename T, bool Present struct emit_new_field { static void emit() { /* 字段不存在输出替代宏 */ } }; template typename T struct emit_new_fieldT, true { static void emit() { format(foo, new_field, offsetof(T, new_field)); } };在main()中调用 emitteremit_new_fieldfoo_t, foo_has_new_fieldfoo_t::value::emit();在 boringssl_const.h 或 boringssl_masterkey.h 中处理 feature flag#ifdef NEW_FEATURE_FLAG // 新版本特有逻辑 #endif在 boringssl_android_offset.sh 的sslVerMap中注册新版本sslVerMap[5]17 # android17-release注意事项与常见坑网络访问脚本启动时会尝试git fetch --tags网络不可用时仅告警脚本会继续使用本地已缓存的分支/tagAndroid 脚本 #L23。若本地从未克隆过对应 tag 且断网则会失败。CPU 架构偏移地址与架构强相关。工具必须在与目标设备相同的 CPU 架构上编译并运行为 Android ARM64 设备生成偏移时须在 aarch64 主机上运行。跨架构生成会得到错误的偏移值——这是运行此类脚本时最容易被忽视的前提。不要手改生成文件shell 脚本不再在工具输出之后追加任何硬编码#definekern/boringssl_a_*_kern.c里的所有常量均由工具统一输出。手动修改生成文件会与工具输出漂移正确做法是删除文件后重新运行脚本。boringssl-offset.c需要 C17std::void_t依赖 C17 标准编译时必须带-stdc17驱动脚本已自动传入该参数boringssl_android_offset.sh#L51-L52。非 Android BoringSSL 脚本的-std差异从源码看boringssl_non_android_offset.sh#L46 的g命令未显式指定-std其行为取决于主机 g 的默认标准如需复现 Android 脚本的严格语义可参照其参数显式补充。小结utils/目录的价值在于把eBPF 探针所需结构体偏移这一最容易随上游库漂移的手工环节转化成了可重复执行的编译期探测流水线驱动脚本负责拉取特定 tag 的库源码并包装输出boringssl-offset.c用 C17 类型特征 偏模板特化在编译期自动适配字段增删而boringssl_const.h/boringssl_masterkey.h把版本差异收敛为 feature flag 驱动的条件编译。三者配合使得新增一个 Android 版本的适配成本从改字段表 写 sed 规则 手算偏移降低到注册一行 tag 重跑脚本。对需要维护类似 uprobe/eBPF 抓密钥探针的项目而言这套工具输出偏移、头文件声明消费、flag 驱动分支的分层模式具有很强的参考价值。【免费下载链接】ecaptureCapturing SSL/TLS plaintext without a CA certificate using eBPF. Supported on Linux/Android kernels for amd64/arm64.项目地址: https://gitcode.com/GitHub_Trending/ec/ecapture创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考