DLL-InjectionIn der Informatik bezeichnet DLL-Injection eine Technik, mit der man Code im Adressraum eines anderen Prozesses zur Ausführung bringt, indem man diesen Prozess zwingt, eine programmfremde Dynamic Link Library (DLL) zu laden. Im Prinzip ist diese Technik bei allen Betriebssystemen verfügbar, die dynamische Bibliotheken unterstützen, der Begriff DLL-Injection bezieht sich jedoch gewöhnlich auf das Betriebssystem Microsoft Windows. Diese Technik wird nur benötigt, wenn der Quellcode eines Programms, dessen Verhalten man beeinflussen möchte, nicht verfügbar ist. Somit wird DLL-Injection häufig von sogenannten Third-Party Anbietern genutzt, um das Verhalten eines Programms in einer Weise anzupassen, die vom Entwickler des ursprünglichen Programms nicht vorgesehen wurde. Ein typisches Beispiel für eine die Technik der DLL-Injection nutzende Anwendung ist ein Profiler. Verfügbare Techniken unter WindowsUnter Microsoft Windows gibt es verschiedene Techniken, eine DLL-Injection zu bewerkstelligen. Die wichtigsten sind dabei folgende:
Nutzung durch bösartige SoftwareDie Nutzung von DLL-Injection ist für bösartige Software sehr attraktiv. Diese Technik ermöglicht es, Code unter dem Deckmantel eines anderen Programms auszuführen. Dies ist deshalb interessant, da dadurch Zugriffe auf das Internet vor einer Desktop-Firewall verschleiert werden können. Hierüber können beispielsweise auf dem infizierten Computer ausgespähte Passwörter unbemerkt versendet werden. Um diesem Problem zu begegnen, versuchen einige Desktop-Firewalls, durch eine Analyse des Systems eine DLL-Injection zu erkennen, was ihnen jedoch nicht immer gelingt. Gegenmaßnahmen
Injizieren einer beliebigen DLLDer folgende Code zeigt den minimalen Weg, eine beliebige DLL in einen entfernten Prozess zu injizieren und auf einem Einstiegspunkt dieser DLL einen eigenen Thread zu starten. #include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <memory>
#include <system_error>
#include <charconv>
#include <vector>
using namespace std;
using XHANDLE = unique_ptr<void, decltype([]( void *h ) { h && h != INVALID_HANDLE_VALUE && CloseHandle( (HANDLE)h ); })>;
using XHMODULE = unique_ptr<void, decltype([]( void *hm ) { hm && FreeLibrary( (HMODULE)hm ); })>;
MODULEENTRY32W getModule( char const *module );
[[noreturn]]
void throwSysErr( char const *str );
size_t maxReadableRange( void *pRegion );
int main( int argc, char **argv )
{
try
{
if( argc < 4 )
return EXIT_FAILURE;
char const
*processId = argv[1],
*dllName = argv[2],
*exportName = argv[3];
DWORD dwProcessId = [&]() -> DWORD
{
DWORD dwRet;
if( from_chars_result fcr = from_chars( processId, processId + strlen( processId ), dwRet ); fcr.ec != errc() || *fcr.ptr )
throw system_error( (int)(!*fcr.ptr ? fcr.ec : errc::invalid_argument), generic_category(), "wrong process id");
return dwRet;
}();
XHANDLE xhProcess( OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwProcessId ) );
if( !xhProcess.get() )
throwSysErr( "can't open process unlimited" );
XHMODULE xhmDll;
unsigned mapRetries = 0;
MODULEENTRY32W me;
for( ; ; )
{
xhmDll.reset( LoadLibraryA( dllName ) );
if( !xhmDll.get() )
throwSysErr( "can't load library" );
me = getModule( dllName );
if( VirtualAllocEx( xhProcess.get(), me.modBaseAddr, me.modBaseSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE ) )
{
xhmDll.reset( nullptr );
if( !VirtualAlloc( me.modBaseAddr, me.modBaseSize, MEM_RESERVE, PAGE_NOACCESS ) )
throwSysErr( "can't reserve address range of previously mapped DLL" );
++mapRetries;
continue;
}
break;
}
LPTHREAD_START_ROUTINE procAddr = (LPTHREAD_START_ROUTINE)GetProcAddress( (HMODULE)xhmDll.get(), exportName );
if( !procAddr )
throwSysErr( "can't get procedure entry point" );
size_t dllReadable = maxReadableRange( me.modBaseAddr );
if( SIZE_T bytesCopied; !WriteProcessMemory( xhProcess.get(), me.modBaseAddr, me.modBaseAddr, dllReadable, &bytesCopied ) || bytesCopied != me.modBaseSize )
throwSysErr( "can't copy DLL to remote process" );
DWORD dwRemoteThreadId;
XHANDLE xhRemoteThread( CreateRemoteThread( xhProcess.get(), nullptr, 0, procAddr, nullptr, 0, &dwRemoteThreadId ) );
if( !xhRemoteThread.get() )
throwSysErr( "failed to create remote thread" );
}
catch( system_error const &se )
{
cout << se.what() << endl;
cout << "error code: " << (DWORD)se.code().value() << endl;
}
}
MODULEENTRY32W getModule( char const *module )
{
MODULEENTRY32W me;
auto errRet = [&]() -> MODULEENTRY32W { me.dwSize = 0; return me; };
wstring wModule;
wModule.reserve( strlen( module ) );
for( ; *module; wModule += (wchar_t)(unsigned char)*module++ );
wstring moduleAbsolute( GetFullPathNameW( wModule.data(), 0, (LPWSTR)L"", nullptr ), L'\0' );
if( moduleAbsolute.size()
|| GetFullPathNameW( wModule.data(), moduleAbsolute.size(), (LPWSTR)moduleAbsolute.c_str(), nullptr ) + 1 != moduleAbsolute.size() )
return errRet();
XHANDLE xhToolHelp( CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, GetCurrentProcessId() ) );
if( xhToolHelp.get() == INVALID_HANDLE_VALUE )
return errRet();
me.dwSize = sizeof me;
if( !Module32FirstW( xhToolHelp.get(), &me ) )
return errRet();
for( ; ; )
{
constexpr size_t PATH_LENGTH = 256;
wchar_t modulePath[PATH_LENGTH];
if( !GetModuleFileNameW( me.hModule, modulePath, PATH_LENGTH ) )
return errRet();
if( _wcsicmp( modulePath, moduleAbsolute.c_str() ) == 0 )
return me;
me.dwSize = sizeof me;
if( !Module32NextW( xhToolHelp.get(), &me ) )
return errRet();
}
}
size_t maxReadableRange( void *pRegion )
{
constexpr char const *VQ_ERR = "can't determine readable size of region";
auto query = []( void *p ) -> MEMORY_BASIC_INFORMATION
{
MEMORY_BASIC_INFORMATION mbi;
if( !VirtualQuery( p, &mbi, sizeof mbi) )
throwSysErr( VQ_ERR );
return mbi;
};
pRegion = query( pRegion ).AllocationBase;
MEMORY_BASIC_INFORMATION mbi;
constexpr DWORD MEMORY_TYPES = PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY | PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY;
for( char *scn = (char *)pRegion; ; scn += mbi.RegionSize )
if( (mbi = query( scn )).AllocationBase != pRegion || mbi.State != MEM_COMMIT && !(mbi.AllocationProtect & MEMORY_TYPES) )
return scn - (char *)pRegion;
return 0;
}
[[noreturn]]
void throwSysErr( char const *str )
{
throw system_error( (int)GetLastError(), system_category(), str );
}
Das wesentliche Problem beim Injizieren einer beliebigen DLL in einen entfernten Prozess ist, dass man mit Des Weiteren belegt die geladene DLL in der Regel mehr Adressraum als tatsächlich physisch Pages des Executables gemappt sind, d. h. man kann nicht so ohne weiteres dem oben in der Funktion Literatur
|