August 9, 2026
Writing Your First PIC Shellcode with Crystal Palace
The previous post covered what Crystal Palace is and why it exists. This one is hands-on. We’re building two PICOs from scratch, a…

By S12 - 0x12Dark Development
7 min read
The previous post covered what Crystal Palace is and why it exists. This one is hands-on. We're building two PICOs from scratch, a MessageBox and a CreateProcess launcher, and walking through every file you need to produce working PIC shellcode by the end
If you haven't read the first post, the short version: Crystal Palace is a linker that takes normal COFF object files and transforms them into position independent code. You write C, compile it with MinGW, and Crystal Palace handles the PIC conversion. No manual PEB walks in your source, no fighting relocations, no stack strings. The linker does it
Want to go deeper into Windows offensive development?
Video-based courses from beginner to advanced, and text-based modules (mini courses) with new releases constantly. Plus a technique database with 100+ real techniques updated weekly, and custom C2 agents and consulting for teams.
0x12 Dark Development Skip to content Join our offensive development courses and modules, and explore the techniques database with 100+ real…
Two Things Every PICO Needs
Every Crystal Palace project that uses Win32 APIs needs two things that live outside your actual capability code: a resolve.c bridge and the LibTCG library. These are not per-project, you write them once and reuse them across everything
resolve.c
Crystal Palace's DFR system, the mechanism that resolves MODULE$Function calls to real API addresses at runtime, expects a single function called resolve that takes two hashes and returns a pointer. LibTCG provides the underlying lookup functions, but it splits that into two separate calls: findModuleByHash and findFunctionByHash. The bridge between them is something you write:
typedef void * PVOID;
typedef unsigned long DWORD;
PVOID findModuleByHash(DWORD hash);
PVOID findFunctionByHash(PVOID hModule, DWORD hash);
PVOID resolve(DWORD moduleHash, DWORD funcHash) {
PVOID hMod = findModuleByHash(moduleHash);
if (!hMod) return 0;
return findFunctionByHash(hMod, funcHash);
}typedef void * PVOID;
typedef unsigned long DWORD;
PVOID findModuleByHash(DWORD hash);
PVOID findFunctionByHash(PVOID hModule, DWORD hash);
PVOID resolve(DWORD moduleHash, DWORD funcHash) {
PVOID hMod = findModuleByHash(moduleHash);
if (!hMod) return 0;
return findFunctionByHash(hMod, funcHash);
}Compile it once, keep the object file somewhere reusable:
x86_64-w64-mingw32-gcc -c resolve.c -o resolve.x64.o \
-O1 -nostdlib -fno-asynchronous-unwind-tables \
-fno-ident -fno-stack-protector -fno-exceptions \
-fno-jump-tables -fno-toplevel-reorderx86_64-w64-mingw32-gcc -c resolve.c -o resolve.x64.o \
-O1 -nostdlib -fno-asynchronous-unwind-tables \
-fno-ident -fno-stack-protector -fno-exceptions \
-fno-jump-tables -fno-toplevel-reorderlibtcg.x64.zip
LibTCG is Crystal Palace's standard library. It provides the implementations of findModuleByHash, findFunctionByHash, reflective DLL loading, PICO execution, and a handful of utilities. It ships with the Crystal Palace distribution but it's also available on GitHub. Drop the zip somewhere accessible from your projects. You reference it in every .spec with mergelib
What a PICO Looks Like
A PICO is a COFF object file where:
- API calls use the
MODULE$Functionnaming convention instead of direct Win32 calls - APIs are declared with
__declspec(dllimport)so the compiler generates__imp_MODULE$Functionsymbols that Crystal Palace intercepts - The entry point is a function called
go(), notmain, notDllMain - There are no CRT dependencies, no IAT, no PE headers
The .spec file is what turns that COFF into a working PIC blob. It loads your object, configures DFR, merges the resolver and LibTCG, and exports the final binary
MessageBox Example
The simplest possible PICO, the default demo from Crystal Palace. One API call, one module, no state
test.c
#include <windows.h>
WINUSERAPI int WINAPI USER32$MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
void go() {
USER32$MessageBoxA(NULL, "Hello World (COFF)", "Test!", MB_OK);
}#include <windows.h>
WINUSERAPI int WINAPI USER32$MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
void go() {
USER32$MessageBoxA(NULL, "Hello World (COFF)", "Test!", MB_OK);
}The #include <windows.h> is fine here, we're only using it for the type definitions (HWND, LPCSTR, UINT, MB_OK). We're not calling MessageBoxA through its IAT entry
The declaration with USER32$MessageBoxA is what Crystal Palace sees, and __declspec (implied by WINUSERAPI) is what makes the compiler emit the right symbol name
test.spec
x64:
load "test.x64.o"
make pic +gofirst +optimize
load "../../resolve.x64.o"
merge
mergelib "../libtcg.x64.zip"
dfr "resolve" "ror13"
exportx64:
load "test.x64.o"
make pic +gofirst +optimize
load "../../resolve.x64.o"
merge
mergelib "../libtcg.x64.zip"
dfr "resolve" "ror13"
exportThe paths for resolve.x64.o and libtcg.x64.zip are relative to where your .spec lives. Adjust them to match your folder structure. In this case the resolve and libtcg sit two levels up from the PICO's directory
x86_64-w64-mingw32-gcc -c test.c -o test.x64.o \
-O1 -nostdlib -fno-asynchronous-unwind-tables \
-fno-ident -fno-stack-protector -fno-exceptions \
-fno-jump-tables -fno-toplevel-reorder
./cpl build MSGBOX/.spec x64 MSGBOX/demo.binx86_64-w64-mingw32-gcc -c test.c -o test.x64.o \
-O1 -nostdlib -fno-asynchronous-unwind-tables \
-fno-ident -fno-stack-protector -fno-exceptions \
-fno-jump-tables -fno-toplevel-reorder
./cpl build MSGBOX/.spec x64 MSGBOX/demo.binAnd then you have the shellcode available
xxd -i ../MSGBOX/demo.bin
unsigned char ___MSGBOX_demo_bin[] = {
0x48, 0x83, 0xec, 0x28, 0x41, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x8d,
0x05, 0x73, 0x01, 0x00, 0x00, 0x48, 0x8d, 0x15, 0x72, 0x01, 0x00, 0x00,
0xb9, 0x00, 0x00, 0x00, 0x00, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41,
0x52, 0x41, 0x53, 0x48, 0x83, 0xec, 0x20, 0xb9, 0x83, 0x42, 0xc8, 0x63,
0xba, 0xa8, 0xa2, 0x4d, 0xbc, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x41, 0x5b, 0x41, 0x5a, 0x41, 0x59, 0x41, 0x58, 0x5a, 0x59,
0xff, 0xd0, 0x48, 0x83, 0xc4, 0x28, 0xc3, 0x53, 0x48, 0x83, 0xec, 0x20,
0x89, 0xd3, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x0a,
0x89, 0xda, 0x48, 0x89, 0xc1, 0xe8, 0x75, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x5b, 0xc3, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x60, 0x00, 0x00,
0x00, 0x48, 0x8b, 0x40, 0x18, 0x4c, 0x8b, 0x50, 0x20, 0x4d, 0x85, 0xd2,
0x74, 0x55, 0x41, 0xbb, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x30, 0x0f, 0xb6,
0xc0, 0x8d, 0x44, 0x02, 0xe0, 0x49, 0x83, 0xc0, 0x01, 0x4d, 0x39, 0xc8,
0x74, 0x14, 0xc1, 0xc8, 0x0d, 0x89, 0xc2, 0x41, 0x0f, 0xb6, 0x00, 0x3c,
0x60, 0x7f, 0xe3, 0x0f, 0xb6, 0xc0, 0x01, 0xd0, 0xeb, 0xe3, 0x39, 0xc8,
0x74, 0x21, 0x4d, 0x8b, 0x12, 0x4d, 0x85, 0xd2, 0x74, 0x1d, 0x4d, 0x8b,
0x42, 0x50, 0x41, 0x0f, 0xb7, 0x42, 0x48, 0x83, 0xe8, 0x01, 0x0f, 0xb7,
0xc0, 0x4d, 0x8d, 0x4c, 0x00, 0x01, 0x44, 0x89, 0xd8, 0xeb, 0xc7, 0x4d,
0x8b, 0x52, 0x20, 0x4c, 0x89, 0xd0, 0xc3, 0x57, 0x56, 0x53, 0x48, 0x83,
0xec, 0x40, 0x48, 0x89, 0xcb, 0x89, 0xd6, 0x48, 0x8d, 0x7c, 0x24, 0x20,
0x48, 0x89, 0xfa, 0xe8, 0x75, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00,
0x00, 0x48, 0x89, 0xf9, 0xe8, 0x5c, 0x00, 0x00, 0x00, 0x44, 0x8b, 0x18,
0x49, 0x01, 0xdb, 0x45, 0x8b, 0x43, 0x20, 0x49, 0x01, 0xd8, 0x45, 0x8b,
0x4b, 0x24, 0x49, 0x01, 0xd9, 0x41, 0xba, 0x00, 0x00, 0x00, 0x00, 0x41,
0x8b, 0x00, 0x48, 0x01, 0xd8, 0x44, 0x89, 0xd2, 0xc1, 0xca, 0x0d, 0x0f,
0xbe, 0x08, 0x01, 0xca, 0x48, 0x83, 0xc0, 0x01, 0x80, 0x38, 0x00, 0x75,
0xef, 0x39, 0xd6, 0x74, 0x0a, 0x49, 0x83, 0xc0, 0x04, 0x49, 0x83, 0xc1,
0x02, 0xeb, 0xd8, 0x41, 0x0f, 0xb7, 0x11, 0x41, 0x8b, 0x43, 0x1c, 0x48,
0x8d, 0x14, 0x93, 0x8b, 0x04, 0x02, 0x48, 0x01, 0xd8, 0x48, 0x83, 0xc4,
0x40, 0x5b, 0x5e, 0x5f, 0xc3, 0x48, 0x8b, 0x41, 0x10, 0x89, 0xd2, 0x48,
0x8d, 0x44, 0xd0, 0x70, 0xc3, 0x48, 0x89, 0x0a, 0x48, 0x63, 0x41, 0x3c,
0x48, 0x01, 0xc1, 0x48, 0x89, 0x4a, 0x08, 0x48, 0x83, 0xc1, 0x18, 0x48,
0x89, 0x4a, 0x10, 0xc3, 0x54, 0x65, 0x73, 0x74, 0x21, 0x00, 0x48, 0x65,
0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x28, 0x43,
0x4f, 0x46, 0x46, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};xxd -i ../MSGBOX/demo.bin
unsigned char ___MSGBOX_demo_bin[] = {
0x48, 0x83, 0xec, 0x28, 0x41, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x8d,
0x05, 0x73, 0x01, 0x00, 0x00, 0x48, 0x8d, 0x15, 0x72, 0x01, 0x00, 0x00,
0xb9, 0x00, 0x00, 0x00, 0x00, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41,
0x52, 0x41, 0x53, 0x48, 0x83, 0xec, 0x20, 0xb9, 0x83, 0x42, 0xc8, 0x63,
0xba, 0xa8, 0xa2, 0x4d, 0xbc, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x41, 0x5b, 0x41, 0x5a, 0x41, 0x59, 0x41, 0x58, 0x5a, 0x59,
0xff, 0xd0, 0x48, 0x83, 0xc4, 0x28, 0xc3, 0x53, 0x48, 0x83, 0xec, 0x20,
0x89, 0xd3, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x0a,
0x89, 0xda, 0x48, 0x89, 0xc1, 0xe8, 0x75, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x5b, 0xc3, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x60, 0x00, 0x00,
0x00, 0x48, 0x8b, 0x40, 0x18, 0x4c, 0x8b, 0x50, 0x20, 0x4d, 0x85, 0xd2,
0x74, 0x55, 0x41, 0xbb, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x30, 0x0f, 0xb6,
0xc0, 0x8d, 0x44, 0x02, 0xe0, 0x49, 0x83, 0xc0, 0x01, 0x4d, 0x39, 0xc8,
0x74, 0x14, 0xc1, 0xc8, 0x0d, 0x89, 0xc2, 0x41, 0x0f, 0xb6, 0x00, 0x3c,
0x60, 0x7f, 0xe3, 0x0f, 0xb6, 0xc0, 0x01, 0xd0, 0xeb, 0xe3, 0x39, 0xc8,
0x74, 0x21, 0x4d, 0x8b, 0x12, 0x4d, 0x85, 0xd2, 0x74, 0x1d, 0x4d, 0x8b,
0x42, 0x50, 0x41, 0x0f, 0xb7, 0x42, 0x48, 0x83, 0xe8, 0x01, 0x0f, 0xb7,
0xc0, 0x4d, 0x8d, 0x4c, 0x00, 0x01, 0x44, 0x89, 0xd8, 0xeb, 0xc7, 0x4d,
0x8b, 0x52, 0x20, 0x4c, 0x89, 0xd0, 0xc3, 0x57, 0x56, 0x53, 0x48, 0x83,
0xec, 0x40, 0x48, 0x89, 0xcb, 0x89, 0xd6, 0x48, 0x8d, 0x7c, 0x24, 0x20,
0x48, 0x89, 0xfa, 0xe8, 0x75, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00,
0x00, 0x48, 0x89, 0xf9, 0xe8, 0x5c, 0x00, 0x00, 0x00, 0x44, 0x8b, 0x18,
0x49, 0x01, 0xdb, 0x45, 0x8b, 0x43, 0x20, 0x49, 0x01, 0xd8, 0x45, 0x8b,
0x4b, 0x24, 0x49, 0x01, 0xd9, 0x41, 0xba, 0x00, 0x00, 0x00, 0x00, 0x41,
0x8b, 0x00, 0x48, 0x01, 0xd8, 0x44, 0x89, 0xd2, 0xc1, 0xca, 0x0d, 0x0f,
0xbe, 0x08, 0x01, 0xca, 0x48, 0x83, 0xc0, 0x01, 0x80, 0x38, 0x00, 0x75,
0xef, 0x39, 0xd6, 0x74, 0x0a, 0x49, 0x83, 0xc0, 0x04, 0x49, 0x83, 0xc1,
0x02, 0xeb, 0xd8, 0x41, 0x0f, 0xb7, 0x11, 0x41, 0x8b, 0x43, 0x1c, 0x48,
0x8d, 0x14, 0x93, 0x8b, 0x04, 0x02, 0x48, 0x01, 0xd8, 0x48, 0x83, 0xc4,
0x40, 0x5b, 0x5e, 0x5f, 0xc3, 0x48, 0x8b, 0x41, 0x10, 0x89, 0xd2, 0x48,
0x8d, 0x44, 0xd0, 0x70, 0xc3, 0x48, 0x89, 0x0a, 0x48, 0x63, 0x41, 0x3c,
0x48, 0x01, 0xc1, 0x48, 0x89, 0x4a, 0x08, 0x48, 0x83, 0xc1, 0x18, 0x48,
0x89, 0x4a, 0x10, 0xc3, 0x54, 0x65, 0x73, 0x74, 0x21, 0x00, 0x48, 0x65,
0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x28, 0x43,
0x4f, 0x46, 0x46, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};Just copy the shellcode, and paste to a simple shellcode executor program like this:
#include <windows.h>
#include <stdio.h>
int main() {
char shellcode[] = {
0x48, 0x83, 0xec, 0x28, 0x41, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x8d,
0x05, 0x73, 0x01, 0x00, 0x00, 0x48, 0x8d, 0x15, 0x72, 0x01, 0x00, 0x00,
0xb9, 0x00, 0x00, 0x00, 0x00, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41,
0x52, 0x41, 0x53, 0x48, 0x83, 0xec, 0x20, 0xb9, 0x83, 0x42, 0xc8, 0x63,
0xba, 0xa8, 0xa2, 0x4d, 0xbc, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x41, 0x5b, 0x41, 0x5a, 0x41, 0x59, 0x41, 0x58, 0x5a, 0x59,
0xff, 0xd0, 0x48, 0x83, 0xc4, 0x28, 0xc3, 0x53, 0x48, 0x83, 0xec, 0x20,
0x89, 0xd3, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x0a,
0x89, 0xda, 0x48, 0x89, 0xc1, 0xe8, 0x75, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x5b, 0xc3, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x60, 0x00, 0x00,
0x00, 0x48, 0x8b, 0x40, 0x18, 0x4c, 0x8b, 0x50, 0x20, 0x4d, 0x85, 0xd2,
0x74, 0x55, 0x41, 0xbb, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x30, 0x0f, 0xb6,
0xc0, 0x8d, 0x44, 0x02, 0xe0, 0x49, 0x83, 0xc0, 0x01, 0x4d, 0x39, 0xc8,
0x74, 0x14, 0xc1, 0xc8, 0x0d, 0x89, 0xc2, 0x41, 0x0f, 0xb6, 0x00, 0x3c,
0x60, 0x7f, 0xe3, 0x0f, 0xb6, 0xc0, 0x01, 0xd0, 0xeb, 0xe3, 0x39, 0xc8,
0x74, 0x21, 0x4d, 0x8b, 0x12, 0x4d, 0x85, 0xd2, 0x74, 0x1d, 0x4d, 0x8b,
0x42, 0x50, 0x41, 0x0f, 0xb7, 0x42, 0x48, 0x83, 0xe8, 0x01, 0x0f, 0xb7,
0xc0, 0x4d, 0x8d, 0x4c, 0x00, 0x01, 0x44, 0x89, 0xd8, 0xeb, 0xc7, 0x4d,
0x8b, 0x52, 0x20, 0x4c, 0x89, 0xd0, 0xc3, 0x57, 0x56, 0x53, 0x48, 0x83,
0xec, 0x40, 0x48, 0x89, 0xcb, 0x89, 0xd6, 0x48, 0x8d, 0x7c, 0x24, 0x20,
0x48, 0x89, 0xfa, 0xe8, 0x75, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00,
0x00, 0x48, 0x89, 0xf9, 0xe8, 0x5c, 0x00, 0x00, 0x00, 0x44, 0x8b, 0x18,
0x49, 0x01, 0xdb, 0x45, 0x8b, 0x43, 0x20, 0x49, 0x01, 0xd8, 0x45, 0x8b,
0x4b, 0x24, 0x49, 0x01, 0xd9, 0x41, 0xba, 0x00, 0x00, 0x00, 0x00, 0x41,
0x8b, 0x00, 0x48, 0x01, 0xd8, 0x44, 0x89, 0xd2, 0xc1, 0xca, 0x0d, 0x0f,
0xbe, 0x08, 0x01, 0xca, 0x48, 0x83, 0xc0, 0x01, 0x80, 0x38, 0x00, 0x75,
0xef, 0x39, 0xd6, 0x74, 0x0a, 0x49, 0x83, 0xc0, 0x04, 0x49, 0x83, 0xc1,
0x02, 0xeb, 0xd8, 0x41, 0x0f, 0xb7, 0x11, 0x41, 0x8b, 0x43, 0x1c, 0x48,
0x8d, 0x14, 0x93, 0x8b, 0x04, 0x02, 0x48, 0x01, 0xd8, 0x48, 0x83, 0xc4,
0x40, 0x5b, 0x5e, 0x5f, 0xc3, 0x48, 0x8b, 0x41, 0x10, 0x89, 0xd2, 0x48,
0x8d, 0x44, 0xd0, 0x70, 0xc3, 0x48, 0x89, 0x0a, 0x48, 0x63, 0x41, 0x3c,
0x48, 0x01, 0xc1, 0x48, 0x89, 0x4a, 0x08, 0x48, 0x83, 0xc1, 0x18, 0x48,
0x89, 0x4a, 0x10, 0xc3, 0x54, 0x65, 0x73, 0x74, 0x21, 0x00, 0x48, 0x65,
0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x28, 0x43,
0x4f, 0x46, 0x46, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
HANDLE hAlloc = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(hAlloc, shellcode, sizeof(shellcode));
EnumChildWindows((HWND)NULL, (WNDENUMPROC)hAlloc, NULL);
}#include <windows.h>
#include <stdio.h>
int main() {
char shellcode[] = {
0x48, 0x83, 0xec, 0x28, 0x41, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x8d,
0x05, 0x73, 0x01, 0x00, 0x00, 0x48, 0x8d, 0x15, 0x72, 0x01, 0x00, 0x00,
0xb9, 0x00, 0x00, 0x00, 0x00, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41,
0x52, 0x41, 0x53, 0x48, 0x83, 0xec, 0x20, 0xb9, 0x83, 0x42, 0xc8, 0x63,
0xba, 0xa8, 0xa2, 0x4d, 0xbc, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x41, 0x5b, 0x41, 0x5a, 0x41, 0x59, 0x41, 0x58, 0x5a, 0x59,
0xff, 0xd0, 0x48, 0x83, 0xc4, 0x28, 0xc3, 0x53, 0x48, 0x83, 0xec, 0x20,
0x89, 0xd3, 0xe8, 0x15, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x0a,
0x89, 0xda, 0x48, 0x89, 0xc1, 0xe8, 0x75, 0x00, 0x00, 0x00, 0x48, 0x83,
0xc4, 0x20, 0x5b, 0xc3, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x60, 0x00, 0x00,
0x00, 0x48, 0x8b, 0x40, 0x18, 0x4c, 0x8b, 0x50, 0x20, 0x4d, 0x85, 0xd2,
0x74, 0x55, 0x41, 0xbb, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x30, 0x0f, 0xb6,
0xc0, 0x8d, 0x44, 0x02, 0xe0, 0x49, 0x83, 0xc0, 0x01, 0x4d, 0x39, 0xc8,
0x74, 0x14, 0xc1, 0xc8, 0x0d, 0x89, 0xc2, 0x41, 0x0f, 0xb6, 0x00, 0x3c,
0x60, 0x7f, 0xe3, 0x0f, 0xb6, 0xc0, 0x01, 0xd0, 0xeb, 0xe3, 0x39, 0xc8,
0x74, 0x21, 0x4d, 0x8b, 0x12, 0x4d, 0x85, 0xd2, 0x74, 0x1d, 0x4d, 0x8b,
0x42, 0x50, 0x41, 0x0f, 0xb7, 0x42, 0x48, 0x83, 0xe8, 0x01, 0x0f, 0xb7,
0xc0, 0x4d, 0x8d, 0x4c, 0x00, 0x01, 0x44, 0x89, 0xd8, 0xeb, 0xc7, 0x4d,
0x8b, 0x52, 0x20, 0x4c, 0x89, 0xd0, 0xc3, 0x57, 0x56, 0x53, 0x48, 0x83,
0xec, 0x40, 0x48, 0x89, 0xcb, 0x89, 0xd6, 0x48, 0x8d, 0x7c, 0x24, 0x20,
0x48, 0x89, 0xfa, 0xe8, 0x75, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00,
0x00, 0x48, 0x89, 0xf9, 0xe8, 0x5c, 0x00, 0x00, 0x00, 0x44, 0x8b, 0x18,
0x49, 0x01, 0xdb, 0x45, 0x8b, 0x43, 0x20, 0x49, 0x01, 0xd8, 0x45, 0x8b,
0x4b, 0x24, 0x49, 0x01, 0xd9, 0x41, 0xba, 0x00, 0x00, 0x00, 0x00, 0x41,
0x8b, 0x00, 0x48, 0x01, 0xd8, 0x44, 0x89, 0xd2, 0xc1, 0xca, 0x0d, 0x0f,
0xbe, 0x08, 0x01, 0xca, 0x48, 0x83, 0xc0, 0x01, 0x80, 0x38, 0x00, 0x75,
0xef, 0x39, 0xd6, 0x74, 0x0a, 0x49, 0x83, 0xc0, 0x04, 0x49, 0x83, 0xc1,
0x02, 0xeb, 0xd8, 0x41, 0x0f, 0xb7, 0x11, 0x41, 0x8b, 0x43, 0x1c, 0x48,
0x8d, 0x14, 0x93, 0x8b, 0x04, 0x02, 0x48, 0x01, 0xd8, 0x48, 0x83, 0xc4,
0x40, 0x5b, 0x5e, 0x5f, 0xc3, 0x48, 0x8b, 0x41, 0x10, 0x89, 0xd2, 0x48,
0x8d, 0x44, 0xd0, 0x70, 0xc3, 0x48, 0x89, 0x0a, 0x48, 0x63, 0x41, 0x3c,
0x48, 0x01, 0xc1, 0x48, 0x89, 0x4a, 0x08, 0x48, 0x83, 0xc1, 0x18, 0x48,
0x89, 0x4a, 0x10, 0xc3, 0x54, 0x65, 0x73, 0x74, 0x21, 0x00, 0x48, 0x65,
0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x20, 0x28, 0x43,
0x4f, 0x46, 0x46, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
HANDLE hAlloc = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(hAlloc, shellcode, sizeof(shellcode));
EnumChildWindows((HWND)NULL, (WNDENUMPROC)hAlloc, NULL);
}Execution:
CreateProcess Example
Same pattern, more APIs, and actual Windows behavior, spawning a process from a blob of shellcode.
notepad.c
#include <windows.h>
__declspec(dllimport) BOOL WINAPI KERNEL32$CreateProcessA(
LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES,
BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
__declspec(dllimport) DWORD WINAPI KERNEL32$WaitForSingleObject(HANDLE, DWORD);
__declspec(dllimport) BOOL WINAPI KERNEL32$CloseHandle(HANDLE);
void go(void) {
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
KERNEL32$CreateProcessA(
"C:\\Windows\\System32\\notepad.exe",
NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi
);
KERNEL32$WaitForSingleObject(pi.hProcess, INFINITE);
KERNEL32$CloseHandle(pi.hProcess);
KERNEL32$CloseHandle(pi.hThread);
}#include <windows.h>
__declspec(dllimport) BOOL WINAPI KERNEL32$CreateProcessA(
LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES,
BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
__declspec(dllimport) DWORD WINAPI KERNEL32$WaitForSingleObject(HANDLE, DWORD);
__declspec(dllimport) BOOL WINAPI KERNEL32$CloseHandle(HANDLE);
void go(void) {
STARTUPINFOA si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
KERNEL32$CreateProcessA(
"C:\\Windows\\System32\\notepad.exe",
NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi
);
KERNEL32$WaitForSingleObject(pi.hProcess, INFINITE);
KERNEL32$CloseHandle(pi.hProcess);
KERNEL32$CloseHandle(pi.hThread);
}Three APIs from KERNEL32, all declared explicitly with __declspec(dllimport) and the MODULE$Function naming. The structs (STARTUPINFOA, PROCESS_INFORMATION) come from windows.h and are just type definitions, so including it is fine. What matters is that none of the actual calls go through the IAT
notepad.spec
x64:
load "notepad.x64.o"
make pic +gofirst +optimize
load "../../resolve.x64.o"
merge
mergelib "../libtcg.x64.zip"
dfr "resolve" "ror13"
exportx64:
load "notepad.x64.o"
make pic +gofirst +optimize
load "../../resolve.x64.o"
merge
mergelib "../libtcg.x64.zip"
dfr "resolve" "ror13"
exportIdentical structure to the MessageBox spec. The only thing that changes between PICOs is the first load line
Build:
x86_64-w64-mingw32-gcc -c notepad.c -o notepad.x64.o \
-O1 -nostdlib -fno-asynchronous-unwind-tables \
-fno-ident -fno-stack-protector -fno-exceptions \
-fno-jump-tables -fno-toplevel-reorder
./cpl build NOTEPAD/.spec x64 NOTEPAD/demo.binx86_64-w64-mingw32-gcc -c notepad.c -o notepad.x64.o \
-O1 -nostdlib -fno-asynchronous-unwind-tables \
-fno-ident -fno-stack-protector -fno-exceptions \
-fno-jump-tables -fno-toplevel-reorder
./cpl build NOTEPAD/.spec x64 NOTEPAD/demo.binAnd then just paste the shellcode and execute:
Conclusions
Two PICOs, the same .spec skeleton, the same resolver, the same library. That's the pattern for every PICO you'll write in Crystal Palace, the capability changes, the infrastructure stays the same. Once resolve.x64.o and libtcg.x64.zip are in place, adding a new PICO is just writing the C and pointing a spec at it
📌 Follow me: 🐦 X | 💬 Discord Server | 📸 Instagram | Newsletter | YouTube
S12.