# 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/` 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//` — 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