DLL hijacking - техника, которая может дать множество преимуществ: повыситься до NT AUTHORITY/SYSTEM, получить исполнение от лица привилегированного пользователя, действовать от лица легитимного приложения и т. д.
Естественно, перед тем как ее осуществить, нужно найти подходящее приложение и библиотеку, которую можно подменить.
Вручную это может быть долго и рутинно. Если цель - отыскать библиотеки, загружаемые во время выполнения, задача превращается в неочевидный и длительный реверс-инжиниринг.
Цель статьи - описать подход, который позволит реализовать средство автоматической диагностики приложения и подмены библиотек.
1. Сбор списка библиотек, теоретически подверженных подмене
Для начала разберемся, какие библиотеки приложение использует во время работы.
Сразу на ум приходит таблица импорта (Import Address Table) в заголовке PE исполняемого файла.

Приложение также может явно загрузить библиотеку с помощью семейства функций из Kernel32.dll: LoadLibraryA, LoadLibraryW, LoadLibraryExA, LoadLibraryExW.
Если присмотреться пристальнее к тому, как устроены исполняемые файлы и что делает загрузчик, список пополняется:
Библиотеки отложенной загрузки (
delay-load imports)Библиотеки, на которые ведут
forwardersБиблиотеки, загружаемые до
Kernel32.dll
И, разумеется, это не полный список.
Однако следить за каждым вариантом - не самая удобная стратегия. Есть ли у них общий знаменатель?
Большая часть динамических загрузок в итоге приходит к вызову LdrLoadDll из ntdll.dll:
NTSYSAPI
NTSTATUS
NTAPI
LdrLoadDll(
Inopt_ PCWSTR DllPath,
Inopt_ PULONG DllCharacteristics,
In PCUNICODE_STRING DllName,
Out PVOID *DllHandle
);Таким образом, перехватив вызовы этой функции и сохранив DllName, мы можем собрать обширный список библиотек.
Как можно организовать перехват?
Для этого можно внедрить трамплин в начало функции LdrLoadDll и перенаправить его на собственную хук-функцию, которая будет логировать название загружаемой библиотеки.
Пример реализации можно найти здесь.
Итого, стратегия сбора следующая:
Разрабатываем библиотеку
Hooker.dll, которая:при загрузке в процесс устанавливает хук на функцию
LdrLoadDllхук записывает аргумент
DllNameв файл
Разрабатываем консольное приложение, которое:
создает процесс с необходимым исполняемым файлом в состоянии
suspendedпроцесс создается в рамках объекта
Jobв созданном процессе выделяет память и записывает туда путь к
Hooker.dllсоздает в процессе поток с функцией
LoadLibraryAи аргументом - адресом выделенной памяти со строкойвозобновляет процесс
по истечении таймаута завершает его

Какой выбрать таймаут - вопрос практики. Часто достаточно дать приложению поработать 30 секунд, чтобы поймать явные вызовы.
В результате получается Injector, который после отработки оставляет файл со списком библиотек.
2. Auto DLL hijacking
Теперь, когда у нас есть список библиотек, нужно понять, какие из них реально удастся подменить.
На практике список бывает большим, поэтому без автоматизации не обойтись.
Какие требования мы ставим?
Необходимо средство, которое для произвольного приложения может подменить произвольную библиотеку.
Присмотримся к тому, как устроена таблица экспорта в PE-файле. Обычно в библиотеке есть экспортируемые функции: их код скомпилирован и лежит, как правило, в секции .text. В таблице каждая запись соответствует конкретной экспортируемой функции.
Соответственно, запись содержит RVA к коду функции и ее имя для поиска по таблице.
Однако иногда вместо кода запись может ссылаться на строку ASCII. Это и есть механизм перенаправления (forwarders).

Строка forwarder сообщает, что код функции находится в другой библиотеке, причем имя функции может отличаться от исходного.
В качестве примера приведена таблица Kernel32.dll. Когда приложение импортирует функцию AcquireSRWLockExclusive, загрузчик загружает Kernel32.dll, видит перенаправление, подгружает ntdll.dll и ищет функцию RtlAcquireSRWLockExclusive.
Теперь вернемся к нашей исходной задаче. Есть приложение, которое использует некоторую библиотеку.

Мы хотим подменить ее и внедриться в это взаимодействие. Что если воспользоваться механизмом перенаправления?
Мы создадим библиотеку, которая будет называться так же, как и та, которую хотим подменить, и будет экспортировать те же функции, но перенаправлять их на настоящую библиотеку.
Полезную нагрузку поместим в DllMain. В качестве минимальной нагрузки используем небольшую функцию, которая создает файл с расширением .injected, чтобы сигнализировать о том, что подмена произошла.
В результате получим следующую схему:

Добавление полезной нагрузки в
DllMainсчитается нерекомендуемой практикой, потому что эта функция выполняется при удержанииLoaderLock. Однако для диагностики небольшой фрагмент кода отрабатывал корректно (эмпирическое наблюдение), хотя теоретически возможенdeadlock.
Elliot Killick в своей статье описал способ разместить полезную нагрузку в
DllMainс учетом блокировкиLoaderLockи исполнить ее без вреда для процесса.
Итак, первым шагом компилируем библиотеку с полезной нагрузкой в DllMain без таблицы экспорта.
Далее создаем свою таблицу экспорта: формируем секцию, корректно размещаем в ней данные и указываем в заголовке PE адрес этой секции как адрес таблицы экспорта.
Содержимое секции:
_IMAGE_EXPORT_DIRECTORYимя целевой библиотеки
массив ординалов
массив
RVAна имена функциймассив
RVAна код функций (в нашем случае - на строкиforwarders)имена функций
строки
forwarders

В результате получаем модуль, который для произвольной библиотеки может создать проксирующую с полезной нагрузкой, архитектура которой отображена на рисунке.
Заключение
Таким образом, мы сумели решить следующие задачи:
для любой используемой приложением библиотеки создавать проксирующую библиотеку, причем в автоматическом режиме
для произвольного приложения собирать список используемых им библиотек
Реализовав описанные наработки, можно получить единый проект, который может оказаться удобным инструментом при проведении тестирования на проникновение.
