Files
Necropolis-C2/docs/architecture.md
T
2026-07-07 04:39:43 +01:00

183 lines
11 KiB
Markdown

# 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 |