Tutorials - DLL Injection: Injizieren von Dlls in eine oder mehrere Zielanwendungen

Sprachenübersicht/Programmierung/C / C++/ C#/Security

DLL Injection: Injizieren von Dlls in eine oder mehrere Zielanwendungen

Diese Seite wurde 12530 mal aufgerufen.

Dieser Artikel wurde in einem Wikiweb System geschrieben, das heißt, Sie können die Artikel jederzeit editieren, wenn Sie einen Fehler gefunden haben, oder etwas hinzufügen wollen.

Editieren Versionen Linkpartnerschaft Bottom Printversion

Keywords: DLL Injection, C

Inhaltsverzeichnis



Einleitung Top



Unter dem injizieren von Dlls versteht man das einschleusen einer meist eigenen Dll in einen Zielprozess, dabei wird sich zu nutze gemacht das Dlls wie normale Executables einen eigenen Einstiegspunkt besitzen der aufgerufen wird wenn der Zielprozess die Dll lädt/entlädt oder einen Thread erstellt/entfernt.

Dies ermöglicht es uns zum Beispiel bestimmte Verhaltensmuster des Zielprozesses zu verändern oder zu überwachen.

Programme wie Fraps, GameCam oder Mumble arbeiten zum Beispiel so um die FPS anzuzeigemn, Videos aufuzunehmen oder zusätzliche Informationen anzuzeigen.

Methode 1 Top



Dem Autor sind aktuell 4 Methoden zur injektion bekannt von denen eine Möglichkeit über die Registry erreicht werden kann und deshalb hier keine Erwähnung finden wird.

Die erste Methode besteht darin die Dll in einen bereits laufenden Prozess einzuschleusen, dazu wird zuerst die ProzessId des Zielprozesses ermittelt und dann ein Thread im Zielprozess veranlasst zu starten, welcher die Dll lädt. Das ganze hat den Nachteil, dass das Zielprogramm dabei schon gestartet und auch initialisiert wurde, was bedeutet das bestimmte Aktonen im zielprogramm evtl. bereits schon abgelaufen sind auf die man gerne durch die Dll zugreifen möchte um diese zu beeinflussen.

Um die Dll wieder aus dem Zielprozess zu entfernen nutzt man die gleiche Möglichkeit wie zum einschleusen der Dll nur wird statt LoadLibrary FreeLibrary benutzt.

Hier die beiden nötigen Funktionen:

Code:


HMODULE k_Injector::TryInjectDll(DWORD adw_ProcessId, const std::wstring& as_DllFile)
{
    //Find the address of the LoadLibrary api, luckily for us, it is loaded in the same address for every process
    HMODULE hLocKernel32 = GetModuleHandleW(L"KERNEL32");
    FARPROC hLocLoadLibrary = GetProcAddress(hLocKernel32, "LoadLibraryW");
    
    //Adjust token privileges to open system processes
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;
    if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
        tkp.PrivilegeCount = 1;
        tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        AdjustTokenPrivileges(hToken, 0, &tkp, sizeof(tkp), NULL, NULL);
        CloseHandle(hToken);
    }

    //Open the process with all access
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, adw_ProcessId);
    if (hProc == NULL)
        return NULL;

    //Allocate memory to hold the path to the Dll File in the process's memory
    LPVOID hRemoteMem = VirtualAllocEx(hProc, NULL, as_DllFile.size()*sizeof(wchar_t), MEM_COMMIT, PAGE_READWRITE);

    //Write the path to the Dll File in the location just created
    DWORD numBytesWritten;
    WriteProcessMemory(hProc, hRemoteMem, as_DllFile.c_str(), as_DllFile.size()*sizeof(wchar_t), &numBytesWritten);

    //Create a remote thread that starts begins at the LoadLibrary function and is passed are memory pointer
    HANDLE hRemoteThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)hLocLoadLibrary, hRemoteMem, 0, NULL);

    //Wait for the thread to finish
    ::WaitForSingleObject( hRemoteThread, INFINITE );
    DWORD  hLibModule = 0;
    ::GetExitCodeThread( hRemoteThread, &hLibModule );

    //Free the memory created on the other process
    ::VirtualFreeEx(hProc, hRemoteMem, as_DllFile.size()*sizeof(wchar_t), MEM_RELEASE);

    //Release the handle to the other process
    ::CloseHandle(hProc);

    return (HMODULE)hLibModule;
}


bool k_Injector::TryUnInjectDll(DWORD adw_ProcessId, HMODULE ah_ModuleHandle)
{
    //Open the process with all access
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, adw_ProcessId);
    if (hProc == NULL)
        return false;

    bool lb_ReturnValue = false;

    HMODULE hLocKernel32 = GetModuleHandleW(L"KERNEL32");
    FARPROC hLocLoadLibrary = GetProcAddress(hLocKernel32, "FreeLibrary");

    if(ah_ModuleHandle != NULL)
    {
        HANDLE hRemoteThread = ::CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)hLocLoadLibrary, (void*)ah_ModuleHandle, 0, NULL );

        if( hRemoteThread != NULL )
        {
            DWORD ldw_ReturnCode;
            ::WaitForSingleObject( hRemoteThread, INFINITE );
            ::GetExitCodeThread( hRemoteThread, &ldw_ReturnCode );
            ::CloseHandle( hRemoteThread );

            lb_ReturnValue = ldw_ReturnCode != 0;
        }
    }

    ::CloseHandle(hProc);

    return lb_ReturnValue;
}



Hier wird nachdem die nötigen Funktionspointer belegt wurden die Injektoranwendung selbst verändert und zwar wird dem Prozess ein zusätzlicher Previlegienstatus zugewiesen. SE_DEBUG_NAME dient dazu das unsere Anwendung jedes prozesshandle anfordern und damit arbeiten kann. Anschliessend wird sich der Prozesshandle vom zielprozess geholt. Damit kann dann ein Thread im Zielprozess erstellt werden, dies nutzen wir aus um die LoadLibrary Funktion mit unserer Dll im Zielprozess aufzurufen. Dadurch wird die Dll im Zielprozess geladen, da es keine Rolle spielt in welchem Thread die Dll geladen wird kann man hier dank der Dll Einstiegsfunktion beginnen seine Aktionen auszuführen.

Mithilfe von GetExitCodeThread können wir anschliessend noch den libraryHandle im Zielprozess herausfinden. Dieses Handle kann man nun in die Uninject Funktion speisen um die Dll wieder zu entladen.

Methode 2 Top



Die 2. Injektionsmöglichkeit ist zugleich die Möglichkeit mit der meisten Kontrolle über den Zielprozess, da bei dieser Methode der Zielprozess selbst gestartet wird, gestoppt wird, die Dll injiziert wird und der prozess wieder gestartet wird. Dadurch hat man mit ziemlicher Sicherheit seine Dll eingeschleust ohne, dass das Zielprogramm bereits vollständig geladen werden konnte. Der Nachteil dieser Methode ist das die Dll nicht ohne weiteres entladen werden kann, solange man aber das ModuleHandle aufbewahrt sollte das mit der Uninject Funktion aus der ersten Methode möglich sein.

Der folgende Quelltext Auszug startet das Programm und wartet auf die Beendigung des selben (nicht sehr aufgeräumt, jeder kann es aber anpassen wenn er möchte):

Code:


BOOL AdjustDacl(HANDLE h, DWORD DesiredAccess)
{
    SID world = { SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, 0 };
    
    EXPLICIT_ACCESS ea =
    {
        DesiredAccess, SET_ACCESS, NO_INHERITANCE,
        {
            0, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_USER,
            reinterpret_cast<LPTSTR>(&world)
        }
    };

    ACL* pdacl = 0;
    DWORD err = SetEntriesInAcl(1, &ea, 0, &pdacl);
    if(err == ERROR_SUCCESS)
    {
        err = SetSecurityInfo(h, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, 0, 0, pdacl, 0);
        LocalFree(pdacl);
        return(err == ERROR_SUCCESS);
    }
    else
        return FALSE;
}

BOOL EnableTokenPrivilege(HANDLE htok, LPCTSTR szPrivilege,    TOKEN_PRIVILEGES& tpOld)
{
    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if(LookupPrivilegeValue(0, szPrivilege, &tp.Privileges[0].Luid))
    {
        DWORD cbOld = sizeof tpOld;
        if (AdjustTokenPrivileges(htok,    FALSE, &tp, cbOld, &tpOld, &cbOld))
            return(ERROR_NOT_ALL_ASSIGNED != GetLastError());
        else return FALSE;
    }
    else
        return FALSE;
}


BOOL RestoreTokenPrivilege(HANDLE htok, const TOKEN_PRIVILEGES&    tpOld)
{
    return(AdjustTokenPrivileges(htok, FALSE, const_cast<TOKEN_PRIVILEGES*>(&tpOld), 0,    0, 0));
}

HANDLE GetProcessHandleWithEnoughRights(DWORD PID, DWORD AccessRights)
{
    HANDLE hProcess = ::OpenProcess(AccessRights, FALSE, PID);
    if(hProcess == NULL)
    {
        HANDLE hpWriteDAC = OpenProcess(WRITE_DAC, FALSE, PID);
        if(hpWriteDAC == NULL)
        {
            HANDLE htok;
            if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &htok))
                return(FALSE);
            
            TOKEN_PRIVILEGES tpOld;
            if (EnableTokenPrivilege(htok, SE_TAKE_OWNERSHIP_NAME, tpOld))
            {
                HANDLE hpWriteOwner = OpenProcess(WRITE_OWNER, FALSE, PID);
                if (hpWriteOwner != NULL)
                {
                    BYTE buf[512];
                    DWORD cb = sizeof buf;
                    if (GetTokenInformation(htok, TokenUser, buf, cb, &cb))
                    {
                        DWORD err = SetSecurityInfo(hpWriteOwner, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, reinterpret_cast<TOKEN_USER*>(buf)->User.Sid, 0, 0, 0);
                        if(err == ERROR_SUCCESS)
                        {
                            if (!DuplicateHandle(GetCurrentProcess(), hpWriteOwner, GetCurrentProcess(), &hpWriteDAC, WRITE_DAC, FALSE, 0) )
                                hpWriteDAC = NULL;
                        }
                    }
                    CloseHandle(hpWriteOwner);
                }
                RestoreTokenPrivilege(htok,    tpOld);
            }
            CloseHandle(htok);
        }
        
        if(hpWriteDAC)
        {
            AdjustDacl(hpWriteDAC, AccessRights);

            if(!DuplicateHandle( GetCurrentProcess(), hpWriteDAC, GetCurrentProcess(), &hProcess, AccessRights, FALSE, 0))
                hProcess = NULL;
            CloseHandle(hpWriteDAC);
        }
    }
    return hProcess;
}

BOOL WINAPI InjectLibW(DWORD dwProcessId, PCWSTR pszLibFile)
{
    BOOL fOk = FALSE;
    HANDLE hProcess = NULL, hThread = NULL;
    PWSTR pszLibFileRemote = NULL;

    hProcess = GetProcessHandleWithEnoughRights(dwProcessId,
        PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD |
        PROCESS_VM_OPERATION | PROCESS_VM_WRITE);

    if(hProcess == NULL)
        return FALSE;
    
    int cch = 1 + lstrlenW(pszLibFile);
    int cb = cch * sizeof(WCHAR);
    
    pszLibFileRemote = (PWSTR)VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
    
    if(pszLibFileRemote != NULL)
    {
        if(WriteProcessMemory(hProcess, pszLibFileRemote, (PVOID)pszLibFile, cb, NULL))
        {
            PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
                GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
            if(pfnThreadRtn != NULL)
            {
                hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);

                if(hThread != NULL)
                {
                    WaitForSingleObject(hThread, INFINITE);
                    fOk = TRUE;
                    CloseHandle(hThread);
                }
            }
        }
        VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);
    }
    CloseHandle(hProcess);
    return fOk;
}

BOOL WINAPI InjectLibA(DWORD dwProcessId, PCSTR    pszLibFile)
{
    PWSTR pszLibFileW = (PWSTR) _alloca((lstrlenA(pszLibFile) + 1) * sizeof(WCHAR));
    wsprintfW(pszLibFileW, L"%S", pszLibFile);
    return(InjectLibW(dwProcessId, pszLibFileW));
}


int CDECL main(int argc, char **argv)
{
    INT nProcessId = -1;

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    std::string AppFile, AppPath;
    AppFile = ...
    AppPath = ...
    if(FALSE==CreateProcess(AppFile.c_str(),NULL,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,AppPath.c_str(),&si,&pi))
    {
        return -1;
    }
    
    
    if (!InjectLibA(pi.dwProcessId,DllPath.c_str())) 
    {
        return -1;
    }

    if(0xFFFFFFFF==ResumeThread(pi.hThread))
    {
        return -1;
    }

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );

    // Close process and thread handles. 
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

    return 0;
}



In dieser Variante wird zu aller erst der Zielprozess selbst gestartet, aber durch den Flag CREATE_SUSPENDED bleibt der Prozess gleich nach dem erstellen pausiert, so das uns genügend Zeit bleibt unsere Dll zu injizieren, was vor allem dann wichtig ist wenn unsere Dll geladen werden muss bevor eine andere Dll die automatisch vom Zielprozess geladen werden würde eingespeist sein muss. Der restliche ablauf ähnelt dem von Injektionsvariante 1.

Methode 3 Top



Die letzte Methode und eine mit der automatischen Fähigkeit das sie in alle laufenden und neu erstellte Prozesse injizieren kann ist die WindowsHook Methode. dabei wird ein globaer WindowsHook erstellt welcher dafür sorgt das die Dll in der sich der Callback für den Hook befindet automatisch in die Prozesse geladen wird. Das hat allerdings den Nachteil das eben alle prozesse dann die Dll geladen haben, daher ist es unbedingt nötig den Hook wieder zu entfernen, dies ist besonders dann nötig wenn man diese Dll ausgiebig testen möchte. Dlls die nicht wieder entladen wurden kann man zB nicht löschen und damit ist das schreiben der Dll nach dem kompilieren, etc. nicht mehr möglich.

Meist kann man das nur beheben indem man entweder die betroffenden Prozesse beendet oder den Rechner neu startet. Die Anwendung dieser Methode ist dabei einfacher als die beiden anderen Methoden.

Zuerst braucht man das Injektionsstartprogramm welches nur die Aufgabe hat zwei Funktion aus der Dll aufzurufen, die nötigen Methoden können dabei wie folgt aussehen:

Code:


void k_Application::StartInjection(const std::wstring& as_Dll)
{
    HMODULE lh_Module = ::LoadLibraryW(as_Dll.c_str());
    if (lh_Module != NULL)
    {
        typeBaseFunc l_InstallHook_ = (typeBaseFunc)::GetProcAddress(lh_Module, "InstallHook");
        if (l_InstallHook_ != NULL)
            l_InstallHook_();
    }
}


void k_Application::EndInjection(const std::wstring& as_Dll)
{
    HMODULE lh_Module = ::LoadLibraryW(as_Dll.c_str());
    if (lh_Module != NULL)
    {
        typeBaseFunc l_UnInstallHook_ = (typeBaseFunc)::GetProcAddress(lh_Module, "RemoveHook");
        if (l_UnInstallHook_ != NULL)
            l_UnInstallHook_();
    }
}



Diese beiden Methoden kann man nach dem Programmstart ausführen (StartInjection) und kurz vor dem beenden (EndInjection).
Kommentar hinzufügen

Die Dll selbst braucht dann nur noch diese 3 Funktionen:

Code:

HHOOK gh_GlobalHook = NULL;

static LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) 
{
    CWPSTRUCT *lr_WndProc_ = reinterpret_cast<CWPSTRUCT *>(lParam);
    if (lr_WndProc_) 
    {
        switch (lr_WndProc_->message) 
        {
            ...
        }
    }
    return CallNextHookEx(gh_GlobalHook, nCode, wParam, lParam);
}


extern "C" __declspec(dllexport) void __cdecl RemoveHook()
{
    if (gh_GlobalHook != NULL)
    {
        ::UnhookWindowsHookEx(gh_GlobalHook);
        gh_GlobalHook = NULL;
    }
}


extern "C" __declspec(dllexport) void __cdecl InstallHook()
{
    HMODULE lh_Self = NULL;
    ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (wchar_t *) &InstallHook, &lh_Self);
    if (lh_Self == NULL) 
    {
        ...
    } 
    else 
    {
        gh_GlobalHook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, lh_Self, 0);
        if (gh_GlobalHook == NULL)
        {
            ...
        }
    }
}



Indem man SetWindowsHookEx aufruft werden bestimmte Teilbereiche von Windows oder der aktuellen Anwendung abgefangen. Globale Hooks benötigen dabei eine Funktion die explizit in einer Dll liegen. Indem man einen Hook benutzt der nur global wirkt können wir erreichen das die Dll in der eben diese Funktion liegt immer geladen wird wenn sich der Hook in einen Prozess einklingt. Dies ist vielleicht nicht vergleichbar und so sicher wie mit den eher direkteren Wegen der beiden anderen Injektionsvarianten, aber es ist die einzige Variante die keine Angabe eines Zielprozesses benötigt UND die Dlls noch vor dem eigentlichen starten der Prozesses injiziert. Eine Kombination aus Variante1 und der Verwendung einer Liste von aktuell laufenden Prozessen injiziert die Dll in vielen Fällen leider zu spät, egal wie schnell man neu erstellte Prozesse erkennt.

Benutzen der injizierten Dll Top



Ein kleiner Einstiegspunkt der injizierten Dll sollte zum Beispiel so aussehen:

Code:

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch(ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH: return ProcessAttach(hModule);
    case DLL_PROCESS_DETACH: return ProcessDetach(hModule);
    }
    return TRUE;
}



In ProcessAttach könnte man sich nun zum Beispiel daran wenden beliebige Funktionen zu hijacken (darauf gehe ich hier nicht näher ein) um wie Fraps die FPS zu messen und diese anzuzeigen oder um Videos aufzunehmen. Mit ProcessDetach wiederum sollte man alle erstellten Resourcen oder Änderungen wieder rückgängig machen.

Fragen, Kritik und andere Dinge an: Mark (A T) Dras . biz

Gibt es noch irgendwelche Fragen, oder wollen Sie über den Artikel diskutieren?

Editieren Versionen Linkpartnerschaft Top Printversion

Haben Sie einen Fehler gefunden? Dann klicken Sie doch auf Editieren, und beheben den Fehler, keine Angst, Sie können nichts zerstören, das Tutorial kann wiederhergestellt werden

Sprachenübersicht/Programmierung/C / C++/ C#/Security/DLL Injection: Injizieren von Dlls in eine oder mehrere Zielanwendungen