update readme
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# Implant Design
|
||||
|
||||
## Overview
|
||||
|
||||
The necropolis implant is a lightweight Go binary that connects to the libp2p network and
|
||||
communicates with its operator entirely through direct p2p streams, with pubsub as a
|
||||
fallback. It has no hardcoded server IPs or DNS names — only a cryptographic reference
|
||||
to the operator's public key.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
implant/
|
||||
├── main.go # Entry point
|
||||
└── core/ # All implant runtime logic
|
||||
├── agent.go # Core lifecycle, beacon loop, command dispatch, handlers
|
||||
├── shell.go # Shell path selection (/bin/bash, cmd.exe, etc.)
|
||||
├── shell_unix.go # PTY-based interactive shell (Linux/macOS)
|
||||
├── shell_windows.go # ConPTY-based interactive shell (Windows)
|
||||
├── portfwd.go # TCP port forwarding tunnel
|
||||
├── socks.go # SOCKS proxy tunnel (identical to portfwd currently)
|
||||
├── ps.go # Process listing (Linux /proc, Windows tasklist)
|
||||
├── antivm.go # VM detection framework (cross-platform, build-tag gated)
|
||||
├── antivm_linux.go # 50+ Linux-specific VM detection techniques
|
||||
├── antivm_darwin.go # macOS-specific VM detection techniques
|
||||
├── antivm_windows.go# Windows-specific VM detection techniques
|
||||
├── antivm_stub.go # No-op stub when compiled without `-tags=antivm`
|
||||
├── embed_evasion.go # Embeds valak.dll via //go:embed
|
||||
├── evasion_windows.go # Evasion loader + sleep obfuscation
|
||||
├── valak.dll # Zig evasion DLL (FreshyCalls, indirect syscalls, AMSI/ETW bypass, module stomping)
|
||||
├── evasion_stub.go # No-op stub when compiled without `-tags=evasion`
|
||||
├── agent_relay.go # Implant-to-implant relay advertisement and routing
|
||||
├── embedded_pubkey.go # Generated: operator's public key embedded at build time
|
||||
├── embedded_implant_key.go # Generated: implant's unique keypair embedded at build time
|
||||
├── embedded_authtoken.go # Generated: operator-specific auth token embedded at build time
|
||||
└── embedded_boxpubkey.go # Generated: NaCl box public key embedded at build time
|
||||
```
|
||||
|
||||
## Build Process
|
||||
|
||||
1. Operator runs `necropolis generate --os linux --arch amd64` (from CLI or standalone)
|
||||
2. `server/core/generate.go` unpacks the embedded source tree from `server/core/embedsrc/implant_src.tar.gz`
|
||||
3. Injects the operator's public key and a fresh implant Ed25519 keypair into the source tree
|
||||
4. Optionally applies build tags (`-tags=antivm`, `-tags=evasion`) and quiet-mode stubs
|
||||
5. Cross-compiles with `CGO_ENABLED=0` for the target OS/arch
|
||||
6. When `--evasion` is set, embeds `valak.dll` (1.5 MB Zig kernel evasion DLL) into the
|
||||
binary via `//go:embed`. The DLL is loaded at runtime from memory and provides FreshyCalls
|
||||
syscall dispatch, AMSI/ETW bypass, module stomping, EDR callback removal.
|
||||
7. Applies garble obfuscation (`--obfuscate`), UPX compression (`--upx`), and strip (`--quiet`)
|
||||
8. The resulting binary is self-contained — no source tree, no external dependencies
|
||||
|
||||
Each build generates a unique implant keypair. The implant keeps the same PeerID across restarts.
|
||||
|
||||
## Core Lifecycle
|
||||
|
||||
### Startup
|
||||
|
||||
1. Load operator's public key from embedded `embedded_pubkey.go`
|
||||
2. Load implant's private key from embedded `embedded_implant_key.go`
|
||||
3. Derive libp2p PeerID from implant public key
|
||||
4. Initialise libp2p host with:
|
||||
- TCP + WebSocket transports (no UDP/QUIC for sandbox compatibility)
|
||||
- AutoNAT + relay client for NAT traversal
|
||||
- DHT client for peer discovery
|
||||
- GossipSub pubsub
|
||||
5. If `--peer` flag is provided, connect to operator directly
|
||||
6. If `--wss` flag is provided, connect via WebSocket Secure (TLS over TCP/443)
|
||||
7. Start DHT discovery loop to find operator via rendezvous namespace
|
||||
8. Start DHT dead-drop poll loop (`pollDHTCmdLoop`) — polls `/necropolis/cmd/<id>/<nonce>` every 30s for signed command envelopes
|
||||
9. Once connected, open persistent beacon stream (`/bc/1.0.0`) to operator
|
||||
10. Register via signed `Z1` (beacon register with system metadata)
|
||||
11. Start beacon loop and cover traffic loop
|
||||
|
||||
### Main Loop
|
||||
|
||||
```
|
||||
beaconLoop (every 10-15s):
|
||||
Send Z1 (beacon register) on persistent /bc/1.0.0 stream
|
||||
Sleep(interval + random(jitter))
|
||||
|
||||
streamKeepaliveLoop (every 5s):
|
||||
Write MsgTypeCover on persistent stream to prevent relay idle timeout
|
||||
|
||||
coverTrafficLoop (every 4-7s):
|
||||
Publish random noise to the beacon pubsub topic
|
||||
|
||||
discoverOperatorLoop (every 15s):
|
||||
Find operator via DHT rendezvous
|
||||
If beacon stream is nil, reconnect
|
||||
|
||||
pollDHTCmdLoop (every 30s):
|
||||
Poll DHT dead-drop at /necropolis/cmd/<id>/<nonce> for signed command envelopes
|
||||
Process through existing command handler
|
||||
|
||||
commandStream handler:
|
||||
Read length-prefixed envelope from /bc/1.0.0/cmd stream
|
||||
Verify Ed25519 signature against embedded operator pubkey
|
||||
Dispatch by message type
|
||||
Send result on persistent beacon stream
|
||||
```
|
||||
|
||||
### Session Mode (Shell, Portfwd, SOCKS)
|
||||
|
||||
```
|
||||
on incoming stream:
|
||||
Read target/winsize from stream header
|
||||
Establish local connection / PTY
|
||||
Bidirectional io.Copy between libp2p stream and local resource
|
||||
On disconnect or Ctrl+]: cleanup and return
|
||||
```
|
||||
|
||||
## Command Handlers
|
||||
|
||||
All command handlers follow the same pattern:
|
||||
|
||||
1. Deserialise protobuf request from envelope
|
||||
2. Execute operation (local filesystem, process execution, etc.)
|
||||
3. Serialise protobuf result
|
||||
4. Send result via `sendResult()` — tries persistent stream first, falls back to pubsub
|
||||
|
||||
| Handler | Protobuf | Operation |
|
||||
|---|---|---|
|
||||
| `handlePs` | Z12 → Z13 | List processes via `/proc` (Linux) or `tasklist` (Windows) |
|
||||
| `handleLs` | Z16 → Z17 | Read directory entries |
|
||||
| `handleCd` | Z19 → Z21 | `os.Chdir()` |
|
||||
| `handlePwd` | Z20 → Z21 | `os.Getwd()` |
|
||||
| `handleExecute` | Z14 → Z15 | `exec.CommandContext()` with optional output capture |
|
||||
| `handleDownload` | Z22 → Z23 | `os.ReadFile()` with 100MB limit |
|
||||
| `handleUpload` | Z24 → Z25 | `os.WriteFile()` with optional overwrite and 100MB limit |
|
||||
| `handleKill` | (none) → (none) | Log stack trace, `os.Exit(0)` |
|
||||
| `handleScreenshot` | Z2 → Z3 | Returns "not implemented" stub |
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Feature | Windows | Linux | macOS |
|
||||
|---|---|---|---|
|
||||
| TCP transport | ✓ | ✓ | ✓ |
|
||||
| WebSocket transport | ✓ | ✓ | ✓ |
|
||||
| Process list | ✓ (tasklist) | ✓ (/proc) | ✓ (stub) |
|
||||
| File ops | ✓ | ✓ | ✓ |
|
||||
| Interactive shell | ✓ (ConPTY) | ✓ (PTY) | ✓ (PTY) |
|
||||
| Port forwarding | ✓ | ✓ | ✓ |
|
||||
| VM detection | 50+ checks | 50+ checks | 8 checks |
|
||||
| Kernel evasion | ✓ (DLL) | — | — |
|
||||
|
||||
## Evasion System
|
||||
|
||||
When compiled with `--evasion`, the implant includes a Zig DLL (`valak.dll`) built from
|
||||
`evasion/` in the source tree. The DLL is embedded via `//go:embed valak.dll` at compile
|
||||
time and loaded from memory via reflective PE loader at runtime. After loading, the DLL
|
||||
stomps its `.text` section into a legitimate signed Microsoft DLL (e.g. `CryptoAPI.dll`,
|
||||
`dwrite.dll`), frees the original allocation, and wipes its PE headers. Once stomped, the
|
||||
code executes from inside a Microsoft-signed address range with no orphaned PE signature.
|
||||
It provides kernel-level evasion primitives ported from the Tenshu C2 framework:
|
||||
|
||||
| Technique | Implementation | Persistence |
|
||||
|---|---|---|---|
|
||||
| Module stomping | `.text` copied into signed MS DLL range; original headers wiped | On load |
|
||||
| Image header wiping | MZ/PE\0\0 zeroed at original allocation after stomp | On load |
|
||||
| Sleep obfuscation (PAGE_NOACCESS) | DLL .text set PAGE_NOACCESS via VirtualProtect during idle | Per sleep cycle |
|
||||
| Indirect syscalls | FreshyCalls + HAL's Gate SSN extraction, random ntdll gadget | Available via DLL call |
|
||||
| ETW patch | Three-tier: NtTraceControl → HWBP → RET patch | On init |
|
||||
| AMSI bypass | DR0 hardware breakpoint + VEH with SuspendThread/ResumeThread | Installed on init |
|
||||
| EDR callback removal | SeDebugPrivilege + NtSetInformationProcess(40) | On init |
|
||||
If the DLL fails to load, evasion is skipped. No fallback to pure Go implementations — the DLL is the only evasion path.
|
||||
|
||||
## Transport Configuration
|
||||
|
||||
The implant explicitly disables libp2p's default transports and enables only:
|
||||
|
||||
1. **WebSocket** — primary transport, works through most proxies
|
||||
2. **WebSocket Secure** — WSS is a dial address (`/dns4/host/tcp/443/wss/p2p/peerid`), not a separate transport layer; it uses the same WebSocket transport over TLS
|
||||
3. **TCP** — fallback for direct connections
|
||||
|
||||
No UDP, no QUIC, no multicast. This maximises sandbox/container compatibility.
|
||||
|
||||
The implant always connects with `network.WithAllowLimitedConn` to work through relay
|
||||
circuits when direct connections are unavailable.
|
||||
|
||||
When started with `-wss`, the implant connects via `/dns4/<host>/tcp/443/wss/p2p/<peer-id>`
|
||||
before falling back to DHT discovery and dead-drop polling.
|
||||
Reference in New Issue
Block a user