הזרקת DLL

הזרקת DLL (באנגלית: DLL injection) היא טכניקה המשמשת להפעלת קוד בתוך מרחב הכתובות (אנ') של תהליך אחר על ידי אילוץ התהליך האחר לטעון ספריית קישור דינמי (DLL). השימוש בהזרקת DLL נעשה לעיתים קרובות במטרה להשפיע על התנהגותה של תוכנה כלשהי באופן שמחבריה לא חזו או התכוונו. לדוגמה, הקוד המוזרק יכול לבצע יירוט קריאה לשגרה או הודעה המועברת בין תוכניות (Hooking). ישנן מספר דרכים לבצע הזרקת קוד במערכת ההפעלה Microsoft Windows: קובצי DLL המפורטים בערך ה-Registry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs נטענים בכל תהליך הטוען את User32.dll. קובצי DLL הרשומים תחת מפתח הרישום HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs נטענים בכל תהליך הקורא לפונקציות ה-Win32 API הבאות: CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, CreateProcessWithTokenW ו-WinExex. פונקציות המבצעות פעולות על תהליכים כמו CreateRemoteThread מאפשרות להזריק DLL לתוכנה בזמן ריצה. הזרקת DLL מסוג זה נעשית במספר שלבים: פתיחת Handle (אנ') לתהליך מרוחק. ניתן לעשות זאת על ידי יצירת תהליך חדש או על ידי שימוש בתוצרים של תהליך קיים: למשל, חלון בעל שם ידוע שנוצר במסגרת התהליך או השגת רשימת של התהליכים הרצים בזמן מסוים ואיתור התהליך הרצוי על פי השם של קובץ ההרצה שלו. כדי לשמור את המחרוזת של התהליך שמתארת את הנתיב של ה-DLL בזיכרון של התהליך המרוחק, יש להקצות זיכרון בתהליך המרוחק ולהעתיק אליו את המחרוזת עם הנתיב של ה-DLL (שיועבר לפונקציה LoadLibrary). יצירת ת'רד בתהליך המרוחק. כאשר פותחים ת'רד חדש, מעבירים לו כתובת אשר ממנה יש להתחיל את הריצה. על מנת לבצע הזרקת DLL, יש להעביר את הכתובת של הפונקציה LoadLibrary, יחד עם הכתובת של הנתיב של ה-DLL המוזרק בתוך התהליך המרוחק, אשר נשמרה בזיכרון של התהליך המרוחק בשלב השני. על מנת לבצע הזרקת DLL, יש לדעת מה הכתובת של הפונקציה LoadLibrary, שממנה יש להתחיל את הריצה. כמו כן, יש לשמור בזיכרון של התהליך המרוחק מחרוזת שמתארת את הנתיב של ה-DLL אותו רוצים להזריק. לכן יש לבדוק מה הכתובת LoadLibrary בתהליך ממנו מבוצעת ההזרקה (הכתובת תהיה זהה לכתובת של LoadLibrary בתהליך המרוחק). כדי לשמור את המחרוזת של התהליך שמתארת את הנתיב של ה-DLL בזיכרון של התהליך המרוחק, יש להקצות זיכרון בתהליך המרוחק ולהעתיק אליו את המחרוזת עם הנתיב של ה-DLL (שיועבר לפונקציה LoadLibrary). #include <Windows.h> #include <TlHelp32.h> #include <iostream> #include <memory> #include <system_error> #include <charconv> #include <vector> #include <cassert> #if defined(_MSC_VER) #pragma warning(disable: 6387) #endif using namespace 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( "can't open remote process with unlimited access" ); XHMODULE xhmLocalLoader; MODULEENTRY32W meLocalLoader; for( ; ; ) { xhmLocalLoader.reset( LoadLibraryW( LOADER_DLL_NAME ) ); if( !xhmLocalLoader.get() ) throwSysErr( "can't locally load loader DLL" ); // get module starting address and size meLocalLoader = getModuleDescription( (HMODULE)xhmLocalLoader.get() ); // try to allocate memory range in the foreign process with the same size the DLL in our process occupies if( VirtualAllocEx( xhProcess.get(), meLocalLoader.modBaseAddr, meLocalLoader.modBaseSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE ) ) break; // allocation failed, free library xhmLocalLoader.reset( nullptr ); // try to reserve address range which the library occupied before to prevent // recycling of that address range with the next LoadLibrary() call. if( !VirtualAlloc( meLocalLoader.modBaseAddr, meLocalLoader.modBaseSize, MEM_RESERVE, PAGE_NOACCESS ) ) throwSysErr( "can't reserve address range of previously mapped DLL" ); } LPTHREAD_START_ROUTINE loaderThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress( (HMODULE)xhmLocalLoader.get(), ::LOADER_THREAD_PROC ); if( !loaderThreadProc ) throwSysErr( "can't get procedure entry point" ); // coppy all readable DLL-contents to the destination process if( SIZE_T copied; !WriteProcessMemory( xhProcess.get(), meLocalLoader.modBaseAddr, meLocalLoader.modBaseAddr, meLocalLoader.modBaseSize, &copied ) && GetLastError() != ERROR_PARTIAL_COPY ) throwSysErr( "can't copy loader DLL to remote process" ); // create two concatenated C strings that contain the DLL to load as well as the parameter // given to the remotely loaded DLL wstring data( getAbsolutePath( remoteLoadedDll, "can't get absolute path to DLL to be remotely loaded" ) ); data += L'\0'; data += initData; data += L'\0'; size_t dataSize = data.size() * sizeof(wchar_t); auto initStrErr = []() { throwSysErr( "failed to copy initialization data to loader DLL" ); }; void *remoteData; // remotely allocate memory large enough to hold at least our both strings if( !(remoteData = VirtualAllocEx( xhProcess.get(), nullptr, dataSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) ) initStrErr(); // write our both strings to remote memory if( SIZE_T copied; !WriteProcessMemory( xhProcess.get(), remoteData, data.data(), dataSize, &copied ) || copied != dataSize ) initStrErr(); // create a remote DLL loader thread; the given entry point has the same address in our process as well as the remote address // tive this thread the address of our both remotely copied strings XHANDLE xhRemoteInitThread( CreateRemoteThread( xhProcess.get(), nullptr, 0, loaderThreadProc, remoteData, 0, nullptr ) ); if( !xhRemoteInitThread.get() ) throwSysErr( "failed to create remote initializaton thread" ); // wait on our remote loader thread to finish // it should that very soon as its only task is to copy the strings for the remotely loaded DLL and load this DLL itself if( WaitForSingleObject( xhRemoteInitThread.get(), INFINITE ) == WAIT_FAILED ) throwSysErr( "can't wait for remote initialization thread" ); DWORD dwInitThreadExitCode; if( !GetExitCodeThread( xhRemoteInitThread.get(), &dwInitThreadExitCode ) ) throwSysErr( "can't get initialization thread's success code" ); // check for remote loader's exit-code, it should be NO_ERROR (0) if( dwInitThreadExitCode != NO_ERROR ) throw system_error( (int)dwInitThreadExitCode, system_category(), "LoadLibary() error in remote loader dll" ); } catch( exception const &se ) { cout << se.what() << endl; } } MODULEENTRY32W getModuleDescription( HMODULE hmModule ) { // returns the absolute path to for a given module handle 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; }; // local DLL's module path wstring moduleAbsolute( getModulePath( hmModule, "can't get absolute path for local loader DLL" ) ); XHANDLE xhToolHelp( CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, GetCurrentProcessId() ) ); auto toolHelpErr = []() { throwSysErr( "can't list modules in injecting process" ); }; if( xhToolHelp.get() == INVALID_HANDLE_VALUE ) toolHelpErr(); MODULEENTRY32W me; me.dwSize = sizeof me; if( !Module32FirstW( xhToolHelp.get(), &me ) ) toolHelpErr(); for( ; ; ) { // has the current image in the snapshot the same path like the DLL which is given by the module handle // no need to compare case insensitive because we got both paths from the kernel so that they should exactly match if( getModulePath( me.hModule, "can't get absolute path for toolhelp-enumerated DLL name" ) == moduleAbsolute ) return me; me.dwSize = sizeof me; if( !Module32NextW( xhToolHelp.get(), &me ) ) toolHelpErr(); } } [[noreturn]] void throwSysErr( char const *str ) { throw system_error( (int)GetLastError(), system_category(), str ); } DWORD dumbParseDWORD( wchar_t const *str ) { // idiot's from_chars because there's no from_chars for unicode characters DWORD dwRet = 0; while( *str ) dwRet = dwRet * 10 + (unsigned char)(*str++ - L'0'); return dwRet; } wstring getAbsolutePath( wchar_t const *makeAbsolute, char const *errStr ) { // get absolute path of a given relative path wstring path( MAX_PATH, L'\0' ); DWORD dwLength; if( !(dwLength = GetFullPathNameW( makeAbsolute, MAX_PATH, path.data(), nullptr )) ) throwSysErr( errStr ); // if deRet == MAX_PATH we might miss a zero-termination character, treat this as an error else if( dwLength >= MAX_PATH ) throw invalid_argument( errStr ); path.resize( dwLength ); return path; } אורי חדד, המדריך למזריק (DLL) המתחיל, Digital Whisper גיליון 118, עמ' 1–31 אוראל ארד, Code Injection, Digital Whisper גיליון 13, עמ' 1–16 "CreateProcess". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה) "GetWindowThreadProcessId Function". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה) "EnumProcesses". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה) "GetModuleBaseName". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה) "VirtualAllocEx". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה) "WriteProcessMemory". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה) "CreateRemoteThread". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה) "LoadLibrary". Platform SDK for Windows XP SP2. Microsoft. נבדק ב-31 באוגוסט 2008. {{cite web}}: (עזרה)