OpenSSL Module
Relevant source files
The following files were used as context for generating this wiki page:
- cli/cmd/root.go
- kern/openssl.h
- kern/openssl_masterkey.h
- kern/openssl_masterkey_3.0.h
- user/config/iconfig.go
- user/module/imodule.go
- user/module/probe_openssl.go
- user/module/probe_openssl_keylog.go
- user/module/probe_openssl_lib.go
- user/module/probe_openssl_text.go
- utils/openssl_offset_3.0.sh
- utils/openssl_offset_3.1.sh
- utils/openssl_offset_3.2.sh
- utils/openssl_offset_3.3.sh
- utils/openssl_offset_3.4.sh
- utils/openssl_offset_3.5.sh
Purpose and Scope
The OpenSSL Module captures TLS/SSL plaintext traffic and master secrets from applications using the OpenSSL library (versions 1.0.x, 1.1.x, and 3.x). It uses eBPF uprobes to hook into SSL_read and SSL_write functions without requiring application modification or root CA certificates. The module supports three output modes: text (plaintext display), pcap (network packet capture), and keylog (master secret extraction for decryption).
For BoringSSL support (Android and non-Android variants), see BoringSSL Module. For Go TLS capture, see Go TLS Module. For master secret extraction implementation details across all libraries, see Master Secret Extraction.
Sources: user/module/probe_openssl.go:15-106, cli/cmd/root.go:86-101
Supported OpenSSL Versions
The module supports a wide range of OpenSSL versions through intelligent bytecode selection and offset grouping:
| Version Range | Bytecode File | Offset Groups |
|---|---|---|
| 1.0.2a - 1.0.2u | openssl_1_0_2a_kern.o | Single group (26 versions) |
| 1.1.0a - 1.1.0l | openssl_1_1_0a_kern.o | Single group (12 versions) |
| 1.1.1a | openssl_1_1_1a_kern.o | Group A |
| 1.1.1b - 1.1.1c | openssl_1_1_1b_kern.o | Group B |
| 1.1.1d - 1.1.1i | openssl_1_1_1d_kern.o | Group C |
| 1.1.1j - 1.1.1w | openssl_1_1_1j_kern.o | Group D |
| 3.0.0 - 3.0.11, 3.0.13 - 3.0.17 | openssl_3_0_0_kern.o | Standard 3.0.x |
| 3.0.12 | openssl_3_0_12_kern.o | Special case (unique offsets) |
| 3.1.0 - 3.1.8 | openssl_3_1_0_kern.o | Compatible with 3.0.x |
| 3.2.0 - 3.2.2 | openssl_3_2_0_kern.o | 3.2 base group |
| 3.2.3 | openssl_3_2_3_kern.o | 3.2 variant A |
| 3.2.4 - 3.2.5 | openssl_3_2_4_kern.o | 3.2 variant B |
| 3.3.0 - 3.3.1 | openssl_3_3_0_kern.o | 3.3 base group |
| 3.3.2 | openssl_3_3_2_kern.o | 3.3 variant A |
| 3.3.3 - 3.3.4 | openssl_3_3_3_kern.o | 3.3 variant B |
| 3.4.0 | openssl_3_4_0_kern.o | 3.4 base |
| 3.4.1 - 3.4.2 | openssl_3_4_1_kern.o | 3.4 variant |
| 3.5.0 - 3.5.4 | openssl_3_5_0_kern.o | 3.5 base group |
Note: OpenSSL 3.0.12 is a special case where internal structure offsets differ from surrounding versions (3.0.11 and 3.0.13), requiring dedicated bytecode.
Sources: user/module/probe_openssl_lib.go:30-62, user/module/probe_openssl_lib.go:73-187
Version Detection and Bytecode Selection
Detection Process
Version Detection Algorithm
The detectOpenssl function performs binary analysis on the OpenSSL shared library:
- ELF Parsing: Opens the shared library as an ELF file and validates architecture (x86_64 or aarch64)
- Section Scanning: Locates the
.rodatasection containing read-only data - Version String Extraction: Reads the section in 1MB chunks, searching for the regex pattern
(OpenSSL\s\d\.\d\.[0-9a-z]+) - Edge Case Handling: Uses overlapping reads (subtracting 30 bytes per iteration) to handle version strings split across buffer boundaries
Sources: user/module/probe_openssl.go:178-278, user/module/probe_openssl_lib.go:189-282
Downgrade Strategy
Version Comparison Logic
The isVersionLessOrEqual function compares version strings by:
- Stripping the "openssl " prefix
- Splitting by dots (e.g.,
3.0.12→["3", "0", "12"]) - Parsing each segment into numeric and alphabetic parts (e.g.,
12a→(12, "a")) - Comparing numerically first, then alphabetically for suffixes
Sources: user/module/probe_openssl_lib.go:341-422, user/module/probe_openssl_lib.go:284-317
eBPF Hook Architecture
Uprobe Hook Points
Uprobe Implementation Details
The two-stage probe pattern works as follows:
Entry Probe (
probe_entry_SSL):- Captures SSL context pointer (
PT_REGS_PARM1) and buffer pointer (PT_REGS_PARM2) - Reads SSL version from
ssl->versionoffset - Calls
process_SSL_bio()to extract file descriptor and BIO type fromssl->bio->num - Stores context in
active_ssl_*_args_mapkeyed bypid_tgid
- Captures SSL context pointer (
Return Probe (
probe_ret_SSL):- Retrieves return value (bytes read/written) from
PT_REGS_RC - Looks up stored context from entry probe
- Calls
process_SSL_data()to read actual buffer data usingbpf_probe_read_user() - Sends
ssl_data_event_tto userspace via perf event array
- Retrieves return value (bytes read/written) from
Sources: kern/openssl.h:268-323, kern/openssl.h:164-191, user/module/probe_openssl_text.go:46-151
Connection Tracking Integration
Connection Lifecycle Management
The module maintains bidirectional mappings between process/file descriptors and network connections:
- pidConns:
map[pid]map[fd]{tuple, sock}- Lookup connection info by process and file descriptor - sock2pidFd:
map[sock][pid, fd]- Reverse lookup for cleanup on socket destruction
When SSLDataEvent is processed, GetConn(pid, fd) retrieves the tuple (source:port-dest:port) and socket pointer, enriching the captured data with network context.
Sources: user/module/probe_openssl.go:83-106, user/module/probe_openssl.go:406-488, kern/openssl.h:374-525
Structure Offset Calculation
Offset Generation Process
Offset Extraction Mechanism
The offset generation scripts automate the process of extracting structure member offsets from OpenSSL source code:
- Repository Setup: Clone or fetch OpenSSL repository at
deps/openssl - Version Iteration: Loop through supported versions (e.g., 3.0.0 to 3.0.17)
- Build Configuration: Run
./configandmake build_generatedto generate headers - Offset Compilation: Compile
offset.cwhich usesoffsetof()macro to calculate structure member positions - Header Generation: Execute the compiled binary to output C preprocessor defines
Example Offset Output (from offset.c):
printf("#define SSL_ST_VERSION %d\n", offsetof(struct ssl_st, version));
printf("#define SSL_ST_SESSION %d\n", offsetof(struct ssl_st, session));
printf("#define SSL_SESSION_ST_MASTER_KEY %d\n", offsetof(struct ssl_session_st, master_key));Sources: utils/openssl_offset_3.0.sh:1-95, utils/openssl_offset_3.2.sh:1-82, utils/openssl_offset_3.3.sh:1-82
OpenSSL 3.x Structural Changes
Starting with OpenSSL 3.2.0, internal structures were refactored:
| OpenSSL Version | Structure Changes | Offset Mapping |
|---|---|---|
| 3.0.x, 3.1.x | Direct ssl_st members | SSL_ST_VERSION, SSL_ST_WBIO, SSL_ST_RBIO |
| 3.2.x onwards | Indirection via ssl_connection_st | SSL_CONNECTION_ST_VERSION, SSL_CONNECTION_ST_WBIO, etc.Mapped back via #define SSL_ST_VERSION SSL_CONNECTION_ST_VERSION |
The mapping layer allows the eBPF code to use consistent symbolic names (SSL_ST_*) while the actual offset values differ based on the OpenSSL version's internal structure layout.
Sources: utils/openssl_offset_3.2.sh:58-67, utils/openssl_offset_3.3.sh:58-67
Master Secret Extraction
TLS 1.2 vs TLS 1.3 Architecture
Master Secret Structure
The mastersecret_t structure accommodates both TLS 1.2 and 1.3:
struct mastersecret_t {
// Common fields
s32 version; // TLS version
u8 client_random[SSL3_RANDOM_SIZE]; // 32 bytes
// TLS 1.2 specific
u8 master_key[MASTER_SECRET_MAX_LEN]; // 48 bytes
// TLS 1.3 specific
u32 cipher_id;
u8 early_secret[EVP_MAX_MD_SIZE]; // 64 bytes
u8 handshake_secret[EVP_MAX_MD_SIZE]; // 64 bytes
u8 handshake_traffic_hash[EVP_MAX_MD_SIZE]; // 64 bytes
u8 client_app_traffic_secret[EVP_MAX_MD_SIZE]; // 64 bytes
u8 server_app_traffic_secret[EVP_MAX_MD_SIZE]; // 64 bytes
u8 exporter_master_secret[EVP_MAX_MD_SIZE]; // 64 bytes
};Sources: kern/openssl_masterkey.h:25-39, kern/openssl_masterkey.h:81-251, kern/openssl_masterkey_3.0.h:82-247
Keylog File Generation (TLS 1.3)
HKDF Key Derivation for TLS 1.3
TLS 1.3 uses HKDF (HMAC-based Key Derivation Function) to derive traffic secrets from handshake secrets. The eBPF program captures the raw handshake_secret and handshake_traffic_hash, and userspace performs:
clientHandshakeSecret = HKDF-Expand-Label(
handshake_secret,
"c hs traffic",
handshake_traffic_hash,
hash_length
)This matches the TLS 1.3 RFC 8446 key schedule, producing SSLKEYLOGFILE-compatible output that Wireshark can use for decryption.
Sources: user/module/probe_openssl.go:490-583, user/module/probe_openssl.go:509-559
Hook Function Selection
Default Master Key Hook Functions (defined at module level):
var masterKeyHookFuncs = []string{
"SSL_do_handshake",
"SSL_connect",
"SSL_accept",
"SSL_in_before",
}For OpenSSL 1.0.x, SSL_in_before is replaced with SSL_state because the former is a macro rather than a function in older versions.
Sources: user/module/probe_openssl.go:178-196, user/module/probe_openssl_keylog.go:32-94
MOpenSSLProbe Implementation
Core Structure
Field Descriptions:
- bpfManager: Manages eBPF program lifecycle (loading, attaching, detaching)
- eventFuncMaps: Maps eBPF maps to their corresponding event decoders
- pidConns: Connection tracking by PID and file descriptor
- sock2pidFd: Reverse mapping from socket pointer to [PID, FD] for cleanup
- keylogger: File handle for writing SSLKEYLOGFILE format output
- masterKeys: Deduplication map (client_random hex → bool) to avoid duplicate key writes
- eBPFProgramType: Determines which eBPF programs to load (text/pcap/keylog)
- sslVersionBpfMap: Version string to bytecode filename mapping
- isBoringSSL: Flag to select BoringSSL-specific code paths
Sources: user/module/probe_openssl.go:83-106, user/module/probe_openssl.go:109-176
Initialization Flow
Sources: user/module/probe_openssl.go:109-176, user/module/probe_openssl.go:280-350, user/module/probe_openssl_text.go:18-188
Capture Modes
Mode Comparison
| Mode | eBPF Maps Used | Output Format | Use Case |
|---|---|---|---|
| Text | tls_events, connect_events | Plaintext with metadata | Real-time monitoring, debugging |
| Pcap | tls_events, connect_events, skb_events | Pcapng with DSB blocks | Wireshark analysis, network forensics |
| Keylog | mastersecret_events | SSLKEYLOGFILE format | Pre-decryption key extraction |
Event Type Mapping:
// Text Mode
m.eventFuncMaps[tls_events] = &event.SSLDataEvent{}
m.eventFuncMaps[connect_events] = &event.ConnDataEvent{}
// Keylog Mode
if m.isBoringSSL {
m.eventFuncMaps[mastersecret_events] = &event.MasterSecretBSSLEvent{}
} else {
m.eventFuncMaps[mastersecret_events] = &event.MasterSecretEvent{}
}
// Pcap Mode (includes all of Text Mode plus)
m.eventFuncMaps[skb_events] = &event.TcSkbEvent{}Sources: user/module/probe_openssl_text.go:190-234, user/module/probe_openssl_keylog.go:97-118
Text Mode Architecture
Text mode captures SSL data events and enriches them with connection information:
Sources: user/module/probe_openssl.go:741-783, user/module/probe_openssl_text.go:18-188
Keylog Mode Architecture
Keylog mode focuses exclusively on master secret extraction:
- Only attaches uprobes to master key hook functions (no data capture)
- Writes secrets in NSS Key Log Format compatible with Wireshark
- Deduplicates by client_random to avoid repeated keys
- For TLS 1.3, performs HKDF key derivation in userspace
Example Keylog Output:
CLIENT_RANDOM 52d7... a8c9...
CLIENT_HANDSHAKE_TRAFFIC_SECRET 52d7... 9f3e...
SERVER_HANDSHAKE_TRAFFIC_SECRET 52d7... b2a1...
CLIENT_TRAFFIC_SECRET_0 52d7... 3c4d...
SERVER_TRAFFIC_SECRET_0 52d7... 7e8f...
EXPORTER_SECRET 52d7... 1a2b...Sources: user/module/probe_openssl_keylog.go:32-118, user/module/probe_openssl.go:490-650
Configuration and Filtering
OpensslConfig Structure
type OpensslConfig struct {
BaseConfig
Openssl string // Path to libssl.so
SslVersion string // User-specified version
Model string // text/pcap/keylog
KeylogFile string // Output path for keylog mode
PcapFile string // Output path for pcap mode
PcapFilter string // BPF filter expression
ElfType uint8 // ElfTypeSo = 0
IsAndroid bool // Android platform flag
AndroidVer string // Android version (e.g., "13", "14")
CGroupPath string // Cgroup path for filtering
}Version Specification:
--ssl_version="openssl 3.0.12"- Exact version--ssl_version="boringssl_a_14"- Android BoringSSL variant- Auto-detect if not specified
Sources: user/module/probe_openssl.go:108-176, cli/cmd/root.go:156-175
Constant Editors for Filtering
func (m *MOpenSSLProbe) constantEditor() []manager.ConstantEditor {
return []manager.ConstantEditor{
{
Name: "target_pid",
Value: uint64(m.conf.GetPid()),
},
{
Name: "target_uid",
Value: uint64(m.conf.GetUid()),
},
{
Name: "less52",
Value: kernelLess52, // 0 or 1
},
}
}These constants are rewritten in the eBPF bytecode at load time, allowing efficient filtering without map lookups. The passes_filter() function in eBPF checks these values before processing events.
Sources: user/module/probe_openssl.go:361-395, kern/openssl.h:269-271
Error Handling and Edge Cases
Null Secret Detection
The module validates that extracted secrets are non-null before writing them:
func (m *MOpenSSLProbe) mk13NullSecrets(hashLen int,
ClientHandshakeSecret, ClientTrafficSecret0,
ServerHandshakeSecret, ServerTrafficSecret0,
ExporterSecret [64]byte) bool {
isNullCount := 5
// Check each secret; decrement counter if non-zero byte found
for i := 0; i < hashLen; i++ {
if ClientHandshakeSecret[i] != 0 { isNullCount-- }
// ... check other secrets
}
return isNullCount != 0 // true if any secret is all zeros
}This prevents writing incomplete or invalid key material to the keylog file.
Sources: user/module/probe_openssl.go:697-739, user/module/probe_openssl.go:652-672
File Descriptor Fallback
When ssl->bio->num is 0 (not set), the module checks the ssl_st_fd map populated by SSL_set_fd hooks:
*fd = (u32)ssl_bio_num_addr;
if (*fd == 0) {
u64 ssl_addr = (u64)ssl;
u64 *fd_ptr = bpf_map_lookup_elem(&ssl_st_fd, &ssl_addr);
if (fd_ptr) {
*fd = (u32)*fd_ptr;
}
}This handles applications that explicitly call SSL_set_fd() instead of using BIO functions.
Sources: kern/openssl.h:225-267, kern/openssl.h:528-543
BIO Type Filtering
The module reads the BIO method type to distinguish between different I/O types:
#define BIO_TYPE_SOURCE_SINK 0x0400
#define BIO_TYPE_DESCRIPTOR 0x0100Events with bio_type > BIO_TYPE_SOURCE_SINK | BIO_TYPE_DESCRIPTOR may indicate non-socket BIOs (e.g., memory BIOs) and are logged as warnings but not discarded, as the fd may still be valid from ssl_st_fd map.
Sources: kern/openssl.h:193-223, user/module/probe_openssl.go:764-779
Summary
The OpenSSL Module demonstrates sophisticated binary instrumentation through:
- Version Agnostic Design: 100+ OpenSSL versions supported through offset grouping (26 versions of 1.0.2.x share one bytecode file)
- Dual TLS Protocol Support: Handles both TLS 1.2 (single master_key) and TLS 1.3 (multiple traffic secrets with HKDF derivation)
- Multi-Modal Output: Text (real-time), pcap (Wireshark-compatible), and keylog (pre-decryption keys)
- Intelligent Fallback: Automatic version downgrade, libcrypto fallback, and default bytecode selection
- Connection Enrichment: Integrates kprobe-based TCP connection tracking to map SSL events to network tuples
The module's architecture separates concerns cleanly: version detection occurs at initialization, eBPF programs handle kernel-space data capture, and userspace processes events with protocol-specific parsers and key derivation logic.
Sources: user/module/probe_openssl.go:1-795, user/module/probe_openssl_lib.go:1-449, kern/openssl.h:1-544