В компьютерном программировании внедрение DLL — это метод, используемый для запуска кода в адресном пространстве другого процесса путем принудительной загрузки им динамически подключаемой библиотеки . [1] Внедрение DLL часто используется внешними программами для влияния на поведение другой программы таким образом, который ее авторы не предполагали или не намеревались сделать. [1] [2] [3] Например, внедренный код может перехватывать вызовы системных функций, [4] [5] или считывать содержимое текстовых полей пароля , что невозможно сделать обычным способом. [6] Программа, используемая для внедрения произвольного кода в произвольные процессы, называется инжектором DLL .
В Microsoft Windows существует несколько способов заставить процесс загрузить и выполнить код в DLL, который не был задуман авторами:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
, загружаются в каждый процесс, который загружает User32.dll во время первоначального вызова этой DLL. [7] [8] [9] Начиная с Windows Vista , библиотеки AppInit_DLL отключены по умолчанию. [10] Начиная с Windows 7, инфраструктура AppInit_DLL поддерживает подпись кода . Начиная с Windows 8 , вся функциональность AppInit_DLL отключается при включении безопасной загрузки , независимо от подписи кода или настроек реестра. [11]HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs
загружаются в каждый процесс, который вызывает функции Win32 APICreateProcess
, CreateProcessAsUser
, CreateProcessWithLogonW
, CreateProcessWithTokenW
и WinExec
. Это правильный способ использования легального внедрения DLL в текущей версии Windows - Windows 10. Библиотека DLL должна быть подписана действительным сертификатом.CreateRemoteThread
или методы внедрения кода , такие как AtomBombing, [12] могут использоваться для внедрения DLL в программу после ее запуска. [5] [6] [13] [14] [15] [16]32.dll
, можно загрузить библиотеку с именем 32.dll
[ необходима цитата ] . В прошлом эта техника демонстрировала свою эффективность против метода защиты процессов от внедрения DLL. [24]LoadLibrary
, а аргумент — адресу строки, только что загруженной в целевой процесс. [13] [26]LoadLibrary
, можно записать код для выполнения в цель и запустить поток в этом коде. [6]DLL_THREAD_ATTACH
уведомлений, отправляемых каждому загруженному модулю при запуске потока. [27]SetWindowsHookEx
. [2] [5] [6] [28] [29] [30]SuspendThread
или NtSuspendThread
для приостановки всех потоков, а затем используйте функцию SetThreadContext
или NtSetContextThread
для изменения контекста существующего потока в приложении для выполнения внедренного кода, который в свою очередь может загрузить DLL. [4] [31] [32]LoadLibrary
или LoadLibraryEx
без указания полного пути к загружаемой DLL. [33] [34] [35]В операционных системах типа Unix с динамическим компоновщиком на основе ld.so (в BSD ) и ld-linux.so (в Linux ) произвольные библиотеки могут быть связаны с новым процессом, если указать путь к библиотеке в переменной среды LD_PRELOAD , которая может быть установлена глобально или индивидуально для одного процесса. [37]
Например, в системе Linux эта команда запускает команду «prog» с общей библиотекой из файла «test.so», связанной с ней во время запуска:
LD_PRELOAD = "./test.so" прог
Такая библиотека может быть создана таким же образом, как и другие общие объекты . С GCC это включает компиляцию исходного файла, содержащего новые глобальные переменные для связывания, с опцией -fpic или -fPIC , [38] и связывание с опцией -shared . [39] Библиотека имеет доступ к внешним символам, объявленным в программе, как и любая другая библиотека.
В macOS следующая команда запускает команду «prog» с общей библиотекой из файла «test.dylib», связанной с ней во время запуска: [40]
DYLD_INSERT_LIBRARIES = "./test.dylib" DYLD_FORCE_FLAT_NAMESPACE = 1 программа
Также возможно использовать методы отладчика в Unix-подобных системах. [41]
Поскольку нет LoadLibrary()
вызова для загрузки DLL в сторонний процесс, вам придется скопировать локально загруженную DLL в удаленно выделенную память. Следующий прокомментированный код показывает, как это сделать.
#include <Windows.h> #include <TlHelp32.h> #include <iostream> #include <memory> #include <system_error> #include <charconv> #include <vector> #include <cassert> #если определено(_MSC_VER) #pragma предупреждение(отключить: 6387) #endifс использованием пространства имен std ; using XHANDLE = unique_ptr < void , decltype ([]( void * h ) { h && h != INVALID_HANDLE_VALUE && CloseHandle ( ( HANDLE ) h ); }) > ; using XHMODULE = unique_ptr < remove_reference_t < decltype ( * HMODULE ()) > , decltype ([]( HMODULE hm ) { hm && FreeLibrary ( hm ); }) > ; MODULEENTRY32W getModuleDescription ( HMODULE hmModule ); size_t maxReadableRange ( void * pRegion ); string getAbsolutePathA ( char const * fileName , char const * err ); DWORD dumbParseDWORD ( wchar_t const * str ); wstring getAbsolutePath ( wchar_t const * makeAbsolute , char const * errStr ); [[ noreturn ]] void throwSysErr ( char const * str ); constexpr wchar_t const * LOADER_DLL_NAME = L "loaderDll.dll" ; constexpr char const * LOADER_THREAD_PROC = "loadLibraryThread" ; int wmain ( int argc , wchar_t ** argv ) { try { if ( argc < 3 ) return EXIT_FAILURE ; wchar_t const * processId = argv [ 1 ], * remoteLoadedDll = argv [ 2 ], * initData = argc >= 4 ? argv [ 3 ] : L "" ; DWORD dwProcessId = dumbParseDWORD ( processId ); XHANDLE xhProcess ( OpenProcess ( PROCESS_ALL_ACCESS , FALSE , dwProcessId ) ); if ( ! xhProcess . get () ) throwSysErr ( "невозможно открыть удаленный процесс с неограниченным доступом" ); XHMODULE xhmLocalLoader ; MODULEENTRY32W meLocalLoader ; for ( ; ; ) { xhmLocalLoader . reset ( LoadLibraryW ( LOADER_DLL_NAME ) ); if ( ! xhmLocalLoader . get () ) throwSysErr ( "невозможно локально загрузить DLL загрузчика" ); // получить начальный адрес и размер модуля meLocalLoader = getModuleDescription ( ( HMODULE ) xhmLocalLoader . get () ); // попытаться выделить область памяти во внешнем процессе с тем же размером, который занимает DLL в нашем процессе if ( VirtualAllocEx ( xhProcess . get (), meLocalLoader . modBaseAddr , meLocalLoader . modBaseSize , MEM_RESERVE | MEM_COMMIT , PAGE_EXECUTE_READWRITE ) ) break ; // выделение не удалось, освободить библиотеку xhmLocalLoader . reset ( nullptr ); // попытка зарезервировать диапазон адресов, который библиотека занимала ранее, чтобы предотвратить // повторное использование этого диапазона адресов при следующем вызове LoadLibrary(). if ( ! VirtualAlloc ( meLocalLoader . modBaseAddr , meLocalLoader . modBaseSize , MEM_RESERVE , PAGE_NOACCESS ) ) throwSysErr ( "невозможно зарезервировать диапазон адресов ранее сопоставленной DLL" ); } LPTHREAD_START_ROUTINE loaderThreadProc = ( LPTHREAD_START_ROUTINE ) GetProcAddress ( ( HMODULE ) xhmLocalLoader . get (), :: LOADER_THREAD_PROC ); if ( ! loaderThreadProc ) throwSysErr ( "невозможно получить точку входа в процедуру" ); // копируем все читаемое содержимое DLL в целевой процесс if ( SIZE_T скопировано ; ! WriteProcessMemory ( xhProcess . get (), meLocalLoader . modBaseAddr , meLocalLoader . modBaseAddr , meLocalLoader . modBaseSize , & скопировано ) && GetLastError () != ERROR_PARTIAL_COPY ) throwSysErr ( "невозможно скопировать DLL загрузчика в удаленный процесс" ); // создаем две объединенные строки C, содержащие DLL для загрузки, а также параметр, // переданный удаленно загруженной DLL wstring data ( getAbsolutePath ( remoteLoadedDll , "невозможно получить абсолютный путь к DLL для удаленной загрузки" ) ); data += L '\0' ; data += initData ; data += L '\0' ; size_t dataSize = data . размер () * размер ( wchar_t ); авто initStrErr = []() { throwSysErr ( "не удалось скопировать данные инициализации в DLL-загрузчик" ); }; void * remoteData ; // удаленно выделяем память, достаточную для хранения как минимум обеих наших строк if ( ! ( remoteData = VirtualAllocEx ( xhProcess . get (), nullptr , dataSize , MEM_RESERVE | MEM_COMMIT , PAGE_READWRITE )) initStrErr ( ); // записываем обе наши строки в удаленную память if ( SIZE_T скопировано ; ! WriteProcessMemory ( xhProcess . get (), remoteData , data . data (), dataSize , & скопировано ) || скопировано != dataSize ) initStrErr (); // создаем поток удаленного загрузчика DLL; указанная точка входа имеет тот же адрес в нашем процессе , что и удаленный адрес // передаем этому потоку адрес наших обеих удаленно скопированных строк XHANDLE xhRemoteInitThread ( CreateRemoteThread ( xhProcess.get (), nullptr , 0 , loaderThreadProc , remoteData , 0 , nullptr ) ); if ( ! xhRemoteInitThread.get ( ) ) throwSysErr ( "failed to create remote initializaton thread" ); // ждем завершения нашего удаленного потока загрузчика // он должен это сделать очень скоро , поскольку его единственная задача - скопировать строки для удаленно загруженной DLL и загрузить эту DLL if ( WaitForSingleObject ( xhRemoteInitThread.get ( ), INFINITE ) == WAIT_FAILED ) throwSysErr ( "can't wait for remote initialization thread" ); DWORD dwInitThreadExitCode ; если ( ! GetExitCodeThread ( xhRemoteInitThread . get (), & dwInitThreadExitCode ) ) throwSysErr ( "невозможно получить код успеха потока инициализации" ); // проверка кода выхода удаленного загрузчика, он должен быть NO_ERROR (0) if ( dwInitThreadExitCode != NO_ERROR ) throw system_error ( ( int ) dwInitThreadExitCode , system_category (), "Ошибка LoadLibrary() в dll удаленного загрузчика" ); } catch ( exception const & se ) { cout << se . what () << endl ; } } MODULEENTRY32W getModuleDescription ( HMODULE hmModule ) { // возвращает абсолютный путь для заданного дескриптора модуля auto getModulePath = []( HMODULE hm , char const * err ) -> wstring { wchar_t modulePath [ MAX_PATH ]; if ( DWORD dwRet = GetModuleFileNameW ( hm , modulePath , MAX_PATH ); ! dwRet || dwRet >= MAX_PATH ) throwSysErr ( err ); return modulePath ; }; // путь к модулю локальной DLL wstring moduleAbsolute ( getModulePath ( hmModule , "невозможно получить абсолютный путь для локальной DLL-загрузчика" ) ); XHANDLE xhToolHelp ( CreateToolhelp32Snapshot ( TH32CS_SNAPMODULE , GetCurrentProcessId () ) ); auto toolHelpErr = []() { throwSysErr ( "невозможно перечислить модули в процессе внедрения" ); }; if ( xhToolHelp . get () == INVALID_HANDLE_VALUE ) toolHelpErr (); MODULEENTRY32W me ; me . dwSize = sizeof me ; if ( ! Module32FirstW ( xhToolHelp . get (), & me ) ) toolHelpErr (); for ( ; ; ) { // имеет ли текущее изображение в снимке тот же путь, что и DLL, которая задана дескриптором модуля // нет необходимости сравнивать без учета регистра, поскольку мы получили оба пути из ядра, так что они должны точно совпадать if ( getModulePath ( me . hModule , "can't get absolute path for toolhelp-enumerated DLL name" ) == moduleAbsolute ) return me ; me.dwSize = sizeof me ; if ( ! Modules32NextW ( xhToolHelp.get ( ) , & me ) ) toolHelpErr ( ) ; } } [[ noreturn ]] void throwSysErr ( char const * str ) { throw system_error ( ( int ) GetLastError (), system_category (), str ); } DWORD dumbParseDWORD ( wchar_t const * str ) { // from_chars идиота, потому что для символов юникода нет from_chars DWORD dwRet = 0 ; while ( * str ) dwRet = dwRet * 10 + ( unsigned char )( * str ++ - L '0' ); return dwRet ; } wstring getAbsolutePath ( wchar_t const * makeAbsolute , char const * errStr ) { // получить абсолютный путь для заданного относительного пути wstring path ( MAX_PATH , L '\0' ); DWORD dwLength ; if ( ! ( dwLength = GetFullPathNameW ( makeAbsolute , MAX_PATH , path . data (), nullptr )) ) throwSysErr ( errStr ); // если deRet == MAX_PATH , мы можем пропустить нулевой символ завершения, рассматривать это как ошибку, иначе if ( dwLength >= MAX_PATH ) throw invalid_argument ( errStr ); path . resize ( dwLength ); return path ; }
Основная проблема, решенная здесь, заключается в том, что локально загруженная DLL, скопированная в удаленный процесс, должна занимать те же адреса, что и в процессе внедрения. Приведенный выше код делает это, выделяя память для того же диапазона адресов, который был занят ранее в процессе внедрения. Если это не удается, DLL локально освобождается, прежний диапазон адресов помечается как зарезервированный, и вызов LoadLibrary()
повторяется. Резервируя прежний диапазон адресов, код предотвращает LoadLibrary()
назначение следующего диапазона адресов, который использовался ранее.
Главным недостатком такого подхода является то, что DLL, скопированная во внешний процесс, заключается в том, что нет никаких других зависимостей библиотеки DLL этой DLL, загруженной во внешнее адресное пространство, или указателей, например вызовов функций, на DLL, загруженные внешним процессом, которые корректируются в соответствии с зависимостями скопированной DLL. К счастью, DLL обычно имеют предпочтительные адреса загрузки, которые учитываются загрузчиком ядра . Некоторые DLL, например, kernel32.dll
надежно загружаются в начале, когда адресное пространство процесса занято исполняемым образом и его зависимыми DLL. Они обычно имеют надежные и неконфликтующие адреса. Таким образом, скопированная DLL может использовать любые kernel32.dll
вызовы, например, для загрузки другой DLL со всеми преимуществами локально загруженной DLL, т. е. имея все относительные зависимости библиотек. Путь к этой DLL копируется во внешнее адресное пространство и задается как параметр void для функции потока. Вышеуказанная реализация также позволяет иметь дополнительные параметры, которые передаются удаленно скопированной DLL после строки с удаленно загруженной DLL, чтобы передать ее в эту DLL.
Следующий код является источником удаленно скопированной DLL-библиотеки загрузчика, которая выполняет только kernel32.dll
вызовы:
#include <Windows.h> #include <атомарный> с использованием пространства имен std ; BOOL APIENTRY DllMain ( HMODULE hModule , DWORD ul_reason_for_call , LPVOID lpReserved ) { return TRUE ; } DWORD WINAPI loadLibraryThread ( LPVOID lpvThreadParam ); // MSVC / clang-cl искажение #if defined(_M_IX86) #pragma comment(linker, "/export:loadLibraryThread=?loadLibraryThread@@YGKPAX@Z") #elif defined(_M_X64) #pragma comment(linker, "/export:loadLibraryThread=?loadLibraryThread@@YAKPEAX@Z") #else #error неподдерживаемая платформа #endifDWORD WINAPI loadLibraryThread ( LPVOID lpvThreadParam ) { // используем атомарные типы, чтобы не дать "оптимизатору" заменить мой код на // вызовы библиотек wsclen или memcpy на внешние адреса, которые на самом деле недопустимы // с этой скопированной DLL // игнорируем любые барьеры атомарной загрузки, так как это не должно быть быстро atomic_wchar_t const // путь к библиотеке для загрузки изнутри * libPath = ( atomic_wchar_t * ) lpvThreadParam , // указатель на параметры, заданные для этой библиотеки * data = libPath ; // передаем данные в фактические параметры while ( * data ++ ); HANDLE hOutboundEvent ; // создаем именованное событие для уведомления удаленной DLL о том, что данные уже скопированы // необходимо, поскольку выполнение удаленной DLL начинается сразу после LoadLibrary()S if ( ! ( hOutboundEvent = CreateEventA ( nullptr , FALSE , FALSE , "nasty hackers" )) ) return GetLastError (); // размер параметров, переданных DLL size_t dataSize = 0 ; while ( data [ dataSize ++ ] ); if ( dataSize >= MAX_PATH ) return ERROR_INVALID_PARAMETER ; // очищаем LoadLibrary() со всеми зависимостями DLL HMODULE hm = LoadLibraryW ( ( wchar_t * ) libPath ); if ( ! hm ) return GetLastError (); // получить адрес экспорта параметров из загруженной DLL wchar_t volatile ( & initData )[ MAX_PATH ] = * ( wchar_t ( * )[ MAX_PATH ]) GetProcAddress ( hm , "initData" ); // загруженная DLL не предоставляет такой экспорт, т. е. она не полагается на параметры ? if ( ! initData ) return NO_ERROR ; // копируем параметры в DLL for ( size_t i = 0 ; i != dataSize ; initData [ i ] = data [ i ], ++ i ); // уведомляем о доступности параметров if ( ! SetEvent ( hOutboundEvent ) ) return GetLastError (); return NO_ERROR ; }
Последний код показывает пример DLL, загруженной загрузчиком DLL, который выводит параметры в файл.
#include <Windows.h> #include <fstream> #include <atomic> с использованием пространства имен std ; #if defined(_MSC_VER) #pragma warning(disable: 6387) // возвращаемый дескриптор может быть нулевым #endif#if defined(_M_IX86) #pragma comment(linker, "/export:DllMain=_DllMain@12") #elif defined(_M_X64) #pragma comment(linker, "/export:DllMain=_DllMain@12") #else #error неподдерживаемая платформа #endifс использованием пространства имен std ; DWORD WINAPI myThread ( LPVOID lpvThreadParam ); BOOL APIENTRY DllMain ( HMODULE hModule , DWORD dwReason , LPVOID lpReserved ) { switch ( dwReason ) { case DLL_PROCESS_ATTACH : // создать поток, так как нет экспорта, вызванного из загрузчика DLL CreateThread ( nullptr , 0 , myThread , nullptr , 0 , nullptr ); default : break ; } return TRUE ; } extern "C" __declspec ( dllexport ) wchar_t initData [ MAX_PATH ] = { 0 }; DWORD WINAPI myThread ( LPVOID lpvThreadParam ) { // ждем заполнения initData загрузчиком DLL // пропустите это, если вы не полагаетесь на initData // так как именованное событие "nasty hackers" было создано нашими собственными DLL // LoadLibrary() мы просто подключаемся к именованному событию, но не создаем его if ( WaitForSingleObject ( CreateEventA ( nullptr , FALSE , FALSE , "nasty hackers" ), INFINITE ) != WAIT_OBJECT_0 ) return 0 ; // записываем параметры в файл для проверки функции // следующий код не работает, если DLL не связана статически по неизвестным причинам wofstream wofs ; wofs . open ( "c: \\ Users \\ xxx \\ test.txt" , ofstream :: out | ofstream :: trunc ); wofs << initData << endl ; return 0 ; }
Важным фактом является то, что нет экспортов, вызываемых из DLL-загрузчика, вместо этого вся инициализация выполняется из DllMain
. Единственный экспорт — это initData
, который получает параметры, заданные процессом внедрения через DLL-загрузчик. И нужно знать, что поток, созданный из функции DllMain, не планируется до тех пор, пока его DLL_THREAD_ATTACH
функция не завершится успешно. Поэтому может не быть никакой синхронизации изнутри DllMain
с созданным потоком.
{{cite web}}
: CS1 maint: неподходящий URL ( ссылка )позиционно-независимого кода (PIC), подходящего для использования в общей библиотеке, если поддерживается целевой машиной.
sqq.
-fpic
общий объект, который затем можно связать с другими объектами для формирования исполняемого файла.
sqq.
-shared