Difficulty: Advanced

Module 6: Beacon User Data (BUD)

The contract between loader and Beacon — pre-resolved syscalls, RTL functions, and full memory layout awareness delivered through a single structured data block.

Module Objective

Understand the USER_DATA structure and every sub-structure it contains. By the end of this module you will know how Crystal-Loaders populates BUD, what data Beacon expects to receive, and why this mechanism is critical for sleep masking, indirect syscalls, and clean memory cleanup. This is what makes Crystal-Loaders more than just a loader: it provides Beacon with a complete runtime environment.

1. What Is BUD?

BUD (Beacon User Data) is the mechanism by which a UDRL (User-Defined Reflective Loader) communicates loader-resolved data to the Beacon payload after loading it into memory. It is not a Cobalt Strike "feature" in the UI sense — it is a structured binary contract between the loader and Beacon's internal runtime.

The concept was introduced with the original UDRL feature in Cobalt Strike 4.4, but it was significantly expanded in CS 4.11 to support a much richer set of pre-resolved data. Crystal-Loaders targets the 4.11 BUD specification.

How BUD Is Delivered

The loader populates a USER_DATA structure in memory and then passes a pointer to it via a special DllMain call. Cobalt Strike defines a custom fdwReason value:

C#define DLL_BEACON_USER_DATA  0x0d   // fdwReason = 13

// The loader calls Beacon's DllMain with:
DllMain(hInstance, DLL_BEACON_USER_DATA, (LPVOID)&userData);

Beacon's DllMain checks for this reason code and, when it receives it, copies the pointer and stores the USER_DATA internally. This call happens after the standard DLL_PROCESS_ATTACH call but before Beacon enters its main command loop.

Once Beacon has the USER_DATA pointer, it uses the pre-resolved data for four critical capabilities:

CapabilityBUD Field UsedWhy It Matters
Indirect syscall executionsyscalls (SYSCALL_API)Beacon can call Nt* functions through pre-resolved syscall;ret gadgets without resolving them itself
Sleep mask memory trackingallocatedMemoryThe sleep mask knows exactly which memory regions to encrypt/decrypt and what permissions to restore
Memory cleanup on exitallocatedMemoryBeacon can cleanly free all loader-allocated memory using the correct deallocation API for each region
RTL function callsrtls (RTL_API)Beacon can convert DOS paths to NT paths and manage heap memory without resolving these functions itself

2. The USER_DATA Structure

The top-level structure that ties everything together. Every field is a pointer to a sub-structure (or a fixed-size buffer), and Beacon validates the version field before touching anything else.

Ctypedef struct {
    unsigned int        version;          // CS version: 0x041100 = 4.11
    PSYSCALL_API        syscalls;         // Pre-resolved syscall entries (36 Nt*)
    char                custom[BEACON_USER_DATA_CUSTOM_SIZE]; // User-defined field
    PRTL_API            rtls;             // Pre-resolved RTL functions
    PALLOCATED_MEMORY   allocatedMemory;  // Memory region tracking
} USER_DATA;

Field-by-Field Breakdown

FieldTypeDescription
versionunsigned intMust match the Cobalt Strike version that built the payload. For CS 4.11 this is 0x041100. Beacon validates this value immediately — if it does not match, Beacon ignores the entire USER_DATA structure. This prevents version-mismatched loaders from corrupting Beacon's state.
syscallsPSYSCALL_APIPointer to the SYSCALL_API structure containing all 36 Nt* function entries. Each entry holds the function address, a syscall;ret gadget address, and the System Service Number. Beacon uses these for all native API calls, routing them through indirect syscalls.
custom[BEACON_USER_DATA_CUSTOM_SIZE]char[]A user-defined buffer (sized by BEACON_USER_DATA_CUSTOM_SIZE) that the loader can use to pass arbitrary data to Beacon. This could be encryption keys, configuration flags, a nonce, or any other operator-defined data. Beacon does not interpret these bytes — they are available to post-exploitation scripts and BOFs via the Beacon User Data API.
rtlsPRTL_APIPointer to the RTL_API structure with 3 resolved RTL (Runtime Library) function addresses from ntdll and the PEB. These are functions Beacon needs internally but that are not part of the syscall table.
allocatedMemoryPALLOCATED_MEMORYPointer to the ALLOCATED_MEMORY structure that tracks up to 6 memory regions with up to 8 sections each. This gives Beacon (and its sleep mask) a complete map of every memory region the loader allocated, including section-level metadata for encryption and permission management.

Version Validation Is Strict

If you build a loader targeting CS 4.11 but try to use it with a CS 4.9 Beacon DLL, the version check will fail and Beacon will proceed without any BUD data. This means no indirect syscalls, no sleep mask memory tracking, and no RTL functions. The loader will appear to work (Beacon runs), but all advanced evasion features that depend on BUD will be silently disabled.

3. SYSCALL_API — The Syscall Table

The SYSCALL_API structure is the largest component of BUD. It contains 36 entries, one for each Nt* function that Beacon may need to call during its lifecycle. Each entry is a SYSCALL_API_ENTRY with three fields:

Ctypedef struct {
    PVOID fnAddr;     // Address of the Nt* function in ntdll.dll
    PVOID jmpAddr;    // Address of a syscall;ret gadget in ntdll for indirect execution
    DWORD sysnum;     // The System Service Number (SSN) for this syscall
} SYSCALL_API_ENTRY;

Understanding the Three Fields

FieldPurposeUsed By
fnAddrThe actual address of the Nt* stub in ntdll.dll. This is where the function's code begins (the mov r10, rcx / mov eax, SSN prologue). Used for direct calls when indirect syscalls are not needed or as a fallback.Beacon (direct calls)
jmpAddrThe address of a syscall; ret instruction pair found within a different Nt* function in ntdll. By jumping to this gadget instead of executing syscall inline, the return address on the stack points into ntdll — not into Beacon's private memory. This is the core of indirect syscall execution.LibGate trampoline
sysnumThe System Service Number extracted by LibGate's Hell's Gate / Halo's Gate algorithm. This number is loaded into EAX before the syscall instruction to specify which kernel service to invoke.LibGate SSN setup

The full SYSCALL_API structure contains 36 named entries. Crystal-Loaders resolves all of them during the loading process using LibGate:

Ctypedef struct {
    SYSCALL_API_ENTRY ntAllocateVirtualMemory;
    SYSCALL_API_ENTRY ntProtectVirtualMemory;
    SYSCALL_API_ENTRY ntFreeVirtualMemory;
    SYSCALL_API_ENTRY ntGetContextThread;
    SYSCALL_API_ENTRY ntSetContextThread;
    SYSCALL_API_ENTRY ntResumeThread;
    SYSCALL_API_ENTRY ntCreateThreadEx;
    SYSCALL_API_ENTRY ntOpenProcess;
    SYSCALL_API_ENTRY ntOpenThread;
    SYSCALL_API_ENTRY ntClose;
    SYSCALL_API_ENTRY ntCreateSection;
    SYSCALL_API_ENTRY ntMapViewOfSection;
    SYSCALL_API_ENTRY ntUnmapViewOfSection;
    SYSCALL_API_ENTRY ntQueryVirtualMemory;
    SYSCALL_API_ENTRY ntDuplicateObject;
    SYSCALL_API_ENTRY ntReadVirtualMemory;
    SYSCALL_API_ENTRY ntWriteVirtualMemory;
    SYSCALL_API_ENTRY ntReadFile;
    SYSCALL_API_ENTRY ntWriteFile;
    SYSCALL_API_ENTRY ntCreateFile;
    SYSCALL_API_ENTRY ntQueueApcThread;
    SYSCALL_API_ENTRY ntCreateProcess;
    SYSCALL_API_ENTRY ntOpenProcessToken;
    SYSCALL_API_ENTRY ntTestAlert;
    SYSCALL_API_ENTRY ntSuspendProcess;
    SYSCALL_API_ENTRY ntResumeProcess;
    SYSCALL_API_ENTRY ntQuerySystemInformation;
    SYSCALL_API_ENTRY ntQueryDirectoryFile;
    SYSCALL_API_ENTRY ntSetInformationProcess;
    SYSCALL_API_ENTRY ntSetInformationThread;
    SYSCALL_API_ENTRY ntQueryInformationProcess;
    SYSCALL_API_ENTRY ntQueryInformationThread;
    SYSCALL_API_ENTRY ntOpenSection;
    SYSCALL_API_ENTRY ntAdjustPrivilegesToken;
    SYSCALL_API_ENTRY ntDeviceIoControlFile;
    SYSCALL_API_ENTRY ntWaitForMultipleObjects;
} SYSCALL_API;

Complete Syscall Reference Table

All 36 Nt* functions in the SYSCALL_API, listed in source order:

#FunctionPurposeCategory
1ntAllocateVirtualMemoryAllocate memory regions in a process address spaceMemory
2ntProtectVirtualMemoryChange memory page permissions (RW, RX, etc.)Memory
3ntFreeVirtualMemoryFree previously allocated memory regionsMemory
4ntGetContextThreadRead a thread's CPU context (registers, flags)Threads
5ntSetContextThreadSet a thread's CPU context (used in sleep mask ROP chains)Threads
6ntResumeThreadResume a suspended threadThreads
7ntCreateThreadExCreate a new thread (modern API with extended parameters)Threads
8ntOpenProcessOpen a handle to a processProcess
9ntOpenThreadOpen a handle to an existing threadThreads
10ntCloseClose any NT handle (files, threads, processes, events, etc.)Handles
11ntCreateSectionCreate a section object for memory mappingMemory
12ntMapViewOfSectionMap a section object into a process address spaceMemory
13ntUnmapViewOfSectionUnmap a previously mapped section from a processMemory
14ntQueryVirtualMemoryQuery information about a memory region (type, protection, size)Memory
15ntDuplicateObjectDuplicate a handle (within or across processes)Handles
16ntReadVirtualMemoryRead memory from a remote processMemory
17ntWriteVirtualMemoryWrite memory to a remote processMemory
18ntReadFileRead data from a fileFile I/O
19ntWriteFileWrite data to a fileFile I/O
20ntCreateFileCreate or open a file (NT-level file I/O)File I/O
21ntQueueApcThreadQueue an Asynchronous Procedure Call to a threadThreads
22ntCreateProcessCreate a new processProcess
23ntOpenProcessTokenOpen the access token associated with a processToken
24ntTestAlertTest and clear pending APCs for the current threadThreads
25ntSuspendProcessSuspend all threads in a target processProcess
26ntResumeProcessResume all threads in a previously suspended processProcess
27ntQuerySystemInformationQuery system-wide information (process list, handle table, etc.)System
28ntQueryDirectoryFileQuery directory contents for file enumerationFile I/O
29ntSetInformationProcessSet process properties (e.g., DEP policy, mitigation settings)Process
30ntSetInformationThreadSet thread properties (e.g., ThreadHideFromDebugger)Threads
31ntQueryInformationProcessQuery process information (PEB address, debug status, etc.)Process
32ntQueryInformationThreadQuery thread information (start address, priority, etc.)Threads
33ntOpenSectionOpen an existing section object by nameMemory
34ntAdjustPrivilegesTokenEnable or disable privileges in an access tokenToken
35ntDeviceIoControlFileSend a device I/O control request to a driverDevice I/O
36ntWaitForMultipleObjectsWait on an array of handles simultaneouslySync

4. RTL_API — Runtime Library Functions

Not everything Beacon needs is a syscall. Three runtime library values are resolved by the loader and passed separately in the RTL_API structure:

Ctypedef struct {
    PVOID rtlDosPathNameToNtPathNameUWithStatusAddr;
    PVOID rtlFreeHeapAddr;
    PVOID rtlGetProcessHeapAddr;
} RTL_API, *PRTL_API;

Why These Three?

FieldWhat It DoesWhy Beacon Needs It
rtlDosPathNameToNtPathNameUWithStatusAddrConverts a DOS-style path like C:\Windows\System32\cmd.exe into an NT-style path like \??\C:\Windows\System32\cmd.exeThe Nt* file APIs (NtCreateFile) require NT path format. Without this function, Beacon would need to resolve and call it dynamically every time it performs file I/O, adding overhead and resolution artifacts.
rtlFreeHeapAddrFrees a heap allocation made with RtlAllocateHeap (the underlying implementation of HeapFree)Beacon uses heap allocations internally for buffers, strings, and temporary data. Having the free function pre-resolved avoids repeated runtime lookups into ntdll.
rtlGetProcessHeapAddrThe address of RtlGetProcessHeap, which returns the default process heap handleBoth RtlAllocateHeap and RtlFreeHeap require a heap handle as their first argument. Rather than reading the PEB each time, the loader provides the address of the function that retrieves it.

ResolveRtlFunctions Implementation

The loader resolves these three values during initialization, before the BUD handoff:

Cvoid ResolveRtlFunctions(PRTL_API rtls)
{
    // Find ntdll base address via PEB walking + hash comparison
    PVOID ntdll = findModuleByHash(NTDLL_HASH);

    // Resolve the RTL functions from ntdll's export table
    rtls->rtlDosPathNameToNtPathNameUWithStatusAddr = findFunctionByHash(
        ntdll, RTL_DOSPATHNAME_HASH);
    rtls->rtlFreeHeapAddr = findFunctionByHash(
        ntdll, RTL_FREEHEAP_HASH);
    rtls->rtlGetProcessHeapAddr = findFunctionByHash(
        ntdll, RTL_GETPROCESSHEAP_HASH);
}

PEB Access on x64

On 64-bit Windows, the PEB is accessible via the GS segment register at offset 0x60. The __readgsqword(0x60) intrinsic reads an 8-byte pointer from GS:[0x60], giving the base address of the PEB structure. The ProcessHeap field is at offset 0x30 within the PEB. This is a well-known technique used across offensive tooling for resolving runtime data without calling any API functions.

5. ALLOCATED_MEMORY — Memory Region Tracking

This is the most complex component of BUD, and arguably the most important. The ALLOCATED_MEMORY structure gives Beacon a complete map of every memory region the loader allocated, with section-level granularity. This map is critical for sleep masking — without it, the sleep mask would have no way to know which regions to encrypt, what permissions to change, or how to restore them after waking up.

Ctypedef struct {
    ALLOCATED_MEMORY_REGION AllocatedMemoryRegions[6];  // Up to 6 tracked regions
} ALLOCATED_MEMORY;

Each region represents a single top-level memory allocation (e.g., the Beacon DLL image, the sleep mask code, a BOF buffer). Within each region, up to 8 sections describe the internal layout:

Ctypedef struct {
    ALLOCATED_MEMORY_PURPOSE                 Purpose;
    PVOID                                    AllocationBase;
    SIZE_T                                   RegionSize;
    DWORD                                    Type;          // MEM_COMMIT, MEM_RESERVE, etc.
    ALLOCATED_MEMORY_SECTION                 Sections[8];   // Up to 8 sections per region
    ALLOCATED_MEMORY_CLEANUP_INFORMATION     CleanupInformation;
} ALLOCATED_MEMORY_REGION;
Ctypedef struct {
    ALLOCATED_MEMORY_LABEL  Label;           // TEXT, RDATA, DATA, PDATA, RELOC, etc.
    PVOID                   BaseAddress;
    SIZE_T                  VirtualSize;
    DWORD                   CurrentProtect;
    DWORD                   PreviousProtect;
    BOOL                    MaskSection;     // TRUE = encrypt this section during sleep
} ALLOCATED_MEMORY_SECTION;

The CleanupInformation field in each region tells Beacon how to clean up the allocation on exit. It contains the allocation method, a cleanup flag, and method-specific additional information:

Ctypedef struct {
    BOOL                                             Cleanup;
    ALLOCATED_MEMORY_ALLOCATION_METHOD               AllocationMethod;
    ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION   AdditionalCleanupInformation;
} ALLOCATED_MEMORY_CLEANUP_INFORMATION;

The AdditionalCleanupInformation is a union that carries method-specific data needed for correct deallocation:

Ctypedef union {
    HEAPALLOC_INFO    HeapAllocInfo;    // HeapHandle, DestroyHeap
    MODULESTOMP_INFO  ModuleStompInfo;  // ModuleHandle
} ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION;

typedef struct {
    PVOID HeapHandle;   // Handle to the heap this region was allocated from
    BOOL  DestroyHeap;  // TRUE if the heap itself should be destroyed on cleanup
} HEAPALLOC_INFO;

typedef struct {
    PVOID ModuleHandle; // Handle to the stomped module
} MODULESTOMP_INFO;

Section Fields Explained

FieldDescription
LabelIdentifies what the section contains: .text (code), .rdata (read-only data), .data (writable data), .pdata (exception handlers), .reloc (relocations), the PE header, or a generic buffer.
BaseAddressThe virtual address where this section starts. Combined with VirtualSize, this defines the exact byte range.
VirtualSizeThe size of this section in bytes. May differ from the on-disk raw size due to alignment and uninitialized data (.bss).
CurrentProtectThe current memory protection (e.g., PAGE_EXECUTE_READ for .text, PAGE_READWRITE for .data). The sleep mask saves this before changing protections.
PreviousProtectThe previous memory protection before the last VirtualProtect call. Used to track protection transitions.
MaskSectionA boolean flag. If TRUE, the sleep mask will encrypt this section's memory during sleep and decrypt it on wake. Typically TRUE for all sections containing Beacon code or data.

ALLOCATED_MEMORY Layout Example

ALLOCATED_MEMORY
Region 0: PURPOSE_BEACON_MEMORY
AllocationBase: 0x1A000000 | Size: 0x5C000
Section 0: LABEL_PEHEADER | 0x1000 | PAGE_READWRITE | Mask: TRUE
Section 1: LABEL_TEXT | 0x3E000 | PAGE_EXECUTE_READ | Mask: TRUE
Section 2: LABEL_RDATA | 0x12000 | PAGE_READONLY | Mask: TRUE
Section 3: LABEL_DATA | 0x4000 | PAGE_READWRITE | Mask: TRUE
Section 4: LABEL_PDATA | 0x2000 | PAGE_READONLY | Mask: TRUE
Section 5: LABEL_RELOC | 0x1000 | PAGE_READONLY | Mask: TRUE
Region 1: PURPOSE_SLEEPMASK_MEMORY
AllocationBase: 0x1B000000 | Size: 0x8000
Section 0: LABEL_TEXT | 0x6000 | PAGE_EXECUTE_READ | Mask: FALSE
Section 1: LABEL_DATA | 0x2000 | PAGE_READWRITE | Mask: FALSE

Why MaskSection Is FALSE for Sleep Mask Memory

The sleep mask cannot encrypt itself. The sleep mask code must remain in cleartext and executable while it is running the encryption routine on all other Beacon regions. If the sleep mask encrypted its own .text section, execution would immediately crash. This is why the sleep mask's own region has MaskSection = FALSE for all its sections.

6. Enums Explained

Three enum types provide semantic meaning to the raw integer values stored in the ALLOCATED_MEMORY structures. Understanding these is essential for interpreting the memory map correctly.

ALLOCATED_MEMORY_PURPOSE

Cenum ALLOCATED_MEMORY_PURPOSE {
    PURPOSE_EMPTY           = 0,   // Slot is unused
    PURPOSE_GENERIC_BUFFER  = 1,   // General-purpose buffer (loader scratch space)
    PURPOSE_BEACON_MEMORY   = 2,   // The Beacon DLL image
    PURPOSE_SLEEPMASK_MEMORY = 3,  // The sleep mask code
    PURPOSE_BOF_MEMORY      = 4    // A loaded BOF (Beacon Object File)
};

Purpose Enum Usage

ValueWhen UsedSleep Mask Behavior
PURPOSE_EMPTYDefault. Indicates the region slot is not in use.Skipped entirely
PURPOSE_GENERIC_BUFFERLoader scratch space, temporary allocationsEncrypted if MaskSection is TRUE
PURPOSE_BEACON_MEMORYThe main Beacon DLL loaded by the UDRLAll sections encrypted during sleep
PURPOSE_SLEEPMASK_MEMORYThe sleep mask's own code and dataNever encrypted (it would crash)
PURPOSE_BOF_MEMORYBeacon Object Files loaded at runtimeEncrypted if still resident during sleep

ALLOCATED_MEMORY_LABEL

Cenum ALLOCATED_MEMORY_LABEL {
    LABEL_EMPTY     = 0,   // Slot is unused / not assigned
    LABEL_BUFFER    = 1,   // Generic buffer (non-PE data)
    LABEL_PEHEADER  = 2,   // PE/COFF headers
    LABEL_TEXT      = 3,   // .text section (executable code)
    LABEL_RDATA     = 4,   // .rdata section (read-only data, vtables, strings)
    LABEL_DATA      = 5,   // .data section (writable initialized data)
    LABEL_PDATA     = 6,   // .pdata section (exception handler tables)
    LABEL_RELOC     = 7,   // .reloc section (base relocation fixups)

    LABEL_USER_DEFINED = 1000  // Starting value for user-defined labels
};

The label tells the sleep mask (and Beacon) what kind of data lives in each section. This matters because different sections need different permission transitions during sleep:

LabelNormal PermissionSleep PermissionWhy
LABEL_TEXTPAGE_EXECUTE_READPAGE_READWRITEMust become writable for encryption, non-executable to avoid memory scans
LABEL_RDATAPAGE_READONLYPAGE_READWRITEMust become writable for encryption
LABEL_DATAPAGE_READWRITEPAGE_READWRITEAlready writable, just needs encryption
LABEL_PDATAPAGE_READONLYPAGE_READWRITEMust become writable for encryption
LABEL_PEHEADERPAGE_READWRITEPAGE_READWRITEAlready writable, encrypt to hide PE header artifacts

ALLOCATED_MEMORY_ALLOCATION_METHOD

Cenum ALLOCATED_MEMORY_ALLOCATION_METHOD {
    METHOD_UNKNOWN       = 0,   // Unknown or unset allocation method
    METHOD_VIRTUALALLOC  = 1,   // Allocated via VirtualAlloc / NtAllocateVirtualMemory
    METHOD_HEAPALLOC     = 2,   // Allocated via HeapAlloc / RtlAllocateHeap
    METHOD_MODULESTOMP   = 3,   // Overwrote an existing mapped module (module stomping)
    METHOD_NTMAPVIEW     = 4,   // Mapped via NtMapViewOfSection

    METHOD_USER_DEFINED  = 1000 // Starting value for user-defined methods
};

Why Allocation Method Matters

Different allocation methods require different deallocation APIs. When Beacon exits or needs to clean up a memory region, it must call the correct free function:

Allocation MethodDeallocation API
METHOD_VIRTUALALLOCNtFreeVirtualMemory
METHOD_HEAPALLOCRtlFreeHeap (via RTL_API)
METHOD_MODULESTOMPNo deallocation needed (memory belongs to the stomped module)
METHOD_NTMAPVIEWNtUnmapViewOfSection

Calling NtFreeVirtualMemory on a heap allocation, or NtUnmapViewOfSection on a VirtualAlloc region, would cause heap corruption or an access violation. The allocation method enum prevents these catastrophic cleanup errors.

7. Why BUD Matters for Sleep Masking

Sleep masking is the technique where Beacon encrypts its own memory during sleep intervals to avoid detection by memory scanners. Without BUD, the sleep mask would need to hardcode memory addresses, discover regions through heuristic scanning, or use a simplified single-region approach. BUD gives the sleep mask a complete, loader-provided map of every region and every section within those regions.

Sleep Mask Lifecycle with BUD

Step 1: Beacon enters sleep — transfers control to the sleep mask function
Step 2: Sleep mask reads ALLOCATED_MEMORY from BUD to enumerate all tracked regions
Step 3: For each region, iterates through Sections[8] looking for MaskSection == TRUE
Step 4: For each maskable section: encrypts the memory with XOR/RC4/AES using a per-sleep random key
Step 5: Changes section permissions to PAGE_READWRITE (removes EXECUTE to avoid RX memory scans)
Step 6: Calls NtWaitForSingleObject with the sleep duration (using the pre-resolved syscall from BUD)
Step 7: After waking: decrypts each section and restores original permissions from CurrentProtect
Step 8: Beacon resumes execution with all memory restored to its pre-sleep state

The Key Insight

Without BUD, the sleep mask would need to either:

BUD solves all of these problems by giving the sleep mask a structured, complete, loader-provided memory map with section-level granularity and correct permission metadata. The sleep mask simply iterates the array and processes each entry — no guessing, no scanning, no hardcoding.

The sleep mask also benefits from the pre-resolved SYSCALL_API in BUD. During the sleep cycle, it needs to call NtProtectVirtualMemory (to change permissions) and NtWaitForSingleObject (to sleep). If it had to resolve these itself, it would need its own PEB walking and export table parsing code — duplicating work the loader already did. With BUD, the sleep mask just reads the pre-resolved entries.

8. The FixSectionPermissions Function

This function is the bridge between the PE loading logic (LibTCG) and the BUD memory tracking system. After the loader copies Beacon's PE sections into memory, FixSectionPermissions sets the correct per-section memory protections and populates the ALLOCATED_MEMORY_REGION structure for BUD.

Cvoid FixSectionPermissions(PDLLDATA dlldata, PBYTE dst, PALLOCATED_MEMORY_REGION region)
{
    // Set the region-level metadata
    region->AllocationBase = dst;
    region->RegionSize     = SizeOfDLL(dlldata);
    region->Type           = MEM_COMMIT;

    // Iterate each PE section and apply granular protections
    PIMAGE_SECTION_HEADER sectionHdr = IMAGE_FIRST_SECTION(dlldata->NtHeaders);
    for (WORD i = 0; i < dlldata->NtHeaders->FileHeader.NumberOfSections; i++)
    {
        DWORD protect = PAGE_READONLY;  // Default: read-only

        // Determine the appropriate protection from section characteristics
        // NOTE: Simplified for clarity. The actual source has 7 cascading
        // conditions handling PAGE_WRITECOPY, PAGE_READONLY, PAGE_READWRITE,
        // PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY, PAGE_EXECUTE_READ, and
        // PAGE_EXECUTE_READWRITE based on the full set of characteristic flags.
        if (sectionHdr->Characteristics & IMAGE_SCN_MEM_EXECUTE)
            protect = PAGE_EXECUTE_READ;     // .text: RX
        else if (sectionHdr->Characteristics & IMAGE_SCN_MEM_WRITE)
            protect = PAGE_READWRITE;        // .data: RW

        // Apply the protection
        DWORD oldProtect;
        KERNEL32$VirtualProtect(
            dst + sectionHdr->VirtualAddress,  // Section base address
            sectionHdr->SizeOfRawData,         // Section size
            protect,                           // New protection
            &oldProtect                        // Previous protection (saved)
        );

        // Track the section metadata in the BUD region
        region->Sections[i].Label           = GetLabelFromSectionHeader(sectionHdr->Name);
        region->Sections[i].BaseAddress     = dst + sectionHdr->VirtualAddress;
        region->Sections[i].VirtualSize     = sectionHdr->SizeOfRawData;
        region->Sections[i].CurrentProtect  = protect;
        region->Sections[i].PreviousProtect = oldProtect;
        region->Sections[i].MaskSection     = TRUE;  // All Beacon sections are maskable

        sectionHdr++;
    }
}

What This Function Accomplishes

StepWhat HappensWhy It Matters
Region metadataSets AllocationBase, RegionSize, and TypeGives the sleep mask the top-level allocation boundaries for this region
Section iterationWalks each PE section via IMAGE_FIRST_SECTIONEach section needs different permissions based on its characteristics flags
Protection logicMaps PE characteristics to Windows memory protectionsReplaces the monolithic RWX allocation with granular per-section permissions, eliminating detection vector 3
VirtualProtect callActually changes the page permissionsThe memory was initially allocated as RW for writing; now each section gets its correct final protection
BUD trackingRecords label, base, size, protection, and mask flag for each sectionBuilds the section map that the sleep mask will use to encrypt/decrypt and restore permissions

The GetLabelFromSectionHeader helper function converts PE section names (like .text, .rdata) into the corresponding ALLOCATED_MEMORY_LABEL enum values. This abstraction allows the sleep mask to reason about section types without parsing PE section name strings.

FixSectionPermissions Data Flow

PE Section Table
from NtHeaders
FixSectionPermissions
for each section
VirtualProtect
set RX/RW/R
BUD Section[i]
label + base + size
+ protect + mask

9. Module Summary

Key Takeaways

Module 6 Quiz: Beacon User Data

Q1: What version number does Crystal-Loaders set in USER_DATA for Cobalt Strike 4.11?

Correct! The version field is set to 0x041100, which represents Cobalt Strike 4.11. Beacon validates this value immediately when receiving the USER_DATA pointer — if it does not match the Beacon's own version, the entire BUD structure is ignored and all advanced evasion features that depend on it are silently disabled.

Q2: What does the MaskSection field in ALLOCATED_MEMORY_SECTION control?

Correct! The MaskSection boolean tells the sleep mask whether to encrypt this particular section during sleep intervals. When set to TRUE, the sleep mask will XOR/RC4/AES encrypt the section's contents, change its permissions to RW (removing the executable flag), and later decrypt and restore permissions on wake. It is set to FALSE for the sleep mask's own memory, since the sleep mask cannot encrypt itself while it is executing.

Q3: Why does the loader track ALLOCATED_MEMORY_ALLOCATION_METHOD?

Correct! Different allocation methods require different deallocation APIs. Calling NtFreeVirtualMemory on a heap allocation would corrupt the heap. Calling NtUnmapViewOfSection on a VirtualAlloc region would cause an access violation. The allocation method enum tells Beacon exactly which free function to call for each region: NtFreeVirtualMemory for VirtualAlloc, RtlFreeHeap for HeapAlloc, nothing for module stomping, and NtUnmapViewOfSection for mapped sections.