Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
LdrShuffle — Técnica de ejecución/inyección de código mediante la manipulación de la estructura de módulos DLL en el PEB | Kitploit
Herramientas/GitHubGitHub/rwxstoned/ldrshuffle
Análisis de CódigoExplotaciónMovimiento LateralShellcodePost-ExplotaciónPruebas de PenetraciónRed TeamingDesarrollo de PayloadsExplotación de Binarios
GitHubrwxstoned/ldrshuffle

LdrShuffle

Técnica de ejecución/inyección de código mediante la manipulación de la estructura de módulos DLL en el PEB

2894410hace 1 añoRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio

LdrShuffle

Ejecución de código sigilosa mediante la modificación del EntryPoint de los módulos cargados en tiempo de ejecución.

Resumen

Los procesos de Windows tienen varios módulos cargados en tiempo de ejecución. Cada uno de estos módulos tiene una función DllMain() definida, que se invocará en la creación/destrucción de procesos o subprocesos (cuatro escenarios posibles).

Para llamar correctamente a esas funciones durante la vida del proceso, las funciones del cargador de Windows (ntdll!Ldrp*) consultan una lista de entradas que contienen parámetros clave (incluido el campo EntryPoint) para cada módulo.

Al sobrescribir este EntryPoint de una DLL, conseguimos que la ejecución de código se redirija a un lugar de nuestra elección.

Casos de uso

Esto puede utilizarse tanto como primitiva de ejecución de código como para el proxying de API, es decir, para ejecutar ciertas APIs con una pila de llamadas no sospechosa, ya que serán invocadas por funciones legítimas de Windows.

También puede utilizarse para desencadenar la ejecución en un proceso remoto, siempre que el atacante tenga la capacidad de leer y escribir memoria en el proceso objetivo. De forma similar a la Threadless Injection, esto permite ejecutar código en un proceso sin invocar las API clásicas relacionadas con la ejecución (CreateRemoteThread, QueueUserAPC).

Desafíos

La carga/descarga de módulos dentro de un proceso de Windows es un tema complejo que presenta muchos desafíos, potencial de inestabilidad, condiciones de carrera y fallos. Un obstáculo bien conocido relacionado con la ejecución de código como parte de una función DllMain(), por ejemplo, radica en que hay un Loader Lock activo y en que nos encontramos en un subproceso que no se ha configurado por completo, o que está en proceso de terminación.

Por lo tanto, he intentado documentar adecuadamente qué es posible y qué no. Por ejemplo, aunque la mayoría de las llamadas API habituales pueden realizarse, ejecutar un beacon completo conlleva ciertos requisitos de estar en un proceso separado, para evitar interbloqueos causados por las funciones utilizadas en wininet.dll o winhttp.dll.

Implementación

Repaso sobre la carga de DLL en Windows

Cada proceso mantiene una lista de estructuras _LDR_DATA_TABLE_ENTRY en tiempo de ejecución. Estas estructuras contienen muchos detalles relevantes de la DLL, como su EntryPoint (que sobrescribiremos), su nombre, ciertos hashes, marcas de tiempo, varias banderas, etc. Algunas de estas estructuras están documentadas y otras no.

Estas pueden visualizarse mediante este comando de WinDbg:

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 ''

Tenga en cuenta la bandera DontCallForThreads. Como su nombre indica, si esa bandera está establecida, el sistema operativo NO llamará al DllMain() de ese módulo para eventos de subproceso (es decir, DLL_THREAD_ATTACH o DLL_THREAD_DETACH).

Al crear una DLL, se debe seguir la siguiente plantilla para trabajar en conjunto con las funciones del cargador del sistema operativo:

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.
}

Detalles técnicos sobre la implementación

Configuración de una llamada API

Como se ha descrito anteriormente, la técnica sobrescribe temporalmente el EntryPoint de una DLL para redirigir la ejecución. Dado que no tenemos control más allá de la redirección de la ejecución, hay que hacer algunos arreglos en paralelo para manejar qué queremos ejecutar, con qué argumentos y cómo recuperar el valor de retorno.

Esto se logra definiendo una estructura DATA_T en el heap, de modo que permanezca accesible a lo largo de los distintos pasos.

Esa estructura se define de la siguiente manera:

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;

Para configurar una ejecución de API, hay que preparar estos campos. El valor ret es el que recogerá el valor de retorno tras la ejecución. El event se utiliza para la sincronización, para señalar que la ejecución ha terminado. Todos los demás campos son entradas que definen qué API llamar (function), con qué argumentos (dwArgs y args[]), la dirección de la función Runner() a la que se redirige la ejecución, y copias de respaldo de las entradas DLL originales sobrescritas (bakOriginalBase y bakEntryPoint).

El campo createThread debe establecerse a 1 para aquellas funciones API complejas que no se ejecutan bien en un entorno DllMain() (esto incluye muchas librerías wininet y winhttp).

Aquí hay un ejemplo de configuración de una llamada a MessageBoxA() como se ve en el 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");

Modificación de la _LDR_DATA_TABLE_ENTRY

La función UpdateLdr() es la responsable de realizar la modificación adecuada en la _LDR_DATA_TABLE_ENTRY del módulo objetivo.

Descargar herramienta