Difficulty: Beginner

Module 2: Crystal Palace — The PIC Linker

The spec-driven linker that transforms compiled COFF objects into flat position-independent shellcode — no PE headers, no import tables, just executable machine code.

Module Objective

Understand what Crystal Palace is, how the .spec linker-script language works, and how every directive in the Crystal-Loaders loader.spec contributes to the final PIC shellcode blob. By the end of this module you will be able to read any Crystal Palace spec file and explain exactly what it does at each step.

1. What Is Crystal Palace?

Crystal Palace is a Java-based linker and linker-script language designed by Raphael Mudge specifically for building PIC (position-independent code) DLL loaders. It was released in 2025 as part of the Tradecraft Garden project and is designed for building PIC DLL loaders, including User-Defined Reflective Loaders (UDRLs) for Cobalt Strike.

Crystal Palace takes compiled COFF object files (.o) produced by MinGW and transforms them into flat PIC shellcode with embedded resources. The transformation is driven by .spec files — declarative specifications that describe how to assemble, transform, and link the final payload.

Crystal Palace Is NOT a Compiler

This is a common misunderstanding. Crystal Palace does not compile C code. It processes already-compiled COFF object files. The C compiler (MinGW GCC) runs first, producing .o files. Crystal Palace then takes those object files and performs the linking, relocation resolution, and PIC transformation that would normally be handled by a traditional linker like ld — but instead of producing a PE executable, it produces raw position-independent shellcode.

The key insight is that Crystal Palace replaces the traditional Windows linker entirely. Instead of producing a PE with headers, section tables, and import directories, it produces a single contiguous block of executable bytes that can run from any memory address.

2. The Build Pipeline

The Crystal-Loaders build process follows a two-stage pipeline. First the C compiler produces COFF objects, then Crystal Palace links them into PIC shellcode:

Crystal-Loaders Build Pipeline

C Source
loader.c
MinGW GCC
x86_64-w64-mingw32-gcc
COFF Object
loader.x64.o
Crystal Palace
loader.spec
PIC Shellcode
flat binary blob

The Makefile uses MinGW to cross-compile the C source into a COFF object file:

MakefileCC_64 = x86_64-w64-mingw32-gcc
$(CC_64) -DWIN_X64 -shared -masm=intel -Wall -Wno-pointer-arith -c src/loader.c -o bin/loader.x64.o

Compiler Flags Explained

FlagPurpose
-DWIN_X64Defines the WIN_X64 preprocessor macro, enabling x64-specific code paths in the source
-sharedUses DLL calling conventions — important because the loader will eventually load a DLL (Beacon)
-masm=intelUses Intel assembly syntax for any inline ASM blocks (instead of AT&T syntax)
-WallEnables all standard compiler warnings
-Wno-pointer-arithSuppresses pointer arithmetic warnings (needed for the raw memory manipulation in PIC code)
-cCompile only, do not link — Crystal Palace does the linking

The -c flag is the critical one. It tells GCC to stop after compilation and produce a .o object file instead of calling the linker. Crystal Palace handles everything from that point forward.

3. The Spec Language

Crystal Palace specs are declarative scripts that describe the complete linking and transformation process. Here is the actual loader.spec from Crystal-Loaders, annotated line by line:

loader.specname     "Beacon BUD Loader"
describe "PIC loader to pass memory allocation information via Beacon User Data"
author   "Daniel Duggan (@_RastaMouse)"

x64:
    load "bin/loader.x64.o"
        make pic +gofirst +optimize +disco
        dfr "resolve" "ror13"
        mergelib "../libgate.x64.zip"
        mergelib "../libtcg.x64.zip"

    generate $KEY 128

    push $DLL
        xor $KEY
        preplen
        link "dll"

    push $KEY
        preplen
        link "key"

    export

The spec reads top-down like a recipe. First it loads and transforms the compiled code, then it generates a random XOR key, encrypts the DLL payload, and links everything together into the final output. Let's break down each block:

Block 1: Metadata

The first three lines are purely informational — they name the spec, describe what it does, and credit the author. Crystal Palace uses these for identification and logging.

Block 2: Code Transformation (x64:)

The x64: block targets 64-bit architecture. Inside it, load brings in the compiled COFF object, make pic transforms it into position-independent code, dfr rewrites all API import references for runtime resolution, and mergelib pulls in LibGate and LibTCG library code.

Block 3: Data Preparation

generate $KEY 128 creates 128 bytes of cryptographically random data. The push $DLL block pushes the Beacon DLL onto the data stack, XORs it with the key, prepends a 4-byte length header, and links it into a section named "dll". The key itself gets the same length-prefix treatment and is linked into a section named "key".

Block 4: Export

export emits the final PIC shellcode — the code blob with all linked data sections appended.

4. Directive Reference

Every Crystal Palace directive available in the spec language, with its purpose and usage:

DirectivePurposeExample
nameNames the spec for identificationname "Beacon BUD Loader"
describeHuman-readable descriptiondescribe "PIC loader..."
authorAuthor attributionauthor "Daniel Duggan"
x64:Architecture block (x64-only in Crystal-Loaders)x64:
loadLoad a compiled COFF object file into the linkerload "bin/loader.x64.o"
make picTransform the loaded COFF into position-independent codemake pic +gofirst +optimize +disco
+gofirstPlace the go() function at byte offset 0 of the PIC blobFlag on make pic
+optimizeEnable code size optimization passesFlag on make pic
+discoRandomize function order in the PIC blob (the first function is preserved when used with +gofirst)Flag on make pic
dfrDynamic Function Resolution — rewrite all __imp_MODULE$Function references to call a resolverdfr "resolve" "ror13"
mergelibMerge an external library's object files into the current buildmergelib "../libgate.x64.zip"
generateGenerate random data (e.g., an XOR key) and store in a variablegenerate $KEY 128
pushPush data onto the linker's data stack for processingpush $DLL
xorXOR the top-of-stack data with a named keyxor $KEY
preplenPrepend a 4-byte little-endian length prefix to the top-of-stack datapreplen
linkLink data into a named section of the PIC bloblink "dll"
exportEmit the final PIC shellcode as the build outputexport
patchWrite a value into a named symbol location (used in postex loader)patch "pGetModuleHandle" $GMH

The Data Stack Model

Crystal Palace uses a stack-based data model similar to Forth or PostScript. The push directive places data on top of the stack. Subsequent operations like xor and preplen modify the top-of-stack in place. Finally, link pops the data off and attaches it to the PIC blob as a named section. This stack model allows arbitrary data transformations to be chained declaratively.

5. Dynamic Function Resolution (DFR)

DFR is the mechanism that makes PIC code possible. In normal PE executables, the loader resolves API imports at load time using the Import Address Table. PIC shellcode has no IAT, so API addresses must be resolved at runtime by the shellcode itself.

The Problem

In the C source, Windows API functions are declared as DLL imports using the MODULE$Function naming convention:

C// These declarations tell the compiler: "this function comes from an external DLL"
// The '$' separator tells Crystal Palace: module = KERNEL32, function = VirtualAlloc
DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc(LPVOID, SIZE_T, DWORD, DWORD);
DECLSPEC_IMPORT BOOL   WINAPI KERNEL32$VirtualProtect(LPVOID, SIZE_T, DWORD, PDWORD);
DECLSPEC_IMPORT int    WINAPI MSVCRT$strncmp(const char *, const char *, SIZE_T);

After compilation, these become __imp_KERNEL32$VirtualAlloc symbols in the COFF object's symbol table. In a normal linking process, these would be resolved against import libraries (.lib files). Crystal Palace does something fundamentally different.

The Solution: DFR Rewriting

The dfr "resolve" "ror13" directive tells Crystal Palace to:

  1. Find every instruction in the code that references an __imp_MODULE$Function symbol
  2. Extract the module name and function name from the symbol
  3. Compute the ROR13 hash of both the module name and function name
  4. Rewrite each reference to instead call the user-supplied resolve() function, passing the two hash values as DWORD arguments

DFR Rewriting Process

COFF symbol
__imp_KERNEL32$VirtualAlloc
Crystal Palace
dfr "resolve" "ror13"
call resolve(
0x6A4ABC5B, 0xE553A458)

The resolve() function that Crystal Palace calls into is defined in loader.c:

C (loader.c)char * resolve(DWORD modHash, DWORD funcHash)
{
    char * mod = findModuleByHash(modHash);
    return findFunctionByHash(mod, funcHash);
}

findModuleByHash walks the PEB InMemoryOrderModuleList (the linked list of loaded DLLs maintained by the Windows loader), hashes each module name using the ROR13 algorithm, and compares against moduleHash. findFunctionByHash then walks the found module's Export Address Table, hashes each exported function name, and returns the matching function pointer.

Two Resolution Methods

ror13 Mode
  • Replaces module + function names with 4-byte ROR13 hashes
  • Two DWORD arguments passed to resolve()
  • Strings never appear in the final shellcode
  • Slightly harder to reverse-engineer
  • Used by the main UDRL loader
strings Mode
  • Embeds actual ASCII module and function name strings
  • Two char* pointers passed to resolve()
  • Plain-text strings visible in the shellcode
  • Easier to debug and develop with
  • Used by the postex loader (dfr "resolve" "strings")

Why Not Always Use ror13?

The postex loader uses "strings" mode because it needs to resolve GetModuleHandle and GetProcAddress — two functions whose addresses are patched in via the patch directive from an Aggressor script that already has access to the running Beacon. In this context, the resolver function expects string pointers, not hashes, because it delegates to the real GetProcAddress rather than walking export tables manually.

6. The PIC Transformation

The make pic directive is where the real magic happens. It converts a standard COFF object file — which assumes fixed memory addresses — into a position-independent blob that can execute from any address.

What make pic Actually Does

  1. Resolve all internal relocations — Function calls between different .text sections within the same object are patched to use relative offsets
  2. Flatten all sections — The separate .text, .data, and .rdata sections are merged into a single contiguous byte stream
  3. Convert absolute addresses to RIP-relative — Any remaining absolute address references are rewritten to use x64 RIP-relative addressing so the code works regardless of where it is loaded
  4. Place go() at offset 0 — With the +gofirst flag, the entry point function is positioned at the very start of the blob so the caller can simply jump to byte 0
  5. Optimize — With +optimize, Crystal Palace removes dead code and minimizes the output size
  6. Randomize function order — With +disco, Crystal Palace randomizes the order of functions in the PIC blob (the first function is preserved when used with +gofirst)

COFF to PIC Transformation

COFF Object
.text + .data + .rdata
+ relocations + symbols
make pic
resolve, flatten,
RIP-relativize
Flat PIC Blob
No PE headers
No section table
No import directory

What the Final Output Looks Like

The result of the entire spec pipeline is a raw byte stream with this layout:

There are no PE headers, no section tables, no import directories, and no relocations left to process. The shellcode finds its own data sections at runtime by using RIP-relative addressing to locate the linked sections after the code body.

This is why Crystal Palace-based loaders evade static analysis tools that look for PE artifacts. The payload is not a PE file at any point during execution — it is raw machine code with embedded encrypted data.

Module 2 Knowledge Check

Q1: What does the dfr directive do in a Crystal Palace spec?

The dfr (Dynamic Function Resolution) directive scans the COFF object for all __imp_MODULE$Function symbols and rewrites each reference to call the user-supplied resolver function instead. In "ror13" mode, the module and function names are replaced with their ROR13 hashes. This eliminates all static import references from the PIC shellcode.

Q2: What does the +gofirst flag ensure?

The +gofirst flag tells Crystal Palace to place the go() entry-point function at byte offset 0 of the final PIC blob. This means the caller can execute the shellcode by simply jumping to the start of the buffer — no need to parse headers or look up an entry point. The other flags (+optimize and +disco) handle size optimization and function order randomization respectively.

Q3: What is the difference between "ror13" and "strings" DFR methods?

In "ror13" mode, Crystal Palace computes the ROR13 hash of the module name and function name, then passes those two DWORD hash values to the resolver function. In "strings" mode, Crystal Palace embeds the actual ASCII strings in the shellcode and passes pointers to them instead. The ror13 method avoids having readable API names in the shellcode, while the strings method is used when the resolver delegates to GetProcAddress directly (as in the postex loader).