Skip to content

Loaded DLLs

Inside a Process

A DLL is a file of shared code that a program loads to do its work, and most processes load many. An investigator looks for code that should not be there, such as a Windows DLL name in the wrong folder or a DLL nobody signed. Windows' own DLLs normally load from System32, signed by Microsoft. Start with each DLL's folder and signer, then set the loader's lists against what is mapped.

What It Is

A DLL (dynamic-link library) is a file of ready-made code that many programs share.

Why Windows Needs It
Almost every program opens files and draws windows. Windows ships that code once, in DLLs such as kernel32.dll and user32.dll, so no program has to carry its own copy. Each program loads the ones it needs when it starts.
An Everyday Example
Notepad, Word and Chrome all load kernel32.dll, so one copy on disk serves them all. A DLL's code runs inside the program that loads it, with that program's name and rights, which is why a DLL that should not be there makes a good disguise.
Three programs, one copy of each DLL (example values)
  • notepad.exeLoadskernel32.dlluser32.dll
  • winword.exeLoadskernel32.dlluser32.dll
  • chrome.exeLoadskernel32.dlluser32.dllws2_32.dll

C:\Windows\System32, one copy of each

  • kernel32.dllFiles and processes
  • user32.dllWindows and buttons
  • ws2_32.dllThe network

version.dll in C:\Users\Public. A Windows name in a folder anyone can write to. The program that loads it runs its code with that program's name and rights.

Kept by the Kernel
The kernel records each image mapped into the process with its file (the VAD), the better witness of what is really there.
In the Process's Own Memory
The loader's three lists sit in the PEB, in the process's own memory, which code in the process can change.

A module's folder is the first thing to read, because each folder holds a known kind of module.

Where a process's modules come from

FolderWhat Loads From ItWorth a Look When
C:\Windows\System32Windows' own DLLs for 64-bit programsThe file is new, or unsigned even through a catalogue
C:\Windows\SysWOW64Windows' own DLLs for 32-bit programsThe file is unsigned
C:\Windows\WinSxSSeveral versions of the same DLL, so each program gets the one it was built forThe file is unsigned
The program's own folderThe program's own DLLsThe name copies a Windows DLL, or a user can write to the folder
User folders such as AppData, Temp or C:\Users\PublicPrograms installed for one personA Windows process loads from one

The Search Order

When a program names a DLL without its full path, Windows tries a fixed order of places and loads the first match. Below are the main stops. Microsoft's full list adds a few special cases, such as redirection files, API sets, manifests and the 16-bit system folder.

The program's own folder comes before System32, so a DLL with a Windows name planted beside a program loads first, which Microsoft calls DLL preloading or binary planting. A Windows DLL name outside the system folders is the thing to check.

The main places Windows looks for a DLL named without a path, for an ordinary desktop program with safe DLL search mode on (the default)
  1. DLLs already loaded, and Known DLLsOne already loaded, or listed in the KnownDLLs Registry key, is used as it is
  2. The folder the program loaded fromA planted copy here wins, before System32
  3. The system folderC:\Windows\System32
  4. The Windows folderC:\Windows
  5. The current folderThe process's current working folder
  6. The folders in PATHIn the order the variable lists them

Windows loads the first match, from the top.

Normal

A clean Windows 11 install

version.dll as a program on a clean install loads it, from System32 and signed by Microsoft.
NamePathSigned By
version.dllC:\Windows\System32\version.dllMicrosoft Windows

Suspicious

srv-app-01,

The same name loaded by logshipper.exe on srv-app-01 on 19 Oct 2026, an illustration outside the ws-042 incident.
NamePathSigned By
version.dllC:\ProgramData\LogShipper\version.dllNot signed
PathFound in the program's own folder before System32: Windows' version.dll lives in System32. DLL Search Order Hijacking and Side-Loading

The Loader's Lists

The loader keeps every DLL in three lists in the PEB, in load order, in memory order and in initialisation order. Code in the process can change them, so tools that read the lists (windows.dlllist, ListDLLs, Process Explorer) show only what the lists say.

windows.malware.ldrmodules takes every memory region that starts with an MZ header, the two letters every program and DLL file begins with, private memory included. It says whether each list holds an entry at its base, beside the file the kernel recorded for the region. The kernel writes that path without a drive letter, so set the drive aside when comparing.

A region in no list is a lead, not a verdict. In the published example output, the program's own executable is missing from the initialisation list, and resource files from all three. Every row below is a 64-bit process.

windows.malware.ldrmodules on a 64-bit spooler with example addresses, the published sample's pattern first, then ws-042's spooler
RegionInLoadInInitInMemMappedPathWhat It Means
spoolsv.exeTrueFalseTrue\Windows\System32\spoolsv.exeThe program itself, normally missing from the initialisation list
kernel32.dllTrueTrueTrue\Windows\System32\kernel32.dllA normally loaded DLL, in all three lists
spoolsv.exe.muiFalseFalseFalse\Windows\System32\en-US\spoolsv.exe.muiA language resource file, normal in no list
Private region on ws-042FalseFalseFalseNoneStarts with MZ, in no list and with no file, a lead for windows.malware.malfind

What Normal Looks Like

On a healthy machine this part looks like this.

  • Windows DLLs load from System32 (SysWOW64 for a 32-bit program), some as side-by-side assemblies from WinSxS, signed by Microsoft.
  • Programs also load their own DLLs from their install folders, so a module outside System32 is not a finding on its own.
  • Many Windows files are signed in a separate catalogue file, not inside the file itself, and only some tools read catalogues.
  • A DLL loaded the normal way sits in all three loader lists at the same base, with the same path as the image the kernel mapped.

Why It Matters

A trusted, signed program can still run someone else's code through a DLL it loads from the wrong folder. Module paths and signers show it, and memory shows modules the loader never listed.

Normal and Suspicious

A clean machine sits beside a suspicious one, and the pointer marks where to look.

Normal

A clean Windows 11 install

Windows DLLs in the print spooler on a clean install, from System32 and signed by Microsoft.
NamePathSigned By
ntdll.dllC:\Windows\System32\ntdll.dllMicrosoft Windows
kernel32.dllC:\Windows\System32\kernel32.dllMicrosoft Windows
See It in the Window

Suspicious

ws-042,

updsvc64.exe's modules on ws-042 at 10:08 on 12 Oct 2026.
NamePathSigned By
updsvc64.exeC:\Windows\Temp\updsvc64.exeNot signed
Signed ByNot signed, and run from Temp: no publisher vouches for it, so hash it and find where it came from. Renamed, Unsigned or Falsely Signed Binaries
ntdll.dllC:\Windows\System32\ntdll.dllMicrosoft Windows
See It in the Window

What a Malicious Example Shows

Each card is what malicious use of this part looks like, and where it shows.

  • A Windows DLL name loaded from a program's own folder, beside a signed program copied to a folder a user can write to.Where it shows: Sysmon event 7 or DeviceImageLoadEvents showing a path outside the system folders, unsigned or signed by someone unexpected.
  • A module in the loader's lists whose path differs from the file the kernel mapped there.Where it shows: windows.dlllist's Path against windows.malware.ldrmodules' MappedPath.
  • A mapped DLL in none of the three loader lists, with its file behind it. Resource files (.mui) are the normal exception.Where it shows: windows.malware.ldrmodules with False under InLoad, InInit and InMem and a MappedPath. windows.dlllist and ListDLLs do not show it.
  • A region in private memory that starts with an MZ header, in no list and with no file behind it.Where it shows: windows.malware.ldrmodules with an empty MappedPath, and windows.malware.malfind on the same region.

Live and in Memory

Each question has the tools that answer it on a running machine, and in a memory image, a copy of its memory saved to a file.

Each module's folder and signer say whether it belongs, and Windows' own DLLs usually load from System32, SysWOW64 or WinSxS, signed by Microsoft. Live tools and windows.dlllist read the loader's own lists. windows.malware.ldrmodules sets those lists against every region that starts with an MZ header, so code the lists never held stands out.

  • Which DLLs has a process loaded, from where, and who signed them?

    On a Running Machine

    In a Memory Image

  • Is a module mapped that the loader does not list?

    On a Running Machine

    In a Memory Image

  • Which loads were recorded as they happened?

    On a Running Machine

    In a Memory Image

To take it offline, read Dumping a Process and Memory

What It Misses

Every way of looking has gaps, so cover these from another source.

  • A list of loaded modules misses code that never went through the loader. Reflectively loaded and injected code show only to the injection checks.
  • A DLL that loaded and unloaded before you looked is gone from a live list and from the PEB. Only Sysmon event 7 or DeviceImageLoadEvents kept it.
  • No memory plugin prints a signer, so write the module out with windows.dlllist's dump option and check its signature offline.

False Positives

These look odd but are normal, so rule them out before you act.

  • A mapped image in no loader list is a lead, not a verdict, since the program itself and resource files (.mui) have ordinary gaps.
  • Many Windows files are signed through a catalogue, not inside the file, so a check that reads only embedded signatures calls them unsigned. Confirm with Sigcheck or Get-AuthenticodeSignature.
  • A module outside System32 is often a program's own DLL, so look harder when a user can write to its folder or its name copies a Windows DLL.

Red Flags It Explains

Each red flag below is confirmed by reading this part.

Checks That Use It

These checks read this part as one of their steps.

Hunt It

The same hunt is written for four tools, so pick the one you use. Defender XDR and Sentinel queries are in KQL, which you paste into Advanced hunting in the Defender portal or into Sentinel's Logs page. Splunk queries are in SPL, for its Search page, and Velociraptor queries are in VQL, which you run as a hunt.

Needs Defender for Endpoint, whose certificate checks on files (catalogue signatures included) fill DeviceFileCertificateInfo. A module it has not checked has no row there and is left out, so a quiet result is not proof. A third-party module in a Windows program, such as a security product's, is a lead to explain, not a verdict.

KQLDefender XDR, Advanced hunting
// Modules loaded into programs that run from the Windows folder, whose file is unsigned or not trusted, rarest firstlet Untrusted = DeviceFileCertificateInfo    | where Timestamp > ago(30d)    | where IsSigned == false or IsTrusted == false    | distinct SHA1;DeviceImageLoadEvents| where Timestamp > ago(7d)| where InitiatingProcessFolderPath startswith @"c:\windows\"| where SHA1 in (Untrusted)| summarize Devices = dcount(DeviceId), Loads = count(), Processes = make_set(InitiatingProcessFileName, 10), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by FileName, FolderPath, SHA1| order by Devices asc, Loads asc

Sources

MITRE ATT&CK is a free public catalogue of the techniques attackers use. Each technique has an ID, such as T1055 for Process Injection, so defenders can name the same behaviour the same way. Reading this part helps find these techniques.