Как работает разрешение имён DLL в Windows — порядок поиска и SxS

· · Windows, DLL, Загрузчик, Безопасность, Разработка Windows

Когда заходит речь о работе с нативными DLL в Windows, с завидной регулярностью возникает следующая путаница.

  • Куда на самом деле ведёт LoadLibrary("foo.dll")?
  • DLL положена в ту же папку, что и исполняемый файл, — почему тогда загружается другая DLL?
  • Что имеет приоритет: System32 или папка приложения?
  • На каком этапе вступают в действие manifest, API set и Known DLLs?
  • Что меняется при использовании SetDllDirectory или AddDllDirectory?
  • Из-за чего чаще всего происходят атаки внедрения DLL или DLL hijacking?

Просто «запомнить порядок поиска в одну строку» для практической работы недостаточно. На деле загрузчик Windows сначала оценивает несколько особых правил, прежде чем начать последовательно обходить файловую систему.

В этой статье мы разберём разрешение имён DLL в Windows с практической точки зрения, включая различия между unpackaged app и packaged app, Known DLLs, loaded-module list, API set, side-by-side manifest и влияние семейства API LoadLibraryEx. Материал опирается на общедоступную информацию Microsoft Learn по состоянию на март 2026 года.123456789

1. Сначала — вывод

Сначала перечислим только практические выводы.

  • Разрешение имён DLL в Windows — это не «сначала поиск по файловой системе». Такие элементы, как DLL redirection, API set, SxS manifest, loaded-module list и Known DLLs, входят в предварительный этап порядка поиска.1
  • В стандартной форме unpackaged app с включённым safe DLL search mode папка приложения занимает высокую позицию, но перед ней оцениваются перечисленные выше особые правила.1
  • Даже если DLL загружается с указанием полного пути, её зависимые DLL автоматически не фиксируются на том же полном пути. Зависимые DLL ищутся только по имени модуля, поэтому могут разрешаться из другого места.1
  • Known DLLs — это механизм, посредством которого ОС привязывает определённые известные ей DLL к системной копии; обычное размещение файла на стороне приложения не может это переопределить.1
  • API set — это не «само имя физической DLL», а виртуальный псевдоним, скрывающий DLL с реализацией. Если, увидев имя вида api-ms-win-..., рассуждать о нём так же, как об обычном поиске DLL, легко ошибиться.3
  • SetDllDirectory не просто меняет порядок поиска — она обладает поведением, которое фактически отключает safe DLL search mode, поэтому её неосторожное использование может дать обратный эффект с точки зрения безопасности.1
  • На практике безопаснее явно сужать область поиска, комбинируя указание полного пути, SetDefaultDllDirectories, AddDllDirectory и флаги LOAD_LIBRARY_SEARCH_* функции LoadLibraryEx.4562

Иными словами, с практической точки зрения разумно считать, что разрешение имён DLL в Windows определяется не только тем, «какая папка на каком месте», но и тем, «какие предварительные правила разрешают имя» и «как API изменили пространство поиска».

2. У разрешения имён DLL есть предварительные правила до «поиска по папкам»

Согласно описанию DLL search order на Microsoft Learn, при загрузке DLL как часть порядка поиска сначала обрабатываются следующие элементы.1

  1. DLL redirection
  2. API sets
  3. SxS manifest redirection
  4. loaded-module list
  5. Known DLLs

И только после этого начинается поиск по файловой системе: app folder, System32, папка Windows, PATH и так далее.1

Если упустить это из виду, может показаться странным, что что-то определяется раньше папки приложения, но применительно к описанию загрузчика Windows именно это и есть основная логика.

Нужно разрешить имя DLLDLL redirectionAPI setSxS manifest redirectionloaded-module listKnown DLLsПорядок поиска по файловой системеОпределяется, какая DLL действительно загружается

3. Стандартный порядок поиска для unpackaged app

Для обычного desktop-приложения, загружающего DLL без указания полного пути, Microsoft Learn описывает стандартный порядок поиска для unpackaged app. В состоянии по умолчанию, когда включён safe DLL search mode, основной поток такой.1

  1. DLL redirection
  2. API sets
  3. SxS manifest redirection
  4. loaded-module list
  5. Known DLLs
  6. Начиная с Windows 11 21H2 — package dependency graph
  7. Папка, из которой было загружено приложение
  8. System32
  9. 16-bit system folder
  10. Папка Windows
  11. current folder
  12. PATH

На практике особенно важны эти три момента.

  • По умолчанию current folder оказывается довольно далеко в конце списка. Safe DLL search mode затрудняет выдвижение current folder вперёд.1
  • Однако позиция в конце списка не означает безопасность. Пока в области поиска остаётся директория, которой может управлять злоумышленник, сохраняется возможность DLL preloading.2
  • Начиная с Windows 11 21H2, package dependency graph появляется и в описании поиска для unpackaged app. Это изменение легко упустить, если помнить только старое описание.1

4. Packaged app и unpackaged app — не одно и то же

Microsoft Learn определяет для packaged app отдельный порядок поиска. В packaged app package dependency graph действует на более раннем этапе, и сама логика поиска несколько отличается.1

Если упустить это различие, после перехода на MSIX или внедрения Windows App SDK возникает такая путаница:

  • DLL, которая находится при unpackaged-запуске во время разработки, не находится в production-пакете
  • зависимости через package manifest смешиваются со старой зависимостью от PATH, и условия воспроизведения меняются
  • «порядок поиска DLL в Windows выглядит вот так» объясняется одной-единственной таблицей, из-за чего теряются отличия в поведении packaged app

В статьях и при ревью архитектуры безопаснее сначала разделить: «речь идёт о packaged app или об unpackaged app?»1

5. Что делают Known DLLs и loaded-module list

Части разрешения DLL, которые чаще всего противоречат интуиции, — это loaded-module list и Known DLLs.

5.1 loaded-module list

Microsoft Learn поясняет, что система может проверить, не загружена ли уже в память DLL с тем же именем модуля.1

То есть перед поиском по файловой системе выполняется проверка:

  • не загружено ли уже это имя DLL,
  • и, как следствие, есть ли вообще необходимость идти искать заново.

Поэтому, если в ходе расследования упустить факт, что «в этом процессе DLL с тем же именем из другой папки уже была загружена раньше», условия воспроизведения будут прочитаны неверно.

5.2 Known DLLs

Known DLLs — это список DLL, которые Windows считает известными для данной версии; его можно посмотреть в HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\KnownDLLs. Для DLL из этого списка система использует свою копию известной DLL.1

Здесь важно то, что Known DLLs — это не та ситуация, где обычное приложение может «выиграть», разместив одноимённую DLL в своей папке. Если понимать это как простую гонку «кто первым» с System32, поведение будет истолковано неверно.

6. API set — это не «имя физической DLL», а контрактное имя

Увидев имя вроде api-ms-win-core-..., легко задуматься: «откуда искать этот файл DLL?» Но Microsoft Learn поясняет, что API set — это виртуальный псевдоним для физической DLL, механизм, отделяющий реализацию от контракта.3

То есть представление вида:

  • имя API set = имя физического файла DLL как есть
  • разрешение API set = такой же файловый поиск, как для обычной DLL

неточно.

Если держать в уме модель API set, легче объяснить, что:

  • всё остаётся согласованным, даже если имя реализующей DLL отличается в зависимости от версии Windows или типа устройства
  • вызывающей стороне не нужно фиксированно знать, «какая хост-DLL реализует данную функцию»3

7. Manifest и side-by-side (SxS) — альтернативное решение проблемы DLL versioning

DLL redirection и SxS manifest описываются не как небольшая хитрость с порядком поиска, а как механизм для предотвращения конфликтов DLL versioning.789

Согласно Microsoft Learn:

  • manifest — это XML, описывающий side-by-side assembly или isolated application
  • side-by-side assembly — единица именования, binding, versioning и deployment
  • на основе зависимостей, записанных в manifest, загрузчик решает, к какой версии выполнить bind

89

Поэтому на практике эти три вещи нужно рассматривать отдельно:

  • простое размещение private DLL в папке приложения
  • использование DLL redirection, например .local
  • использование side-by-side binding через manifest

Все они похожи в том, что «влияют на разрешение DLL», но их проектный замысел не одинаков.

8. Что меняется с LoadLibraryEx, SetDllDirectory и AddDllDirectory

8.1 SetDllDirectory

SetDllDirectory меняет порядок поиска, но Microsoft Learn прямо указывает, что она фактически отключает safe DLL search mode.1

То есть даже если использовать её с намерением «просто добавить одну папку, специфичную для приложения», в результате меняется пространство поиска, включая обращение с current folder.

Более того, если вызвать SetDllDirectory в родительском процессе, её влияние может распространиться и на стандартный порядок поиска в дочернем процессе.1

По этой причине на практике безопаснее не использовать SetDllDirectory небрежно и постоянно, а полагаться на:

  • SetDefaultDllDirectories
  • AddDllDirectory
  • LOAD_LIBRARY_SEARCH_* функции LoadLibraryEx

456

8.2 AddDllDirectory

Пути, добавленные через AddDllDirectory, используются в сочетании с LOAD_LIBRARY_SEARCH_USER_DIRS. Microsoft Learn указывает, что порядок поиска при добавлении нескольких директорий не определён.15

Поэтому стоит избегать проектирования, при котором:

  • добавляется несколько директорий
  • и от порядка их обхода строго что-то ожидается

8.3 SetDefaultDllDirectories

SetDefaultDllDirectories описывается как API для исключения из стандартного DLL search path директорий, склонных становиться уязвимостью, и ограничения области поиска.4

Особенно важно учитывать три свойства.

  • Действует на уровне процесса
  • После вызова сохраняется на протяжении всего времени жизни процесса
  • Однажды заданный стандартный путь поиска нельзя просто вернуть к исходной стандартной форме

С точки зрения безопасности этот API удобен для проектирования по принципу «сразу после запуска перейти к более безопасному пространству поиска».4

8.4 LoadLibraryEx

LoadLibraryEx позволяет менять поведение поиска с помощью флагов LOAD_WITH_ALTERED_SEARCH_PATH и LOAD_LIBRARY_SEARCH_*.61

На практике этот API хорошо подходит для таких требований, как:

  • включить в область поиска и папку исходной DLL, вместе с зависимыми DLL
  • ограничить поиск только папкой приложения, System32 и явно добавленными пользовательскими директориями

9. Даже полный путь не фиксирует зависимые DLL

Это тот момент в теме, который очень важен на практике, но легко упускается. Microsoft Learn поясняет, что даже когда первая DLL загружается с указанием полного пути, её зависимые DLL ищутся только по имени модуля.14

То есть из того, что:

  • вы явно загрузили C:\\MyApp\\plugins\\foo.dll

не следует, что:

  • bar.dll, от которой зависит foo.dll, обязательно будет взята из той же папки.

При таком заблуждении возникает довольно неприятный сбой:

  • в среде разработки всё работает
  • на стороне распространения разрешается другая bar.dll
  • конфликт зависимых DLL становится зависимым от окружения при воспроизведении

10. Как избежать DLL preloading / hijacking

В документации Microsoft Learn по DLL security поясняется, что к DLL preloading attack и binary planting attack приводит сочетание динамической загрузки без полного пути и наличия в области поиска директорий, которыми может управлять злоумышленник.2

На практике проще ориентироваться на такую базовую схему.

  • Сокращать загрузку по «голому» имени вроде LoadLibrary("foo.dll")
  • При необходимости использовать указание полного пути
  • Сужать процесс-умолчательный путь поиска через SetDefaultDllDirectories
  • Добавлять через AddDllDirectory только явно разрешённые директории
  • Явно задавать область поиска с помощью флагов LOAD_LIBRARY_SEARCH_SYSTEM32, LOAD_LIBRARY_SEARCH_APPLICATION_DIR, LOAD_LIBRARY_SEARCH_USER_DIRS и подобных
  • Избегать current folder и неосторожной зависимости от PATH

Особенно опасна ситуация, когда процесс, работающий с правами администратора, имеет неоднозначный путь поиска. Microsoft Learn также поясняет, что при загрузке вредоносной DLL она выполняется с привилегиями этого процесса.2

11. Практический чек-лист для принятия решений

При ревью проектирования загрузки DLL в Windows проверка как минимум следующего сокращает число инцидентов.

  1. Является ли приложение packaged app или unpackaged app
  2. Какие DLL происходят из статического связывания, а какие загружаются динамически
  3. Указывается ли полный путь или только имя модуля
  4. Не используется ли SetDllDirectory
  5. Позволяет ли конфигурация использовать SetDefaultDllDirectories и LOAD_LIBRARY_SEARCH_*
  6. Используется ли AddDllDirectory несколько раз с неявным ожиданием определённого порядка
  7. Каким способом управляются зависимости — через manifest, SxS, private DLL или redirection
  8. Нет ли слабых с точки зрения безопасности предположений о current folder или PATH
  9. Не разрешаются ли зависимые DLL из другого места в другом окружении

Проверив отдельно эти девять пунктов, можно устранить на достаточно раннем этапе такие проблемы, как «DLL не найдена», «загружена не та DLL», «не запускается только в production» и «остановка на ревью уязвимостей».

12. Заключение

Разрешение имён DLL в Windows — это не просто «порядок обхода папок». На деле оно определяется наложением DLL redirection, API set, SxS manifest, loaded-module list, Known DLLs, а также пространства поиска, изменяемого вызовами API.134

На практике важнее всего сводится к этим пяти пунктам.

  • Не заучивать порядок поиска как единую таблицу
  • Разделять packaged и unpackaged
  • Понимать, что даже при указании полного пути зависимые DLL могут обрабатываться иначе
  • Не использовать SetDllDirectory бездумно
  • Для безопасного варианта использовать SetDefaultDllDirectories и флаги поиска LoadLibraryEx

Разрешение имени DLL — это место, где часто разом всплывают сбои запуска, различия окружений и проблемы безопасности. Именно поэтому стоит понимать не только «в каком порядке ищет Windows», но и «что вообще Windows считает предпосылками разрешения имён».

Связанные статьи

Источники

  1. Microsoft Learn: Dynamic-link library search order
  2. Microsoft Learn: Dynamic-Link Library Security
  3. Microsoft Learn: Windows API sets
  4. Microsoft Learn: SetDefaultDllDirectories function
  5. Microsoft Learn: AddDllDirectory function
  6. Microsoft Learn: LoadLibraryEx function
  7. Microsoft Learn: Dynamic-link library redirection
  8. Microsoft Learn: Manifests
  9. Microsoft Learn: About Side-by-Side Assemblies
  1. Microsoft Learn, Dynamic-link library search order, дата обращения: 24 марта 2026 г.  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

  2. Microsoft Learn, Dynamic-Link Library Security, дата обращения: 24 марта 2026 г.  2 3 4 5

  3. Microsoft Learn, Windows API sets, дата обращения: 24 марта 2026 г.  2 3 4 5

  4. Microsoft Learn, SetDefaultDllDirectories function, дата обращения: 24 марта 2026 г.  2 3 4 5 6 7

  5. Microsoft Learn, AddDllDirectory function, дата обращения: 24 марта 2026 г.  2 3 4

  6. Microsoft Learn, LoadLibraryEx function, дата обращения: 24 марта 2026 г.  2 3 4

  7. Microsoft Learn, Dynamic-link library redirection, дата обращения: 24 марта 2026 г.  2

  8. Microsoft Learn, Manifests, дата обращения: 24 марта 2026 г.  2 3

  9. Microsoft Learn, About Side-by-Side Assemblies, дата обращения: 24 марта 2026 г.  2 3

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Когда на Windows действительно требуются права администратора — UAC, защищённые области и как это определить на этапе проектирования

Разбираем на практических примерах, когда в Windows требуются права администратора — с точки зрения UAC, защищённых областей, служб, драй...

Если ваше Windows-приложение приняли за вирус — как реагировать на ложные срабатывания Microsoft Defender и жить с влиянием на производительность

Разбираем правильный порядок действий, если Microsoft Defender ложно определяет ваше Windows-приложение как вредоносное: как устроена сов...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Разработка приложений для Windows

Размещение и способ загрузки DLL напрямую влияют на то, запустится ли Windows-приложение, на форму его распространения и на воспроизводимость проблем при их возникновении, поэтому эту тему стоит рассматривать в контексте разработки Windows-приложений.

Технические консультации и ревью дизайна

Разрешение имён DLL — тема, находящаяся на пересечении расследования дефектов, портирования, устранения уязвимостей и проектирования распространения, поэтому её удобно прорабатывать в формате ревью архитектуры или технической консультации.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

В каком порядке Windows ищет DLL, если её имя указано в LoadLibrary?
Прежде чем последовательно обходить файловую систему, оцениваются предварительные правила. А именно: сначала действуют DLL redirection, API set, SxS manifest redirection, loaded-module list и Known DLLs, и только затем начинается поиск по файловой системе — папка приложения, System32, папка Windows, current folder, PATH. В стандартном состоянии, когда включён safe DLL search mode, current folder оказывается довольно далеко в конце списка. Кроме того, для packaged app и unpackaged app сама логика порядка поиска различается.
Если загружать DLL по полному пути, будут ли зависимые DLL читаться из той же папки?
Не обязательно. Даже если первая DLL загружается с указанием полного пути, её зависимые DLL ищутся только по имени модуля, поэтому они могут разрешаться из другого места. При таком заблуждении получается неприятная зависимость от окружения: в среде разработки всё работает, а на стороне распространения разрешается другая зависимая DLL. Если нужно контролировать и зависимые DLL, задавайте пространство поиска явно, например через флаги LOAD_LIBRARY_SEARCH_* функции LoadLibraryEx.
Почему не стоит использовать SetDllDirectory?
Потому что она не просто меняет порядок поиска, а фактически отключает safe DLL search mode. Даже если вы хотели всего лишь добавить одну папку, специфичную для приложения, в результате меняется всё пространство поиска, включая обращение с current folder, что может дать обратный эффект с точки зрения безопасности. Более того, если вызвать её в родительском процессе, это может повлиять и на порядок поиска в дочернем процессе. Безопаснее использовать вместо неё SetDefaultDllDirectories, AddDllDirectory и флаги поиска LoadLibraryEx.
Как предотвратить DLL hijacking (атаку DLL preloading)?
К атаке приводит сочетание динамической загрузки без полного пути и наличия в области поиска директорий, которыми может управлять злоумышленник, поэтому нужно ограничивать оба фактора. А именно: сокращайте число вызовов LoadLibrary с «голым» именем, при необходимости используйте полный путь, сужайте процесс-умолчательный путь поиска через SetDefaultDllDirectories, добавляйте через AddDllDirectory только явно разрешённые директории и избегайте current folder и неосторожной зависимости от PATH. Особенно опасна ситуация, когда процесс, работающий с правами администратора, имеет неоднозначный путь поиска: вредоносная DLL в этом случае выполняется с привилегиями этого процесса.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог