Files
Necropolis-C2/docs/server-operator-design.md
T
2026-07-07 04:50:23 +01:00

193 lines
10 KiB
Markdown

# Operator Design
## Architecture
The "server" is not a traditional server — it's an **operator node** that participates in the
libp2p network as a peer. There is no central infrastructure, no VPS requirement, and no
listening port that needs to be exposed to the internet.
```
┌──────────────────────────────────────────────────────────┐
│ Operator Node Process │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ server/core/operator.go │ │
│ │ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ RunCLI() │ │ Implant │ │ handlers │ │ │
│ │ │ (liner) │ │ Registry │ │ (ps, ls, exec, │ │ │
│ │ │ │ │ │ │ cd, pwd, d/l, │ │ │
│ │ │ │ │ │ │ upload, shell, │ │ │
│ │ │ │ │ │ │ portfwd, socks,│ │ │
│ │ │ │ │ │ │ kill, deadman) │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ └──────────────┴─────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────┴──────┐ │ │
│ │ │ transport │ │ │
│ │ │ (libp2p) │ │ │
│ │ └─────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ server/core/generate.go │ │
│ │ Cross-compile implants with embedded keys │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
libp2p Network
```
## Directory Structure
```
server/
└── core/
├── run.go # Entry point: load keys, create operator, start CLI
├── operator.go # Core operator logic (708 lines)
│ ├── ImplantRecord # In-memory implant metadata
│ ├── Operator # Main struct: keys, node, messenger, implant registry
│ ├── NewOperator() # Constructor — creates libp2p node, messenger
│ ├── Start() # Connects to network, starts DHT, listens for beacons
│ ├── handleBeaconStream # Persistent stream reader from implants
│ ├── handleMessage # Message dispatch by type
│ ├── sendCommandToImplant # Sends signed command via direct stream
│ ├── OpenShell() # Interactive PTY shell via direct libp2p stream
│ ├── Portfwd() # TCP port forwarding through implant
│ ├── Ls/Cd/Pwd/Ps/Execute/Download/Upload/Kill/DeadMan
│ └── disconnectCheckLoop # Detects silent implants via heartbeat timeout
├── cli.go # Readline-based interactive console (liner)
│ ├── RunCLI() # Main loop — parser/dispatch
│ ├── commandHelp # Help text for all commands
│ └── saveHistory/shortenStr
├── generate.go # Implant build system (470 lines)
│ ├── RunGenerate() # CLI flag parser
│ ├── BuildImplant() # Key generation, source extraction, cross-compilation
│ ├── prepareBuildDir() # Extracts embedded source, injects keys
│ ├── ensureGo() # Auto-installs Go if missing
│ ├── ensureGarble() # Auto-installs garble if missing
│ └── writeQuietStub() # Daemonise stub for --quiet mode
├── socks_proxy.go # SOCKS5 proxy through implant (397 lines)
│ ├── SocksStart/Stop/List
│ ├── handleSocksConn # Full SOCKS5 handshake (auth, connect, relay)
│ ├── LoadSocksCreds/SaveSocksCreds
│ └── pickImplantPeerID/pickRandomImplant
└── embedsrc/
└── embed.go # Embeds implant_src.tar.gz via Go 1.16 embed
```
## Key Responsibilities
### 1. Implant Discovery & Registration
The operator does NOT actively discover implants. Instead:
1. The operator **advertises itself** in the DHT under the rendezvous key
`necropolis/<operator-peerid>`
2. Implants find the operator via DHT lookup and open a persistent beacon stream
3. The operator also accepts inbound beacon streams (`/bc/1.0.0`) from any peer
4. When a direct stream can't be established, the operator publishes signed command
envelopes to the DHT dead-drop at `/necropolis/cmd/<id>/<nonce>` — implants poll
this every 30s
5. Each `Z1` (beacon register) is signature-verified against the sender's public key
6. Implants are tracked in an in-memory `map[string]*ImplantRecord` keyed by PeerID
```
Beacon Stream Flow:
Implant opens /bc/1.0.0 stream ──→ Operator accepts in handleBeaconStream
└── Loop:
read length-prefixed protobuf
verify signature
dispatch by envelope.Type
update LastCheckin
```
### 2. Implant Tracking
Each `ImplantRecord` stores:
```go
type ImplantRecord struct {
Name, Hostname, UUID, Username string
UID, GID, OS, Arch string
PID int32
PeerID, Version, ActiveC2 string
Locale string
LastCheckin time.Time
Interval, Jitter time.Duration
PublicKey crypto.PubKey
Disconnected bool
}
```
A background goroutine (`disconnectCheckLoop`) runs every 30 seconds. Any implant whose
`LastCheckin` exceeds `Interval + Jitter + 30s` is marked `Disconnected = true`.
### 3. Command Dispatch
Commands are sent over **direct libp2p streams** (`/bc/1.0.0/cmd`) — not over pubsub.
```
Operator CLI Operator core Implant
│ │ │
│ exec("whoami") │ │
│ ─────────────────→ │ │
│ │ Z14{Path:"whoami"} │
│ │ sign envelope │
│ │ open /bc/1.0.0/cmd │
│ │ ───────────────────→ │
│ │ │ exec.Command("whoami")
│ │ │ capture output
│ │ Z15{Stdout:"root\n"} │
│ │ ←─────────────────── │
│ "command sent" │ │
│ ← result printed via │ │
│ handleExecuteResult() │ │
```
### 4. Interactive Sessions
Shell, port forwarding, and SOCKS all use **direct libp2p streams** with bidirectional
`io.Copy`. These bypass the envelope/signing layer entirely — once the stream is
established, raw bytes flow in both directions.
| Feature | Protocol ID | How It Works |
|---|---|---|
| Shell | `/x/sh/1.0.0` | Operator sends winsize, implant starts PTY, bidirectional I/O |
| Portfwd | `/x/pf/1.0.0` | Operator sends target address, implant dials TCP, relay |
| SOCKS | `/x/sk/1.0.0` | Operator handles SOCKS5 handshake locally, sends target over stream |
Shell is terminated by typing `exit` or pressing **Ctrl+]** (0x1d byte), which triggers an
escape in the `shellEscaper` reader on the operator side.
### 5. Implant Generation
The operator binary is self-contained — it embeds the entire implant source tree as
`implant_src.tar.gz` via Go's `//go:embed`. At generation time:
1. The embedded tarball is extracted to a temp directory
2. The operator's public key and a fresh implant keypair are written as Go source files
3. Optional build tags are added (antivm, evasion, quiet mode)
4. When `--evasion` is set, the embedded `valak.dll` (Zig kernel evasion DLL) is
compiled into the implant binary via `//go:embed`. At runtime the DLL is loaded
from memory and provides FreshyCalls syscall dispatch, AMSI/ETW bypass, module stomping,
AMSI bypass via hardware breakpoint, and ETW patching.
5. `go build` (or `garble build`) cross-compiles for the target
6. UPX compression and ELF stripping are applied as post-processing
## SOCKS5 Proxy
The operator can act as a SOCKS5 proxy, routing traffic through an implant:
```
SOCKS client (browser) ←→ Operator (SOCKS5 handler) ←libp2p stream→ Implant ←→ Target
```
Features:
- Username/password authentication (SHA-256 hashed, saved to `~/.necropolis/socks.json`)
- Random implant selection per request
- Multiple simultaneous proxy instances on different ports