Skip to main content
NJannasch.Dev
eBPFSecurityGo

eBPF SSL Uprobes: Read HTTPS in Plaintext

Attach uprobes to SSL library functions to read traffic before encryption / after decryption. No certificates, no proxy, no app changes.

Find your libssl

find /usr/lib -name 'libssl.so*' -type f 2>/dev/null
# Typically: /usr/lib/x86_64-linux-gnu/libssl.so.3

nm -D /usr/lib/x86_64-linux-gnu/libssl.so.3 | grep SSL_write
# Expected: T SSL_write

BPF program (C)

SEC("uprobe/ssl_write")
int uprobe_ssl_write_entry(struct pt_regs *ctx) {
    // buf contains PLAINTEXT before OpenSSL encrypts it
    void *buf = PT_REGS_PARM2(ctx);
    int len = PT_REGS_PARM3(ctx);
    return submit_ssl_event(buf, len, SSL_WRITE_EVENT);
}

SEC("uretprobe/ssl_read")
int uretprobe_ssl_read_exit(struct pt_regs *ctx) {
    // Buffer now contains DECRYPTED plaintext
    return submit_ssl_event(buf, ret, SSL_READ_EVENT);
}

Node.js gotcha

Node.js bundles OpenSSL statically — system libssl uprobes won’t fire. Attach to the Node binary directly:

nm -D /usr/bin/node | grep SSL_write
# If nm shows nothing:
readelf -Ws /usr/bin/node | grep SSL_write
if nodePath, err := exec.LookPath("node"); err == nil {
    attachSSLUprobes(nodePath, "node")
}

Same fix applies to Deno, CGO-linked Go binaries, and Rust with vendored OpenSSL.