Fallen JLI MVP
Alter: 40 Anmeldedatum: 08.03.2003 Beiträge: 2860 Wohnort: Münster Medaillen: 1 (mehr...)
|
Verfasst am: 05.08.2008, 19:08 Titel: Injizieren von Dlls in eine oder mehrere Zielanwendungen |
|
|
1. Einleitung
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 Einstiegs punkt 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 anzuzeigen, Videos aufzunehmen oder zusätzliche Informationen anzuzeigen.
2. Injektionsmöglichkeiten
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 Aktionen 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:
CPP: | 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;
} |
Erklärung:
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.
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):
CPP: | 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;
} |
Kleine Erklärung:
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.
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:
CPP: | 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).
Die Dll selbst braucht dann nur noch diese 3 Funktionen:
CPP: | 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)
{
...
}
}
} |
Erklärung:
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.
3. Benutzen der injizierten Dll
Ein kleiner Einstiegspunkt der injizierten Dll sollte zum Beispiel so aussehen:
CPP: | 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.
Das ganze ist schon recht kompliziert daher stellt Fragen so das ich euch helfen kann, auch wenn die Anwendungsgebiete nicht ganz klar sein sollten, einfach Fragen. Normale Kommentare oder Kritik sind natürlich auch gerne gesehen.
mfg Mark |
|