██████╗ ███████╗██████╗ ██████╗██████╗
██╔══██╗██╔════╝██╔══██╗██╔════╝╚════██╗
██████╔╝█████╗ ██║ ██║██║ █████╔╝
██╔══██╗██╔══╝ ██║ ██║██║ ██╔═══╝
██║ ██║███████╗██████╔╝╚██████╗███████╗
╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝╚══════╝
A fileless, containerized red team Command & Control platform
Flutter · Go · SurrealDB · bwrap · memfd
redc2 is an end-to-end red teaming platform inspired by Havoc C2 and Core Impact, built for operators who demand full control over their tooling stack. From agent deployment to post-exploitation, every component is designed around a single principle — leave no trace. Tools never touch disk, environments are containerized and disposable, and all operational state lives in an embedded database that starts and dies with the operator. The result is a self-contained offensive platform that runs entirely from a single binary, pairs a modern Flutter UI with a Go backend over a zero-overhead FFI bridge, and executes arbitrary tooling through fileless memory techniques that bypass conventional endpoint defenses.
┌─────────────────────────────────────────────────────────────────────────────┐
│ FLUTTER UI (redc2) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ Sessions │ │ Listeners │ │ Exploits │ │ Footprint │ │
│ │ (Terminal) │ │ (Bubbles) │ │ (Tools UI) │ │ (Tools UI) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └───────┬────────┘ │
│ └─────────────────┴──────────────────┴──────────────────┘ │
│ │ │
│ FFI (dart:ffi) │
└────────────────────────────────────┼────────────────────────────────────────┘
│
┌─────────────▼─────────────┐
│ libh.so │
│ (Go → C shared library) │
│ │
│ GoInit() GoGetTools() │
│ GoRunExploit() etc. │
└─────────────┬─────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌───────────▼──────────┐ ┌───────▼──────────┐ ┌────────▼────────────┐
│ SurrealDB │ │ SessionBubble │ │ Tool Executor │
│ (embedded, memory) │ │ HTTP C2 Server │ │ (memfd + bwrap) │
│ │ │ │ │ │
│ tools sessions │ │ /register │ │ ELF → memfd_create │
│ listeners commands │ │ /command-var │ │ /proc/self/fd/N │
│ outputs exploits │ │ /out-var │ │ bwrap rootfs env │
└──────────────────────┘ └──────────────────┘ └─────────────────────┘
No binary ever touches disk. Tools stored as base64 in SurrealDB are decoded entirely in RAM and executed through a Linux anonymous file descriptor.
┌──────────────────────────────────────────────────────────────────┐
│ TOOL LIFECYCLE — zero disk contact │
│ │
│ SurrealDB Go runtime Linux kernel │
│ ┌──────────┐ ┌─────────────┐ ┌────────────────┐ │
│ │binary_b64│──b64──▶│ base64.Decode│ │ memfd_create() │ │
│ └──────────┘ └──────┬──────┘ └───────┬────────┘ │
│ │ []byte │ fd=N │
│ └──────── Write ──────▶│ │
│ │ │
│ exec via │ │
│ /proc/self/fd/N ◀──┘ │
│ │
│ ✓ Never written to /tmp, /var, or any mountpoint │
│ ✓ fd auto-closed after exec (MFD_CLOEXEC) │
│ ✓ Kernel >= 3.17 required │
└──────────────────────────────────────────────────────────────────┘
Each tool category runs inside its own Debian bookworm-slim rootfs, extracted to /dev/shm (tmpfs) and isolated with bubblewrap. No Alpine, no musl — full glibc compatibility guaranteed.
┌─────────────────────────────────────────────────────────────────────┐
│ EXECUTION ENVIRONMENTS │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ env-web │ │ env-nxc │ │ env-impacket │ │
│ │ ────────── │ │ ────────── │ │ ────────────────────── │ │
│ │ nikto │ │ netexec │ │ secretsdump │ │
│ │ sqlmap │ │ crackmapexec│ │ psexec / wmiexec │ │
│ │ gobuster │ │ smbclient │ │ GetUserSPNs │ │
│ └──────────────┘ └──────────────┘ └──────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ env-certipy │ │ env-ruby │ │ env-perl │ │
│ │ ────────── │ │ ────────── │ │ ────────────────────── │ │
│ │ certipy-ad │ │ WinRM gems │ │ ldap-utils │ │
│ │ PKI attacks │ │ Metasploit │ │ Net::LDAP │ │
│ └──────────────┘ └──────────────┘ └──────────────────────────┘ │
│ │
│ All environments: │
│ • Base: debian:bookworm-slim (docker export → flat tar) │
│ • Mount: /dev/shm/.tool-cache/<env> (tmpfs, never persisted) │
│ • Exec: bwrap --ro-bind rootfs / -- /tool <args> │
└─────────────────────────────────────────────────────────────────────┘
Tools are not compiled into the binary. They live in SurrealDB and are loaded at runtime from operator-controlled URLs.
ADDING A TOOL (from the Exploit tab)
Operator Flutter UI Go backend
│ │ │
│ paste binary URL │ │
│ paste schema URL │ │
│─────────────────────────▶│ │
│ │ GoAddTool(b64, sch) │
│ │──────────────────────▶│
│ │ │── HTTP GET binary.json
│ │ │── HTTP GET schema.json
│ │ │── merge manifests
│ │ │── UpsertTool → SurrealDB
│ │◀──────────────────────│
│ tool appears in UI │ │
│◀─────────────────────────│ │
binary.json schema.json
┌───────────────────────┐ ┌────────────────────────────┐
│ { │ │ { │
│ "id": "nxc", │ │ "id": "nxc", │
│ "version": "1.x", │ │ "name": "NetExec", │
│ "binary_b64": "...", │ │ "category": "lateral", │
│ "data_tar_b64": "..."│ │ "argv_template": [ │
│ } │ │ "{binary}", "{target}", │
└───────────────────────┘ │ "-u", "{user}", ... │
│ ], │
↑ ELF + optional rootfs tar.gz │ "fields": [ ... ] │
│ } │
└────────────────────────────┘
All runtime state — sessions, listeners, commands, terminal output, tool results — persists in an embedded SurrealDB instance that starts automatically (launched into a memfd, no disk install needed).
┌──────────────────────────────────────────────────────────────┐
│ SURREALDB SCHEMA │
│ │
│ sessions listeners commands outputs │
│ ─────────── ────────── ──────────── ─────────── │
│ id name port port │
│ remote_addr port tool data │
│ port protocol args timestamp │
│ last_seen status status │
│ status created_at created_at exploits │
│ created_at executed_at ────────── │
│ tool_name │
│ adapters tools params │
│ ───────── ─────────────────────────── success │
│ name id · name · description output │
│ ip category · placement │
│ updated_at binary_b64 · data_tar_b64 │
│ argv_template · fields │
│ version · added_at │
│ │
│ ⚑ Record IDs with hyphens (tools:env-nxc) must be │
│ backtick-escaped in SQL; use type::string(id) to │
│ coerce RecordID → plain string server-side. │
└──────────────────────────────────────────────────────────────┘
The entire backend is compiled into a single C shared library. Dart binds to it with zero network overhead.
Dart/Flutter side Go/CGo side
────────────────────────────────────────────────────────────────
FfiBridge.instance.runExploit( GoRunExploit(toolName, paramsJson)
toolName: "nxc", │
params: {"target": "10.0.0.1"} ├─ json.Unmarshal(params)
) ├─ RunToolInMemory(id, params)
│ │ ├─ GetTool(id) ← SurrealDB
│ Pointer<Utf8> │ ├─ memfdCreate + write ELF
│◀──────────────── *C.char ────────────│ ├─ extractTarGzB64 → /dev/shm
│ │ ├─ BuildArgv(template, params)
result.toDartString() │ └─ exec.Command(argv...).Output()
jsonDecode(...) ├─ CreateExploit(result) ← SurrealDB
└─ jsonStr({success, message, output})
Memory rule: Go allocates → Go frees.
GoFreeString() called from Dart after every string consume.
┌──────────────────────────────────────────────────────────────────────┐
│ ● Havoc C2 Platform ⚙ ? │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ╔════════════════════════════════════════════════════════════╗ │
│ ║ Network Graph (force-directed, drag/zoom/pan) ║ │
│ ║ ║ │
│ ║ [C&C] ──── [session1] ──── [session2] ║ │
│ ║ ╲──────────────────────────────── [E&E] ║ │
│ ╚════════════════════════════════════════════════════════════╝ │
│ ← drag divider to resize ────────────────────────────────────────→ │
│ │
│ [ ◈ INTERNAL ] [ ◈ EXTERNAL ] │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ INTERNAL EXTERNAL │
│ ┌────────────────────────┐ ┌────────────────────────┐ │
│ │ Sessions │ Listeners │ │ Exploits │ Footprint │ │
│ │ │ │ │ │
│ │ ┌──────────────────┐ │ │ TOOLS CONFIG │ │
│ │ │ 10.0.0.55 │ │ │ ──────────────┬─────── │ │
│ │ │ User: CORP\admin │ │ │ ▼ scanning │ nmap │ │
│ │ │ [ONLINE] >_ │ │ │ nmap │ ───── │ │
│ │ └──────────────────┘ │ │ ▶ lateral │ target │ │
│ │ │ │ ▶ ad-attacks │ │ │
│ │ + Create Listener │ │ ▶ web │ [ RUN ]│ │
│ └────────────────────────┘ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Agent SessionBubble (Go HTTP)
─────────────────────────────────────────────────────────
POST /register?id=agent-001
CreateSession(id, remoteAddr)
GET /command-var ← poll every N seconds
← Header: tool: shell
← Header: args: whoami
POST /out-var?out=NT AUTHORITY\SYSTEM
AppendOutput(port, data)
GET /get-var ← operator polls from Flutter
← "NT AUTHORITY\SYSTEM\n..."
GET /Rubeus.exe ← tool delivery
← 302 + binary bytes
| Layer | Technology | Role |
|---|---|---|
| UI | Flutter / Dart | Cross-platform desktop GUI |
| FFI | dart:ffi + package:ffi |
Zero-overhead Go↔Dart bridge |
| Backend | Go (CGo, c-shared) |
C2 logic, HTTP bubbles, tool exec |
| Database | SurrealDB v2 (embedded) | Persistent operational state |
| Execution | memfd_create(2) |
Fileless ELF loading |
| Isolation | bwrap (bubblewrap) |
Per-tool Debian rootfs containers |
| Rootfs | docker export (flat tar) |
Portable glibc environments |
| Comms | Raw HTTP over TCP | Agent ↔ SessionBubble protocol |
# Go + CGo toolchain
go version # >= 1.21
gcc --version # CGo requires a C compiler
# Flutter SDK
flutter --version # >= 3.x
# Runtime
bwrap --version # bubblewrap for env isolationcd server/
./lib_build.sh
# → server/libh.so# Copy the library where Flutter can find it
cp server/libh.so server/redc2/
cd server/redc2/
flutter run -d linux# Example: impacket environment
docker run --name tmp-env debian:bookworm-slim bash -c \
"apt-get update && apt-get install -y python3-impacket && exit"
docker export tmp-env | gzip > env-impacket.tar.gz
docker rm tmp-env
# Base64-encode for the binary manifest
base64 -w 0 env-impacket.tar.gz > env-impacket.b64■ SurrealDB record IDs with hyphens (e.g. tools:env-nxc)
must be backtick-escaped in raw SQL strings:
SELECT * FROM `tools:env-nxc`
Go values passed to DB.Query() stay clean (no backticks).
■ type::string(id) is the correct SurrealDB syntax to coerce
a RecordID Thing into a plain string server-side.
Do NOT use: string(meta::id(id))
■ surrealdb.go v0.3.2 does not reliably unmarshal RecordID
into Go string fields. Always coerce server-side.
■ When SurrealDB returns status=ERR, the Go handler must
short-circuit before attempting to decode the result field.
■ Debian bookworm-slim (glibc) resolved all crypt_r / statx
symbol errors that were caused by Alpine (musl) rootfs images.
■ memfd_create syscall number on linux/amd64 = 319
Requires kernel >= 3.17.
This project is intended for authorized penetration testing and red team operations only.
Use against systems without explicit written permission is illegal.
The authors assume no liability for misuse.
Built for operators, by operators.
[redc2] ██████████████░░░░░░ 72% operational