Version Detection and Bytecode Selection
Relevant source files
The following files were used as context for generating this wiki page:
Purpose and Scope
This page documents eCapture's sophisticated version detection and bytecode selection system, which enables the tool to work across multiple SSL/TLS library versions and kernel configurations without requiring manual version specification. The system automatically identifies the target library version, selects compatible eBPF bytecode, and falls back to safe defaults when exact matches are unavailable.
For information about the eBPF compilation process that produces these bytecode variants, see Build System. For details on module initialization and lifecycle, see Module System and Lifecycle.
System Overview
eCapture compiles over 130 different eBPF bytecode variants to support:
- OpenSSL: versions 1.0.2a through 3.5.4
- BoringSSL: Android 12-16 variants and non-Android versions
- GnuTLS: versions 3.6.12 through 3.8.7
- Go TLS: built-in crypto/tls library
- System utilities: bash, zsh, mysqld, postgres
Each SSL/TLS library version has different internal structure layouts (offsets), requiring version-specific bytecode. The system performs runtime detection to select the correct variant.
Sources: variables.mk:189-228, user/module/probe_openssl_lib.go:30-42, CHANGELOG.md:14-23
Version Detection Architecture
Sources: user/module/probe_openssl_lib.go:189-282, user/module/probe_openssl_lib.go:284-317, user/module/probe_openssl_lib.go:341-369
ELF Parsing and Version Extraction
The detectOpenssl() function implements a robust version string extraction mechanism:
Process Steps
- ELF File Opening: Opens the target shared library using
os.OpenFile()andelf.NewFile() - Architecture Validation: Ensures the binary is
EM_X86_64orEM_AARCH64 - Section Location: Finds the
.rodatasection containing read-only data (version strings) - Chunk Reading: Reads in 1MB chunks with 30-byte overlap to handle edge cases
- Regex Matching: Applies pattern
OpenSSL\s\d\.\d\.[0-9a-z]+to extract version
Edge Case Handling
| Scenario | Solution |
|---|---|
| Version string spans buffer boundary | 30-byte overlap (OpenSslVersionLen) ensures capture |
.rodata section not found | Returns ErrProbeOpensslVerNotFound |
| Multiple version strings in binary | Uses first match found |
| BoringSSL without version string | Falls back to Android detection or default |
Example version strings extracted:
"OpenSSL 1.1.1j 16 Feb 2021"→"openssl 1.1.1j""OpenSSL 3.2.0 23 Nov 2023"→"openssl 3.2.0"
Sources: user/module/probe_openssl_lib.go:189-282, user/module/probe_openssl_lib.go:41-42
Detection Flow Diagram
Sources: user/module/probe_openssl_lib.go:206-282
Version-to-Bytecode Mapping System
The initOpensslOffset() function initializes the sslVersionBpfMap with 100+ version mappings. This map serves as the authoritative source for version-to-bytecode associations.
Mapping Table Structure
| Version Family | Version Range | Bytecode File | Notes |
|---|---|---|---|
| OpenSSL 1.0.2 | 1.0.2a - 1.0.2u | openssl_1_0_2a_kern.o | All 1.0.2 versions share offsets |
| OpenSSL 1.1.0 | 1.1.0a - 1.1.0l | openssl_1_1_0a_kern.o | All 1.1.0 versions share offsets |
| OpenSSL 1.1.1a | 1.1.1a only | openssl_1_1_1a_kern.o | Unique offset group |
| OpenSSL 1.1.1b-c | 1.1.1b - 1.1.1c | openssl_1_1_1b_kern.o | Second offset group |
| OpenSSL 1.1.1d-i | 1.1.1d - 1.1.1i | openssl_1_1_1d_kern.o | Third offset group |
| OpenSSL 1.1.1j+ | 1.1.1j - 1.1.1w | openssl_1_1_1j_kern.o | Most common 1.1.1 variant |
| OpenSSL 3.0.x | 3.0.0 - 3.0.11, 3.0.13+ | openssl_3_0_0_kern.o | Default for 3.0 series |
| OpenSSL 3.0.12 | 3.0.12 only | openssl_3_0_12_kern.o | Special case with unique offsets |
| OpenSSL 3.1.x | 3.1.0 - 3.1.8 | openssl_3_1_0_kern.o | Shares base with 3.0 series |
| OpenSSL 3.2.0-2 | 3.2.0 - 3.2.2 | openssl_3_2_0_kern.o | Early 3.2 series |
| OpenSSL 3.2.3 | 3.2.3 only | openssl_3_2_3_kern.o | Structure changes |
| OpenSSL 3.2.5 | 3.2.5 only | openssl_3_2_4_kern.o | Additional changes |
| OpenSSL 3.3.0-1 | 3.3.0 - 3.3.1 | openssl_3_3_0_kern.o | Early 3.3 series |
| OpenSSL 3.3.2 | 3.3.2 only | openssl_3_3_2_kern.o | Mid-series changes |
| OpenSSL 3.3.3-4 | 3.3.3 - 3.3.4 | openssl_3_3_3_kern.o | Latest 3.3 series |
| OpenSSL 3.4.0 | 3.4.0 only | openssl_3_4_0_kern.o | Initial 3.4 release |
| OpenSSL 3.4.1+ | 3.4.1 - 3.4.2 | openssl_3_4_1_kern.o | Updated 3.4 series |
| OpenSSL 3.5.x | 3.5.0 - 3.5.4 | openssl_3_5_0_kern.o | Latest supported |
| BoringSSL A12-13 | Android 12-13 | boringssl_a_13_kern.o | Google's fork |
| BoringSSL A14 | Android 14 | boringssl_a_14_kern.o | Updated structures |
| BoringSSL A15 | Android 15 | boringssl_a_15_kern.o | Latest Android |
| BoringSSL A16 | Android 16 | boringssl_a_16_kern.o | Upcoming Android |
| BoringSSL NA | Non-Android | boringssl_na_kern.o | Generic BoringSSL |
Mapping Code Structure
Sources: user/module/probe_openssl_lib.go:73-187, user/module/probe_openssl_lib.go:44-62
Downgrade Version Selection Algorithm
When an exact version match is not found in sslVersionBpfMap, the downgradeOpensslVersion() function implements a progressive truncation algorithm to find the closest compatible older version.
Algorithm Steps
Sources: user/module/probe_openssl_lib.go:341-369
Version Comparison Logic
The isVersionLessOrEqual() function performs lexicographic version comparison:
- Extract version parts: Split by dots (
"3.0.12"→["3", "0", "12"]) - Parse numeric and suffix:
"1a"→num=1, suffix="a" - Compare iteratively: Compare numbers first, then suffixes
Example comparisons:
isVersionLessOrEqual("openssl 3.0.11", "openssl 3.0.12")→trueisVersionLessOrEqual("openssl 3.0.12", "openssl 3.0.11")→falseisVersionLessOrEqual("openssl 1.1.1j", "openssl 1.1.1k")→true
Sources: user/module/probe_openssl_lib.go:371-448
Default Fallback Behavior
When all detection and downgrade attempts fail, the system applies platform-specific defaults:
Linux Platform Defaults
Rationale:
libssl.so.3indicates OpenSSL 3.x series (most likely 3.0.x)- Older library names default to 1.1.1j (most stable/common 1.1.1 variant)
Android Platform Defaults
Android version mapping:
--android_ver=13→boringssl_a_13_kern.o--android_ver=14→boringssl_a_14_kern.o--android_ver=15→boringssl_a_15_kern.o--android_ver=16→boringssl_a_16_kern.o
Sources: user/module/probe_openssl_lib.go:284-317, user/module/probe_openssl_lib.go:361-368
CO-RE vs Non-CO-RE Bytecode Selection
eCapture maintains two bytecode variants for each version: CO-RE (Compile Once, Run Everywhere) and non-CO-RE (kernel-specific). The selection happens automatically based on kernel BTF support.
Selection Criteria
| Criterion | CO-RE Mode | Non-CO-RE Mode |
|---|---|---|
| Kernel BTF Support | CONFIG_DEBUG_INFO_BTF=y | Not required |
| Minimum Kernel Version | 5.2+ (x86_64), 5.5+ (aarch64) | 4.18+ (x86_64), 5.5+ (aarch64) |
| Bytecode Suffix | _core.o | _noncore.o |
| Relocation | Runtime via libbpf | Compile-time via kernel headers |
| Portability | Works across kernel versions | Tied to specific kernel |
BTF Detection Flow
Compilation Targets
The build system generates both variants simultaneously:
CO-RE compilation (x86_64 example):
clang -target bpfel -D__TARGET_ARCH_x86 -I./kern/bpf/x86 \
-O2 -g -c -o openssl_3_0_0_kern_core.o openssl_3_0_0_kern.cNon-CO-RE compilation (x86_64 example):
clang -D__TARGET_ARCH_x86 -I/lib/modules/$(uname -r)/build/include \
-DNOCORE -emit-llvm -S -o openssl_3_0_0_kern.ll openssl_3_0_0_kern.c
llc -march=bpf -filetype=obj -o openssl_3_0_0_kern_noncore.o openssl_3_0_0_kern.llSources: variables.mk:236-268, README.md:86-101
Bytecode Loading and Validation
After bytecode selection, the module performs validation before attachment:
Loading Process
Error Handling Strategy
| Error Condition | Recovery Action | Log Message |
|---|---|---|
Version not found in .rodata | Use default based on library name | "OpenSSL/BoringSSL version not found, used default version" |
| Bytecode file not found | Fatal error, exit | "BPF bytecode file is not matched" |
| Uprobe attachment failed | Log warning, continue | "attach uprobe failed" |
| Symbol not found | Skip that hook | "symbol not found, ignored" |
Sources: user/module/probe_openssl_lib.go:189-282, user/module/probe_openssl_lib.go:284-317
Version Support Matrix
This table summarizes the complete version support landscape:
| Library | Minimum Version | Maximum Version | Total Variants | Notes |
|---|---|---|---|---|
| OpenSSL 1.0.2 | 1.0.2a | 1.0.2u | 1 | Legacy support |
| OpenSSL 1.1.0 | 1.1.0a | 1.1.0l | 1 | Legacy support |
| OpenSSL 1.1.1 | 1.1.1a | 1.1.1w | 4 | Most common in production |
| OpenSSL 3.0 | 3.0.0 | 3.0.17 | 2 | Default for Ubuntu 22.04+ |
| OpenSSL 3.1 | 3.1.0 | 3.1.8 | 1 | Shares base with 3.0 |
| OpenSSL 3.2 | 3.2.0 | 3.2.5 | 3 | Multiple structure changes |
| OpenSSL 3.3 | 3.3.0 | 3.3.4 | 3 | Latest stable |
| OpenSSL 3.4 | 3.4.0 | 3.4.2 | 2 | Recent release |
| OpenSSL 3.5 | 3.5.0 | 3.5.4 | 1 | Newest supported |
| BoringSSL Android | 12 | 16 | 4 | Per-Android-version |
| BoringSSL | Non-Android | N/A | 1 | Generic variant |
Total bytecode files: 22 SSL/TLS variants × 2 modes (CO-RE + non-CO-RE) = 44 bytecode files just for OpenSSL/BoringSSL
Sources: user/module/probe_openssl_lib.go:44-62, variables.mk:189-228, CHANGELOG.md:14-23
Configuration Options
Users can override automatic detection using CLI flags:
Command-Line Overrides
| Flag | Purpose | Example | Use Case |
|---|---|---|---|
--ssl_version | Force specific version | --ssl_version="openssl 3.0.12" | Known version, skip detection |
--libssl | Specify library path | --libssl=/opt/openssl/lib/libssl.so | Non-standard installation |
--android_ver | Set Android version | --android_ver=14 | Android BoringSSL detection |
--nocore | Disable CO-RE mode | --nocore | Force non-CO-RE bytecode |
Example Usage
Manual version specification:
sudo ecapture tls --ssl_version="openssl 3.2.3" --libssl=/usr/local/lib/libssl.so.3Android device:
adb shell "ecapture tls --android_ver=14"Force non-CO-RE mode (older kernels):
sudo ecapture tls --nocoreSources: README.md:165-169, README_CN.md:144-149
Logging and Diagnostics
The version detection system provides detailed logging at multiple stages:
Log Message Categories
| Stage | Log Level | Example Message | Interpretation |
|---|---|---|---|
| Detection Success | INFO | "Openssl Version=openssl 3.0.12" | Exact version found |
| Default Fallback | WARN | "OpenSSL/BoringSSL version not found, used default version OpenSSL Version=linux_default_3_0" | Using generic default |
| Downgrade Applied | ERROR | "OpenSSL Version=openssl 3.6.0, used downgrade version openssl 3.5.4" | Newer version, using closest older |
| Bytecode Match | INFO | "BPF bytecode file is matched. bpfFileName=openssl_3_0_0_kern_core.o" | Bytecode selection complete |
| BTF Mode | INFO | "BTF bytecode mode: CORE. btfMode=0" | CO-RE enabled |
Troubleshooting Guide
Problem: "OpenSSL/BoringSSL version not found"
Solutions:
- Check if
.rodatasection exists:readelf -S /path/to/libssl.so | grep rodata - Manually specify version:
--ssl_version="openssl x.x.x" - For BoringSSL, use:
--ssl_version="boringssl_a_13"
Problem: "BPF bytecode file is not matched"
Solutions:
- Verify bytecode files exist:
ls user/bytecode/*.o - Check build completed:
make - Try forcing version:
--ssl_version
Problem: Capture works but data is garbled
Cause: Incorrect version/offset mismatch
Solutions:
- Enable debug mode:
ecapture tls --debug - Try adjacent versions:
--ssl_version="openssl 3.0.11"vs"openssl 3.0.13" - Check library version:
openssl versionorstrings /lib/libssl.so.3 | grep "OpenSSL"
Sources: user/module/probe_openssl_lib.go:300-316, README.md:89-101