Wie die Windows-DLL-Namensauflösung funktioniert – Suchreihenfolge und SxS
· Go Komura · Windows, DLL, Loader, Sicherheit, Windows-Entwicklung
Sobald es um native DLLs unter Windows geht, taucht mit erstaunlicher Regelmäßigkeit dieselbe Art von Verwirrung auf.
- Wenn man
LoadLibrary("foo.dll")schreibt, wo sucht Windows dann eigentlich? - Die DLL liegt im selben Ordner wie die ausführbare Datei – warum wird trotzdem eine andere DLL geladen?
- Hat
System32Vorrang, oder der Anwendungsordner? - In welcher Phase greifen manifest, API set und Known DLLs?
- Was ändert sich, wenn man
SetDllDirectoryoderAddDllDirectoryverwendet? - Wodurch werden DLL-Einschleusungsangriffe und DLL hijacking begünstigt?
Sich die Suchreihenfolge als Einzeiler zu merken, hilft in der Praxis nicht weiter. Tatsächlich wertet der Windows-Loader mehrere spezielle Regeln aus, bevor er überhaupt beginnt, das Dateisystem der Reihe nach zu durchsuchen.
Dieser Artikel ordnet die DLL-Namensauflösung unter Windows praxisnah ein – einschließlich der Unterschiede zwischen unpackaged app und packaged app, Known DLLs, der loaded-module list, API set, side-by-side manifest und der Auswirkungen der LoadLibraryEx-Familie von APIs.
Der Inhalt basiert auf den öffentlich zugänglichen Informationen von Microsoft Learn mit Stand März 2026.123456789
In diesem Artikel verwendete Begriffe
Bevor es losgeht, hier eine kurze Übersicht über Begriffe, die im Folgenden ohne weitere Erklärung vorkommen. Details werden in den jeweiligen Kapiteln behandelt.
| Begriff | Kurz gesagt |
|---|---|
| packaged app / unpackaged app | Eine Anwendung, die als Paket (z. B. MSIX) verteilt und installiert wird, oder eine Anwendung im herkömmlichen Stil, bei der die ausführbare Datei einfach in einem Ordner liegt. Die Definition der Suchreihenfolge selbst unterscheidet sich (Kapitel 3, 4) |
| safe DLL search mode | Eine standardmäßig aktivierte Einstellung, die den current folder in der Suchreihenfolge nach hinten verschiebt. Wird deaktiviert, indem der Registrierungswert SafeDllSearchMode auf 0 gesetzt wird1 |
| DLL redirection | Ein Mechanismus, bei dem eine Datei namens Anwendungsname.exe.local im selben Ordner wie die ausführbare Datei den Loader dazu bringt, zuerst den Ordner der ausführbaren Datei zu durchsuchen. Wird auch .local oder DotLocal genannt (Kapitel 7)7 |
| SxS (side-by-side) | Ein Mechanismus, der es erlaubt, mehrere Versionen derselben DLL nebeneinander zu betreiben, wobei im manifest festgelegt wird, an welche Version gebunden wird. Steht für side-by-side, im Deutschen gelegentlich als „Nebeneinanderstellung” umschrieben (Kapitel 7)9 |
| loaded-module list | Ein Mechanismus, mit dem das System prüft, ob eine DLL mit demselben Modulnamen bereits im Speicher dieses Prozesses geladen ist. Unabhängig davon, aus welchem Ordner sie geladen wurde – ist sie bereits geladen, wird diese Instanz verwendet (5.1)1 |
| Known DLLs | Die Liste der DLLs, die Windows für die jeweilige Version als bekannt einstuft. Für diese DLLs wird die systemseitige Kopie verwendet (5.2)1 |
| API set | Ein Vertragsname wie api-ms-win-.... Ein virtueller Alias, der die tatsächlich implementierende physische DLL verbirgt (Kapitel 6)3 |
| package dependency graph | Die Menge der abhängigen Pakete, die im Paketmanifest der Anwendung im Abschnitt Dependencies als PackageDependency eingetragen sind, zusammen mit dem Paket selbst. Die Suche erfolgt in der im manifest angegebenen Reihenfolge1 |
1. Das Wichtigste zuerst
Zunächst die praxisrelevanten Schlussfolgerungen im Überblick.
- Die DLL-Namensauflösung unter Windows ist nicht „zuerst das Dateisystem durchsuchen”. Elemente wie DLL redirection, API set, SxS manifest, loaded-module list und Known DLLs stehen vor der eigentlichen Suchreihenfolge.1
- In der Standardform einer unpackaged app mit aktiviertem safe DLL search mode steht der Anwendungsordner weit oben, aber davor werden die genannten Sonderregeln ausgewertet.1
- Auch wenn eine DLL per Vollpfad geladen wird, werden deren abhängige DLLs dadurch nicht automatisch auf denselben Vollpfad festgelegt. Abhängige DLLs werden nur anhand des Modulnamens gesucht und können daher von einem anderen Ort aufgelöst werden.1
Known DLLsist ein Mechanismus, mit dem das Betriebssystem bestimmte bekannte DLLs an die systemseitige Kopie bindet – das lässt sich durch eine normale anwendungsseitige Platzierung nicht überschreiben.1- Ein API set ist nicht „der eigentliche DLL-Name selbst”, sondern ein virtueller Alias, der die implementierende DLL verbirgt. Wer bei einem Namen wie
api-ms-win-...mit demselben Gefühl wie bei einer gewöhnlichen DLL-Suche denkt, missversteht das leicht.3 SetDllDirectoryändert nicht nur die Suchreihenfolge, sondern hat die Eigenschaft, den safe DLL search mode effektiv zu deaktivieren – ein unbedachter Einsatz kann sich daher sicherheitstechnisch ins Gegenteil verkehren.1- In der Praxis ist es sicherer, Vollpfad-Angaben,
SetDefaultDllDirectories,AddDllDirectoryund dieLOAD_LIBRARY_SEARCH_*-Flags vonLoadLibraryExzu kombinieren, um den Suchbereich explizit einzugrenzen.4562
Kurz gesagt: Es ist praxisnah, die DLL-Namensauflösung unter Windows nicht nur als „welcher Ordner an welcher Stelle steht”, sondern als „welche vorgelagerten Regeln den Namen zuerst auflösen” und „wie die APIs den Suchraum verändert haben” zu verstehen.
2. Vor der Ordnersuche stehen vorgelagerte Regeln der DLL-Namensauflösung
Laut der Beschreibung der DLL search order auf Microsoft Learn werden beim Laden einer DLL zunächst die folgenden Elemente als Teil der Suchreihenfolge behandelt.1
- DLL redirection
- API sets
- SxS manifest redirection
- loaded-module list
- Known DLLs
Erst danach beginnt die Suche im Dateisystem über app folder, System32, den Windows-Ordner, PATH und weitere Orte.1
Wer das übersieht, empfindet es vielleicht als seltsam, dass „vor dem Anwendungsordner” schon etwas entschieden wird – aber genau das ist als Beschreibung des Windows-Loaders der eigentliche Kernpunkt.
flowchart TD
A["DLL-Name soll aufgelöst werden"] --> B["DLL redirection"]
B --> C["API set"]
C --> D["SxS manifest redirection"]
D --> E["loaded-module list"]
E --> F["Known DLLs"]
F --> G["Suchreihenfolge im Dateisystem"]
G --> H["Die tatsächlich geladene DLL steht fest"]
3. Die Standard-Suchreihenfolge für unpackaged apps
Bei einer gewöhnlichen Desktop-Anwendung, die eine DLL ohne Vollpfad-Angabe lädt, beschreibt Microsoft Learn die Standard-Suchreihenfolge für unpackaged apps. Im Standardzustand mit aktiviertem safe DLL search mode gilt folgende Reihenfolge.1
| # | Gesuchter Ort | Art | Anmerkung |
|---|---|---|---|
| 1 | DLL redirection | vorgelagerte Regel | Ob Anwendungsname.exe.local existiert (Kapitel 7) |
| 2 | API sets | vorgelagerte Regel | Vom Vertragsnamen zur implementierenden DLL (Kapitel 6) |
| 3 | SxS manifest redirection | vorgelagerte Regel | binding über das manifest (Kapitel 7) |
| 4 | loaded-module list | vorgelagerte Regel | Ob ein gleichnamiges Modul bereits geladen ist (5.1) |
| 5 | Known DLLs | vorgelagerte Regel | Bei bekannter DLL die systemseitige Kopie (5.2) |
| 6 | package dependency graph | vorgelagerte Regel | Ab Windows 11, Version 21H2. Die Suche erfolgt in der im manifest angegebenen Reihenfolge |
| 7 | Ordner, aus dem die Anwendung geladen wurde | Dateisystem | Ab hier beginnt die eigentliche Ordnersuche |
| 8 | Systemordner | Dateisystem | Normalerweise %SystemRoot%\System32. Der Ort, den GetSystemDirectory liefert |
| 9 | 16-Bit-Systemordner | Dateisystem | Der System-Ordner aus der 16-Bit-Ära. Es gibt keine Funktion, um diesen Pfad abzufragen, gesucht wird dort aber trotzdem. Für moderne Anwendungen spielt das praktisch keine Rolle mehr; es genügt zu wissen, dass „dieser Eintrag als Position in der Reihenfolge erhalten geblieben ist” |
| 10 | Windows-Ordner | Dateisystem | Der Ort, den GetWindowsDirectory liefert |
| 11 | current folder | Dateisystem | Ist safe DLL search mode deaktiviert, rückt dieser Eintrag auf Position 8 vor |
| 12 | In PATH aufgeführte Verzeichnisse |
Dateisystem | Anwendungsspezifische Pfade aus dem Registrierungsschlüssel App Paths sind hier nicht enthalten |
Diese Tabelle ist nicht zum Auswendiglernen gedacht. Sie dient dazu, herauszufinden, auf welcher Stufe der eigene Fall entschieden wird. Wird ein Problem bereits bei 1–6 entschieden, lässt es sich durch keine noch so geschickte Ordneranordnung ab Position 7 beheben.
In der Praxis sind vor allem diese drei Punkte relevant.
- Der current folder steht standardmäßig ziemlich weit hinten. Der safe DLL search mode erschwert es, den current folder nach vorn zu bringen.1
- Allerdings bedeutet „weiter hinten” nicht „sicher”. Solange ein Verzeichnis, das der Angreifer kontrollieren kann, im Suchbereich verbleibt, bleibt Raum für DLL preloading.2
- Ab Windows 11 21H2 taucht der package dependency graph auch in der Suchbeschreibung für unpackaged apps auf. Ein Unterschied, den man leicht übersieht, wenn man nur die ältere Beschreibung im Kopf hat.1
4. packaged app und unpackaged app sind nicht dasselbe
Microsoft Learn definiert für packaged apps eine eigene Suchreihenfolge. Bei packaged apps greift der package dependency graph an früherer Stelle, und das Suchkonzept selbst unterscheidet sich etwas.1
Übersieht man diesen Unterschied, entsteht nach einer MSIX-Umstellung oder der Einführung des Windows App SDK folgende Verwirrung:
- Eine DLL, die im unpackaged-Entwicklungsbetrieb gefunden wird, fehlt in der produktiven package
- Abhängigkeiten aus dem package manifest vermischen sich mit der klassischen
PATH-Abhängigkeit, und die Reproduktionsbedingungen ändern sich - Man erklärt „so ist die Windows-DLL-Suchreihenfolge” anhand einer einzigen Tabelle und lässt die abweichenden Verhaltensweisen von packaged apps unter den Tisch fallen
In Artikeln und Design-Reviews ist es sicherer, zuerst zu klären, ob es um eine packaged app oder eine unpackaged app geht.1
5. Was Known DLLs und die loaded-module list eigentlich tun
Am ehesten der Intuition widersprechen bei der DLL-Auflösung die loaded-module list und Known DLLs.
5.1 loaded-module list
Microsoft Learn erklärt, dass das System prüfen kann, ob eine DLL mit demselben Modulnamen bereits im Speicher geladen ist.1
Das heißt, vor der Dateisystemsuche wird geprüft,
- ob dieser DLL-Name bereits geladen ist, und
- ob es infolgedessen überhaupt nötig ist, danach zu suchen.
Wenn man bei einer Untersuchung die Tatsache übersieht, dass „in diesem Prozess bereits eine gleichnamige DLL aus einem anderen Ordner geladen war”, liest man die Reproduktionsbedingungen falsch.
5.2 Known DLLs
Known DLLs ist die Liste der DLLs, die Windows für die jeweilige Version als bekannt einstuft, einsehbar unter HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\KnownDLLs. Für eine DLL auf dieser Liste verwendet das System seine eigene Kopie dieser bekannten DLL.1
Wichtig ist hier: Known DLLs ist nicht die Art von Mechanismus, bei der eine gewöhnliche Anwendung „gewinnt”, indem sie eine gleichnamige DLL in ihrem Anwendungsordner ablegt.
Wer das als einfaches Wettrennen mit System32 versteht, missversteht das Verhalten.
6. Ein API set ist ein Vertragsname, kein „eigentlicher DLL-Name”
Bei einem Namen wie api-ms-win-core-... liegt es nahe zu fragen, „wo finde ich diese DLL-Datei?”. Microsoft Learn erklärt jedoch, dass ein API set ein virtueller Alias für eine physische DLL ist – ein Mechanismus, der Implementierung und Vertrag voneinander trennt.3
Das heißt, folgende Überlegung ist unzutreffend:
- API-set-Name = der physische DLL-Dateiname selbst
- Auflösung eines API set = dieselbe Dateisuche wie bei einer gewöhnlichen DLL
Mit dem Konzept des API set im Hinterkopf lässt sich leichter erklären, dass
- die Sache konsistent bleibt, auch wenn sich der Name der implementierenden DLL je nach Windows-Version oder Gerätetyp unterscheidet, und
- die aufrufende Seite nicht fest wissen muss, „welche Host-DLL das implementiert”.3
7. manifest und side-by-side (SxS) sind eine andere Antwort auf das Problem der DLL-Versionierung
DLL redirection und SxS manifest werden nicht als bloßer Kniff der Suchreihenfolge beschrieben, sondern als Mechanismen, um Konflikte bei der DLL-Versionierung zu vermeiden.789
Microsoft Learns Einordnung lautet:
- Ein manifest ist XML, das eine side-by-side assembly oder eine isolated application beschreibt
- Eine side-by-side assembly ist die Einheit für Benennung, binding, Versionierung und Verteilung
- Anhand der im manifest festgehaltenen Abhängigkeiten entscheidet der Loader, an welche Version gebunden wird
In der Praxis muss man daher diese drei Fälle getrennt betrachten:
- Einfach eine private DLL im app folder ablegen
- DLL redirection wie
.localverwenden - side-by-side binding über ein manifest verwenden
Alle diese Ansätze sind sich insofern ähnlich, als sie „die DLL-Auflösung beeinflussen”, aber ihre Entwurfsabsicht ist nicht dieselbe. Microsoft Learn empfiehlt die Faustregel: Soll eine bestehende Anwendung unverändert bleiben, DLL redirection; bei einer neu entwickelten Anwendung eine side-by-side-Komponente.7
7.1 Was .local konkret bewirkt
Weil dieser Punkt oft nur mit einem Satz abgehandelt wird, hier das Verhalten bei unpackaged apps im Detail.7
- Was abgelegt wird: Die Redirect-Datei heißt
Ausführbare-Datei.exe.local. BeiEditor.exealsoEditor.exe.local, im selben Ordner wie die ausführbare Datei. Auch die zu ladende DLL wird in denselben Ordner gelegt - Inhalt: Der Dateiinhalt wird ignoriert. Allein ihre Existenz ist das Signal, das den Loader beim DLL-Laden dazu bringt, zuerst den Ordner der ausführbaren Datei zu prüfen
- Wirkungsbereich: Wirkt sowohl beim Laden per Vollpfad als auch beim Laden nur über den Modulnamen. Unabhängig davon, welcher Pfad an
LoadLibraryoderLoadLibraryExübergeben wird, wird die gleichnamige DLL im Ordner der ausführbaren Datei geladen, sofern sie dort vorhanden ist. Diese Funktion ist dazu gedacht, Situationen wie bei COM zu retten, wo es nur einen einzigen Registrierungsort gibt - Wenn nicht gefunden: Fehlt die Datei im Ordner der ausführbaren Datei, greift wieder die normale Suchreihenfolge
- Ordnerform: Es funktioniert auch, wenn man einen Ordner namens
Editor.exe.localanlegt und die DLL darin ablegt - Systemweit aktivieren: Unter
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Optionseinen DWORD-Wert namensDevOverrideEnableanlegen, auf 1 setzen und neu starten. Mit dieser Einstellung greift die Umleitung per.localauch dann, wenn die Anwendung ein application manifest besitzt - Bei packaged apps: Der Ablageort ändert sich – hier wird
<Installationsort des Pakets>\microsoft.system.package.metadata\application.local\geprüft
Ein Nebeneffekt, der bei Übersehen zu einer Sackgasse bei der Untersuchung führt: Wird DLL redirection verwendet und kann die Anwendung nicht auf alle Laufwerke oder Verzeichnisse der Suchreihenfolge zugreifen, bricht LoadLibrary die Suche in dem Moment ab, in dem der Zugriff verweigert wird. Ohne DLL redirection wird ein unzugängliches Verzeichnis übersprungen, und die Suche geht weiter.7
Wenn nur in einer Umgebung mit .local gemeldet wird, dass „die eigentlich weiter hinten liegende DLL nicht gefunden wird”, sollten Sie diesen Unterschied als Erstes prüfen.
8. Was sich mit LoadLibraryEx, SetDllDirectory und AddDllDirectory ändert
8.1 SetDllDirectory
SetDllDirectory ändert die Suchreihenfolge, aber Microsoft Learn hält ausdrücklich fest, dass es den safe DLL search mode effektiv deaktiviert.1
Das heißt: Selbst wenn man es nur einsetzen wollte, um „einen einzigen anwendungseigenen Ordner zu ergänzen”, ändert sich dadurch der Suchraum insgesamt, einschließlich der Behandlung des current folder.
Wird SetDllDirectory zudem im übergeordneten Prozess aufgerufen, kann sich diese Wirkung auch auf die Standard-Suchreihenfolge des Kindprozesses erstrecken.1
Deshalb ist es in der Praxis sicherer, sich nicht unbedacht auf SetDllDirectory zu verlassen, sondern auf
SetDefaultDllDirectoriesAddDllDirectory- die
LOAD_LIBRARY_SEARCH_*-Flags vonLoadLibraryEx
8.2 AddDllDirectory
Mit AddDllDirectory hinzugefügte Pfade werden zusammen mit LOAD_LIBRARY_SEARCH_USER_DIRS verwendet.
Bei mehreren hinzugefügten Verzeichnissen ist die Suchreihenfolge laut Microsoft Learn nicht festgelegt.15
Ein Design, das
- mehrere Verzeichnisse hinzufügt und
- deren Suchreihenfolge strikt erwartet,
sollte daher vermieden werden.
8.3 SetDefaultDllDirectories
SetDefaultDllDirectories wird als API beschrieben, mit der sich aus dem Standard-DLL-Suchpfad die eher anfälligen Verzeichnisse entfernen und der Suchbereich eingrenzen lässt.4
Besonders wichtig sind diese drei Eigenschaften:
- Wirkt pro Prozess
- Gilt nach dem Aufruf für die gesamte Lebensdauer des Prozesses
- Ein einmal gesetzter Standard-Suchpfad lässt sich nicht einfach wieder in die ursprüngliche Standardform zurückversetzen
Sicherheitstechnisch betrachtet lässt sich mit dieser API gut das Design „direkt nach dem Start auf einen sicheren Suchraum umstellen” umsetzen.4
8.4 LoadLibraryEx
Mit LoadLibraryEx lässt sich das Suchverhalten über die Flags LOAD_WITH_ALTERED_SEARCH_PATH und LOAD_LIBRARY_SEARCH_* ändern.61
In der Praxis eignet sich diese API gut für Anforderungen wie:
- Auch den Ordner der ladenden DLL einschließlich der abhängigen DLLs in den Suchbereich einbeziehen
- Den Suchbereich auf Anwendungsordner,
System32und ausdrücklich hinzugefügte Benutzerverzeichnisse beschränken
Vor der Umsetzung sind vier Einschränkungen zu beachten.6
| Einschränkung | Inhalt |
|---|---|
| 2. Parameter | hFile ist für zukünftige Zwecke reserviert – hier ist immer NULL zu übergeben |
| Nicht kombinierbar | LOAD_WITH_ALTERED_SEARCH_PATH lässt sich mit keinem der LOAD_LIBRARY_SEARCH_*-Flags kombinieren |
| Vollpfad erforderlich | Bei Verwendung von LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR muss der erste Parameter ein Vollpfad sein |
| Reihenfolge bei mehreren Flags | Die Suche erfolgt in der Reihenfolge LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR → LOAD_LIBRARY_SEARCH_APPLICATION_DIR → LOAD_LIBRARY_SEARCH_USER_DIRS → LOAD_LIBRARY_SEARCH_SYSTEM32. Die Reihenfolge innerhalb von USER_DIRS ist allerdings nicht festgelegt |
Sobald auch nur ein LOAD_LIBRARY_SEARCH_*-Flag angegeben wird, wird der Standard-Suchpfad überhaupt nicht mehr verwendet.
Wird also nur LOAD_LIBRARY_SEARCH_SYSTEM32 übergeben, wird der Anwendungsordner nicht durchsucht. Genau das ist mit „Eingrenzen” gemeint.
8.5 Minimales Codebeispiel (C/C++)
Fasst man die bisherigen APIs zusammen, ergibt sich folgende Form.
SetDefaultDllDirectories und LOAD_LIBRARY_SEARCH_* sind APIs ab Windows 8, daher muss die Zielversion im Header explizit angegeben werden.46
/* cl /W4 loader.c (Visual Studio 2022 + Windows SDK 10)
* SetDefaultDllDirectories / AddDllDirectory / LOAD_LIBRARY_SEARCH_* gibt es
* erst ab Windows 8. Soll auch Windows 7 unterstützt werden, ist KB2533623
* Voraussetzung, und die Funktionen müssen zur Laufzeit per GetProcAddress
* aus Kernel32.dll bezogen werden. */
#define _WIN32_WINNT 0x0602 /* Windows 8 */
#include <windows.h>
#include <stdio.h>
int wmain(void)
{
/* 1. Den prozessweiten Standard-Suchraum auf die sichere Seite bringen.
* Ziel ist es, current folder und PATH aus dem Suchbereich zu entfernen.
* LOAD_LIBRARY_SEARCH_DEFAULT_DIRS ist die Kombination aus
* APPLICATION_DIR + SYSTEM32 + USER_DIRS.
* Ohne USER_DIRS wirkt sich das AddDllDirectory aus Schritt 2
* nicht auf den Prozessstandard aus. */
if (!SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)) {
wprintf(L"SetDefaultDllDirectories fehlgeschlagen: %lu\n", GetLastError());
return 1;
}
/* 2. Ausdrücklich nur den eigenen Plugin-Ordner hinzufügen.
* An AddDllDirectory muss ein absoluter Pfad übergeben werden. */
const wchar_t *pluginDir = L"C:\\MyApp\\plugins";
DLL_DIRECTORY_COOKIE cookie = AddDllDirectory(pluginDir);
if (cookie == NULL) {
wprintf(L"AddDllDirectory fehlgeschlagen: %lu\n", GetLastError());
return 1;
}
/* 3. Laden.
* Der 2. Parameter hFile ist reserviert, daher immer NULL.
* Werden hier Flags übergeben, wird nicht der Prozessstandard aus
* Schritt 1 verwendet, sondern ausschließlich diese Flag-Kombination.
* Da APPLICATION_DIR fehlt, wird der Anwendungsordner nicht durchsucht. */
HMODULE h = LoadLibraryExW(
L"foo.dll",
NULL,
LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32);
if (h == NULL) {
wprintf(L"LoadLibraryExW fehlgeschlagen: %lu\n", GetLastError());
RemoveDllDirectory(cookie);
return 1;
}
/* 4. Unbedingt prüfen, von wo geladen wurde.
* Bei der Untersuchung zählt weniger "ob es sich laden ließ" als
* "von wo es sich laden ließ". */
{
wchar_t loadedPath[MAX_PATH];
DWORD cap = (DWORD)(sizeof(loadedPath) / sizeof(loadedPath[0]));
DWORD len = GetModuleFileNameW(h, loadedPath, cap);
if (len > 0 && len < cap) {
wprintf(L"geladen von: %s\n", loadedPath);
} else {
wprintf(L"GetModuleFileNameW fehlgeschlagen: %lu\n", GetLastError());
}
}
FreeLibrary(h);
RemoveDllDirectory(cookie);
return 0;
}
Dieses Codebeispiel deckt vier Punkte ab.
SetDefaultDllDirectorieswird zuerst aufgerufen. Einmal aufgerufen, wirkt es für die gesamte Lebensdauer des Prozesses, und eine Rückkehr zum Standard-Suchpfad ist nicht möglich4AddDllDirectoryerhält erst zusammen mitLOAD_LIBRARY_SEARCH_USER_DIRSseine Bedeutung. IstUSER_DIRSnicht inSetDefaultDllDirectoriesenthalten, wird das hinzugefügte Verzeichnis nur bei einemLoadLibraryEx-Aufruf mitLOAD_LIBRARY_SEARCH_USER_DIRSverwendet5- Aufräumen nicht vergessen. Das von
AddDllDirectoryzurückgegebene Cookie lässt sich mitRemoveDllDirectorywieder entfernen5 - Den geladenen Ort ausgeben. Ob die eine Zeile mit
GetModuleFileNameWvorhanden ist, entscheidet im Fehlerfall über die Dauer der Untersuchung
Die von foo.dll abhängigen DLLs werden übrigens ebenfalls innerhalb dieses LOAD_LIBRARY_SEARCH_*-Bereichs gesucht.
Soll der Ordner von foo.dll selbst als Suchort für abhängige DLLs dienen, muss der erste Parameter ein Vollpfad sein und zusätzlich LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR gesetzt werden.
8.6 Verwendung aus C#
Auch unter .NET lässt sich dasselbe Konzept anwenden. Der Suchpfad für P/Invoke wird über das Attribut DefaultDllImportSearchPaths gesteuert, das explizite Laden über NativeLibrary.Load.
// .NET 8 / C# 12
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
// Für alle P/Invoke-Aufrufe in dieser Assembly den Standard-Suchort auf System32 beschränken.
// Einschränkung: Für P/Invoke-Aufrufe mit einem absoluten Pfad gilt dieses Attribut nicht.
// Auf Plattformen außer Windows hat es ebenfalls keine Wirkung.
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
internal static class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetTickCount();
private static void Main()
{
// Das bloße Deklarieren des Attributs bewirkt noch nichts. Es wirkt erst beim Aufruf.
Console.WriteLine($"GetTickCount = {GetTickCount()}");
// Das Plugin liegt in einem eigenen Ordner. Hier darf NICHT
// NativeLibrary.Load("foo.dll", asm, ApplicationDirectory | System32)
// geschrieben werden. ApplicationDirectory verweist auf den Ordner der exe,
// System32 auf den OS-Ordner – keines von beiden sieht den Plugin-Ordner.
// Entweder schlägt das Laden fehl, oder es wird zufällig eine andere foo.dll
// erwischt, die neben der exe liegt.
// Ist der Ablageort bekannt, ist die Angabe eines Vollpfads die sichere Wahl.
string pluginDir = Path.Combine(AppContext.BaseDirectory, "plugins");
string pluginPath = Path.GetFullPath(Path.Combine(pluginDir, "foo.dll"));
if (!File.Exists(pluginPath))
{
throw new FileNotFoundException("Das Plugin wurde nicht gefunden.", pluginPath);
}
// Die Überladung mit einem Pfad liest diese Datei direkt (keine Suche).
IntPtr handle = NativeLibrary.Load(pluginPath);
try
{
// Prüfen, von wo geladen wurde.
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
if (string.Equals(module.ModuleName, "foo.dll", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"geladen von: {module.FileName}");
}
}
}
finally
{
NativeLibrary.Free(handle);
}
}
}
Die Werte von DllImportSearchPath entsprechen den LOAD_LIBRARY_SEARCH_*-Flags.
Schreibt man auf C#-Seite also „nur System32”, gelten dieselben Einschränkungen wie in 8.4. Das Anwendungsverzeichnis wird nicht durchsucht.
Zu beachten ist hier, dass DllImportSearchPath keinen Wert für „einen beliebigen Ordner” besitzt. ApplicationDirectory verweist auf den Ordner der exe, System32 auf den Ordner des Betriebssystems – es gibt keinen Wert, der einen selbst festgelegten Plugin-Ordner repräsentiert. Deshalb greift der Versuch, etwas aus einem eigenen Ordner über Suchflags zu erfassen, ins Leere, und man muss, wie oben gezeigt, den Vollpfad angeben.
Auch beim Laden per Vollpfad gilt jedoch, wie in Kapitel 9 beschrieben: Die DLLs, von denen foo.dll abhängt, werden dadurch nicht festgelegt. Bringt das Plugin eigene abhängige DLLs mit, braucht es genau dieselbe Vorgehensweise wie auf C-Seite: entweder den Ordner mit AddDllDirectory hinzufügen und LOAD_LIBRARY_SEARCH_USER_DIRS wirken lassen, oder alle Abhängigkeiten in einem gemeinsamen Ordner bündeln und LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR verwenden.
9. Auch bei Vollpfad-Angabe werden abhängige DLLs nicht festgelegt
Das ist bei diesem Thema ein Punkt, der praktisch sehr wichtig, aber leicht zu übersehen ist. Microsoft Learn erklärt, dass auch beim Laden der ersten DLL per Vollpfad deren abhängige DLLs nur anhand des Modulnamens gesucht werden.14
Das heißt: Nur weil
C:\\MyApp\\plugins\\foo.dllexplizit geladen wurde,
folgt daraus nicht,
- dass auch
bar.dll, von demfoo.dllabhängt, zwangsläufig aus demselben Ordner bezogen wird.
Bei diesem Missverständnis entsteht der etwas unangenehme Fehlerfall, dass
- es in der Entwicklungsumgebung funktioniert,
- beim Kunden eine andere
bar.dllaufgelöst wird und - der Konflikt bei abhängigen DLLs umgebungsabhängig reproduzierbar wird.
10. So vermeiden Sie DLL preloading / hijacking
Microsoft Learns DLL security-Dokumentation erklärt, dass die Kombination aus dynamischem Laden ohne Vollpfad und einem vom Angreifer kontrollierbaren Suchverzeichnis zu DLL preloading attacks und binary planting attacks führt.2
In der Praxis hilft es, sich an dieser Grundform zu orientieren.
LoadLibrary("foo.dll")-artige Aufrufe mit nacktem Namen reduzieren- Wo nötig Vollpfad-Angaben verwenden
- Den prozessweiten Standard-Suchpfad mit
SetDefaultDllDirectorieseingrenzen - Nur ausdrücklich erlaubte Verzeichnisse mit
AddDllDirectoryhinzufügen - Mit
LOAD_LIBRARY_SEARCH_SYSTEM32,LOAD_LIBRARY_SEARCH_APPLICATION_DIR,LOAD_LIBRARY_SEARCH_USER_DIRSund Ähnlichem den Suchbereich explizit machen - current folder und eine unbedachte Abhängigkeit von
PATHvermeiden
Besonders gefährlich ist die Situation, in der ein mit Administratorrechten laufender Prozess einen mehrdeutigen Suchpfad hat. Auch Microsoft Learn weist darauf hin, dass eine geladene bösartige DLL mit den Rechten dieses Prozesses ausgeführt wird.2
11. Prüfen, welche DLL von wo geladen wurde
Bis hierhin ging es um die Spezifikation. Bei der tatsächlichen Untersuchung ist es schneller, nicht zu raten, sondern zu prüfen. Hier vier Fälle nach Zielsetzung.
| Was Sie wissen wollen | Womit | Wo Sie nachsehen |
|---|---|---|
| Wo gesucht und wo gefunden wurde | Process Monitor10 | Aufzeichnung der Dateizugriffe |
| Was gerade geladen ist | Process Explorer, tasklist /m11 |
Modulliste des Prozesses |
| Welcher Prozess eine bestimmte DLL geladen hat | ListDLLs12 | prozessübergreifende Liste |
| Der beim Debuggen tatsächlich geladene Pfad | Das Modulfenster von Visual Studio | „Debuggen” > „Fenster” > „Module”7 |
11.1 Mit Process Monitor die Spur der Suche verfolgen
Damit erhält man die meisten Informationen. Das Vorgehen sieht so aus.10
- Process Monitor als Administrator starten und die Aufzeichnung beginnen
- „Filter” > „Filter…” öffnen, unter
Process Nameisden Namen der Ziel-exe eingeben und hinzufügen - Im selben Fenster
Pathends with.dllhinzufügen - Die Zielanwendung starten und die Aufzeichnung stoppen, sobald das Problem auftritt
Wichtig ist hier die Spalte Result.
Der Loader versucht, entlang der Suchreihenfolge von oben nach unten zu öffnen; an Orten, wo nichts gefunden wurde, erscheint NAME NOT FOUND, an dem Ort, an dem tatsächlich geöffnet werden konnte, SUCCESS.
Das heißt, die Zeile direkt nach der letzten NAME NOT FOUND-Zeile ist der tatsächlich verwendete Pfad.
Gleicht man das mit der Tabelle aus Kapitel 3 ab, sieht man, auf welcher Stufe der jeweilige Fall entschieden wurde. Wird ein Fall bereits durch eine vorgelagerte Regel (Zeilen 1–6 der Tabelle) entschieden, taucht überhaupt kein Datensatz für die Dateisuche auf. Auch das ist ein wichtiger Hinweis.
11.2 Geladene Module auflisten
Für einen bereits laufenden Prozess lässt sich ohne zusätzliche Werkzeuge nachsehen, was von wo geladen wurde.
tasklist /m foo.dll
Dieser Befehl listet Name und PID aller Prozesse auf, die das angegebene Modul geladen haben.11 Sobald der betreffende Prozess feststeht, lässt sich im unteren Bereich von Process Explorer auf die DLL-Ansicht umschalten, um den vollständigen Pfad der von diesem Prozess geladenen DLLs zu prüfen.
Mit ListDLLs von Sysinternals lässt sich dasselbe über die Kommandozeile und sogar prozessübergreifend prüfen.12
Ob die loaded-module list aus 5.1 wirkt, lässt sich nur auf diesem Weg feststellen. Ist eine gleichnamige DLL bereits aus einem anderen Ordner geladen, sucht der betreffende Prozess gar nicht erst neu.
12. Checkliste für die Praxis
Beim Review eines DLL-Ladedesigns unter Windows lassen sich Probleme vermeiden, wenn mindestens die folgenden Punkte geprüft werden.
- Handelt es sich um eine packaged app oder eine unpackaged app?
- Welche DLLs stammen aus statischer Verlinkung, welche werden dynamisch geladen?
- Vollpfad-Angabe oder nur Modulname?
- Wird
SetDllDirectoryirgendwo verwendet? - Ist die Konfiguration so, dass
SetDefaultDllDirectoriesundLOAD_LIBRARY_SEARCH_*genutzt werden können? - Werden mehrere
AddDllDirectory-Aufrufe verwendet, mit einer stillschweigend erwarteten Reihenfolge? - Über welchen Mechanismus – manifest / SxS / private DLL / redirection – werden Abhängigkeiten verwaltet?
- Gibt es sicherheitstechnisch schwache Annahmen rund um current folder oder
PATH? - Könnten abhängige DLLs in einer anderen Umgebung von einem anderen Ort aufgelöst werden?
- Wurde in der Umgebung, in der das Symptom auftritt, tatsächlich geprüft, von wo geladen wurde (Kapitel 11)?
Werden diese zehn Punkte einzeln geprüft, lassen sich Probleme wie „DLL nicht gefunden”, „falsche DLL geladen”, „startet nur in der Produktion nicht” oder „bleibt beim Sicherheits-Review hängen” schon auf einer sehr frühen Stufe ausräumen.
13. Zusammenfassung
Die DLL-Namensauflösung unter Windows ist nicht bloß eine „Ordnersuchreihenfolge”. Tatsächlich wird sie durch das Zusammenspiel von DLL redirection, API set, SxS manifest, loaded-module list, Known DLLs und dem durch API-Aufrufe veränderten Suchraum bestimmt.134
In der Praxis kommt es vor allem auf diese sechs Punkte an.
- Die Suchreihenfolge nicht als eine einzige Tabelle auswendig lernen. Die Tabelle dient dazu, herauszufinden, auf welcher Stufe der eigene Fall entschieden wurde
- packaged und unpackaged getrennt betrachten
- Verstehen, dass auch bei Vollpfad-Angabe abhängige DLLs anders behandelt werden können
SetDllDirectorynicht unbedacht verwenden- Um auf der sicheren Seite zu bleiben,
SetDefaultDllDirectoriesund die Suchflags vonLoadLibraryExverwenden - Sich nicht mit Vermutungen begnügen, sondern mit Process Monitor und Ähnlichem den tatsächlich geladenen Pfad prüfen
Die DLL-Namensauflösung ist ein Bereich, in dem Startfehler, Umgebungsunterschiede und Sicherheitsprobleme gerne gemeinsam zutage treten. Gerade deshalb lohnt es sich, nicht nur zu verstehen, „in welcher Reihenfolge Windows sucht”, sondern auch, „was Windows von vornherein als Grundlage der Namensauflösung behandelt”.
Verwandte Artikel
- Eine Mindest-Sicherheitscheckliste für die Windows-Anwendungsentwicklung
- Wie weit lässt sich eine Windows-Anwendung wirklich als Single Binary umsetzen?
- Wann sind unter Windows tatsächlich Administratorrechte erforderlich?
- Was ist Reg-Free COM?
Referenzlinks
- Microsoft Learn: Dynamic-link library search order
- Microsoft Learn: Dynamic-Link Library Security
- Microsoft Learn: Windows API sets
- Microsoft Learn: SetDefaultDllDirectories function
- Microsoft Learn: AddDllDirectory function
- Microsoft Learn: LoadLibraryEx function
- Microsoft Learn: Dynamic-link library redirection
- Microsoft Learn: Manifests
- Microsoft Learn: About Side-by-Side Assemblies
- Microsoft Learn: Process Monitor
- Microsoft Learn: ListDLLs
- Microsoft Learn: tasklist
-
Microsoft Learn, Dynamic-link library search order, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25
-
Microsoft Learn, Dynamic-Link Library Security, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Windows API sets, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, SetDefaultDllDirectories function, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, AddDllDirectory function, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, LoadLibraryEx function, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Dynamic-link library redirection, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Manifests, abgerufen am 24. März 2026 ↩ ↩2 ↩3
-
Microsoft Learn, About Side-by-Side Assemblies, abgerufen am 24. März 2026 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Process Monitor ↩ ↩2
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Warum Windows die Meldung „Der Computer wurde durch Windows geschützt“ anzeigt
Warum SmartScreen-Warnungen bei der Verteilung von Windows-Apps auftreten — eine praxisnahe Einordnung von Codesignierung, EV-/OV-Zertifi...
Wann sind unter Windows tatsächlich Administratorrechte nötig? - UAC, geschützte Bereiche und wie man es am Design erkennt
Wir ordnen praxisnah ein, wann unter Windows Administratorrechte erforderlich sind – aus der Perspektive von UAC, geschützten Bereichen, ...
Was bedeutet Windows' „Speicherauslastung“ eigentlich? — Working Set, Private Bytes, Commit und die Auslagerungsdatei richtig lesen
Der Speicherwert im Task-Manager, Working Set, Private Bytes und Commit sind nicht dasselbe. Dieser Artikel erklärt das Verhältnis von vi...
WMI/CIM aus C# und PowerShell verwenden — Praxisleitfaden für Hardwareinformationen, Prozessüberwachung und Remoteabfragen
WMI/CIM ist die Standardlösung, um die Seriennummer eines PCs auszulesen, freien Festplattenspeicher zu überwachen und den Start von Proz...
Windows-Sicherheitsüberwachungsrichtlinien und Ereignisprotokolluntersuchung in der Praxis — Zur IT-Abteilung werden, die Ereignis 4625 lesen kann
Ein praktischer Leitfaden, um auf „Bitte prüfen Sie die Protokolle fehlgeschlagener Anmeldungen“ reagieren zu können. Er behandelt das Ve...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Wie DLLs platziert und geladen werden, wirkt sich unmittelbar darauf aus, ob eine Windows-Anwendung startet, wie sie verteilt wird und wie reproduzierbar Fehler im Störungsfall sind – ein Thema, das sich gut im Kontext der Windows-Anwendungsentwicklung behandeln lässt.
Technische Beratung und Design-Review
Die DLL-Namensauflösung liegt an der Schnittstelle von Fehleruntersuchung, Portierung, Schwachstellenvermeidung und Verteilungsdesign – ein Thema, das sich gut im Rahmen eines Design-Reviews oder einer technischen Beratung strukturieren lässt.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- In welcher Reihenfolge sucht Windows nach einer DLL, wenn LoadLibrary nur den DLL-Namen erhält?
- Bevor das Dateisystem Schritt für Schritt durchsucht wird, werden vorgelagerte Regeln ausgewertet. Konkret greifen zuerst DLL redirection, API set, SxS manifest redirection, loaded-module list und Known DLLs, und erst danach beginnt die Suche im Dateisystem über Anwendungsordner, System32, Windows-Ordner, current folder und PATH. Im Standardzustand mit aktiviertem safe DLL search mode wird der current folder ziemlich weit nach hinten gestellt. Außerdem unterscheidet sich das Konzept der Suchreihenfolge zwischen packaged app und unpackaged app grundsätzlich.
- Wird eine abhängige DLL ebenfalls aus demselben Ordner geladen, wenn ich die DLL selbst per Vollpfad lade?
- Nicht zwangsläufig. Auch wenn die erste DLL per Vollpfad geladen wird, werden die DLLs, von denen sie abhängt, nur anhand des Modulnamens gesucht und können daher von einem anderen Ort aufgelöst werden. Bei diesem Missverständnis funktioniert die Anwendung in der Entwicklungsumgebung, während beim Kunden eine andere abhängige DLL aufgelöst wird – ein unangenehmer, umgebungsabhängiger Fehler. Wer auch die abhängigen DLLs kontrollieren möchte, sollte den Suchraum explizit machen, etwa über die LOAD_LIBRARY_SEARCH_*-Flags von LoadLibraryEx.
- Warum sollte man SetDllDirectory nicht verwenden?
- Weil es nicht nur die Suchreihenfolge ändert, sondern effektiv das safe DLL search mode deaktiviert. Selbst wenn man nur einen einzigen anwendungseigenen Ordner ergänzen wollte, ändert sich der gesamte Suchraum einschließlich der Behandlung des current folder, was sich sicherheitstechnisch ins Gegenteil verkehren kann. Außerdem kann sich ein Aufruf im übergeordneten Prozess auch auf die Suchreihenfolge des Kindprozesses auswirken. Sicherer ist es, stattdessen auf SetDefaultDllDirectories, AddDllDirectory und die Suchflags von LoadLibraryEx zu setzen.
- Wie lässt sich DLL-Hijacking (ein DLL-preloading-Angriff) verhindern?
- Die Kombination aus dynamischem Laden ohne Vollpfad und einem vom Angreifer kontrollierbaren Suchverzeichnis führt zum Angriff, daher schränkt man beides ein. Konkret: LoadLibrary-Aufrufe mit nacktem Namen reduzieren, wo nötig Vollpfad-Angaben verwenden, den prozessweiten Standard-Suchpfad mit SetDefaultDllDirectories eingrenzen, nur ausdrücklich erlaubte Verzeichnisse per AddDllDirectory hinzufügen und den current folder sowie eine unbedachte Abhängigkeit von PATH vermeiden. Besonders gefährlich ist es, wenn ein mit Administratorrechten laufender Prozess einen mehrdeutigen Suchpfad hat, denn eine bösartige DLL wird dann mit den Rechten dieses Prozesses ausgeführt.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.