Files
2026-07-07 04:50:23 +01:00

11 KiB

Necropolis C2 Architecture

Overview

Necropolis is a decentralised C2 framework inspired by Sliver (BishopFox/sliver), built on libp2p (the modular peer-to-peer networking stack from Protocol Labs).

Instead of hosting centralised C2 servers on VPS/cloud infra that can be taken down, blocked, or fingerprinted, necropolis uses the global libp2p DHT and GossipSub for operator discovery, implant communications, and command relay. No central server, no static IP, no DNS.

Why libp2p Instead of HTTP/DNS/mTLS/WireGuard (Sliver's Approach)

Feature Sliver Necropolis
Transport mTLS, HTTP(S), DNS, WireGuard libp2p (TCP, WS, WSS)
Server identity Static IP / domain PeerID (cryptographic)
Discovery Hardcoded C2 endpoints DHT + PubSub topic discovery
Resilience Multiple listeners Any peer can relay
Takedown Block IP/domain Unbounded: must Sybil the DHT
NAT traversal Manual / WireGuard AutoNAT + relay + hole-punching
Encryption Per-binary asymmetric keys libp2p noise/TLS + protobuf envelopes
Implant comms Polling / long-poll / DNS ticks Direct streams + persistent beacon stream

File Reference

necropolis-c2/
├── README.md
│
├── cmd/necropolis/main.go     # Single binary entry point. Runs operator CLI by default.
│
├── server/core/
│   ├── run.go                 # Binary startup: parses flags, creates operator, launches CLI
│   ├── operator.go            # Operator node: implant registry, command dispatch, DHT dead-drop
│   ├── cli.go                 # Interactive console: command history, implant selection, all commands
│   ├── generate.go            # Implant build pipeline: cross-compile, garble, UPX, credential embedding
│   ├── generate_toolchain.go  # Auto-install: Go, MinGit, garble downloads if missing on operator machine
│   ├── socks_proxy.go         # SOCKS5 proxy server on operator side. Credential management.
│   └── embedsrc/embed.go      # Embeds the full implant source tree for self-contained builds
│
├── implant/
│   ├── main.go                # Implant entry point. Parses -peer and -wss flags.
│   └── core/
│       ├── agent.go               # Implant agent: startup, beacon loop, discovery, streams, DHT polling
│       ├── agent_commands.go      # All command handlers: ps, ls, cd, pwd, exec, shell, download, upload, kill, deadman
│       ├── agent_relay.go         # Mesh routing: relay discovery, relay health tracking, circuit relay selection
│       ├── shell.go               # Shell dispatch (platform-specific ConPTY/PTY)
│       ├── shell_windows.go       # Windows ConPTY shell implementation
│       ├── shell_unix.go          # Unix PTY shell implementation
│       ├── ps.go                  # Cross-platform process listing
│       ├── portfwd.go             # TCP port forwarding through implant
│       ├── socks.go               # SOCKS5 client on implant side
│       ├── antivm.go              # VM detection core (scoring engine)
│       ├── antivm_windows.go      # Windows-specific VM checks
│       ├── antivm_linux.go        # Linux-specific VM checks
│       ├── antivm_darwin.go       # macOS-specific VM checks
│       ├── antivm_stub.go         # No-op stub when --antivm not used
│       ├── evasion_windows.go     # Reflective PE loader for Zig DLL, sleep obfuscation
│       ├── evasion_stub.go        # No-op stub for non-evasion builds
│       ├── embed_evasion.go       # Embeds valak.dll as []byte for reflective loading
│       ├── embedded_pubkey.go     # Generated stub: operator Ed25519 public key (replaced at build time)
│       ├── embedded_implant_key.go# Generated stub: implant Ed25519 private key (replaced at build time)
│       ├── embedded_boxpubkey.go  # Generated stub: operator NaCl box public key (replaced at build time)
│       └── embedded_authtoken.go  # Generated stub: 32-byte auth token (replaced at build time)
│
├── evasion/                    # Zig evasion DLL source
│   ├── build.zig              # Zig build configuration
│   ├── main.zig               # DLL exports: init, patch_etw, patch_amsi, stomp_evasion
│   ├── syscall.zig            # FreshyCalls + HAL's Gate syscall resolution, indirect dispatch (no stack spoofing)
│   ├── resolve.zig            # PEB-based module/function resolution via ror13 hashes. No static imports.
│   ├── stomp.zig              # Module stomping: copies DLL .text into signed Microsoft DLL, wipes headers
│   ├── etw.zig                # ETW suppression: NtTraceControl → hardware breakpoint → RET patch fallback
│   ├── amsi.zig               # AMSI bypass: hardware breakpoint on AmsiScanBuffer via VEH
│   ├── api.zig                # Global function pointer storage for dynamically resolved Windows APIs
│   ├── pe.zig                 # PE header structure definitions
│   ├── win32.zig              # Windows type definitions and constants
│   └── arch/hells_gate.s      # x64 assembly stubs: syscall dispatch
│
├── pkg/
│   ├── transport/
│   │   ├── node.go            # libp2p host wrapper: DHT, PubSub, relay, mDNS. Protocol ID constants.
│   │   ├── messenger.go       # Envelope creation/signing/verification, topic management, replay protection
│   │   └── types.go           # Message type constants (MsgTypeRegister, MsgTypePs, etc.)
│   └── cryptography/
│       ├── keys.go            # Ed25519 key pairs, NaCl box encryption, auth token generation
│       └── keys_test.go       # Crypto unit tests: encrypt/decrypt roundtrip, sign/verify, key persistence
│
├── protobuf/
│   ├── apb/necropolis.proto   # Wire message definitions (Envelope, Z1-Z25)
│   ├── apb/necropolis.pb.go   # Generated protobuf Go code
│   ├── cpb/common.proto       # Common types (Response, Request, Process)
│   └── cpb/common.pb.go       # Generated common types
│
├── docs/                       # Architecture and design documentation
├── build.sh                    # Cross-platform release build script
├── go.mod / go.sum             # Go module definition
└── .gitignore

High-Level Architecture

┌─────────────────────────────────────────────────────────────┐
│                     libp2p Network (DHT + Relay)             │
│                                                             │
│   ┌──────────┐              ┌──────────┐   ┌──────────┐     │
│   │ Operator │  persistent  │ Implant  │   │ Implant  │     │
│   │ (Client) │←──beacon─────│ (Agent)  │   │ (Agent)  │     │
│   │          │──command────→│          │   │          │     │
│   │          │  direct str  │          │   │          │     │
│   └────┬─────┘              └────┬─────┘   └────┬─────┘     │
│        │                         │              │            │
│        └─────────────────────────┴──────────────┘            │
│                    DHT discovery + relay circuits             │
└─────────────────────────────────────────────────────────────┘

Key Components

1. Operator Node (Client)

  • Connects to libp2p network with a PeerID derived from an operator key
  • Handles persistent beacon streams from implants (/bc/1.0.0)
  • Sends commands to implants over direct libp2p streams (/bc/1.0.0/cmd)
  • Opens direct libp2p streams for interactive sessions (shell, socks, portfwd)

2. Implant Node (Agent)

  • Compiled with an operator's public key (embedded at build time)
  • Connects to libp2p bootstrap peers or uses embedded peer list
  • Opens persistent beacon stream to operator (/bc/1.0.0) with 5s keepalive
  • Receives commands on direct streams (/bc/1.0.0/cmd) and via pubsub fallback
  • Supports session mode (direct stream for shell, portfwd) over relay circuits

3. Relay Nodes

  • Any libp2p peer can act as a relay (no cost, no registration)
  • AutoNAT + relay protocol for NAT traversal
  • No special server software — standard libp2p relays

Communication Model

Message Type Transport Pattern
Beacon / Heartbeat Persistent direct stream (/bc/1.0.0) Implant → Operator
Command dispatch Direct stream (/bc/1.0.0/cmd) Operator → Implant
Task result Beacon stream or pubsub fallback Implant → Operator
DHT dead-drop DHT value store (/necropolis/cmd/<id>/<nonce>, 30s poll) Operator → Implant
Interactive shell Direct libp2p stream (/x/sh/1.0.0) Bidirectional (Ctrl+] to exit)
File download Direct stream Implant → Operator
SOCKS / Portfwd Direct stream (/x/pf/1.0.0) Proxied through libp2p

Security Model

Each implant build embeds a unique Ed25519 keypair so the implant keeps the same PeerID across restarts. All libp2p transports are encrypted (Noise XX or TLS 1.3). Envelopes are signed with the sender's private key. Only the operator with the correct private key can publish to necropolis/<op-id>/commands. Ephemeral session keys are used for direct streams.

Evasion Architecture

When built with --evasion, the implant carries an embedded Zig DLL (valak.dll) compiled from the evasion/ directory. The DLL contains kernel evasion primitives: FreshyCalls SSN extraction, indirect syscall dispatch, sleep obfuscation (Go-side PAGE_NOACCESS), AMSI bypass via hardware breakpoint, ETW three-tier patching, and EDR callback removal.

At runtime, the implant loads the DLL reflectively from memory (no %TEMP% extraction, no LoadLibrary call). A reflective PE loader in the evasion module maps the DLL's sections into memory, resolves imports, applies relocations, and calls the entry point directly. After loading, the DLL stomps its .text section into a legitimate signed Microsoft DLL's memory range (e.g. CryptoAPI.dll, dwrite.dll) so the code executes from inside a Microsoft-signed address range. The original allocation's PE headers (MZ, PE\0\0) are zeroed so memory scanners find no orphaned PE signature.

Evasion Layer Implementation
Module stomping .text copied into a signed Microsoft DLL range; original headers wiped
Sleep obfuscation (PAGE_NOACCESS) DLL .text set PAGE_NOACCESS during idle via VirtualProtect; Go binary .text unaffected
Indirect syscalls FreshyCalls SSN + random ntdll gadget (no call-stack spoofing)
ETW patch Three-tier: NtTraceControl → HWBP (Dr0 + VEH) → RET patch fallback
AMSI bypass VEH + DR0 hardware breakpoint on AmsiScanBuffer; SuspendThread/ResumeThread
EDR callback removal SeDebugPrivilege escalation + NtSetInformationProcess(40)
Build tag -tags=evasion embeds and activates Zig DLL