183 lines
11 KiB
Markdown
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 |
|