Cracking the Crystal Palace
If you follow this blog, you'll know I've been posting about Crystal Palace a LOT recently, mostly from an attack perspective. Today, I thought I'd channel my blue-teamer alter ego and look at Crystal Palace from a defence perspective. That is, are there any easy "tells" that a capability has been loaded by Crystal Palace?
To target Crystal Palace artefacts specifically, I only looked at the "stuff" that it inserts into the final assembly, rather than any code that can be written (or changed) by the user (including LibTCG).
Three possibilities jumped out:
__tag_func intrinsic
This intrinsic is used with the exportfunc command and PicoGetExport function. It's essentially a way for a PICO to export a function and have it be callable by a loader.
/* this is the linker intrinsic to get the tag for our exported function */
int __tag_myfunc ( );
void go ( )
{
...
/* call exported function in PICO */
PicoGetExport ( pico_src, pico_dst, __tag_myfunc ( ) ) );
}Crystal Palace generates a random 'tag' integer at link-time, which is used to identify the export. However, because this is only a single integer, there's not really enough there to identify anything specifically related to Crystal Palace.
DFR resolver function
Dynamic Function Resolution (DFR) has become a popular convention for referencing APIs in COFFs. Here's an example:
WINBASEAPI LPVOID WINAPI KERNEL32$VirtualAlloc ( LPVOID, SIZE_T, DWORD, DWORD );When turning a COFF into PIC, Crystal Palace requires a 'resolver' function that will resolve these references. The user is free to do this however they want, but the Tradecraft Garden samples do it by walking the PEB & EAT.
#include "tcg.h"
FARPROC resolve ( DWORD mod_hash, DWORD func_hash )
{
HANDLE module = findModuleByHash ( mod_hash );
return findFunctionByHash ( module, func_hash );
}A loader will typically use DFR to load the given capability into memory:
void go ( )
{
char * dll_src = GETRESOURCE ( _DLL_ );
DLLDATA dll_data;
ParseDLL ( dll_src, &dll_data );
char * dll_dst = KERNEL32$VirtualAlloc ( NULL, SizeOfDLL ( &dll_data ), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
...
}When building the final assembly, Crystal Palace rewrites DFR references to calls to this resolve function. So this:
488B0500000000 mov __imp_KERNEL32$VirtualAlloc,%rax
FFD0 call *%raxBecomes this:
B95BBC4A6A mov $0x6A4A`BC5B,%ecx
BA54CAAF91 mov $0x91AF`CA54,%edx
E8F0000000 call resolveUnfortunately, a generic mov ecx, mov edx, call pattern is wayyyyy too generic to be reliable.
__resolve_hook() intrinsic
Don't worry - I saved the best for last. The __resolve_hook() intrinsic works with the addhook command, and is used to hook the IAT of a DLL as it's being loaded. The idea is to hook functions in a DLL and redirect them to another function in a memory-persistent PIC (or PICO), where evasion tradecraft, such as call stack spoofing, can be implemented.
FARPROC WINAPI _GetProcAddress ( HMODULE hModule, LPCSTR lpProcName )
{
FARPROC result = __resolve_hook ( ror13hash ( lpProcName ) );
if ( result != NULL ) {
return result;
}
return GetProcAddress( hModule, lpProcName );
}Each hook is defined in a spec file, e.g:
addhook "KERNEL32$VirtualAlloc" "_VirtualAlloc"Crystal Palace dynamically inserts every hook at link-time, so:
89C1 mov %eax,%ecx
E800000000 call __resolve_hookBecomes something like:
89C1 mov %eax,%ecx
81F9A8A24DBC cmp $0x91AF`CA54,%ecx
7509 jne 0x0000`0000`0000`0035
488D055F010000 lea _VirtualAlloc,%rax
EB03 jmp 0x0000`0000`0000`0038This sequence of instructions is much more concrete and allows us to build a corresponding YARA rule. We obviously want to ignore the specific hashes and addresses, and look for a generic mov, cmp, jne, lea, jmp pattern.
My YARA rules looks like this:
rule Windows_Shellcode_CrystalPalace_HookIntrinsic {
meta:
author = "Rasta Mouse"
description = "Identifies Crystal Palace's __resolve_hook() intrinsic"
threat_name = "Windows.Shellcode.CrystalPalace"
creation_date = "2025-11-29"
last_modified = "2025-11-29"
arch_context = "x86"
scan_context = "file, memory"
os = "windows"
license = "bsd"
strings:
$a = { 89 C1 81 F9 ?? ?? ?? ?? 75 ?? 48 8D ?? ?? ?? ?? ?? E? ?? }
condition:
all of them
}Testing this rule against the Hooking TCG sample, gives us a nice hit :)

+mutate?
Crystal Palace's code mutator introduces noise to make the final output more resilient against signature techniques. It does this by replacing some instructions with one or more instructions that do the same thing.
However, based on my testing, this option is only inserting alternate instructions on each side of this pattern, not within it. Therefore, at the time of writing, this seems like a pretty reliable way of detecting memory-resident PIC that's using Crystal Palace's __resolve_hook() intrinsic.