DLL-инъекция

DLL-инъекция (англ. DLL injection) — техника, применяемая в программировании, которая позволяет запускать код в адресное пространство другого процесса путём принудительной загрузки им динамически подключаемой библиотеки (DLL)[1]. DLL-инъекция обычно используется сторонними программами для изменения поведения другого приложения способом, который не предусмотрен или не ожидается его разработчиками[1][2][3]. Например, внедрённый код может перехватывать системные вызовы функций[4],[5] или получать содержимое текстовых полей паролей, что невозможно стандартными средствами[6]. Программы, предназначенные для внедрения произвольного кода в произвольные процессы, называются DLL-инъекторами.

Подходы в Microsoft Windows

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

  • DLL, перечисленные в разделе реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs, загружаются во все процессы, которые обращаются к User32.dll при первичной загрузке этой библиотеки[7].[8][9] Начиная с Windows Vista AppInit_DLLs по умолчанию отключены[10]. В Windows 7 инфраструктура AppInit_DLL поддерживает подписывание кода. Начиная с Windows 8, функциональность AppInit_DLL полностью отключается при включённой опции Secure Boot, независимо от подписи или настроек реестра[11].
  • DLL, перечисленные в разделе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs, загружаются в каждый процесс, который вызывает функции Win32 API:

CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, CreateProcessWithTokenW и WinExec. Это рекомендованный и «законный» способ DLL-инъекции в современных версиях Windows (до Windows 10 включительно); DLL должна быть подписана действительным сертификатом.

  • Использование функций манипулирования процессом, таких как CreateRemoteThread, или техник, подобных AtomBombing[12], для внедрения DLL в уже запущенный процесс[5][6][13][14][15][16].
    • Открытие дескриптора к целевому процессу, что может быть реализовано как запуск процесса[17][18] или привязка к элементу, создаваемому этим процессом и известному заранее, например, окно с предсказуемым заголовком[19], или получение списка процессов[20] и поиск по имени исполняемого файла[21].
    • Выделение памяти в целевом процессе[22], в которую записывается имя DLL для внедрения[13][23].
      • Этот шаг можно пропустить, если имя подходящей DLL уже присутствует в целевом процессе: например, если процесс ссылается на User32.dll, GDI32.dll, Kernel32.dll или другую библиотеку с окончанием 32.dll, возможно загрузить библиотеку с именем 32.dll[24].
    • Создание нового потока в целевом процессе[25], стартовая функция которого — LoadLibrary, а аргументом является адрес только что загруженной строки[13][26].
      • Вместо передачи имени DLL и запуска потока в LoadLibrary можно записать непосредственно код для исполнения и запустить поток с указанием адреса этого кода[6].
    • Операционная система вызывает инициализационную функцию внедрённой DLL[13][27].
      • Без специальных мер, такой подход может быть обнаружен целевым процессом по уведомлениям DLL_THREAD_ATTACH, отправляемым каждому загруженному модулю при старте потока[27].
  • Использование функций-хуков Windows, например SetWindowsHookEx[2][5][6][28][29][30].
  • Использование функций SuspendThread или NtSuspendThread для приостановки всех потоков, и затем SetThreadContext либо NtSetContextThread для изменения контекста потока таким образом, чтобы выполнить внедряемый код, который, в свою очередь, может загрузить DLL[4][31][32].
  • Эксплуатация изъянов в дизайне Windows и приложения, когда функции LoadLibrary или LoadLibraryEx вызываются без указания полного пути к DLL[33][34][35].
  • Системные шимы операционной системы.
  • Замена специфической для приложения DLL на вредоносную, реализующую те же экспортируемые функции, что и оригинальная DLL[36].
  • Рефлективные DLL или DLL, способные загружать себя самостоятельно. Такая DLL копируется в пространство памяти целевого процесса, после чего управление передаётся её точке входа и она загружает себя из памяти[37].

Подходы в Unix-подобных системах

В Unix-подобных системах с динамическим компоновщиком ld.so (на BSD) и ld-linux.so (на Linux), произвольные библиотеки могут быть подлинкованы к новому процессу через указание пути к библиотеке в переменной окружения LD_PRELOAD, которую можно задавать глобально или для отдельного процесса[38].

Например, в Linux следующая команда запускает программу «prog» с подключением к ней общей библиотеки из файла «test.so» при старте:

LD_PRELOAD="./test.so" prog

Такую библиотеку можно скомпилировать аналогично другим shared object. С помощью GCC это достигается компиляцией исходника с опцией -fpic или -fPIC[39], и компоновкой с опцией -shared[40]. Такая библиотека будет иметь доступ к внешним символам программы, как и любая другая библиотека.

В macOS для аналогичной задачи используют команду:

DYLD_INSERT_LIBRARIES="./test.dylib" DYLD_FORCE_FLAT_NAMESPACE=1 prog

Библиотека из файла «test.dylib» будет подключена к процессу «prog» при запуске[41].

Также в Unix-подобных системах возможно использование техник на основе отладчиков[42].

Пример кода

Копирование DLL, загружаемой LoadLibrary, в удалённый процесс

Поскольку вызова LoadLibrary() для загрузки DLL в другой процесс не существует, нужно скопировать локально загруженную DLL в отведённую память чужого процесса. Далее приведён комментированный пример кода на C++:

#include <Windows.h>
/* ... далее весь приведённый выше C++-код, описывающий процесс копирования и загрузки DLL и создания потока в процессе ... */

Главная задача здесь — обеспечить, чтобы копируемая DLL заняла в удалённом процессе те же адреса, что и в процессе внедрения, что достигается резервированием той же области памяти. Особенность подхода: если не удаётся выделить нужную область, DLL локально выгружается, прежний диапазон адресов резервируется, и LoadLibrary() пробуют снова. Это предотвращает повторное использование старого адресного диапазона новым вызовом LoadLibrary().

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

Далее — пример исходного кода DLL-загрузчика, копируемого с помощью вышеуказанного механизма:

#include <Windows.h>
/* ... и весь приведённый выше код основной экспортируемой функции загрузчика DLL ... */

Также приведён пример DLL, которая загружается загрузчиком (печатает параметры в файл):

#include <Windows.h>
/* ... соответствующий пример на C++ ... */

Важное замечание: экспортируется только переменная данных, приемник параметров (initData), все действия по инициализации происходят в DllMain. Поток, созданный из DllMain, не будет запущен, пока все функции DLL_THREAD_ATTACH не завершат выполнение успешно — синхронизацию из DllMain с потоком выполнять нельзя.

Примечания

  1. 1 2 James Shewmaker. Анализ DLL-инъекции (англ.). GSM Presentation. Bluenotch (2006). Дата обращения: 31 августа 2008. Архивировано 3 декабря 2008 года.
  2. 1 2 Iczelion. Учебник 24: Windows Hooks (англ.). Iczelion's Win32 Assembly Homepage (август 2002). Дата обращения: 31 августа 2008. Архивировано 1 августа 2008 года.
  3. Rocky Pulley. Расширение диспетчера задач с помощью DLL-инъекции (англ.). CodeProject (19 мая 2005). Дата обращения: 1 сентября 2008. Архивировано 6 февраля 2009 года.
  4. 1 2 Nasser R. Rowhani. DLL-инъекция и перехват функций: руководство (англ.). CodeProject (23 октября 2003). Дата обращения: 31 августа 2008. Архивировано 15 апреля 2018 года.
  5. 1 2 3 Ivo Ivanov. Реализация перехвата API (англ.). CodeProject (2 декабря 2002). Дата обращения: 31 августа 2008. Архивировано 14 октября 2008 года.
  6. 1 2 3 4 Robert Kuster. Три способа внедрить код в другой процесс (англ.). CodeProject (20 августа 2003). Дата обращения: 31 августа 2008. Архивировано 20 июля 2008 года.
  7. Работа со значением реестра AppInit_DLLs (англ.). Microsoft Help and Support. Microsoft (21 ноября 2006). Дата обращения: 31 августа 2008.
  8. Raymond Chen. AppInit_DLLs стоит переименовать в Deadlock_Or_Crash_Randomly_DLLs (англ.). The Old New Thing. Microsoft (13 декабря 2007). Дата обращения: 31 августа 2008.
  9. dllmain.c (англ.). ReactOS. ReactOS Foundation (8 июля 2008). Дата обращения: 31 августа 2008.
  10. AppInit_DLLs в Windows 7 и Windows Server 2008 R2 (англ.). Дата обращения: 28 февраля 2024.
  11. AppInit DLLs and Secure Boot (англ.). MSDN. Дата обращения: 29 марта 2016.
  12. 'AtomBombing' в Microsoft Windows через внедрение кода (англ.), Dark Reading. Дата обращения: 20 апреля 2017.
  13. 1 2 3 4 Trent Waddington. InjectDLL (англ.). Дата обращения: 31 августа 2008. Архивировано 30 декабря 2019 года.
  14. DLL Injection (англ.). DreamInCode.net. MediaGroup1 (4 мая 2006). Дата обращения: 31 августа 2008. Архивировано 2 сентября 2008 года.
  15. Greg Jenkins. DLL Injection Framework (англ.). Ring3 Circus. WordPress (ноябрь 2007). Дата обращения: 31 августа 2008. Архивировано 28 июня 2020 года.
  16. Drew Benton. Более полное решение DLL-инъекции с использованием CreateRemoteThread (англ.). CodeProject (17 августа 2007). Дата обращения: 1 сентября 2008.
  17. CreateProcess (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  18. PROCESS_INFORMATION (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  19. GetWindowThreadProcessId Function (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  20. EnumProcesses (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  21. GetModuleBaseName (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  22. VirtualAllocEx (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  23. WriteProcessMemory (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  24. Обход защиты Outpost через продвинутую DLL-инъекцию с кражею handle (англ.). Matousec (1 декабря 2006). Дата обращения: 31 августа 2008. Архивировано 6 февраля 2009 года.
  25. CreateRemoteThread (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  26. LoadLibrary (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  27. 1 2 DllMain (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  28. SetWindowsHookEx Function (англ.). Platform SDK for Windows XP SP2. Microsoft. Дата обращения: 31 августа 2008.
  29. AppInit_DLLs Registry Value and Windows 95 (англ.). Microsoft Help and Support. Microsoft (1 марта 2005). Дата обращения: 31 августа 2008.
  30. DLL-инъекция через SetWindowsHookEx() (англ.). Game Reversal (3 апреля 2008). Дата обращения: 1 сентября 2008. Архивировано 4 апреля 2016 года.
  31. SetThreadContext DLL Injection (англ.) (16 января 2007). Дата обращения: 1 сентября 2008. Архивировано 23 августа 2011 года.
  32. Ben Botto. DLL Injector (англ.) (6 сентября 2008). Дата обращения: 1 сентября 2008. Архивировано 7 февраля 2009 года.
  33. Небезопасная загрузка библиотек может позволить удалённое выполнение кода (англ.). Microsoft (10 июня 2011). Дата обращения: 20 апреля 2016.
  34. Безопасная загрузка библиотек для предотвращения атак через DLL preloading (англ.). Microsoft (10 июня 2011). Дата обращения: 8 августа 2012.
  35. Microsoft Security Advisory: небезопасная загрузка библиотек может позволить удалённое выполнение кода (англ.). Microsoft (10 июня 2011). Дата обращения: 20 апреля 2016.
  36. Nicolas Falliere. Заражение проектов Step 7 через Stuxnet (англ.). Symantec (26 сентября 2010).
  37. Team, Microsoft Defender Security Research Обнаружение рефлективной загрузки DLL с помощью Windows Defender ATP (англ.). Microsoft Security Blog (13 ноября 2017). Дата обращения: 26 марта 2025.
  38. Linus Torvalds. ld.so/ld-linux.so — динамический компоновщик/загрузчик (англ.). UNIX man pages (14 марта 1998). Дата обращения: 31 августа 2008. Архивировано 6 февраля 2009 года.
  39. Опции генерации кода (англ.). Using the GNU Compiler Collection (GCC). Free Software Foundation. — «-fpic Генерирует позиционно-независимый код (PIC) для использования в разделяемых библиотеках.» Дата обращения: 31 августа 2008.
  40. Опции линковки (англ.). Using the GNU Compiler Collection (GCC). Free Software Foundation. — «-shared Формирует разделяемый объект для последующего связывания в исполняемый файл.» Дата обращения: 31 августа 2008.
  41. Трюк с LD_PRELOAD (англ.). Peter Goldsborough. Дата обращения: 17 мая 2017.
  42. Gregory Shpitalnik. Внедрение кода в работающее Linux-приложение (англ.). Code Project (12 февраля 2009). Дата обращения: 18 ноября 2010. Архивировано 12 июня 2010 года.