Per quanto riguarda lo stile scelto per l'esposizione della tecnica presentata in questo articolo, ho deciso di scrivere tutte le info sotto forma di commenti al codice. Per non appesantire troppo il discorso, inoltre, si prenderà in considerazione e si spiegherà in dettaglio solo la parte relativa all'articolo. Tutto quanto necessario alla comprensione del resto del sorgente si può trovare in un articolo precedente linkato nei riferimenti sotto (si veda [1] e si faccia riferimento ad esso se si hanno dubbi). Il consiglio, per leggere il codice di questo articolo, è quello di partire dall'entrypoint (wmain) seguendo il flusso naturale delle istruzioni. L'unica cosa degna di nota da aggiungere è che questo codice funziona solo se l'iniettore è compilato in modalità Release. Pensavo fosse dovuto al fatto che in Visual Studio, di default, gli eseguibili vengono linkati con l'opzione \INCREMENTAL ma anche settandola a NO i risultati non sono cambiati. Non ho indagato in maniera approfondita quindi, almeno per il momento, è meglio evitare la modalità Debug e compilare esclusivamente in Release.
#include <iostream> #include <Windows.h> #include <TlHelp32.h> // sopprime warning di sicurezza per alcune funzioni deprecate tipo wcscat #pragma warning(disable : 4996) using namespace std; typedef HMODULE(WINAPI* pLoadLibraryA)(LPCSTR); typedef HMODULE(WINAPI* pGetModuleHandleA)(LPCSTR); typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR); typedef BOOL(WINAPI* fpDllMain)(HMODULE, DWORD, LPVOID); // Per non ripetere le cose due volte, una volta calcolate // in iniettore, le info della DLL vengono salvate in questa struttura // e scritte in spazio allocato in processo target insieme al Loader, che // potrà utilizzarle direttamente senza ricalcolarle. struct DLL_MAPPING_INFO { ULONG_PTR ImageBase; PIMAGE_NT_HEADERS NtHeaders; PIMAGE_BASE_RELOCATION BaseReloc; PIMAGE_IMPORT_DESCRIPTOR ImportDirectory; pLoadLibraryA fnLoadLibraryA; pGetModuleHandleA fnGetModuleHandleA; pGetProcAddress fnGetProcAddress; }; void GetAppPath(wchar_t *szCurFile) { GetModuleFileName(0, szCurFile, MAX_PATH); for (SIZE_T i = wcslen(szCurFile) - 1; i >= 0; i--) { if (szCurFile[i] == '\\') { szCurFile[i + 1] = '\0'; break; } } } int main() { // Percorso DLL wchar_t szDllFile[MAX_PATH] = { 0 }; GetAppPath(szDllFile); std::wcscat(szDllFile, L"MySampleDLL.dll"); // PID processo target DWORD ProcessId = GetProcessIdByProcessName(L"MyTarget.exe"); DLL_MAPPING_INFO Dll_Info; HANDLE hFile = CreateFile(szDllFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); DWORD FileSize = GetFileSize(hFile, NULL); LPVOID FileBuffer = VirtualAlloc(NULL, FileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // Salva immagine su disco della DLL in memoria appena allocata nel processo dell'iniettore ReadFile(hFile, FileBuffer, FileSize, NULL, NULL); PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)FileBuffer; PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)FileBuffer + pDosHeader->e_lfanew); // Handle al processo target HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId); // Alloca tanto spazio in processo target da contenere l'immagine in memoria della DLL LPVOID ExecutableImage = VirtualAllocEx(hProcess, NULL, pNtHeaders->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // Scrive headers in spazio appena allocato in processo target WriteProcessMemory(hProcess, ExecutableImage, FileBuffer, pNtHeaders->OptionalHeader.SizeOfHeaders, NULL); PIMAGE_SECTION_HEADER pSectHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pNtHeaders + sizeof(IMAGE_NT_HEADERS)); // Scrive sezioni for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) { if ((pSectHeader[i].VirtualAddress == 0) || (pSectHeader[i].SizeOfRawData == 0)) { ++i; continue; } WriteProcessMemory( hProcess, (PVOID)((LPBYTE)ExecutableImage + pSectHeader[i].VirtualAddress), (PVOID)((LPBYTE)FileBuffer + pSectHeader[i].PointerToRawData), pSectHeader[i].SizeOfRawData, NULL); } // Alloca tanto spazio in processo target da contenere info della DLL (DLL_MAPPING_INFO) più // il codice del Loader necessario a fixare Import table e Relocations. LPVOID LoaderMemory = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); Dll_Info.ImageBase = (ULONG_PTR)ExecutableImage; Dll_Info.NtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)ExecutableImage + pDosHeader->e_lfanew); Dll_Info.BaseReloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)ExecutableImage + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); Dll_Info.ImportDirectory = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)ExecutableImage + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); Dll_Info.fnLoadLibraryA = LoadLibraryA; Dll_Info.fnGetModuleHandleA = GetModuleHandleA; Dll_Info.fnGetProcAddress = GetProcAddress; // Scrive le info della DLL nello spazio appena allocato nel processo target WriteProcessMemory(hProcess, LoaderMemory, &Dll_Info, sizeof(DLL_MAPPING_INFO), NULL); // Scrive il codice del Loader nello spazio appena allocato nel processo target // Sfrutta aritmetica dei puntatori per calcolare dimensione di LibraryLoader WriteProcessMemory(hProcess, (LPVOID)((LPBYTE)LoaderMemory + sizeof(DLL_MAPPING_INFO)), LibraryLoader, (ULONG_PTR)LL_end - (ULONG_PTR)LibraryLoader, NULL); // Crea thread remoto per eseguire Loader nel processo target HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)((LPBYTE)LoaderMemory + sizeof(DLL_MAPPING_INFO)), LoaderMemory, 0, NULL); // Attende che il Loader termini WaitForSingleObject(hThread, INFINITE); std::cout << "Loader has been completed successfully!" << std::endl; std::cout << "Press enter to Exit!" << std::endl; std::cin.get(); // Libera spazio allocato in processo target VirtualFreeEx(hProcess, LoaderMemory, 0, MEM_RELEASE); return 0; }
void ShowError(const wchar_t *pszText) { wchar_t szErr[MAX_PATH] = { 0 }; wsprintf(szErr, L"%s Error[%d]\n", pszText, GetLastError()); MessageBox(NULL, szErr, L"ERROR", MB_OK); }
DWORD GetProcessIdByProcessName(const wchar_t *pszProcessName) { DWORD dwProcessId = 0; PROCESSENTRY32 pe32 = { 0 }; HANDLE hSnapshot = NULL; BOOL bRet = FALSE; RtlZeroMemory(&pe32, sizeof(pe32)); pe32.dwSize = sizeof(pe32); hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (NULL == hSnapshot) { ShowError(L"CreateToolhelp32Snapshot"); return dwProcessId; } bRet = Process32First(hSnapshot, &pe32); while (bRet) { if (0 == lstrcmpi(pe32.szExeFile, pszProcessName)) { dwProcessId = pe32.th32ProcessID; break; } bRet = Process32Next(hSnapshot, &pe32); } return dwProcessId; }
// Si occupa di fixare rilocazioni e import table della DLL ma, // siccome la DLL è caricata nello spazio di un altro processo, // questo codice deve essere eseguito nello spazio di tale processo. DWORD LibraryLoader(LPVOID Memory) { DLL_MAPPING_INFO* Dll_Info = (DLL_MAPPING_INFO*)Memory; PIMAGE_BASE_RELOCATION pLoc = Dll_Info->BaseReloc; LONGLONG dwDelta = Dll_Info->ImageBase - Dll_Info->NtHeaders->OptionalHeader.ImageBase; // Fixa rilocazioni while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) { WORD *pLocData = (WORD *)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION)); int nNumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); for (int i = 0; i < nNumberOfReloc; i++) { if ((pLocData[i] & 0xF000) == 0xA000) { PULONG_PTR pAddress = (PULONG_PTR)(Dll_Info->ImageBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF)); *pAddress += dwDelta; } } pLoc = (PIMAGE_BASE_RELOCATION)((LPBYTE)pLoc + pLoc->SizeOfBlock); } PIMAGE_IMPORT_DESCRIPTOR pImportTable = Dll_Info->ImportDirectory; // Fixa Import table while (pImportTable->Characteristics) { PIMAGE_THUNK_DATA OrigFirstThunk = (PIMAGE_THUNK_DATA)(Dll_Info->ImageBase + pImportTable->OriginalFirstThunk); PIMAGE_THUNK_DATA FirstThunk = (PIMAGE_THUNK_DATA)(Dll_Info->ImageBase + pImportTable->FirstThunk); char *lpDllName = (char *)(Dll_Info->ImageBase + pImportTable->Name); HMODULE hModule = Dll_Info->fnGetModuleHandleA(lpDllName); if (hModule == NULL) { // usa LoadLibraryA per caricare tale modulo se non è già stato caricato hModule = Dll_Info->fnLoadLibraryA(lpDllName); if (hModule == NULL) { pImportTable++; continue; } } while (OrigFirstThunk->u1.AddressOfData) { FARPROC Function; if (OrigFirstThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) { // Importa attraverso ordinale Function = Dll_Info->fnGetProcAddress(hModule, (LPCSTR)(OrigFirstThunk->u1.Ordinal - IMAGE_ORDINAL_FLAG)); } else { // Importa attraverso nome PIMAGE_IMPORT_BY_NAME lpImportByName = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)Dll_Info->ImageBase + OrigFirstThunk->u1.AddressOfData); Function = Dll_Info->fnGetProcAddress(hModule, (LPCSTR)lpImportByName->Name); } FirstThunk->u1.Function = (ULONG_PTR)Function; OrigFirstThunk++; FirstThunk++; } pImportTable++; } // Chiama entrypoint if (Dll_Info->NtHeaders->OptionalHeader.AddressOfEntryPoint) { fpDllMain EntryPoint = (fpDllMain)((LPBYTE)Dll_Info->ImageBase + Dll_Info->NtHeaders->OptionalHeader.AddressOfEntryPoint); return EntryPoint((HMODULE)Dll_Info->ImageBase, DLL_PROCESS_ATTACH, NULL); } return TRUE; }
// serve solo a calcolare dimensione in byte di LibraryLoader DWORD LL_end() { return 0; }
Anche in questo caso la DLL non verrà elencata tra i moduli di dipendenze del processo target in quanto non ha il relativo descriptor.
Codice sorgente:
Manual_DLL_Injector.zip
Riferimenti:
[1] Caricare DLL in memoria manualmente
[2] https://github.com/Zer0Mem0ry/ManualMap
Nessun commento:
Posta un commento