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

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

История изменений (7 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Добавлены 23 схемы Mermaid, чтобы по рисункам было видно предварительные правила разрешения имён DLL, как сужать пространство поиска API и как проверять, откуда модуль реально загрузился (по правилу «не меньше одной схемы на 500–750 знаков основного текста»). К уже существующей схеме оценки предварительных правил добавлена подпись, номера рисунков проставлены сквозной нумерацией. Сам текст статьи не менялся.
В начало статьи добавлен раздел «Карта знаний этой статьи». Понятия, которые разбирает текст, и связи между ними собраны в краткое описание, схему и ссылку на страницу сведений. Утверждения статьи не менялись.
Текст обновлён по итогам внешнего ревью (1283 замечания). Содержание отдельных правок смотрите в записях ниже.
В примере загрузки плагина на C# исправлено: DLL из отдельной папки пытались подхватить флагами поиска ApplicationDirectory и System32. Первый указывает на папку exe, второй — на папку ОС, папку плагинов ни тот ни другой не смотрит. Загрузку переписали на полный путь и пояснили, что в DllImportSearchPath нет значения «произвольная папка» и что зависимые DLL этим не фиксируются.
Добавлены минимальные примеры на C/C++ и C# (от SetDefaultDllDirectories до RemoveDllDirectory; в C# показан не только атрибут, но и реальный вызов). Новая глава — как проверить, какая DLL откуда загрузилась. В начало добавлена таблица терминов, порядок поиска сведён в таблицу из 12 строк.
В японской статье заголовки «Related articles» и «References» оставались на английском — заменены японскими, формулировки ссылок на связанные статьи приведены к текущим заголовкам.
Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619782)

Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.

Го Комура (2026). Как работает разрешение имён DLL в Windows — порядок поиска и SxS. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619782 https://comcomponent.com/ru/blog/2026/03/24/002-windows-dll-name-resolution/

DOI (последняя версия)
10.5281/zenodo.21619782
DOI (эта версия)
10.5281/zenodo.21619783

Когда речь заходит о нативных DLL в Windows, путаница почти всегда одна и та же.

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

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

Почему заучить порядок поиска недостаточноРазрешение имён DLL нельзя свести к одной строке порядка поиска. Загрузчик Windows сначала оценивает особые правила и только потом ищет по файловой системе.Заучить порядок поиска одной строкойВ работе это не помогаетКак устроен загрузчик WindowsСначала оценивает особые правилаПотом ищет по файловой системе

Рис. 1: Разрешение имени начинается не с обхода папок, а с предварительных особых правил.

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

Термины, которые дальше идут без пояснения

Слова ниже дальше встречаются как есть. Подробности — в соответствующих главах.

Термин Коротко
packaged app / unpackaged app Приложение, которое ставят и разносят как пакет (MSIX и подобное), или обычное, где exe лежит в папке. Само определение порядка поиска у них разное (главы 3 и 4)
safe DLL search mode По умолчанию включён: отодвигает current folder назад в порядке поиска. Выключается, если реестровое значение SafeDllSearchMode поставить в 01
DLL redirection Если рядом с exe положить метку имя-приложения.exe.local, загрузчик сначала смотрит папку exe. Ещё называют .local и DotLocal (глава 7)7
SxS (side-by-side) Несколько версий одной DLL живут рядом, а какую версию привязать, записывают в manifest. SxS — сокращение от side-by-side (глава 7)9
loaded-module list Система проверяет, не загружена ли уже в память процесса DLL с тем же именем модуля. Откуда её когда-то взяли, неважно: если уже загружена, используют её (п. 5.1)1
Known DLLs Список DLL, которые эта версия Windows считает известными. Для них берут системную копию (п. 5.2)1
API set Контрактное имя вида api-ms-win-.... Виртуальный псевдоним, который прячет физическую DLL с реализацией (глава 6)3
package dependency graph Сам пакет приложения плюс пакеты, записанные как PackageDependency в секции Dependencies манифеста. Ищут в том порядке, в каком они записаны в манифесте1

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

Практические выводы — сразу списком.

  • Разрешение имён DLL в Windows — это не «сначала поиск по файловой системе». DLL redirection, API set, SxS manifest, loaded-module list и Known DLLs входят в предварительный этап порядка поиска.1
  • В обычной схеме unpackaged app при включённом safe DLL search mode папка приложения стоит высоко, но перед ней всё равно оценивают перечисленные особые правила.1
  • Даже если 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 изменили пространство поиска.

Три вещи, из которых складывается разрешение имени DLLРезультат разрешения имени DLL в Windows складывается не только из порядка папок, но и из предварительных правил, которые решают, во что превращается имя, и из пространства поиска, которое меняют API.Предварительные правила для имениDLL, которую реально загружаютПорядок поиска по папкамПространство поиска, изменённое APIredirection, Known DLLsфлаги LoadLibraryEx

Рис. 2: Итог складывается из предварительных правил, порядка папок и API.

Карта знаний этой статьи

Статья разбирает, что разрешение имён DLL в Windows — это не просто порядок обхода папок: перед поиском в файловой системе оцениваются правила предыдущего этапа — DLL redirection, API set, манифест SxS, loaded-module list и Known DLLs. У packaged app и unpackaged app сами определения порядка поиска различаются, а положение current folder зависит от того, включён или выключен safe DLL search mode. SetDllDirectory фактически отключает safe DLL search mode, поэтому сужение пространства поиска сочетанием SetDefaultDllDirectories, флагов поиска LoadLibraryEx и AddDllDirectory снижает DLL hijacking. Даже задание полного пути автоматически не фиксирует зависимые DLL; фактический источник загрузки проверяют в Process Monitor или ListDLLs.

Карта знаний разрешения имён DLL в WindowsСхема показывает, что DLL redirection, API set, манифест SxS, loaded-module list, Known DLLs и package dependency graph оцениваются раньше поиска в файловой системе; что у packaged app и unpackaged app сами определения порядка поиска различаются; что SetDllDirectory ослабляет safe DLL search mode, а флаги поиска SetDefaultDllDirectories и LoadLibraryEx сужают пространство поиска и снижают DLL hijacking; и как фактический источник загрузки проверяют средствами вроде Process Monitorиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетнастраиваетсянесовместимо сиспользуетнесовместимо сснижаеттребуетиспользуетне рекомендуетсярекомендуется дляснижаетможет вызватьснижаетне рекомендуетсяиспользуетпроверяетсяпроверяетсяпроверяетсяпорядок поиска DLLDLL hijacking (атака DLL preloading)DLL redirection (.local)API setперенаправление манифеста SxSсписок загруженных модулейKnown DLLspackage dependency graphпоиск в current foldersafe DLL search modepackaged appunpackaged appSetDllDirectorySetDefaultDllDirectoriesAddDllDirectoryLoadLibraryExсужение пространства поиска DLLLoadLibrary по имени без путизагрузка DLL по полному путиразрешение зависимых DLLDllImportSearchPath (.NET)Process Monitor (procmon.exe)ListDLLstasklist /m

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 23, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

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

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

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

Предварительные правила до поиска по файловой системеИмя DLL сначала проходит DLL redirection, API set, SxS manifest redirection, loaded-module list и Known DLLs. К поиску по файловой системе переходят, только если ни одно из этих правил не сработало.Нужно разрешить имя DLLDLL redirectionAPI setSxS manifest redirectionloaded-module listKnown DLLsПорядок поиска по файловой системеОпределяется, какая DLL реально загружается

Рис. 3: Предварительные правила идут по очереди; поиск по файловой системе начинается, только если ни одно не сработало.

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

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

# Куда смотрят Тип Пояснение
1 DLL redirection Предварительное правило Есть ли имя-приложения.exe.local (глава 7)
2 API sets Предварительное правило От контрактного имени к DLL с реализацией (глава 6)
3 SxS manifest redirection Предварительное правило Binding по manifest (глава 7)
4 loaded-module list Предварительное правило Загружен ли уже одноимённый модуль (п. 5.1)
5 Known DLLs Предварительное правило Для известной DLL — системная копия (п. 5.2)
6 package dependency graph Предварительное правило Начиная с Windows 11 версии 21H2. Ищут в порядке, записанном в манифесте
7 Папка, из которой загрузили приложение Файловая система Отсюда начинается собственно обход папок
8 Системная папка Файловая система Обычно %SystemRoot%\System32. Это то, что возвращает GetSystemDirectory
9 16-bit system folder Файловая система Папка System времён 16 бит. Функции, которая вернула бы этот путь, нет, но искать здесь всё равно ищут. Современному приложению это почти никогда не нужно — достаточно знать, что пункт в порядке остался
10 Папка Windows Файловая система То, что возвращает GetWindowsDirectory
11 current folder Файловая система Если safe DLL search mode выключен, этот пункт поднимается на место 8
12 Директории из PATH Файловая система Отдельный путь приложения из реестрового ключа App Paths сюда не входит

Таблица не для заучивания. Она нужна, чтобы попасть, на каком шаге решился ваш случай. Если ответ уже на шагах 1–6, раскладывание файлов по папкам 7 и дальше ничего не изменит.

Как пользоваться таблицей порядка поискаТаблицу порядка поиска не заучивают. По ней определяют, на каком шаге решился ваш случай. Если ответ уже на предварительных правилах, раскладка по папкам ничего не меняет.Предварительные правила (1–6)Файловая система (7–12)Симптом: подхватывается не та DLLНа каком шаге решилось?Менять раскладку по папкам бесполезноЗдесь помогают раскладка и пути

Рис. 4: До правки попадите, на каком шаге решилась проблема.

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

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

Рис. 5: Current folder отодвинули назад, но одной этой меры для защиты мало.

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

Для packaged app Microsoft Learn задаёт отдельный порядок поиска. Там package dependency graph срабатывает раньше, и сама логика поиска немного другая.1

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

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

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

Сначала разделить packaged и unpackagedУ packaged app свой порядок поиска, и package dependency graph срабатывает раньше. Отсюда путаница, когда DLL находится при unpackaged-запуске на разработке и не находится в production-пакете. На ревью архитектуры сначала спрашивают, о каком типе приложения речь.unpackaged apppackaged appО каком приложении речь?Стандартный порядок поиска из главы 3Другой порядок (dependency graph раньше)После MSIX поведение на разработке и в production расходится

Рис. 6: Порядок поиска — не одна таблица: сначала packaged это или unpackaged.

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

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

5.1 loaded-module list

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

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

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

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

Как срабатывает loaded-module listДо поиска по файловой системе система проверяет, не загружена ли уже в память процесса DLL с тем же именем модуля. Если загружена, её и используют — откуда когда-то взяли, неважно. Искать заново уже не нужно.ЗагруженЕщё нетЗапрос на разрешение имени DLLОдноимённый модуль уже загружен?Берут этот модуль (папка не важна)Идут дальше по поискуЕсли одноимённую DLL из другой папки загрузили раньше, легко заблудиться

Рис. 7: Если одноимённый модуль уже загружен, загрузчик за новым экземпляром не идёт.

5.2 Known DLLs

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

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

Как работают Known DLLsKnown DLLs — список DLL, которые эта версия Windows считает известными. Для них берут системную копию. Обычное приложение не перебьёт это, положив одноимённый файл в свою папку: это не гонка с System32.ЕстьНетИмя DLL, которое нужно разрешитьЕсть в Known DLLs?Берут системную копиюИдут дальше по поискуПоложить файл в папку приложения и перебить это нельзя

Рис. 8: Известную DLL привязывают к системной копии, гонки «кто первый» здесь нет.

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
API set — контрактное имяИмя вида api-ms-win- — не имя физического файла DLL, а виртуальный псевдоним, который прячет DLL с реализацией. Контракт и реализация разделены, поэтому имя реализации может отличаться по версии Windows и типу устройства, а вызывающей стороне не нужно заранее знать хост-DLL.Контрактное имя api-ms-win-…Разрешается как виртуальный псевдонимФизическая DLL с реализацией скрытаРеализация может отличаться по версии и устройству, согласованность сохраняетсяДумать об этом как об обычном файловом поиске неточно

Рис. 9: API set — не «имя файла, который ищут», а контрактное имя, которое прячет реализацию.

7. Manifest и side-by-side (SxS) — другой ответ на проблему DLL versioning

DLL redirection и SxS manifest описывают не как мелкий трюк с порядком поиска, а как механизм, который не даёт версиям DLL столкнуться.789

По Microsoft Learn:

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

89

Как связаны manifest и side-by-sideManifest — XML, который описывает side-by-side assembly или isolated application. Side-by-side assembly — единица именования, binding, versioning и deployment. По зависимостям в manifest загрузчик решает, к какой версии выполнить bind. Так избегают столкновения версий DLL.manifest (XML)Описывает зависимый side-by-side assembly и версиюЗагрузчик решает, к какой версии выполнить bindНесколько версий одной DLL могут жить рядом

Рис. 10: SxS — не трюк с порядком поиска, а другой ответ на столкновение версий.

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

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

Все они похожи тем, что «влияют на разрешение DLL», но замысел у них разный. Microsoft Learn подсказывает развилку: если нужно обойтись без правок существующего приложения — DLL redirection; если приложение новое — side-by-side-компоненты.7

Как выбирать между тремя средствамиПоложить private DLL в папку приложения, использовать DLL redirection (.local) и side-by-side binding через manifest — все влияют на разрешение DLL, но замысел разный. Без правок существующего приложения берут DLL redirection, для нового — side-by-side.О каком средстве речь?Положить private DLL в папку приложенияDLL redirection (.local)SxS manifest bindingКогда нужно починить без правок существующего приложенияУправление зависимостями нового приложения

Рис. 11: Три похожих средства выбирают по замыслу, а не потому что «все влияют на DLL».

7.1 Что именно делает .local

Это легко проскочить одной строкой, поэтому поведение для unpackaged app разложено отдельно.7

  • Что кладут: имя файла перенаправления — имя-exe.local. Для Editor.exe рядом с exe кладут Editor.exe.local. DLL, которую нужно подхватить, кладут в ту же папку
  • Содержимое: содержимое файла игнорируется. Сам факт, что файл есть, — сигнал загрузчику при загрузке DLL сначала смотреть папку exe
  • Где срабатывает: и на загрузку по полному пути, и на загрузку только по имени модуля. Какой путь передали в LoadLibrary или LoadLibraryEx, неважно: если в папке exe есть одноимённая DLL, берут её. Спецификация как раз закрывает случаи вроде COM, где точка регистрации одна
  • Если не нашли: если в папке exe файла нет, возвращаются к обычному порядку поиска
  • Вариант с папкой: работает и схема, когда создают папку Editor.exe.local и кладут DLL внутрь неё
  • Включить на всю машину: в HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options заводят DWORD DevOverrideEnable со значением 1 и перезагружаются. После этого перенаправление через .local срабатывает, даже если у приложения есть application manifest
  • Для packaged app: место другое — смотрят <каталог установки пакета>\microsoft.system.package.metadata\application.local\

Есть побочный эффект, без которого разбор легко уходит не туда. Если используют DLL redirection и приложение не может обратиться ко всем дискам и директориям из порядка поиска, LoadLibrary обрывает поиск в момент отказа в доступе. Без DLL redirection недоступную директорию пропускают и поиск продолжается.7 Если только в окружении с .local говорят «DLL, которая должна быть дальше, не находится» — подозревайте эту разницу.

Поведение .localЕсли рядом с exe есть метка имя-exe.local, сам факт её наличия — сигнал сначала смотреть папку exe, содержимое файла не важно. Срабатывает и на загрузку по полному пути. Если одноимённой DLL нет, возвращаются к обычному порядку. Побочный эффект: при отказе в доступе поиск обрывается.ЕстьНетКладут имя-exe.localСначала смотрят сторону exeЕсть одноимённая DLL?Берут её, даже если путь полныйВозвращаются к обычному порядку поискаПобочный эффект: отказ обрывает поиск

Рис. 12: .local срабатывает самим фактом наличия и действует даже на загрузку по полному пути.

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

Ловушка SetDllDirectorySetDllDirectory вызывают, чтобы добавить одну папку приложения, но по сути отключают safe DLL search mode и меняют всё пространство поиска. Вызов в родителе может затронуть порядок поиска у потомка. Безопаснее опереться на SetDefaultDllDirectories, AddDllDirectory и флаги поиска LoadLibraryEx.SetDllDirectory, чтобы добавить одну папкуsafe DLL search mode по сути отключаетсяМеняется всё пространство поиска, включая current folderМожет затронуть порядок поиска у дочернего процессаВместо этого опираться на семейство SetDefaultDllDirectories

Рис. 13: Хотели «добавить одну папку» — ослабили всё пространство поиска.

8.2 AddDllDirectory

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

Поэтому лучше не проектировать так:

  • добавили несколько директорий
  • и от порядка их обхода строго что-то ждут
Как пользоваться AddDllDirectory и на что смотретьПути из AddDllDirectory работают в паре с LOAD_LIBRARY_SEARCH_USER_DIRS. Порядок поиска при нескольких добавленных директориях не определён, поэтому нельзя строить логику, которая ждёт строгого порядка обхода.AddDllDirectory добавляет путьСрабатывает в паре с LOAD_LIBRARY_SEARCH_USER_DIRSПорядок при нескольких добавлениях не определёнНе строить логику, которая зависит от порядка обхода

Рис. 14: На порядок между добавленными пользовательскими директориями в спецификации опираться нельзя.

8.3 SetDefaultDllDirectories

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

Три свойства стоит держать в голове.

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

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

Свойства SetDefaultDllDirectoriesSetDefaultDllDirectories убирает из стандартного пути поиска DLL директории, склонные становиться уязвимостью, и ограничивает область поиска. Действует на процесс, держится всю его жизнь, к исходной стандартной форме путь не вернуть. Удобно вызвать сразу после запуска и сместить процесс в безопасную сторону.Сразу после запуска SetDefaultDllDirectoriesУбирает из поиска места, склонные становиться уязвимостьюНа процесс, всю его жизньК исходной стандартной форме не вернуть

Рис. 15: API, который один раз вызывают сразу после запуска, чтобы сместить весь процесс в безопасную сторону.

8.4 LoadLibraryEx

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

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

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

Перед тем как писать вызов, стоит зафиксировать четыре ограничения.6

Ограничение Суть
Второй аргумент hFile зарезервирован на будущее, всегда передают NULL
Нельзя сочетать LOAD_WITH_ALTERED_SEARCH_PATH не сочетается ни с одним флагом LOAD_LIBRARY_SEARCH_*
Нужен полный путь Если используете LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, первый аргумент обязан быть полным путём
Порядок при нескольких флагах Ищут в порядке LOAD_LIBRARY_SEARCH_DLL_LOAD_DIRLOAD_LIBRARY_SEARCH_APPLICATION_DIRLOAD_LIBRARY_SEARCH_USER_DIRSLOAD_LIBRARY_SEARCH_SYSTEM32. Порядок внутри USER_DIRS при этом не определён

Если задан хотя бы один флаг LOAD_LIBRARY_SEARCH_*, стандартный путь поиска не используется вообще. То есть при одном только LOAD_LIBRARY_SEARCH_SYSTEM32 папку приложения не смотрят. «Сузить» значит именно это.

Если задан флаг поиска, стандартный путь не используетсяДаже один флаг семейства LOAD_LIBRARY_SEARCH_ отключает стандартный путь поиска целиком. Если передан только LOAD_LIBRARY_SEARCH_SYSTEM32, папку приложения не смотрят. В этом и смысл «сузить».НетХотя бы одинЗадан флаг семейства LOAD_LIBRARY_SEARCH_?Используется стандартный путь поискаСтандартный путь поиска не используется вообщеИщут только в диапазоне заданных флаговТолько SYSTEM32 — папку приложения не смотрят

Рис. 16: Флаг не «добавляет», а «подменяет диапазон целиком».

8.5 Минимальный пример (C/C++)

Если сложить API из этой главы в один вызов, получается такая форма. SetDefaultDllDirectories и LOAD_LIBRARY_SEARCH_* — API начиная с Windows 8, поэтому в заголовках нужно явно задать целевую версию.46

/* cl /W4 loader.c  (Visual Studio 2022 + Windows SDK 10)
 * SetDefaultDllDirectories / AddDllDirectory / LOAD_LIBRARY_SEARCH_* —
 * начиная с Windows 8. Если в целях ещё Windows 7, нужен KB2533623
 * и получение адресов из Kernel32.dll через GetProcAddress во время выполнения. */
#define _WIN32_WINNT 0x0602   /* Windows 8 */
#include <windows.h>
#include <stdio.h>

int wmain(void)
{
    /* 1. Сместить процессное пространство поиска в безопасную сторону.
     *    Цель — убрать из поиска current folder и PATH.
     *    LOAD_LIBRARY_SEARCH_DEFAULT_DIRS — это связка
     *    APPLICATION_DIR + SYSTEM32 + USER_DIRS.
     *    Без USER_DIRS шаг 2 (AddDllDirectory) в процессное
     *    умолчание не попадёт. */
    if (!SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)) {
        wprintf(L"SetDefaultDllDirectories failed: %lu\n", GetLastError());
        return 1;
    }

    /* 2. Явно добавить только свою папку плагинов.
     *    В AddDllDirectory передают абсолютный путь. */
    const wchar_t *pluginDir = L"C:\\MyApp\\plugins";
    DLL_DIRECTORY_COOKIE cookie = AddDllDirectory(pluginDir);
    if (cookie == NULL) {
        wprintf(L"AddDllDirectory failed: %lu\n", GetLastError());
        return 1;
    }

    /* 3. Загрузить.
     *    Второй аргумент hFile зарезервирован, всегда NULL.
     *    Если здесь передать флаги, берётся не процессное умолчание
     *    из шага 1, а только эта комбинация флагов.
     *    APPLICATION_DIR нет, поэтому папку приложения не смотрят. */
    HMODULE h = LoadLibraryExW(
        L"foo.dll",
        NULL,
        LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_SYSTEM32);
    if (h == NULL) {
        wprintf(L"LoadLibraryExW failed: %lu\n", GetLastError());
        RemoveDllDirectory(cookie);
        return 1;
    }

    /* 4. Обязательно проверить, откуда загрузили.
     *    При разборе важнее не «получилось ли», а «откуда получилось». */
    {
        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"loaded from: %s\n", loadedPath);
        } else {
            wprintf(L"GetModuleFileNameW failed: %lu\n", GetLastError());
        }
    }

    FreeLibrary(h);
    RemoveDllDirectory(cookie);
    return 0;
}

В этом коде зафиксированы четыре вещи.

  1. Сначала вызывают SetDefaultDllDirectories. Один вызов действует всю жизнь процесса; к стандартному пути поиска вернуться нельзя4
  2. AddDllDirectory имеет смысл только в паре с LOAD_LIBRARY_SEARCH_USER_DIRS. Если в SetDefaultDllDirectories не включили USER_DIRS, добавленная директория используется только в тех вызовах LoadLibraryEx, где задан LOAD_LIBRARY_SEARCH_USER_DIRS5
  3. Делают зачистку. Cookie, который вернул AddDllDirectory, снимают через RemoveDllDirectory5
  4. Печатают, откуда загрузили. Одна строка GetModuleFileNameW сильно меняет время разбора, когда что-то ломается

Зависимые DLL у foo.dll тоже ищут в диапазоне этих LOAD_LIBRARY_SEARCH_*. Если папку самой foo.dll нужно включить в поиск зависимостей, первый аргумент делают полным путём и добавляют LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR.

Ход минимального примераSetDefaultDllDirectories смещает процессное пространство поиска в безопасную сторону, AddDllDirectory явно добавляет папку плагинов, LoadLibraryEx загружает с флагами поиска, GetModuleFileNameW проверяет, откуда загрузили, RemoveDllDirectory делает зачистку.1. SetDefaultDllDirectories смещает умолчание в безопасную сторону2. AddDllDirectory добавляет разрешённую папку3. LoadLibraryEx загружает с флагами4. GetModuleFileNameW проверяет источник загрузки5. RemoveDllDirectory делает зачистку

Рис. 17: Каркас примера — пять шагов: сузить, добавить, загрузить, проверить, зачистить.

8.6 Если вызывать из C#

В .NET работает та же идея. Путь поиска для P/Invoke задают атрибутом DefaultDllImportSearchPaths, явную загрузку — через NativeLibrary.Load.

// .NET 8 / C# 12
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

// Для всех P/Invoke в сборке ограничить умолчательный поиск System32.
// Ограничение: на P/Invoke с абсолютным путём атрибут не действует.
//       На платформах кроме Windows эффекта тоже нет.
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]

internal static class Program
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint GetTickCount();

    private static void Main()
    {
        // Атрибут сам по себе ничего не делает. Срабатывает только при вызове.
        Console.WriteLine($"GetTickCount = {GetTickCount()}");

        // Плагин лежит в отдельной папке. Нельзя писать
        //   NativeLibrary.Load("foo.dll", asm, ApplicationDirectory | System32)
        // ApplicationDirectory — папка exe, System32 — папка ОС,
        // папку плагинов ни тот ни другой не смотрит.
        // Либо загрузка не найдёт файл, либо подхватит чужой foo.dll,
        // который случайно лежит рядом с exe.
        // Если место известно, надёжнее задать полный путь.
        string pluginDir = Path.Combine(AppContext.BaseDirectory, "plugins");
        string pluginPath = Path.GetFullPath(Path.Combine(pluginDir, "foo.dll"));
        if (!File.Exists(pluginPath))
        {
            throw new FileNotFoundException("Плагин не найден.", pluginPath);
        }

        // Перегрузка с путём читает этот файл напрямую (поиск не идёт).
        IntPtr handle = NativeLibrary.Load(pluginPath);

        try
        {
            // Проверить, откуда загрузили.
            foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
            {
                if (string.Equals(module.ModuleName, "foo.dll", StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine($"loaded from: {module.FileName}");
                }
            }
        }
        finally
        {
            NativeLibrary.Free(handle);
        }
    }
}

Значения DllImportSearchPath соответствуют флагам LOAD_LIBRARY_SEARCH_*. Поэтому если на стороне C# написали «только System32», ограничения из п. 8.4 действуют как есть. Каталог приложения не смотрят.

Здесь важно, что у DllImportSearchPath нет значения «произвольная папка». ApplicationDirectory — папка exe, System32 — папка ОС, значения под папку плагинов, которую выбрали сами, нет. Поэтому DLL из отдельной папки флагами поиска не достать — задают полный путь, как выше.

И даже при загрузке по полному пути, как в главе 9, зависимые DLL у foo.dll этим не фиксируются. Если плагин тащит свои зависимости, нужна та же C-практика: добавить папку через AddDllDirectory и включить LOAD_LIBRARY_SEARCH_USER_DIRS, либо сложить зависимости в одну папку и использовать LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR.

Как в C# читать DLL из отдельной папкиУ DllImportSearchPath нет значения «произвольная папка»: ApplicationDirectory — папка exe, System32 — папка ОС. Плагин из отдельной папки флагами поиска не достать, его читают напрямую по полному пути. Зависимые DLL по-прежнему требуют той же практики, что и на C.Значения «произвольная папка» нетНадёжный способПлагин кладут в отдельную папкуДостанут флагами поиска?Флагами не достатьСобрать полный путь и прочитать через NativeLibrary.LoadЗависимые DLL по-прежнему требуют практики со стороны C

Рис. 18: И в .NET папку, которую выбрали сами, надёжнее читать по полному пути.

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

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

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

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

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

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

Из этого заблуждения получается довольно неприятный сбой:

  • в среде разработки всё работает
  • на стороне распространения разрешается другая bar.dll
  • конфликт зависимых DLL становится зависимым от окружения при воспроизведении
Полный путь не фиксирует зависимые DLLДаже если первую DLL загрузили по полному пути, её зависимости ищут только по имени модуля и могут разрешиться из другого места. Отсюда сбой, зависимый от окружения: в разработке работает, у заказчика подхватывается другая зависимая DLL.foo.dll загружают по полному путиСаму foo.dll читают как указалиЗависимую bar.dll ищут только по имени модуляВ другом окружении может разрешиться из другого местаВ разработке работает, у заказчика ломается

Рис. 19: Полным путём фиксируется только первая 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

Сочетание, при котором срабатывает атака DLL preloadingДинамическая загрузка без полного пути плюс директория в области поиска, которой может управлять злоумышленник, складываются в DLL preloading attack. Загруженная вредоносная DLL выполняется с привилегиями процесса. Мера — меньше «голых» имён и уже пространство поиска.Динамическая загрузка без полного путиСкладываются условия DLL preloadingДиректория в поиске, которой может управлять злоумышленникВредоносная DLL выполняется с привилегиями процессаМера: меньше «голых» имён, уже пространство поиска

Рис. 20: Атака срабатывает как произведение «неоднозначного имени» и «места, которым можно управлять».

11. Как проверить, какая DLL откуда загрузилась

До сих пор это была спецификация. На реальном разборе быстрее не гадать, а проверить. Четыре средства по задаче.

Что нужно узнать Чем смотреть Куда смотреть
Где искали и где нашли Process Monitor10 Журнал обращения к файлам
Что сейчас загружено Process Explorer, tasklist /m11 Список модулей процесса
Какой процесс держит данную DLL ListDLLs12 Список по всем процессам
Какой путь реально прочитали во время отладки Окно модулей Visual Studio «Отладка» > «Окна» > «Модули»7

11.1 След поиска в Process Monitor

Здесь больше всего информации. Порядок такой.10

  1. Запустить Process Monitor от имени администратора и начать запись
  2. Открыть «Filter» > «Filter…», добавить Process Name is и имя целевого exe
  3. На том же экране добавить Path ends with .dll
  4. Запустить целевое приложение и остановить запись, когда проявится проблема

Смотреть нужно колонку Result. Загрузчик пытается открывать файлы сверху вниз по порядку поиска, поэтому в местах, где не нашли, стоит NAME NOT FOUND, а там, где открыли, — SUCCESS. Иначе говоря, строка сразу после последней пачки NAME NOT FOUND — путь, который реально взяли.

Сверив с таблицей главы 3, видно, на каком шаге решился этот случай. Если ответ уже на предварительных правилах (строки 1–6 таблицы), записей о походе за файлом может не быть вообще. Это тоже важный след.

Как читать журнал Process MonitorЗагрузчик пытается открывать файлы сверху вниз по порядку поиска, поэтому в местах, где не нашли, стоит NAME NOT FOUND, а там, где открыли, — SUCCESS. Строка сразу после последней пачки NAME NOT FOUND — путь, который взяли. Если записей нет, ответ уже на предварительных правилах.Читать колонку Result сверхуПачка NAME NOT FOUND = места, где искали и не нашлиСледующий SUCCESS — путь, который реально взялиЕсли записей нет, ответ уже на предварительных правилах

Рис. 21: След остаётся в колонке Result; отсутствие записей само по себе тоже след.

11.2 Список уже загруженных модулей

Если процесс уже запущен и нужно только увидеть, что и откуда прочитано, это можно сделать и без дополнительных утилит.

tasklist /m foo.dll

Команда перечисляет имена и PID процессов, которые загрузили указанный модуль.11 Сузив процесс, в нижней панели Process Explorer переключаются на отображение DLL и смотрят полный путь каждой загруженной DLL.

Sysinternals ListDLLs делает то же из командной строки и сразу по всем процессам.12

Сработало ли loaded-module list из п. 5.1, видно только так. Если одноимённую DLL уже загрузили из другой папки, этот процесс за новым экземпляром не пойдёт.

Как смотреть уже загруженные модулиtasklist /m сужает процессы, которые держат указанный модуль, затем Process Explorer в режиме DLL или ListDLLs показывает полный путь. Сработало ли loaded-module list, видно только здесь.tasklist /m foo.dll сужает процессы, которые держат модульПолный путь смотрят в Process Explorer или ListDLLsОткуда загрузили, фиксируется как фактВлияние loaded-module list видно только здесь

Рис. 22: Что загружено сейчас, подтверждают списком модулей, а не догадкой.

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

Когда ревьюите загрузку 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 из другого места в другом окружении
  10. На среде, где виден симптом, проверили ли, откуда реально загрузили (глава 11)

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

Ход проверки на ревьюНа ревью загрузки DLL смотрят форму приложения (packaged / unpackaged), способ загрузки (полный путь или только имя), использование API вроде SetDllDirectory и флагов поиска, управление зависимостями через manifest и зависимые DLL, и на среде с симптомом — откуда реально загрузили. Так инцидентов меньше.Форма приложения (packaged / unpackaged)Способ загрузки (полный путь или только имя)Использование API (SetDllDirectory, флаги)Управление зависимостями (manifest / SxS / PATH)На месте проверить источник загрузки (глава 11)

Рис. 23: Чек-лист можно пройти в порядке: форма → загрузка → API → зависимости → проверка на месте.

13. Итог

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

На практике важнее всего эти шесть пунктов.

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

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

Вывод статьиРазрешение имени DLL — место, где сразу всплывают сбои запуска, различия окружений и вопросы безопасности. Нужно понимать не только порядок поиска, но и то, что Windows считает предпосылками разрешения имён.В каком порядке ищут (порядок папок)Объяснение сходится, только когда понятны обаЧто считают предпосылками (предварительные правила и пространство поиска)Сбои запуска, различия окружений и безопасность можно закрыть вместе

Рис. 24: Практическое знание разрешения имён DLL — не заучить порядок, а понять предпосылки.

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

Источники

  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
  10. Microsoft Learn: Process Monitor
  11. Microsoft Learn: ListDLLs
  12. Microsoft Learn: tasklist
  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 22 23 24 25

  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 6

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

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

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

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

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

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

  10. Microsoft Learn, Process Monitor  2

  11. Microsoft Learn, tasklist  2

  12. Microsoft Learn, ListDLLs  2

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

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

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

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

Разрешение имён 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. Если нужно контролировать и зависимости, задайте пространство поиска явно — например флагами 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, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

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

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