User-Defined Reflective Loader development kit for Starburst, built on the Stardust PIC shellcode framework.
Starburst.UDRL-VS/
├── Starburst.UDRL-VS.sln VS2026 solution
├── Makefile Top-level build orchestrator
├── common/ Shared headers (NT internals, beacon API, macros)
│ └── include/
├── tests/ Unit tests and debug tools
│ ├── Makefile Builds and runs all tests (gcc)
│ ├── include/
│ │ ├── test.h Minimal test assertion framework
│ │ └── mock_pe.h PE structures + Win32 mocks for Linux
│ ├── test_helpers.c HashString, ExprHashStringA, SectionToProtect, FindTextSection, GenerateRc4Key
│ ├── test_pe_ops.c PE parsing, mapping, relocs, userdata
│ ├── test_mask_ops.c XOR, dispatch, full cycle, erase, multi-region
│ └── debug/
│ ├── debug.h Debug instrumentation macros (UDRL_LOG, hexdump, etc.)
│ ├── loader_validate.c Post-load PE validation tool (7 checks)
│ └── mask_validate.c Mask roundtrip validation tool (6 tests)
├── loader/ Reflective DLL loader (PIC shellcode)
│ ├── loader.vcxproj VS2026 project
│ ├── makefile MinGW cross-compilation
│ ├── asm/x64/Stardust.asm NASM entry point
│ ├── include/ Loader headers
│ ├── scripts/ Linker script, shellcode extractor
│ └── src/ Loader source (Main.c = reflective loader)
├── mask/ Sleepmask-VS (BOF/COFF object)
│ ├── mask.vcxproj VS2026 project
│ ├── Makefile MinGW cross-compilation
│ ├── include/ Sleepmask headers + Beacon Gate
│ └── src/main.c Sleepmask implementation
└── examples/ Drop-in replacement examples
├── loader-stomp/Main.c Module-stomping loader
├── loader-headerless/Main.c Headerless loader (no PE headers in memory)
├── mask-timer/main.c Timer-based sleep mask
└── mask-erase/main.c Erase mask (zero + restore from backup)
- NASM: assembler for the x64 entry point
- MinGW-w64:
x86_64-w64-mingw32-g++/x86_64-w64-mingw32-gcc - Python 3 with
pefile: shellcode extraction (pip install pefile) - Visual Studio 2026 (optional): IDE, IntelliSense, debugging
# Build both loader and sleepmask (release)
make
# Build with debug symbols
make debug
# Clean all artifacts
make cleanOpen Starburst.UDRL-VS.sln. Both projects are configured as Makefile projects that invoke the MinGW toolchain. Select Debug|x64 or Release|x64 and build (Ctrl+B).
Position-independent reflective DLL loader built with the Stardust framework. The entry flow is:
Stardust.asm:Startsaves registers, aligns stack, callsPreMainPreMain.c: resolves ntdll from PEB, allocates heap for theINSTANCEstruct, patches the global pointerMain.c: the reflective loader:- Resolves kernel32 + ntdll APIs via PEB walking + custom hashing
- Allocates
CUSTOM_DATAfor stomp region tracking (beacon + sleepmask + BOF) - Parses PE headers, maps sections into a single contiguous allocation
- Resolves IAT via
ResolveIAT()(ordinal + name imports,LdrLoadDll-based) - Processes relocations via
ProcessRelocations()(DIR64 bitfield-based) - Sets
.textexecutable viaNtProtectVirtualMemory - Populates CS
USER_DATAwith version,CUSTOM_DATApointer incustom[32], andALLOCATED_MEMORYregions - Calls
DllMain(DLL_BEACON_USER_DATA, &userData)thenDllMain(DLL_PROCESS_ATTACH, NULL)to transfer execution
| Define | Default | Description |
|---|---|---|
LOAD_MODE |
0 |
0 = VirtualAlloc, 1 = Module Stomp |
STOMP_DLL |
L"dbghelp.dll" |
Sacrificial DLL for module stomping |
FREE_LOADER |
1 |
Free the loader's initial allocation after loading |
Compiled as a COFF object (.o) loaded by the Starburst agent at init. Called each sleep cycle to encrypt/decrypt the agent image in memory.
- Beacon Gate path (
bMask = FALSE): dispatches the queued API call directly - Sleep mask path (
bMask = TRUE): XOR-encrypts all beacon memory regions, executes the queued sleep call, then XOR-decrypts to restore
The loader uses the standard Cobalt Strike USER_DATA and ALLOCATED_MEMORY types from beacon.h. This allows direct compatibility with CS UDRL course code and tooling.
| Field | Purpose |
|---|---|
version |
Starburst version (STARBURST_VERSION, currently 0x010400 = 1.4.0) |
syscalls |
PSYSCALL_API — NULL (unused by Starburst) |
custom[32] |
Opaque scratch space; stores a PCUSTOM_DATA pointer for stomp region tracking |
rtls |
PRTL_API — NULL (unused by Starburst) |
allocatedMemory |
PALLOCATED_MEMORY — pointer to the memory region descriptor |
The loader passes USER_DATA to the agent via the CS DLL_BEACON_USER_DATA (0x0d) calling convention: DllMain(hInst, DLL_BEACON_USER_DATA, &userData) followed by DllMain(hInst, DLL_PROCESS_ATTACH, NULL).
Each ALLOCATED_MEMORY_REGION describes a top-level memory allocation with up to 8 nested ALLOCATED_MEMORY_SECTION entries:
ALLOCATED_MEMORY_REGION
├── Purpose PURPOSE_BEACON_MEMORY / PURPOSE_SLEEPMASK_MEMORY / PURPOSE_BOF_MEMORY
├── AllocationBase Base of the allocation
├── RegionSize Total size
├── CleanupInformation
│ ├── AllocationMethod METHOD_VIRTUALALLOC / METHOD_MODULESTOMP
│ └── ModuleStompInfo { ModuleHandle } (module stomp only)
└── Sections[8]
├── Label LABEL_TEXT / LABEL_DATA / LABEL_RDATA / LABEL_BUFFER
├── BaseAddress Section base address
├── VirtualSize Section size
├── CurrentProtect Current memory protection (PAGE_*)
└── MaskSection TRUE if the sleep mask should encrypt this section
The default loader populates 3 regions: the agent image (with .text and .data sections), sleepmask buffer, and BOF buffer. The ALLOCATED_MEMORY struct holds up to 6 regions.
The kit produces artifacts compatible with the Starburst builder's UDRL-VS flow:
- ZIP the entire
Starburst.UDRL-VS/directory - In the Mythic payload builder, set
loader_type = udrl-vs - Upload the ZIP as
udrl_vs_file - The builder compiles
loader/to produce PIC shellcode, compilesmask/for the sleepmask COFF, then concatenates the loader with the wrapped agent DLL
The final binary layout is: [loader shellcode] + [4-byte DLL length] + [DLL bytes]. For a Crystal Palace-based UDRL, use the separate Starburst.CrystalKit project.
When loader_type = udrl-vs is selected, the sleep mask option is hidden. The builder automatically compiles and embeds the mask from the kit's mask/ directory.
When testing the UDRL loader standalone (outside the kit ZIP flow), the Starburst agent must be built with the correct build parameters. Example payload config (starburst.dll.json):
{
"build_parameters": [
{ "name": "sleep_mask", "value": "sleepmask_vs" },
{ "name": "sleepmask_vs_file", "value": "" },
{ "name": "sleepmask_vs_logging", "value": false },
{ "name": "output_type", "value": "dll" },
{ "name": "loader_type", "value": "default" }
]
}| Parameter | Value | Notes |
|---|---|---|
sleep_mask |
sleepmask_vs |
Required. Must not be default - that compiles out the sleepmask-vs code path entirely. The builder maps this to MASK_SLEEPMASK_VS. |
sleepmask_vs_file |
"" (empty) |
Uses the COFF already embedded in sleepmask_vs_data.h. Upload a .o file here to override. |
sleepmask_vs_logging |
false |
Set true for sleepmask debug output. |
output_type |
dll |
DLL output for reflective loading. |
loader_type |
default |
Use default when loading with this kit's standalone loader.x64.exe. Use udrl-vs only for the ZIP upload flow. |
To embed a custom sleepmask COFF into the agent source (bypassing the builder upload):
cd mask && make # produces dist/sleepmask.x64.o
cd ../loader/scripts
python3 ../../agent_code/scripts/embed_sleepmask.py mask/dist/sleepmask.x64.oThis writes sleepmask_vs_data.h with the COFF byte array. Rebuild the agent with sleep_mask = sleepmask_vs and the embedded COFF will be loaded at init.
APIs are resolved at runtime via PEB module walking + custom hashing (key=7759, shift=6):
Instance.Win32.pVirtualAlloc = LdrFunction( Instance.Modules.Kernel32, HASH_STR("VirtualAlloc") );Use HASH_STR() from Constexpr.h for compile-time hashes:
#define H_MODULE_NTDLL 0xc2ba439d
#define H_MODULE_KERNEL32 0xf232005a- Add
API_ENTRY( FunctionName, ModuleName )toAPI_LISTincommon/include/Shared.h - If the module isn't
NtdllorKernel32, addDLL_ENTRY( ModuleName )toDLL_LIST - The X-macro pattern auto-generates the
INSTANCEstruct fields and resolution inResolveApis() - Use
API( FunctionName )to call it anywhere withSTARDUST_INSTANCEin scope
The examples/ directory contains drop-in replacements for the base loader and mask. Copy the example source file over the corresponding base file to use it.
Module-stomping reflective loader. Loads the agent DLL by stomping over a legitimate system DLL's .text section rather than allocating fresh executable memory. The sacrificial DLL defaults to amsi.dll (configurable via STOMP_DLL). Populates ALLOCATED_MEMORY regions with METHOD_MODULESTOMP cleanup information.
Usage: Copy examples/loader-stomp/Main.c to loader/src/Main.c.
Headerless reflective loader. Maps PE sections at their normal virtual addresses but never writes the DOS/NT headers into the allocation. The header region stays zeroed (from VirtualAlloc) and is marked PAGE_NOACCESS. Memory scanners walking allocation bases for MZ/PE signatures will not find this image.
Usage: Copy examples/loader-headerless/Main.c to loader/src/Main.c.
Timer-based sleep mask. Instead of dispatching the queued WaitForSingleObject/Sleep call directly, creates a waitable timer set to the same duration and blocks on it. The thread waits on a timer object rather than a thread handle, producing a different call stack for anyone inspecting NtWaitForSingleObject hooks.
Usage: Copy examples/mask-timer/main.c to mask/src/main.c.
Erase sleep mask. During sleep, backs up each beacon memory region to a heap allocation, zeros the original memory byte-by-byte, executes the sleep call, then restores from the backup. During sleep there is no beacon code in the original memory regions, only zeroes.
Usage: Copy examples/mask-erase/main.c to mask/src/main.c.
The tests/ directory contains unit tests and debug tools that validate the loader and mask logic without needing a Windows target or Mythic server.
Tests compile natively on Linux with gcc. They use mock_pe.h to provide PE structures and mock Win32 APIs (VirtualAlloc → calloc, VirtualProtect → no-op, __rdtsc → deterministic counter).
cd tests
make test # compile and run all 85 tests
make clean # remove binariesThree test binaries:
| Binary | Tests | Coverage |
|---|---|---|
test_helpers |
28 | HashString (NULL, empty, case-insensitive, collision, manual), ExprHashStringA (NULL, narrow match, case), SectionToProtect (all 8 flag combos + non-memory), FindTextSection (valid/invalid PE, multi-section), GenerateRc4Key (nonzero, byte order, uniqueness) |
test_pe_ops |
30 | LdrpImageHeader (valid/invalid DOS/NT), LdrFunction (resolve, unknown, NULL), PE header parsing, section mapping, DIR64 relocations, zero-delta no-op, section protections, USER_DATA/ALLOCATED_MEMORY population (VirtualAlloc + module stomp), headerless variant, full load cycle |
test_mask_ops |
27 | XOR roundtrip, zero-key identity, key wrapping, bounds check, dispatch (0-arg through 10-arg, NULL), full sleep mask cycle, beacon gate path, erase mask, multi-region, section-level XOR, MaskSection=FALSE skip, RC4 key usage |
Debug tools cross-compile with MinGW for Windows. Include debug.h with -DUDRL_DEBUG to instrument your loader or mask.
debug.h - drop-in instrumentation header:
UDRL_LOG,UDRL_LOG_OK,UDRL_LOG_ERR,UDRL_LOG_INFOmacrosudrl_hexdump()- hex dump with ASCII column (capped at 256 bytes)udrl_validate_pe()- checks DOS/NT signatures, logs section count and entry RVAudrl_validate_userdata()- validates USER_DATA version, allocatedMemory, regionsudrl_log_sections(),udrl_log_relocs(),udrl_log_imports()- trace loader operations- All compile to no-ops when
UDRL_DEBUGis not defined
loader_validate.c - post-load validation tool (7 checks):
x86_64-w64-mingw32-gcc -DUDRL_DEBUG -o loader_validate.exe debug/loader_validate.c
loader_validate.exe 0x<agent_base_hex>Checks: USER_DATA/ALLOCATED_MEMORY fields, PE header integrity, section protections (VirtualQuery), IAT resolution, relocations, region bounds, module stomp consistency.
mask_validate.c - mask roundtrip validation tool (6 tests):
x86_64-w64-mingw32-gcc -DUDRL_DEBUG -o mask_validate.exe debug/mask_validate.c
mask_validate.exeRuns 6 roundtrip tests (16-byte, 4096-byte, multi-region, all-zero, all-0xFF, 1-byte guard) with 32-byte guard zones on each buffer for overflow detection. Accepts a custom mask function pointer via udrl_mask_validate().
- Open the VS2026 solution for IntelliSense and code navigation
- Edit
loader/src/Main.cfor loader logic,mask/src/main.cfor sleep mask behavior - Run
make testintests/to validate changes - Build via
makeor VS2026's Build command - ZIP and upload to Mythic as a custom UDRL for end-to-end testing