Uprobe Walkthrough - NormB/sipnab GitHub Wiki
A step-by-step walkthrough of sipnab's uprobe and eBPF capture: what it is, what it is not, whether your machine can run it at all, and how to use it.
sipnab can show you SIP that only ever exists encrypted on the wire โ no
private key, no SSLKEYLOGFILE, and no restart of the daemon.
This is not an attack on TLS. Nothing here decrypts a captured packet, and none of it works against traffic on the wire or against another machine. What it does is far simpler: it reads the plaintext inside a process you already control, at the moment that process hands the bytes to its TLS library, and before any encryption happens.
| What it needs |
root on the machine running the SIP daemon |
| What it reads | memory of processes on that machine |
| What it cannot do | decrypt a pcap, read another host, or recover a session key |
If you have root on a box, you can already read any process's memory with a debugger. This is the same authority, aimed at one function and filtered to SIP. It gives you nothing you did not already have โ it just makes it practical.
Because it is easy to under-read the sentence above, here is what it means in operational terms.
-
Anyone who can run this can read every SIP session on that host, for every
process that uses a probed TLS library โ not only the daemon you had in mind.
That includes
Authorizationheaders,Proxy-Authorization, any credential a UA sends, and the contents ofMESSAGEbodies. - The captured plaintext is as sensitive as the session itself. Treat a capture from this feature the way you would treat a keylog: it is not a redacted summary, it is the traffic.
- It leaves kernel state. A probe attaches to a library FILE, not to your process, so it costs every process mapping that library for as long as it stays there. sipnab removes them on exit โ see Cleaning up for how to confirm, and what to do if a crash left some behind.
- It cannot transmit. sipnab refuses to send anything derived from this input. Scanner-kill and every other response path stays shut for uprobe captures, and no flag opens it, because sipnab never observed a peer to answer to.
-
Least privilege. Prefer
CAP_SYS_ADMIN+CAP_PERFMON(andCAP_BPFfor the eBPF backend) over blanket root, and run the capture for as long as you need it rather than leaving it attached.
If your threat model does not allow reading process memory on that host, this
feature is not for you โ use --keylog
with a key the endpoint produces deliberately.
It does not work everywhere, and it fails closed rather than quietly. Three requirements, in the order they bite.
uname -s; ls -d /sys/kernel/tracingExpect Linux and the directory to exist. macOS, Windows and any BSD have no
uprobes at all: sipnab refuses with a message saying so, rather than starting a
capture that could never see anything. If the directory is missing, mount it:
sudo mount -t tracefs nodev /sys/kernel/tracingid -uExpect 0. sipnab drops privileges after it attaches, so a server started
with --user cannot attach probes later โ which is why the MCP tool refuses on a
dropped server rather than failing halfway.
ls -l /sys/kernel/btf/vmlinuxBTF is the BPF Type Format: the kernel's description of its own structs. sipnab reads the socket layout from it at load time, so the program keeps working across kernels instead of matching only the one that compiled it.
If this file does not exist, the eBPF backend cannot run on this machine.
That is a property of the kernel build (CONFIG_DEBUG_INFO_BTF=y), not
something sipnab can work around, and no amount of privilege changes it. The
tracefs backend still works โ you simply do not get peer addresses.
A container cannot have BTF its host lacks: it shares the kernel. Checking inside a container tells you about the host.
sudo sipnab --uprobe-listIt installs nothing, and prints what a capture would probe:
FLAVOR INODE PIDS LIBRARY
OpenSSL 21143 12 /proc/954/root/usr/lib/x86_64-linux-gnu/libssl.so.3
wolfSSL 17433084 1 /proc/982702/root/usr/lib/libwolfssl.so.42.2.0
There is no silent empty answer. When nothing matches, --uprobe-list says so
and names both readings:
No TLS library is mapped by any process sipnab can see.
Either nothing here uses OpenSSL or wolfSSL, or sipnab cannot read
/proc/<pid>/maps โ try again as root.
An unprivileged run sees only its own processes, so run it as root before
concluding the host uses no TLS. A library sipnab can see but cannot reach from
its own mount namespace โ almost always one inside a container โ lists as
(UNREACHABLE) with a count of how many, rather than quietly dropping out.
The default. Works on any Linux with tracefs, needs no BTF and no special build.
sudo sipnab --uprobe-listsudo sipnab -N --uprobe-tlssipnab probes every TLS library it finds, because a host commonly runs more than one. Narrow it if only one stack is yours to read:
sudo sipnab -N --uprobe-tls --uprobe-flavor openssl12:57:47.360 0.0.0.0:0 -> 0.0.0.0:0 REGISTER TCP origin=uprobe
Dialogs carry no addresses and port 0, labeled uprobe:<comm>/<pid>. A
uprobe sees the bytes an application handed its TLS library and nothing about
the socket beneath, so sipnab names the process rather than inventing a peer.
origin=uprobe closes the last gap that leaves. Without it, 0.0.0.0:0 -> 0.0.0.0:0 reads exactly like a wire capture whose addressing sipnab failed to
parse, and the reader cannot tell the two apart. Every surface carries the same
mark: the --json line adds "input_origin": "uprobe", and the TUI's raw
viewer appends it to the info line. A wire capture stays unmarked.
The frame pointer beside it is honest about the same thing. --show-frame on a
uprobe: pointer refuses and names the process the bytes came out of,
because there is no capture to seek into and nothing to verify the bytes
against:
sipnab --show-frame 'uprobe:opensips/954#3'If you need the peer, that is the eBPF backend below.
A probe on the wrong symbol captures nothing while looking perfectly
healthy โ the capture runs, attaches, and reports zero messages. OpenSSL 3
applications increasingly call SSL_write_ex rather than SSL_write:
nm -D --undefined-only /usr/sbin/opensips | grep -i ssl_writeThen pass what you found:
sudo sipnab -N --uprobe-tls --uprobe-symbol SSL_write_exAdds the one thing tracefs cannot: who the peer was.
- a sipnab whose build produced the kernel programs, which takes
--features bpfand a nightly toolchain andcargo install bpf-linkeron the build host; -
CONFIG_DEBUG_INFO_BTF=yat run time.
Without either, sipnab refuses rather than falling back to tracefs. The
addresses are the only reason to choose this backend, so a silent downgrade
would leave you with the worst outcome: a capture with no peers and no reason given.
Read this before trusting a binary you built yourself.
The feature compiles the loader. The kernel programs come from a second,
nightly build that the main build runs for you, and that inner build needs
bpf-linker and a nightly toolchain. When either is missing the outer build
still succeeds. It prints a cargo:warning, embeds an empty placeholder
object, and carries on โ so the binary advertises bpf in --version and has
no programs to load.
sipnab catches that at attach time and refuses by name rather than attaching to nothing:
this binary carries the `bpf` feature but no kernel programs: it was built on a
machine without bpf-linker. Rebuild where `cargo install bpf-linker` has run, or
use --uprobe-backend tracefs, which needs neither it nor BTF
Degrading is the right trade for a contributor's laptop, because
--all-features sweeps this feature into every clippy and rustdoc run. It is
the wrong trade for an artifact other people install. Set SIPNAB_BPF_REQUIRED=1
in the build environment and the same missing prerequisite becomes a hard build
failure instead:
SIPNAB_BPF_REQUIRED=1 cargo build --release --features bpfThe release workflow sets it on exactly the targets that ship the feature โ the
Linux glibc builds โ so a published binary advertising bpf carries the
programs. The static musl and macOS artifacts do not carry the feature at all.
sudo sipnab -N --uprobe-tls --uprobe-backend bpf --portrange 0-65535200 OK 127.0.0.1:15061 -> 127.0.0.1:36160 TCP uprobe:python3/349147#0
REGISTER 127.0.0.1:36160 -> 127.0.0.1:15061 TCP uprobe:python3/349147#1
Widen --portrange: a TLS trunk on 5061 is the exception, and the port a
uprobe reports is whatever the socket actually used โ often ephemeral.
A uprobe on the write symbol sees plaintext and no socket. A kernel probe on
tcp_sendmsg sees the socket and only ciphertext. sipnab pairs them by
thread: a TLS library encrypts and sends on the calling thread, back to back.
When that pairing does not hold โ a write the library buffered rather than sent โ sipnab reports the message with no addresses at all, exactly as a tracefs capture does. sipnab does not guess a peer, because a guessed one would be indistinguishable from an observed one.
Probes are kernel state that outlives the process which created them. sipnab removes its own on exit. Confirm with:
sudo grep sipnab_ /sys/kernel/tracing/uprobe_eventsThat should print nothing. If a crash left some behind, remove each by name:
sudo sh -c 'echo "-:sipnab_1234_l0_b64" >> /sys/kernel/tracing/uprobe_events'Never truncate that file. Every other tracer on the host shares it, and emptying it takes their probes with it.
The eBPF backend needs none of this: the kernel frees its programs and maps when sipnab exits.
| What you see | What it means | What to do |
|---|---|---|
--uprobe-list finds no TLS library |
Unprivileged, so /proc/<pid>/maps opens only for your own processes |
Re-run as root before concluding the host uses no TLS |
no kernel programs from a binary you built |
--features bpf compiled the loader, and the inner nightly build found no bpf-linker
|
Rebuild on a host carrying bpf-linker, with SIPNAB_BPF_REQUIRED=1 so the gap fails the build |
| Capture attaches, reports 0 messages | Almost always the wrong symbol |
nm -D --undefined-only <binary> | grep -i ssl_write, then --uprobe-symbol
|
needs this kernel's BTF ... could not read it |
No CONFIG_DEBUG_INFO_BTF
|
Use --uprobe-backend tracefs; no privilege fixes this |
no kernel programs: built on a machine without bpf-linker |
The binary carries the feature but not the programs | Rebuild where cargo install bpf-linker has run, or use tracefs
|
Addresses are 0.0.0.0:0 on the eBPF backend |
The write and its send did not pair on one thread | Expected, and honest โ sipnab never guesses a peer |
| Messages missing under load | The kernel dropped records the reader could not keep up with | sipnab reports the count on exit, so the loss is visible rather than hidden |
| A container's traffic is missing | The path in maps names a different file in sipnab's namespace |
Handled automatically by inode; if naming a library by hand, use the /proc/PID/root/... form |
- Decrypt a pcap. There is no key here: sipnab reads the plaintext before the library encrypts it.
- Read another host. The probe is on a library file on the local machine.
- Recover a session key. sipnab never sees one.
- Work without root, or without Linux.
- Answer a scanner. This input can never transmit, by design.
- Cookbook 7g and 7h โ the short recipes
- CLI reference โ every uprobe flag in one table
- Internals โ how it works and why each part refuses rather than guesses