DLL hijacking - это класс атак, при котором приложение загружает стороннюю динамическую библиотеку вместо той, которую предполагал разработчик. Причина кроется не в ошибке операционной системы, а в сочетании двух обстоятельств: Windows ищет DLL по заранее определённому порядку каталогов, а программист иногда вызывает LoadLibrary с одним лишь именем файла, доверяя этому поиску. Если в каталоге, который просматривается раньше легитимного, окажется файл с совпадающим именем, он и будет загружен, а его код получит все права вызывающего процесса. Понимание этой механики важно защитнику: оно позволяет строить диагностику, аудит и архитектурные меры, которые делают подмену практически невозможной, не прибегая к радикальному запрету динамической загрузки.
Порядок поиска DLL и его управляющие параметры
Когда процесс запрашивает библиотеку по короткому имени, загрузчик Windows следует строго фиксированной последовательности. При включённом режиме SafeDllSearchMode, который действует по умолчанию начиная с Windows XP с пакетом обновления 2, поиск идёт так: каталог, из которого запущено приложение, затем системный каталог System32, затем 16-разрядный системный каталог, затем каталог Windows, затем текущий рабочий каталог процесса и только после этого каталоги, перечисленные в переменной PATH. Каждый из этих этапов - потенциальная точка подмены, если злоумышленник способен записать в него файл, а легитимная библиотека находится в каталоге позже по списку либо отсутствует вовсе.
Отдельную роль играет список KnownDLLs. Он хранится в реестре по пути HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs и включает ядровые библиотеки вроде kernel32.dll и user32.dll. Такие имена разрешаются напрямую из системного каталога, минуя стандартный обход, поэтому подменить их файлом рядом с приложением нельзя. Однако список охватывает лишь небольшое ядро ОС, и любая прикладная или вспомогательная библиотека вне его защиты.
Существует и тонкость, связанная с разрядностью. В 64-разрядной Windows 32-разрядные процессы при обращении к System32 прозрачно перенаправляются в SysWOW64. Программа, которая пытается загрузить библиотеку не той архитектуры, получает ошибку загрузки либо, что опаснее, подхватывает одноимённый файл из неожиданной ветви файловой системы. Смешение разрядностей в скриптах развёртывания и лаунчерах регулярно становится косвенной причиной уязвимых конфигураций, поэтому аудитор обращает внимание на то, из какого именно каталога фактически загружена каждая DLL.
Как возникает подмена через proxy DLL и binary planting
Условие атаки формулируется просто: приложение запрашивает библиотеку по имени без полного пути, а записываемый злоумышленником каталог стоит в порядке поиска раньше каталога с настоящей библиотекой. Классический сценарий получил название binary planting. Жертва получает документ, архив или медиафайл в каталоге Downloads; рядом в той же папке оказывается вредоносная DLL с именем, которое ассоциированная программа пытается загрузить. Текущий рабочий каталог при открытии файла ассоциацией часто совпадает с каталогом документа, и поддельная библиотека загружается ещё до обращения к системным путям. Особенно удобны для атакующего случаи, когда запрошенной библиотеки вообще нет в системе: тогда поиск гарантированно доходит до слабо защищённых каталогов.
Чтобы подмена не разрушила работу программы и не выдала себя немедленным сбоем, применяется приём proxy DLL. Поддельная библиотека экспортирует точно такой же набор функций, что и оригинал, а при вызове перенаправляет управление настоящей библиотеке, лишь выполнив перед этим или после этого собственный код. Для пользователя и для приложения всё выглядит штатно. Важно понимать масштаб угрозы: подсунуть такую библиотеку может любой, кто обладает правом записи в соответствующий каталог, - отдельных привилегий, эксплойтов памяти или обхода контроля учётных записей не требуется. Именно поэтому проблема считается в первую очередь проблемой гигиены путей и прав доступа, а не экзотической техникой.
Подмену следует отличать от DLL injection. При инъекции код принудительно внедряется в уже работающий процесс через API вроде CreateRemoteThread и LoadLibrary, и жертвой выступает сам процесс. При hijacking ничего внедрять не нужно: приложение само, добровольно и штатным образом загружает чужую библиотеку, потому что его логика поиска оказалась слабой. Стопроцентно разные предпосылки требуют и разных контрмер: против инъекций работают ограничения на межпроцессный доступ, а против hijacking - дисциплина путей и прав на каталоги.
Исторический контекст волны DLL preloading 2010 года
Летом 2010 года проблема пережила массовую вспышку. После публикации исследований, показавших, что каталог документа участвует в поиске DLL, в считаные недели уязвимости класса DLL preloading были выявлены у сотен широко распространённых приложений: медиаплееров, офисных пакетов, архиваторов, графических редакторов. Вектор был одинаковым - файл данных открывался из сетевой папки или из Downloads, а программа тянула недостающую библиотеку из того же каталога. Microsoft выпустила руководство и инструментарий для аудита, а производители ПО начали массовую кампанию исправлений, переходя на полные пути и новые API управления поиском.
Та волна научила отрасль трём вещам. Во-первых, уязвимость живёт не в одном продукте, а в паттерне программирования, поэтому лечится системно. Во-вторых, текущий рабочий каталог - самое слабое звено в цепочке поиска. В-третьих, даже качественные программы крупных вендоров оказывались уязвимы, что развеяло представление о проблеме как о болезни маргинального ПО. Современные механизмы смягчения, описанные ниже, во многом выросли именно из опыта тех месяцев.
Диагностика и аудит со стороны защитника
Базовым инструментом обнаружения уязвимых мест в своей инфраструктуре служит Process Monitor из комплекта Sysinternals. Защитная диагностика строится так: при запущенной трассировке активности файловой системы запускается целевое приложение и воспроизводятся его типовые сценарии, включая открытие файлов из пользовательских каталогов. Затем на журнал накладывается фильтр по имени процесса, по операциям с путями, оканчивающимися на .dll, и по результату NAME NOT FOUND. Каждая такая запись означает, что программа искала библиотеку, не нашла её и перешла к следующему каталогу в порядке поиска. Если неудачный поиск происходит в каталоге, куда у обычного пользователя есть право записи, - это кандидат на подмену, подлежащий устранению или компенсирующему контролю.
Дополняет картину проверка фактических путей уже загруженных модулей: инструменты вроде ListDLLs или вкладки модулей в Process Explorer показывают, из какого каталога реально взята каждая библиотека, и позволяют сверить её цифровую подпись. Регулярное сопоставление загруженных путей с ожидаемыми превращает анализ из разового исследования в повторяемую процедуру мониторинга. В зрелых организациях такой аудит включают в цикл приёмки нового ПО перед развёртыванием: приложение проверяется на стенде с характерной файловой структурой, и все обращения к несуществующим DLL фиксируются в отчёте.
Защита со стороны разработчика
Главная мера проста: никогда не полагаться на порядок поиска там, где это критично. Библиотеки, которые программа обязана загрузить, следует запрашивать через LoadLibrary с полным абсолютным путём, вычисленным относительно каталога установки, а не через короткое имя. Для кода, где короткие имена неизбежны, Windows предоставляет API явного управления. SetDefaultDllDirectories сужает область поиска для всего процесса до системных каталогов и явно добавленных путей; LoadLibraryEx с флагами LOAD_LIBRARY_SEARCH_SYSTEM32 и LOAD_LIBRARY_SEARCH_APPLICATION_DIR задаёт безопасный порядок для конкретного вызова; AddDllDirectory добавляет доверенные каталоги в этот строгий режим. Отдельно функция SetDllDirectory с пустым аргументом исключает текущий рабочий каталог из поиска - дёшево и эффективно против binary planting.
Второй уровень - целостность и происхождение кода. Цифровая подпись собственных библиотек и проверка подписи при загрузке позволяют отличить легитимный файл от подменённого, даже если подмена технически удалась. Административный список KnownDLLs полезно держать в курсе при проектировании критичных компонентов, понимая его границы: он защищает только по коротким именам и только для перечисленных модулей. Наконец, сборка и развёртывание должны контролировать разрядность: 32-разрядный и 64-разрядный варианты библиотек раскладываются по своим каталогам осознанно, чтобы перенаправление SysWOW64 не подставляло неожиданные файлы. Совокупность этих мер переводит уязвимость из категории неизбежных в категорию исключённых по построению.
Защита со стороны администратора и современные смягчения платформы
Администратор влияет на ту часть уравнения, которой касается атакующий, - на права записи. Каталоги программ в Program Files должны быть закрыты на запись для всех, кроме доверенных учётных записей развёртывания; то же касается любых собственных каталогов приложений на иных дисках и шаблонов PATH. Переменная PATH не должна содержать каталогов, открытых на запись рядовым пользователям, - аудит этого условия стоит выполнять регулярно. Системное разграничение, при котором пользователь работает без прав администратора, само по себе отсекает подмену в системных каталогах, оставляя атакующему лишь пользовательские папки, которые контролируются политиками исполнения вроде AppLocker или WDAC.
Для периодического аудита удобен короткий PowerShell-сценарий: он проходит по каталогам из PATH и каталогам установленных приложений, вызывает Get-Acl и сообщает о любых ACE, дающих запись группам Users или Everyone. Такой отчёт за минуты выявляет конфигурации, в которых binary planting технически возможен, и встраивается в регулярные проверки соответствия.
Для диагностики подозрительной конфигурации на конкретной машине исторически применяют мониторинг процесса загрузки: фильтр по расширению DLL и результату NAME NOT FOUND в трассировке стартового процесса показывает, где загрузчик ищет библиотеку и не находит - эти места и есть поверхность подмены. Дефицитная запись в мониторе становится заданием на упаковку: либо библиотека должна лежать рядом с приложением, либо вызов нужен по полному пути. Для защитника такая трассировка - способ убедиться, что приложение не загружает библиотеки из каталогов загрузки пользователя или временных папок, и на корпоративном контуре её выполняют регулярно, как проверку линейки криптомодулей.
Наконец, сама платформа эволюционирует в сторону сокращения поверхности атаки. Механизм side-by-side сборок и хранилище WinSxS с манифестами SxS жёстко привязывают приложение к конкретным версиям библиотек, уменьшая роль поиска по каталогам. Пакетирование по модели appx и MSIX изолирует приложение в контейнер с собственным представлением файловой системы и реестра, где подложить чужую DLL извне в поток загрузки практически негде. Флаги поиска, появившиеся после волны 2010 года, стали нормой для нового кода. В совокупности эти изменения означают, что DLL hijacking остаётся реальной угрозой прежде всего там, где эксплуатируется устаревшее или небрежно написанное ПО и слабая дисциплина прав, а грамотная комбинация мер разработчика и администратора сводит риск к пренебрежимо малому остатку.
Для команды безопасности итоговый контрольный список ограничивается несколькими вещами: проверить порядок поиска загрузчика, убедиться, что каталоги приложений не доступны для записи обычным пользователям, регулярно запускать трассировку загрузки у критичных программ и не распространять установщики, которые сами создают уязвые точки входа, - соблюдение этих базовых правил исключает целый класс инцидентов заранее, ещё до того как вступит в силу эвристический анализ.