Cómo funciona la resolución de nombres de DLL en Windows - Orden de búsqueda y SxS

· Actualizado el: · · Windows, DLL, Cargador, Seguridad, Desarrollo en Windows

Cuando se trabaja con DLL nativas en Windows, es muy frecuente que surja este tipo de confusión:

  • Al escribir LoadLibrary("foo.dll"), ¿a dónde va realmente a buscar?
  • Si coloco el archivo en la misma carpeta que el ejecutable, ¿por qué a veces se carga una DLL distinta?
  • ¿Tiene prioridad System32 o la carpeta de la aplicación?
  • ¿En qué etapa entran en juego el manifest, el API set y las Known DLLs?
  • ¿Qué cambia si se usa SetDllDirectory o AddDllDirectory?
  • ¿Qué acciones facilitan un ataque de plantado de DLL (DLL hijacking)?

Este tema no se resuelve en la práctica con solo memorizar el orden de búsqueda en una línea. En realidad, el cargador de Windows evalúa primero una serie de reglas especiales, antes de recorrer el sistema de archivos en orden.

En este artículo se ordena, con un enfoque práctico, la resolución de nombres de DLL en Windows, incluyendo la diferencia entre unpackaged app y packaged app, las Known DLLs, la loaded-module list, el API set, el side-by-side manifest y el efecto de las API de la familia LoadLibraryEx. El contenido parte de la información pública de Microsoft Learn a fecha de marzo de 2026.123456789

Terminología utilizada en este artículo

A continuación se resumen brevemente, uno por uno, los términos que aparecerán más adelante sin explicación previa. Los detalles se tratan en cada capítulo del cuerpo del artículo.

Término En una frase
packaged app / unpackaged app Se refiere a si la aplicación se distribuye e instala como un paquete (por ejemplo MSIX) o si, como en el formato tradicional, el ejecutable simplemente se coloca en una carpeta. La propia definición del orden de búsqueda es distinta en cada caso (capítulos 3 y 4)
safe DLL search mode Configuración activada de forma predeterminada que mueve la carpeta actual (current folder) hacia el final del orden de búsqueda. Se desactiva poniendo el valor de registro SafeDllSearchMode en 01
DLL redirection Mecanismo por el cual, al colocar un archivo con el nombre nombreDeApp.exe.local en la misma ubicación que el ejecutable, el cargador pasa a mirar primero la carpeta del ejecutable. También se le llama .local o DotLocal (capítulo 7)7
SxS (side-by-side) Mecanismo que permite que coexistan varias versiones de una misma DLL, indicando en un manifest a qué versión se debe enlazar. Es la abreviatura de side-by-side, y en japonés a veces se describe como “colocación en paralelo” (capítulo 7)9
loaded-module list Mecanismo por el cual el sistema comprueba si una DLL con el mismo nombre de módulo ya está cargada en la memoria de ese proceso. Independientemente de la carpeta desde la que se haya cargado, si ya está cargada se usa esa copia (5.1)1
Known DLLs Lista de DLL que Windows considera conocidas para esa versión del sistema. Para las DLL que aparecen en ella, se usa la copia del lado del sistema (5.2)1
API set Nombre de contrato del tipo api-ms-win-.... Es un alias virtual que oculta la DLL física que lo implementa (capítulo 6)3
package dependency graph Conjunto formado por el propio paquete de la aplicación y los paquetes de los que depende, declarados como PackageDependency en la sección Dependencies del manifest del paquete. Se buscan en el orden en que aparecen escritos en el manifest1

1. Conclusiones principales

A continuación se presentan, de entrada, las conclusiones orientadas a la práctica.

  • La resolución de nombres de DLL en Windows no consiste simplemente en “buscar primero en el sistema de archivos”. Elementos como la DLL redirection, el API set, el SxS manifest, la loaded-module list y las Known DLLs forman parte de una etapa previa al orden de búsqueda.1
  • En la forma estándar de una unpackaged app con el safe DLL search mode activado, la carpeta de la aplicación ocupa una posición alta, pero antes de llegar a ella se evalúan las reglas especiales mencionadas.1
  • Aunque una DLL se cargue especificando la ruta completa, las DLL de las que depende no quedan fijadas automáticamente a esa misma ruta completa. Las DLL dependientes se tratan como si se buscaran solo por el nombre del módulo, por lo que pueden resolverse desde otra ubicación.1
  • Known DLLs es un mecanismo por el cual el sistema operativo enlaza determinadas DLL conocidas a la copia del lado del sistema; no se trata de algo que se pueda sobrescribir con la colocación habitual de archivos por parte de la aplicación.1
  • El API set no es “el propio nombre de la DLL real”, sino un alias virtual que oculta la DLL de implementación. Ver un nombre del tipo api-ms-win-... y razonar sobre él con la misma lógica que una búsqueda de DLL normal lleva fácilmente a error.3
  • SetDllDirectory no solo cambia el orden de búsqueda, sino que desactiva en la práctica el safe DLL search mode, por lo que usarlo sin cuidado puede resultar contraproducente desde el punto de vista de la seguridad.1
  • En la práctica, lo más seguro es combinar la especificación de ruta completa, SetDefaultDllDirectories, AddDllDirectory y las banderas LOAD_LIBRARY_SEARCH_* de LoadLibraryEx para acotar explícitamente el ámbito de búsqueda.4562

En resumen, conviene entender que la resolución de nombres de DLL en Windows no depende solo de “qué carpeta ocupa qué posición”, sino también de “qué reglas previas resuelven el nombre” y de “cómo se ha modificado el espacio de búsqueda mediante la API utilizada”.

2. La resolución de nombres de DLL tiene reglas previas antes de “buscar en carpetas”

En la explicación del DLL search order de Microsoft Learn, al cargar una DLL primero se tratan como parte del orden de búsqueda los siguientes elementos.1

  1. DLL redirection
  2. API sets
  3. SxS manifest redirection
  4. loaded-module list
  5. Known DLLs

Después de eso, comienza la búsqueda en el sistema de archivos: la carpeta de la aplicación, System32, la carpeta de Windows y el PATH.1

Si se pasa por alto este punto, se puede pensar que “es extraño que algo se decida antes que la carpeta de la aplicación”, pero, como explicación del cargador de Windows, esa es precisamente la idea central.

Orden de resolución de nombres de DLL en WindowsDiagrama que muestra el orden en que se evalúan las reglas antes de resolver un nombre de DLL: DLL redirection, API set, SxS manifest redirection, loaded-module list, Known DLLs y, por último, el orden de búsqueda en el sistema de archivos, hasta determinar la DLL que realmente se carga.Quiere resolverse un nombre de DLLDLL redirectionAPI setSxS manifest redirectionloaded-module listKnown DLLsOrden de búsqueda en el sistema de archivosSe determina la DLL que realmente se carga

3. Orden de búsqueda estándar para aplicaciones unpackaged

En el caso de una aplicación de escritorio habitual, cuando se carga una DLL sin especificar la ruta completa, Microsoft Learn describe el orden de búsqueda estándar para unpackaged app. En el estado predeterminado, con el safe DLL search mode activado, el orden es el siguiente.1

# Dónde se busca Tipo Nota
1 DLL redirection Regla previa Si existe nombreDeApp.exe.local (capítulo 7)
2 API sets Regla previa Del nombre de contrato a la DLL de implementación (capítulo 6)
3 SxS manifest redirection Regla previa Binding mediante manifest (capítulo 7)
4 loaded-module list Regla previa Si ya hay un módulo con el mismo nombre cargado (5.1)
5 Known DLLs Regla previa Si es una DLL conocida, se usa la copia del sistema (5.2)
6 package dependency graph Regla previa A partir de Windows 11 versión 21H2. Se busca en el orden en que aparece escrito en el manifest
7 Carpeta desde la que se cargó la aplicación Sistema de archivos Aquí empieza la búsqueda real en carpetas
8 Carpeta del sistema Sistema de archivos Normalmente %SystemRoot%\System32. Es la ubicación que se obtiene con GetSystemDirectory
9 Carpeta del sistema de 16 bits Sistema de archivos La carpeta System de la era de 16 bits. No existe una función para obtener esta ruta, pero sí se realiza la búsqueda. En las aplicaciones actuales casi nunca hay que tenerla en cuenta; basta con saber que “sigue existiendo como elemento del orden”
10 Carpeta de Windows Sistema de archivos Ubicación que se obtiene con GetWindowsDirectory
11 Carpeta actual (current folder) Sistema de archivos Si el safe DLL search mode está desactivado, esta pasa a la posición 8
12 Directorios listados en PATH Sistema de archivos No incluye las rutas por aplicación de la clave de registro App Paths

Esta tabla no está pensada para memorizarse. Es una tabla para identificar en qué etapa se decide cada caso concreto. Si un problema se decide en las etapas 1 a 6, por más que se cambie la disposición de carpetas de las etapas 7 en adelante, nada cambiará.

En la práctica, hay tres puntos que influyen especialmente:

  • De forma predeterminada, la carpeta actual queda bastante atrás. El safe DLL search mode dificulta que la carpeta actual se adelante en el orden.1
  • Sin embargo, estar más atrás no significa que sea seguro. Mientras quede en el ámbito de búsqueda un directorio que el atacante puede controlar, sigue habiendo margen para el DLL preloading.2
  • A partir de Windows 11 21H2, la explicación de búsqueda de las unpackaged app también incluye el package dependency graph. Es una diferencia fácil de pasar por alto si solo se recuerda la explicación antigua.1

4. Las aplicaciones packaged y unpackaged no son lo mismo

Microsoft Learn define un orden de búsqueda distinto para las packaged app. En una packaged app, el package dependency graph actúa en una etapa más temprana, y el propio enfoque de la búsqueda es algo diferente.1

Si se pasa por alto esta diferencia, tras empaquetar en MSIX o introducir Windows App SDK pueden surgir confusiones como estas:

  • Una DLL que se encuentra al ejecutar en modo unpackaged durante el desarrollo no se encuentra en el paquete de producción
  • Las dependencias definidas en el package manifest se mezclan con la dependencia tradicional del PATH, y las condiciones de reproducción cambian
  • Se explica “el orden de búsqueda de DLL de Windows es así” con una sola tabla, sin recoger la diferencia de comportamiento de las packaged app

En los artículos o en las revisiones de diseño, es más seguro empezar separando si se está hablando de una packaged app o de una unpackaged app.1

5. Qué hacen Known DLLs y la loaded-module list

Lo que más fácilmente contradice la intuición en la resolución de DLL es la loaded-module list y las Known DLLs.

5.1 La loaded-module list

Microsoft Learn explica que el sistema puede comprobar si ya hay cargada en memoria una DLL con el mismo nombre de módulo.1

Es decir, antes de la búsqueda en el sistema de archivos, se evalúa:

  • si ese nombre de DLL ya está cargado
  • y, como consecuencia, si realmente hace falta salir a buscarlo

Por eso, si durante una investigación se pasa por alto el hecho de que “en este proceso ya había cargada previamente una DLL con el mismo nombre desde otra carpeta”, se malinterpretan las condiciones de reproducción.

5.2 Known DLLs

Known DLLs es la lista de DLL que Windows considera conocidas para esa versión, y se puede consultar en HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\KnownDLLs. Si una DLL corresponde a esa lista, el sistema usa la copia de esa DLL conocida.1

Aquí lo importante es que Known DLLs no es el tipo de mecanismo en el que “una aplicación cualquiera puede ganar con solo colocar una DLL del mismo nombre en su carpeta”. Entenderlo como una simple carrera de prioridad con System32 lleva a malinterpretar el comportamiento.

6. Un API set no es el “nombre real de la DLL”, sino un nombre de contrato

Al ver un nombre como api-ms-win-core-..., es fácil pensar de inmediato “¿en qué lugar hay que buscar ese archivo DLL?”. Pero Microsoft Learn explica que el API set es un alias virtual hacia la DLL física, un mecanismo que separa la implementación del contrato.3

Es decir, no es correcto pensar que:

  • el nombre del API set = directamente el nombre del archivo DLL físico
  • resolver el API set = la misma búsqueda de archivo que una DLL normal

Tener presente la idea de API set facilita explicaciones como estas:

  • aunque el nombre de la DLL de implementación cambie según la versión de Windows o el tipo de dispositivo, todo sigue siendo coherente
  • quien llama no necesita saber de forma fija “qué DLL host lo implementa”3

7. El manifest y side-by-side (SxS) son otra solución al problema del versionado de DLL

La DLL redirection y el SxS manifest no son un simple truco de orden de búsqueda: se explican como un mecanismo para evitar conflictos de DLL versioning.789

Según Microsoft Learn:

  • el manifest es un XML que describe un side-by-side assembly o una isolated application
  • el side-by-side assembly es la unidad de nombrado, binding, versioning y deployment
  • según la dependencia registrada en el manifest, el cargador decide a qué versión hacer bind

Esa es la idea general.89

Por eso, en la práctica hay que distinguir estos tres casos:

  • simplemente colocar una DLL privada en la carpeta de la aplicación
  • usar DLL redirection con .local
  • usar side-by-side binding mediante manifest

Los tres se parecen en el sentido de que “afectan a la resolución de la DLL”, pero la intención de diseño no es la misma. Microsoft Learn recomienda esta división: DLL redirection si se quiere resolver el problema sin tocar una aplicación existente, y componentes side-by-side si se está creando una aplicación nueva.7

7.1 Qué hace exactamente .local

Como suele resumirse en una sola línea, aquí se detalla por separado el comportamiento en una unpackaged app.7

  • Qué se coloca: el archivo de redirección se llama nombreDelEjecutable.local. Para Editor.exe, se coloca Editor.exe.local en la misma carpeta que el ejecutable. La DLL que se quiere cargar también se coloca en esa misma carpeta
  • Contenido: el contenido del archivo se ignora. Su sola existencia es la señal que hace que, al cargar una DLL, el cargador mire primero la carpeta del ejecutable
  • Alcance: afecta tanto a la carga con ruta completa como a la carga solo por nombre de módulo. Independientemente de la ruta que se pase a LoadLibrary o LoadLibraryEx, si hay una DLL del mismo nombre en la carpeta del ejecutable, es esa la que se carga. Es una funcionalidad pensada para resolver casos como COM, donde solo hay un lugar de registro posible
  • Si no se encuentra: si no está en la carpeta del ejecutable, se vuelve al orden de búsqueda normal
  • Forma de carpeta: también funciona creando una carpeta llamada Editor.exe.local y colocando la DLL dentro
  • Activarlo para toda la máquina: se crea un valor DWORD llamado DevOverrideEnable en HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options, se pone en 1 y se reinicia. Con esto, la redirección mediante .local funciona incluso si la aplicación tiene un application manifest
  • Caso de packaged app: la ubicación cambia, y se busca en <carpeta de instalación del paquete>\microsoft.system.package.metadata\application.local\

Hay un efecto secundario que, si se pasa por alto, hace que la investigación se vuelva confusa. Si se usa DLL redirection y la aplicación no tiene acceso a todas las unidades o directorios del orden de búsqueda, LoadLibrary interrumpe la búsqueda en el momento en que se deniega el acceso. Si no se usa DLL redirection, un directorio inaccesible simplemente se omite y la búsqueda continúa.7 Si solo en el entorno donde se colocó .local se produce el mensaje de que “no se encuentra la DLL que debería estar más adelante”, conviene sospechar de esta diferencia.

8. Qué cambia con LoadLibraryEx, SetDllDirectory y AddDllDirectory

8.1 SetDllDirectory

SetDllDirectory cambia el orden de búsqueda, pero Microsoft Learn indica explícitamente que desactiva en la práctica el safe DLL search mode.1

Es decir, aunque la intención al usarlo sea “solo quiero añadir una carpeta propia de la aplicación”, el resultado es que cambia el espacio de búsqueda, incluido el tratamiento de la carpeta actual (current folder).

Además, si se llama a SetDllDirectory desde el proceso padre, ese efecto puede alcanzar también al orden de búsqueda estándar del proceso hijo.1

Por eso, en la práctica, en lugar de usar SetDllDirectory de forma habitual y sin cuidado, es más seguro recurrir a:

  • SetDefaultDllDirectories
  • AddDllDirectory
  • las banderas LOAD_LIBRARY_SEARCH_* de LoadLibraryEx

456

8.2 AddDllDirectory

Las rutas añadidas con AddDllDirectory se usan en combinación con LOAD_LIBRARY_SEARCH_USER_DIRS. Microsoft Learn indica que, cuando se añaden varias, el orden de búsqueda entre ellas no está definido.15

Por eso conviene evitar un diseño en el que:

  • se hayan añadido varios directorios
  • y se espere de forma estricta ese orden concreto de búsqueda

8.3 SetDefaultDllDirectories

SetDefaultDllDirectories se describe como una API para limitar el ámbito de búsqueda excluyendo del DLL search path estándar los directorios propensos a vulnerabilidades.4

Hay tres propiedades que conviene tener especialmente presentes:

  • actúa a nivel de proceso
  • una vez llamada, su efecto se mantiene durante toda la vida del proceso
  • no es posible volver la ruta de búsqueda estándar ya configurada a su forma estándar original

Pensando en la seguridad, es una API que facilita un diseño en el que “justo al arrancar se orienta el espacio de búsqueda hacia el lado más seguro”.4

8.4 LoadLibraryEx

LoadLibraryEx permite cambiar el comportamiento de búsqueda mediante las banderas LOAD_WITH_ALTERED_SEARCH_PATH o LOAD_LIBRARY_SEARCH_*.61

En la práctica, es una API que facilita atender requisitos como:

  • querer incluir también la carpeta de la DLL de origen como objetivo de búsqueda, incluyendo las DLL dependientes
  • querer limitarse solo a la carpeta de la aplicación, System32 y los directorios de usuario añadidos explícitamente

Antes de escribir código hay que tener en cuenta cuatro restricciones.6

Restricción Contenido
Segundo argumento hFile está reservado para uso futuro; siempre debe pasarse NULL
Combinación no permitida LOAD_WITH_ALTERED_SEARCH_PATH no se puede combinar con ninguna bandera LOAD_LIBRARY_SEARCH_*
Ruta completa obligatoria Si se usa LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, el primer argumento debe ser una ruta completa
Orden cuando se combinan varias Se buscan en el orden LOAD_LIBRARY_SEARCH_DLL_LOAD_DIRLOAD_LIBRARY_SEARCH_APPLICATION_DIRLOAD_LIBRARY_SEARCH_USER_DIRSLOAD_LIBRARY_SEARCH_SYSTEM32. Sin embargo, el orden dentro de USER_DIRS no está definido

En cuanto se especifica aunque sea una sola bandera LOAD_LIBRARY_SEARCH_*, la ruta de búsqueda estándar deja de usarse por completo. Es decir, si se pasa solo LOAD_LIBRARY_SEARCH_SYSTEM32, la carpeta de la aplicación no se busca. “Limitar” significa exactamente eso.

8.5 Ejemplo de código mínimo (C/C++)

Si se reúnen en un solo lugar las API vistas hasta ahora, se obtiene el siguiente código. SetDefaultDllDirectories y LOAD_LIBRARY_SEARCH_* son API disponibles desde Windows 8, así que hay que declarar explícitamente la versión objetivo de las cabeceras.46

/* cl /W4 loader.c  (Visual Studio 2022 + Windows SDK 10)
 * SetDefaultDllDirectories / AddDllDirectory / LOAD_LIBRARY_SEARCH_* son
 * de Windows 8 en adelante. Si también se quiere dar soporte a Windows 7,
 * hace falta KB2533623 como requisito, y obtener las funciones en tiempo
 * de ejecución desde Kernel32.dll con GetProcAddress. */
#define _WIN32_WINNT 0x0602   /* Windows 8 */
#include <windows.h>
#include <stdio.h>

int wmain(void)
{
    /* 1. Orientar el espacio de búsqueda predeterminado del proceso hacia
     *    el lado seguro. El objetivo es excluir la carpeta actual y el
     *    PATH del ámbito de búsqueda.
     *    LOAD_LIBRARY_SEARCH_DEFAULT_DIRS es la combinación de
     *    APPLICATION_DIR + SYSTEM32 + USER_DIRS.
     *    Si no se incluye USER_DIRS aquí, el AddDllDirectory del paso 2
     *    no se reflejará en el espacio predeterminado del proceso. */
    if (!SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)) {
        wprintf(L"SetDefaultDllDirectories failed: %lu\n", GetLastError());
        return 1;
    }

    /* 2. Añadir explícitamente solo la carpeta de plugins propia.
     *    Lo que se pasa a AddDllDirectory debe ser una ruta absoluta. */
    const wchar_t *pluginDir = L"C:\\MyApp\\plugins";
    DLL_DIRECTORY_COOKIE cookie = AddDllDirectory(pluginDir);
    if (cookie == NULL) {
        wprintf(L"AddDllDirectory failed: %lu\n", GetLastError());
        return 1;
    }

    /* 3. Cargar.
     *    El segundo argumento hFile está reservado, así que siempre NULL.
     *    Al pasar aquí las banderas, en lugar del espacio predeterminado
     *    del paso 1, se usa exclusivamente esta combinación de banderas.
     *    Como se excluye APPLICATION_DIR, la carpeta de la aplicación
     *    no se busca. */
    HMODULE h = LoadLibraryExW(
        L"foo.dll",
        NULL,
        LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32);
    if (h == NULL) {
        wprintf(L"LoadLibraryExW failed: %lu\n", GetLastError());
        RemoveDllDirectory(cookie);
        return 1;
    }

    /* 4. Confirmar siempre desde dónde se cargó.
     *    En una investigación, importa más "desde dónde se pudo cargar"
     *    que "si se pudo cargar". */
    {
        wchar_t loadedPath[MAX_PATH];
        DWORD cap = (DWORD)(sizeof(loadedPath) / sizeof(loadedPath[0]));
        DWORD len = GetModuleFileNameW(h, loadedPath, cap);
        if (len > 0 && len < cap) {
            wprintf(L"loaded from: %s\n", loadedPath);
        } else {
            wprintf(L"GetModuleFileNameW failed: %lu\n", GetLastError());
        }
    }

    FreeLibrary(h);
    RemoveDllDirectory(cookie);
    return 0;
}

Este código atiende cuatro puntos:

  1. Llamar primero a SetDefaultDllDirectories. Una vez llamada, su efecto dura toda la vida del proceso, y no se puede volver a la ruta de búsqueda estándar4
  2. AddDllDirectory solo cobra sentido junto con LOAD_LIBRARY_SEARCH_USER_DIRS. Si SetDefaultDllDirectories no incluye USER_DIRS, el directorio añadido solo se usa en las llamadas a LoadLibraryEx que especifiquen LOAD_LIBRARY_SEARCH_USER_DIRS5
  3. Hacer la limpieza correspondiente. El cookie que devuelve AddDllDirectory se puede retirar pasándolo a RemoveDllDirectory5
  4. Mostrar la ubicación desde la que se cargó. Que exista o no esa línea con GetModuleFileNameW cambia el tiempo de investigación cuando hay un fallo

Cabe señalar que las DLL de las que depende foo.dll también se buscan dentro de este mismo ámbito de LOAD_LIBRARY_SEARCH_*. Si se quiere que la propia carpeta de foo.dll sea también el destino de búsqueda de sus DLL dependientes, hay que poner el primer argumento como ruta completa y añadir además LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR.

8.6 Uso desde C#

.NET permite aplicar el mismo enfoque. La ruta de búsqueda de P/Invoke se controla con el atributo DefaultDllImportSearchPaths, y la carga explícita, con NativeLibrary.Load.

// .NET 8 / C# 12
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

// Para todos los P/Invoke del ensamblado, limitar el destino de búsqueda
// predeterminado a System32.
// Restricción: este atributo no se aplica a los P/Invoke que ya
//              especifican una ruta absoluta. Tampoco tiene efecto en
//              plataformas que no sean Windows.
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]

internal static class Program
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint GetTickCount();

    private static void Main()
    {
        // El atributo, por sí solo, no hace nada. Solo surte efecto al invocar.
        Console.WriteLine($"GetTickCount = {GetTickCount()}");

        // El plugin está en una carpeta dedicada. Aquí NO debe escribirse
        //   NativeLibrary.Load("foo.dll", asm, ApplicationDirectory | System32)
        // ApplicationDirectory apunta a la carpeta del exe, y System32 a la
        // carpeta del sistema operativo: ninguna de las dos ve la carpeta
        // del plugin. O falla la carga, o por casualidad se toma otro
        // foo.dll que estuviera junto al exe.
        // Cuando se conoce la ubicación, lo seguro es indicar la ruta completa.
        string pluginDir = Path.Combine(AppContext.BaseDirectory, "plugins");
        string pluginPath = Path.GetFullPath(Path.Combine(pluginDir, "foo.dll"));
        if (!File.Exists(pluginPath))
        {
            throw new FileNotFoundException("No se encuentra el plugin.", pluginPath);
        }

        // La sobrecarga que recibe una ruta lee ese archivo directamente
        // (no realiza ninguna búsqueda).
        IntPtr handle = NativeLibrary.Load(pluginPath);

        try
        {
            // Comprobar desde dónde se cargó.
            foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
            {
                if (string.Equals(module.ModuleName, "foo.dll", StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine($"loaded from: {module.FileName}");
                }
            }
        }
        finally
        {
            NativeLibrary.Free(handle);
        }
    }
}

Los valores de DllImportSearchPath corresponden a las banderas LOAD_LIBRARY_SEARCH_*. Por eso, aunque en C# se indique “solo System32”, se aplican las mismas restricciones vistas en 8.4. No se busca en el directorio de la aplicación.

Aquí conviene notar que DllImportSearchPath no tiene ningún valor que represente “una carpeta cualquiera”. ApplicationDirectory apunta a la carpeta del exe y System32 a la carpeta del sistema operativo, pero no existe ningún valor que represente una carpeta propia definida por uno mismo, como la de un plugin. Por eso, aunque se intente capturar con las banderas de búsqueda algo que está en una carpeta dedicada, no se consigue, y hay que recurrir a indicar la ruta completa como en el ejemplo anterior.

Además, aunque se cargue con ruta completa, tal como se explica en el capítulo 9 las DLL de las que depende foo.dll no quedan fijadas. Si el plugin trae sus propias DLL dependientes, sigue haciendo falta la misma técnica del lado de C: añadir esa carpeta con AddDllDirectory y activar LOAD_LIBRARY_SEARCH_USER_DIRS, o bien reunir todas las dependencias en una sola carpeta y usar LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR.

9. Especificar la ruta completa no fija también las DLL dependientes

Este es un punto bastante importante en la práctica y, a la vez, fácil de pasar por alto. Microsoft Learn explica que, aunque la primera DLL se cargue especificando la ruta completa, las DLL de las que depende se buscan solo por el nombre del módulo.14

Es decir, no es cierto que:

  • se cargó explícitamente C:\\MyApp\\plugins\\foo.dll
  • por lo tanto, bar.dll, de la que depende foo.dll, también se tomará siempre de la misma carpeta

Si se tiene este malentendido, puede darse el siguiente problema, un tanto complicado:

  • funciona en el entorno de desarrollo
  • en el entorno de distribución se resuelve un bar.dll distinto
  • el conflicto de DLL dependientes pasa a depender del entorno de reproducción

10. Cómo evitar el DLL preloading / hijacking

En el apartado de DLL security de Microsoft Learn se explica que la combinación de una carga dinámica sin ruta completa y un directorio de búsqueda que el atacante puede controlar da lugar al DLL preloading attack o al binary planting attack.2

En la práctica, resulta útil orientarse hacia este esquema básico:

  • reducir las cargas con nombre desnudo del tipo LoadLibrary("foo.dll")
  • usar la especificación de ruta completa cuando sea necesario
  • limitar con SetDefaultDllDirectories la ruta de búsqueda predeterminada del proceso
  • añadir con AddDllDirectory únicamente los directorios permitidos de forma explícita
  • indicar explícitamente el ámbito de búsqueda con LOAD_LIBRARY_SEARCH_SYSTEM32, LOAD_LIBRARY_SEARCH_APPLICATION_DIR, LOAD_LIBRARY_SEARCH_USER_DIRS, etc.
  • evitar la carpeta actual (current folder) y una dependencia descuidada del PATH

Es especialmente peligroso que un proceso que se ejecuta con privilegios de administrador tenga una ruta de búsqueda ambigua. Microsoft Learn también explica que, si se carga una DLL maliciosa, esta se ejecuta con los privilegios de ese proceso.2

11. Cómo comprobar qué DLL se cargó y desde dónde

Hasta aquí se ha hablado de las especificaciones. En una investigación real, es más rápido comprobarlo en lugar de suponerlo. Se presentan cuatro opciones según el objetivo.

Qué se quiere saber Qué se usa Dónde se mira
Dónde se buscó y dónde se encontró Process Monitor10 Registro de accesos a archivos
Qué está cargado en este momento Process Explorer, tasklist /m11 Lista de módulos del proceso
Qué procesos tienen tomada una DLL determinada ListDLLs12 Lista transversal a todos los procesos
Ruta realmente cargada durante la depuración Ventana de módulos de Visual Studio “Depurar” > “Ventanas” > “Módulos”7

11.1 Rastrear la búsqueda con Process Monitor

Esta es la opción con más volumen de información. El procedimiento es el siguiente.10

  1. Iniciar Process Monitor como administrador y comenzar a grabar
  2. Abrir “Filter” > “Filter…” y añadir Process Name is con el nombre del exe objetivo
  3. En la misma pantalla, añadir Path ends with .dll
  4. Ejecutar la aplicación objetivo y detener la grabación en el momento en que ocurra el problema

Lo que hay que observar aquí es la columna Result. El cargador intenta abrir los archivos en el orden de búsqueda, de arriba abajo, así que en los lugares donde no se encuentra aparece NAME NOT FOUND, y en el lugar donde realmente se pudo abrir aparece SUCCESS. Es decir, la línea inmediatamente posterior a la última serie de NAME NOT FOUND es la ruta que realmente se adoptó.

Al contrastarlo con la tabla del capítulo 3, se puede saber en qué etapa se decidió ese caso concreto. Si el caso se decide en las reglas previas (elementos 1 a 6 de la tabla), directamente no aparecerá ningún registro de que se fue a buscar el archivo. Esto también es una pista importante.

11.2 Listar los módulos ya cargados

Para ver, en un proceso que ya está en ejecución, qué se ha cargado y desde dónde, se puede comprobar sin herramientas adicionales.

tasklist /m foo.dll

Este comando lista el nombre y el PID de los procesos que tienen cargado el módulo indicado.11 Una vez identificado el proceso objetivo, si se cambia el panel inferior de Process Explorer a la vista de DLL, se puede comprobar incluso la ruta completa de las DLL que ese proceso tiene cargadas.

Con ListDLLs, de Sysinternals, se puede comprobar lo mismo desde la línea de comandos y, además, de forma transversal a todos los procesos.12

Solo con este método se puede saber si está actuando la loaded-module list del apartado 5.1. Esto es porque, si una DLL del mismo nombre ya está cargada previamente desde otra carpeta, ese proceso no vuelve a salir a buscarla.

12. Lista de verificación para la práctica

Al revisar un diseño de carga de DLL en Windows, comprobar como mínimo lo siguiente reduce los incidentes.

  1. ¿Es la aplicación una packaged app o una unpackaged app?
  2. ¿Qué DLL provienen de enlazado estático y cuáles de carga dinámica?
  3. ¿Se especifica la ruta completa o solo el nombre del módulo?
  4. ¿No se está usando SetDllDirectory?
  5. ¿La configuración permite usar SetDefaultDllDirectories y LOAD_LIBRARY_SEARCH_*?
  6. Si se usa AddDllDirectory varias veces, ¿no se está dando por hecho de forma implícita una dependencia del orden?
  7. ¿Con cuál de manifest, SxS, DLL privada o redirection se gestionan las dependencias?
  8. ¿No hay suposiciones débiles en materia de seguridad en torno a la carpeta actual o al PATH?
  9. ¿Las DLL dependientes no se resuelven desde una ubicación distinta en otro entorno?
  10. En el entorno donde aparece el síntoma, ¿se ha comprobado realmente desde dónde se cargó? (capítulo 11)

Comprobar por separado estos diez puntos permite atajar bastante antes problemas como “no se encuentra la DLL”, “se cargó una DLL distinta”, “solo en producción no arranca” o “la revisión de vulnerabilidades se detiene”.

13. Resumen

La resolución de nombres de DLL en Windows no es simplemente “un orden de búsqueda en carpetas”. En realidad, se decide por la superposición de la DLL redirection, el API set, el SxS manifest, la loaded-module list, las Known DLLs y el espacio de búsqueda modificado mediante llamadas a la API.134

En la práctica, lo más importante se reduce a estos seis puntos:

  • no memorizar el orden de búsqueda como una sola tabla; la tabla sirve para identificar en qué etapa se decidió cada caso concreto
  • distinguir entre packaged y unpackaged
  • entender que, incluso con ruta completa, las DLL dependientes pueden tratarse de forma independiente
  • no usar SetDllDirectory sin cuidado
  • si se quiere ir hacia el lado seguro, usar SetDefaultDllDirectories y las banderas de búsqueda de LoadLibraryEx
  • no quedarse en la suposición: comprobar con Process Monitor u otras herramientas la ruta realmente utilizada

La resolución del nombre de una DLL es un punto donde suelen salir a la superficie a la vez fallos de arranque, diferencias de entorno y problemas de seguridad. Por eso merece la pena entender no solo “en qué orden busca Windows”, sino también qué es, en primer lugar, lo que Windows trata como premisa de la resolución de nombres.

Artículos relacionados

Referencias

  1. Microsoft Learn: Dynamic-link library search order
  2. Microsoft Learn: Dynamic-Link Library Security
  3. Microsoft Learn: Windows API sets
  4. Microsoft Learn: SetDefaultDllDirectories function
  5. Microsoft Learn: AddDllDirectory function
  6. Microsoft Learn: LoadLibraryEx function
  7. Microsoft Learn: Dynamic-link library redirection
  8. Microsoft Learn: Manifests
  9. Microsoft Learn: About Side-by-Side Assemblies
  10. Microsoft Learn: Process Monitor
  11. Microsoft Learn: ListDLLs
  12. Microsoft Learn: tasklist
  1. Microsoft Learn, Dynamic-link library search order, consultado el 24 de marzo de 2026  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

  2. Microsoft Learn, Dynamic-Link Library Security, consultado el 24 de marzo de 2026  2 3 4 5

  3. Microsoft Learn, Windows API sets, consultado el 24 de marzo de 2026  2 3 4 5 6

  4. Microsoft Learn, SetDefaultDllDirectories function, consultado el 24 de marzo de 2026  2 3 4 5 6 7 8 9

  5. Microsoft Learn, AddDllDirectory function, consultado el 24 de marzo de 2026  2 3 4 5 6

  6. Microsoft Learn, LoadLibraryEx function, consultado el 24 de marzo de 2026  2 3 4 5 6

  7. Microsoft Learn, Dynamic-link library redirection, consultado el 24 de marzo de 2026  2 3 4 5 6 7

  8. Microsoft Learn, Manifests, consultado el 24 de marzo de 2026  2 3

  9. Microsoft Learn, About Side-by-Side Assemblies, consultado el 24 de marzo de 2026  2 3 4

  10. Microsoft Learn, Process Monitor  2

  11. Microsoft Learn, tasklist  2

  12. Microsoft Learn, ListDLLs  2

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Desarrollo de aplicaciones para Windows

La forma de colocar y cargar las DLL está directamente relacionada con si una aplicación de Windows arranca o no, con su forma de distribución y con la reproducibilidad de los problemas, por lo que merece tratarse en el contexto del desarrollo de aplicaciones de Windows.

Consultoría técnica y revisión de diseño

La resolución de nombres de DLL es un tema donde confluyen la investigación de fallos, la migración, la prevención de vulnerabilidades y el diseño de la distribución, por lo que resulta un tema fácil de abordar como revisión de diseño o consultoría técnica.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

Cuando se especifica un nombre de DLL con LoadLibrary, ¿en qué orden lo busca Windows?
Antes de recorrer el sistema de archivos en orden, se evalúan una serie de reglas previas. En concreto, primero actúan la DLL redirection, el API set, la SxS manifest redirection, la loaded-module list y las Known DLLs, y solo después comienza la búsqueda en el sistema de archivos: la carpeta de la aplicación, System32, la carpeta de Windows, la carpeta actual (current folder) y el PATH. En el estado predeterminado, con el safe DLL search mode activado, la carpeta actual queda bastante más atrás en el orden. Además, el concepto mismo de orden de búsqueda es distinto entre una packaged app y una unpackaged app.
Si cargo una DLL especificando la ruta completa, ¿las DLL de las que depende también se leerán desde la misma carpeta?
No necesariamente. Aunque la primera DLL se cargue especificando la ruta completa, las DLL de las que depende se tratan como si se buscaran solo por el nombre del módulo, por lo que pueden resolverse desde otra ubicación. Si no se tiene en cuenta este malentendido, puede darse el problema, difícil de rastrear, de que la aplicación funcione en el entorno de desarrollo pero en el entorno de distribución se resuelva una DLL dependiente distinta, generando un fallo dependiente del entorno. Si se quiere controlar también las DLL dependientes, hay que indicar explícitamente el espacio de búsqueda con las banderas LOAD_LIBRARY_SEARCH_* de LoadLibraryEx, entre otras opciones.
¿Por qué no conviene usar SetDllDirectory?
Porque, además de cambiar el orden de búsqueda, tiene el efecto de desactivar en la práctica el safe DLL search mode. Aunque la intención sea solo añadir una carpeta propia de la aplicación, cambia todo el espacio de búsqueda, incluido el tratamiento de la carpeta actual (current folder), lo que puede resultar contraproducente desde el punto de vista de la seguridad. Además, si se llama desde el proceso padre, el efecto puede alcanzar también al orden de búsqueda de los procesos hijos. En su lugar, es más seguro recurrir a SetDefaultDllDirectories, AddDllDirectory y las banderas de búsqueda de LoadLibraryEx.
¿Cómo se puede prevenir el DLL hijacking (ataque de DLL preloading)?
Dado que el ataque surge de la combinación entre la carga dinámica sin ruta completa y un directorio de búsqueda que el atacante puede controlar, hay que restringir ambos factores. En concreto: reducir el uso de LoadLibrary con nombres desnudos, usar rutas completas cuando sea posible, limitar la ruta de búsqueda predeterminada del proceso con SetDefaultDllDirectories, añadir solo los directorios permitidos mediante AddDllDirectory, y evitar la carpeta actual (current folder) y una dependencia descuidada del PATH. Es especialmente peligroso que un proceso que se ejecuta con privilegios de administrador tenga una ruta de búsqueda ambigua, porque en ese caso una DLL maliciosa se ejecutaría con los privilegios de ese proceso.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog