Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
LdrShuffle — Code execution/injection technique using DLL PEB module structure manipulation | Kitploit
Tools/GitHubGitHub/rwxstoned/ldrshuffle
Code AnalysisExploitationLateral MovementShellcodePost-ExploitationPenetration TestingRed TeamingPayload DevelopmentBinary Exploitation
GitHubrwxstoned/ldrshuffle

LdrShuffle

Code execution/injection technique using DLL PEB module structure manipulation

28944101 year agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository

LdrShuffle

Stealthy code execution via modification of the EntryPoint of loaded modules at runtime.

Summary

Windows processses have various modules loaded at runtime. Each of theses modules has a DllMain() function defined, which will be invoked on process or thread creation/destruction (four possible scenarios).

In order to properly call those functions during the lifetime of the process, the Windows Loader functions (ntdll!Ldrp*) will refer to a list of entries containing key parameters (including the EntryPoint field) for each module.

By overwriting this EntryPoint for a DLL, we ensure that code execution will be redirected to a place of our choosing.

Use Cases

This can be used both as a code execution primitive, and for API proxying, ie. in order to run certain APIs with a non-suspicious callstack since they will be invoked by legitimate Windows functions.

This can also be used to trigger execution in a remote process, provided the attacker has the ability to read and write memory on this target process. Similarly to the Threadless Injection, this provides the ability to execute code in a process without invoking classic APIs related to execution (CreateRemoteThread, QueueUserAPC)

Challenges

The modules loading/unloading within a Windows process is a complex subject which presents a lot of challenges, potential for instability, race conditions, and crashes. A well-known hurdle related to running code as part of a DllMain() function for instance, lies in the fact that a Loader Lock is in place and that we are running in a thread which has not been completely setup, or which is in the process of being terminated.

Therefore, I have tried to document properly what is possible and what is not. For instance, while most of the usual API calls can be performed, running a full-fledged beacon comes with certain requirements to be in a separate process, in order to avoid deadlocks caused by the functions used in wininet.dll or winhttp.dll.

Implementation

Refresher on DLL Loading in Windows

Each process maintains a list of _LDR_DATA_TABLE_ENTRY structures at runtime. Those structures contain many details relevant to the DLL, such as its EntryPoint (which we will overwrite), its name, certain hashes, timestamps, various flags, etc. Some of these structures are documented and some are not.

These can be visualized through this WinDbg command:

dt nt!_LDR_DATA_TABLE_ENTRY 0xdeadbeef

0:006> dt nt!_LDR_DATA_TABLE_ENTRY 0x18d1c4838c0
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x0000018d`1c485de0 - 0x0000018d`1c4832b0 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x0000018d`1c485df0 - 0x0000018d`1c4832c0 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x0000018d`1c4832d0 - 0x0000018d`1c482c80 ]
   +0x030 DllBase          : 0x00007ffe`e87e0000 Void
   +0x038 EntryPoint       : 0x00007ffe`e8838d00 Void
   +0x040 SizeOfImage      : 0x2fe000
   +0x048 FullDllName      : _UNICODE_STRING "C:\WINDOWS\System32\KERNELBASE.dll"
   +0x058 BaseDllName      : _UNICODE_STRING "KERNELBASE.dll"
   +0x068 FlagGroup        : [4]  "???"
   +0x068 Flags            : 0x8a2cc
   +0x068 PackagedBinary   : 0y0
   +0x068 MarkedForRemoval : 0y0
   +0x068 ImageDll         : 0y1
(...)
   +0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
   +0x0f8 OriginalBase     : 0x00007ffe`e87e0000
   +0x100 LoadTime         : _LARGE_INTEGER 0x01db5f86`2fa735fc
   +0x108 BaseNameHashValue : 0x235bec4
   +0x10c LoadReason       : 0 ( LoadReasonStaticDependency )
   +0x110 ImplicitPathOptions : 0x4000
   +0x114 ReferenceCount   : 1
   +0x118 DependentLoadFlags : 0x800
   +0x11c SigningLevel     : 0 ''

The address of the structure can be obtained by walking a doubly-link structure referenced in the process PEB in a PEB_LDR_DATA structure.

dt nt!_PEB_LDR_DATA 0xb4b4c3c3

Note the DontCallForThreads flag. As the name indicates, if that flag is set, the OS will NOT call the DllMain() of that module for thread events (ie. DLL_THREAD_ATTACH or DLL_THREAD_DETACH).

When creating a DLL, the following template has to be followed in order to work hand in hand with the OS Loader functions:

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpvReserved )  // reserved
{
    // Perform actions based on the reason for calling.
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
         // Initialize once for each new process.
         // Return FALSE to fail DLL load.
            break;

        case DLL_THREAD_ATTACH:
         // Do thread-specific initialization.
            break;

        case DLL_THREAD_DETACH:
         // Do thread-specific cleanup.
            break;

        case DLL_PROCESS_DETACH:
        
            if (lpvReserved != nullptr)
            {
                break; // do not do cleanup if process termination scenario
            }
            
         // Perform any necessary cleanup.
            break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

Technical Details on the Implementation

Setting Up an API Call

As outlined above, the technique temporary overwrite the EntryPoint of a DLL in order to redirect execution. Since we have no control over anything more than the redirection of the execution, some arrangements have to be made on the side to handle what we want to run, with what arguments, and how to get the return value back.

This is done by defining a DATA_T structure on the heap, in such a way that it will remain accessible throughout the various steps.

That structure is defined as follows:

typedef struct _DATA_T {
    // LDR structures manipulation
    ULONG_PTR   runner;             // malicious entry point to execute
    ULONG_PTR   bakOriginalBase;    // backup of overwritten OriginalBase
    ULONG_PTR   bakEntryPoint;      // backup of overwritten EntryPoint
    HANDLE      event;              // event signalling that the Runner has executed
    // function call
    ULONG_PTR   ret;                // return value
    DWORD       createThread;       // run this API call in a new thread (required for wininet/winhttp)
    ULONG_PTR   function;           // Windows API to call
    DWORD       dwArgs;             // number of args
    ULONG_PTR   args[MAX_ARGS];     // array of args
} DATA_T, * PDATA_T;

To setup an API execution, these fields have to be prepared. the ret value is the one which will collect the return value after execution. The event is used for synchronization, to signal that the execution has been completed. All the other fields are inputs defining what API to call (function), with which arugments (dwArgs and args[]), the address of the Runner() function where execution is redirected, and backups of the overwritten original DLL entries (bakOriginalBase and bakEntryPoint).

The createThread field needs to be set to 1 for those complex API functions which won't run well in a DllMain() setup (this includes many wininet and winhttp libraries).

Here is an example setting up a call to MessageBoxA() as visible in the PoC:

 pDataT->dwArgs = 4;
 pDataT->runner = (ULONG_PTR)Runner;
 pDataT->function = (ULONG_PTR)MessageBoxA;
 pDataT->args[0] = (ULONG_PTR)0;
 pDataT->args[1] = (ULONG_PTR)"Hello";
 pDataT->args[2] = (ULONG_PTR)"LDRSHUFFLE";
 pDataT->args[3] = (ULONG_PTR)MB_OKCANCEL;
 pDataT->event = CreateEventA(NULL, FALSE, FALSE, "ExecEvt");

Modifying the _LDR_DATA_TABLE_ENTRY

The UpdateLdr() function is responsible for performing the right modification in the target module's _LDR_DATA_TABLE_ENTRY.

RestoreLdr() will restore those changes at a later stage (invoked by Runner()).

Download Tool