/* * Memory DLL loading code * Version 0.0.1 * * Copyright (c) 2004 by Joachim Bauch / mail@joachim-bauch.de * http://www.joachim-bauch.de * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ // disable warnings about pointer <-> DWORD conversions #pragma warning( disable : 4311 4312 ) #include #include #include "ntinternals.h" #define DEBUG_OUTPUT 0 #ifdef DEBUG_OUTPUT #include #endif #include "MemoryModule.h" typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); #define GET_NT_HEADER(module) ((PIMAGE_NT_HEADERS)&((const unsigned char *)(module))[((PIMAGE_DOS_HEADER)(module))->e_lfanew]) #define GET_HEADER_DICTIONARY(module, idx) &GET_NT_HEADER(module)->OptionalHeader.DataDirectory[idx] #define CALCULATE_REAL_ADDRESS(base, offset) (((unsigned char *)(base)) + (offset)) // stores number of modules loaded static DWORD ModuleCount = 0; #ifdef DEBUG_OUTPUT static void OutputLastError(const char *msg) { LPVOID tmp; char *tmpmsg; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL); tmpmsg = (char *)LocalAlloc(LPTR, strlen(msg) + strlen(tmp) + 3); sprintf(tmpmsg, "%s: %s", msg, tmp); OutputDebugString(tmpmsg); LocalFree(tmpmsg); LocalFree(tmp); } #endif static void CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, HMODULE module) { DWORD i, size; LPVOID dest; PIMAGE_SECTION_HEADER section; section = IMAGE_FIRST_SECTION(GET_NT_HEADER(module)); for (i=0; iFileHeader.NumberOfSections; i++, section++) { if (section->SizeOfRawData == 0) { // section doesn't contain data in the dll itself, but may define // uninitialized data size = old_headers->OptionalHeader.SectionAlignment; if (size > 0) { dest = VirtualAlloc(CALCULATE_REAL_ADDRESS(module, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE); section->Misc.PhysicalAddress = (DWORD)dest; memset(dest, 0, size); } // section is empty continue; } // commit memory block and copy data from dll dest = VirtualAlloc(CALCULATE_REAL_ADDRESS(module, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE); memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData); section->Misc.PhysicalAddress = (DWORD)dest; } } // Protection flags for memory pages (Executable, Readable, Writeable) static int ProtectionFlags[2][2][2] = { { // not executable {PAGE_NOACCESS, PAGE_WRITECOPY}, {PAGE_READONLY, PAGE_READWRITE}, }, { // executable {PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY}, {PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE}, }, }; static void FinalizeSections(HMODULE module) { DWORD i; PIMAGE_SECTION_HEADER section; // loop through all sections and change access flags section = IMAGE_FIRST_SECTION(GET_NT_HEADER(module)); for (i=0; iFileHeader.NumberOfSections; i++, section++) { DWORD protect, oldProtect, size; int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; if (section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) { // section is not needed any more and can safely be freed VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); continue; } // determine protection flags based on characteristics protect = ProtectionFlags[executable][readable][writeable]; if (section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED) protect |= PAGE_NOCACHE; // determine size of region size = section->SizeOfRawData; if (size == 0) { if (section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) size = GET_NT_HEADER(module)->OptionalHeader.SizeOfInitializedData; else if (section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) size = GET_NT_HEADER(module)->OptionalHeader.SizeOfUninitializedData; } if (size > 0) { // change memory access flags if (VirtualProtect((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, protect, &oldProtect) == 0) #ifdef DEBUG_OUTPUT OutputLastError("Error protecting memory page") #endif ; } } } static void PerformBaseRelocation(HMODULE module, DWORD delta) { DWORD i; PIMAGE_DATA_DIRECTORY directory; directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_BASERELOC); if (directory->Size > 0) { PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_REAL_ADDRESS(module, directory->VirtualAddress); for (; relocation->VirtualAddress > 0; ) { unsigned char *dest = (unsigned char *)CALCULATE_REAL_ADDRESS(module, relocation->VirtualAddress); unsigned short *relInfo = (unsigned short *)CALCULATE_REAL_ADDRESS(relocation, IMAGE_SIZEOF_BASE_RELOCATION); for (i=0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++) { DWORD *patchAddrHL; int type, offset; // the upper 4 bits define the type of relocation type = *relInfo >> 12; // the lower 12 bits define the offset offset = *relInfo & 0xfff; switch (type) { case IMAGE_REL_BASED_ABSOLUTE: // skip relocation break; case IMAGE_REL_BASED_HIGHLOW: // change complete 32 bit address patchAddrHL = (DWORD *)(dest + offset); *patchAddrHL += delta; break; default: //printf("Unknown relocation: %d\n", type); break; } } // advance to next relocation block (char *)relocation += relocation->SizeOfBlock; } } } static int BuildImportTable(HMODULE module) { int result=1; PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_IMPORT); if (directory->Size > 0) { PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_REAL_ADDRESS(module, directory->VirtualAddress); for (; !IsBadReadPtr(importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) { DWORD *thunkRef, *funcRef; HMODULE handle = LoadLibrary((LPCSTR)CALCULATE_REAL_ADDRESS(module, importDesc->Name)); if (handle == INVALID_HANDLE_VALUE) { SetLastError(ERROR_MOD_NOT_FOUND); #if DEBUG_OUTPUT OutputLastError("Can't load library"); #endif result = 0; break; } if (importDesc->OriginalFirstThunk) { thunkRef = (DWORD *)CALCULATE_REAL_ADDRESS(module, importDesc->OriginalFirstThunk); funcRef = (DWORD *)CALCULATE_REAL_ADDRESS(module, importDesc->FirstThunk); } else { // no hint table thunkRef = (DWORD *)CALCULATE_REAL_ADDRESS(module, importDesc->FirstThunk); funcRef = (DWORD *)CALCULATE_REAL_ADDRESS(module, importDesc->FirstThunk); } for (; *thunkRef; thunkRef++, funcRef++) { if IMAGE_SNAP_BY_ORDINAL(*thunkRef) *funcRef = (DWORD)GetProcAddress(handle, (LPCSTR)IMAGE_ORDINAL(*thunkRef)); else { PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_REAL_ADDRESS(module, *thunkRef); *funcRef = (DWORD)GetProcAddress(handle, (LPCSTR)&thunkData->Name); } if (*funcRef == 0) { SetLastError(ERROR_PROC_NOT_FOUND); result = 0; break; } } if (!result) break; } } return result; } static PPEB GetPEB(void) { // XXX: is there a better or even documented way to do this? __asm { // get PEB mov eax, dword ptr fs:[30h] } } static HMODULE FindLibraryInPEB(const unsigned char *name, int incLoadCount) { PPEB_LDR_DATA loaderData; PLDR_MODULE loaderModule; PWSTR longName; size_t i; HMODULE result=NULL; if (name == NULL) return NULL; // convert name to long character name longName = (PWSTR)calloc((strlen(name)+1)*2, 1); for (i=0; iLoaderData; loaderModule = (PLDR_MODULE)(loaderData->InLoadOrderModuleList.Flink); while (1) { if (wcsicmp(longName, loaderModule->BaseDllName.Buffer) == 0) { result = loaderModule->BaseAddress; if (incLoadCount && loaderModule->LoadCount != 0xffff) // we use this module, so increate the load count loaderModule->LoadCount++; goto exit; } // advance to next module loaderModule = (PLDR_MODULE)(loaderModule->InLoadOrderModuleList.Flink); if (loaderModule->BaseAddress == NULL || loaderModule == (PLDR_MODULE)(loaderData->InLoadOrderModuleList.Flink)) // we traversed through the complete list // and didn't find the library goto exit; } exit: free(longName); return result; } // Append a loader module to the end of the loader data list of the PEB #define AppendToChain(module, list, chain) { \ (module)->##chain##.Flink = (list)->##chain##.Flink; \ (module)->##chain##.Blink = (list)->##chain##.Blink; \ ((PLDR_MODULE)((list)->##chain##.Blink))->##chain##.Flink = &(module)->##chain##; \ (list)->##chain##.Blink = &(module)->##chain##; \ }; static PLDR_MODULE InsertModuleInPEB(HMODULE module, unsigned char *name, unsigned char *baseName, DWORD locationDelta) { PLDR_MODULE loaderModule; PPEB_LDR_DATA loaderData = GetPEB()->LoaderData; DWORD entry = GET_NT_HEADER(module)->OptionalHeader.AddressOfEntryPoint; size_t i; loaderModule = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(LDR_MODULE)); if (loaderModule == NULL) return NULL; loaderModule->BaseAddress = module; loaderModule->EntryPoint = (PVOID)(entry ? CALCULATE_REAL_ADDRESS(module, entry) : 0); loaderModule->SizeOfImage = GET_NT_HEADER(module)->OptionalHeader.SizeOfImage; loaderModule->LoadCount = 1; loaderModule->BaseDllName.Buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (strlen(baseName)+1)*2); if (loaderModule->BaseDllName.Buffer == NULL) { HeapFree(GetProcessHeap(), 0, loaderModule); return NULL; } loaderModule->BaseDllName.Length = (USHORT)strlen(baseName)*2; loaderModule->BaseDllName.MaximumLength = (USHORT)HeapSize(GetProcessHeap(), 0, loaderModule->BaseDllName.Buffer); for (i=0; iBaseDllName.Buffer[i] = baseName[i]; loaderModule->FullDllName.Buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (strlen(name)+1)*2); if (loaderModule->BaseDllName.Buffer == NULL) { HeapFree(GetProcessHeap(), 0, loaderModule->BaseDllName.Buffer); HeapFree(GetProcessHeap(), 0, loaderModule); return NULL; } loaderModule->FullDllName.Length = (USHORT)strlen(name)*2; loaderModule->FullDllName.MaximumLength = (USHORT)HeapSize(GetProcessHeap(), 0, loaderModule->FullDllName.Buffer); for (i=0; iFullDllName.Buffer[i] = name[i]; // XXX: are these the correct flags? loaderModule->Flags = IMAGE_DLL | ENTRY_PROCESSED; if (locationDelta != 0) loaderModule->Flags |= IMAGE_NOT_AT_BASE; loaderModule->TimeDateStamp = GET_NT_HEADER(module)->FileHeader.TimeDateStamp; // XXX: do we need more set the hash table? //loaderModule->HashTableEntry.Flink = &loaderModule->HashTableEntry; //loaderModule->HashTableEntry.Blink = &loaderModule->HashTableEntry; AppendToChain(loaderModule, loaderData, InLoadOrderModuleList); AppendToChain(loaderModule, loaderData, InInitializationOrderModuleList); // XXX: insert at the correct position in the chain AppendToChain(loaderModule, loaderData, InMemoryOrderModuleList); return loaderModule; } HMODULE MemoryLoadLibrary(const void *data, unsigned char *name) { HMODULE result; PIMAGE_DOS_HEADER dos_header; PIMAGE_NT_HEADERS old_header; LPVOID headers; unsigned char *baseName; unsigned char fullname[MAX_DLL_NAME_LENGTH], tempname[MAX_DLL_NAME_LENGTH]; DWORD locationDelta; DllEntryProc DllEntry; BOOL successfull; DWORD hasFullName; PLDR_MODULE loaderModule=NULL; // make sure we have a module name if (name == NULL || strlen(name) == 0) { sprintf(tempname, "memorymodule%d", ModuleCount); name = (unsigned char *)&tempname; } // maybe a module with the given name has been loaded already hasFullName = GetFullPathName(name, sizeof(fullname), (LPSTR)&fullname, &baseName); // search for module in PEB result = FindLibraryInPEB(hasFullName ? baseName : name, 1); if (result != NULL) // already loaded this module goto exit; if (hasFullName) // use complete filename as module name name = (unsigned char *)&fullname; else baseName = name; dos_header = (PIMAGE_DOS_HEADER)data; if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { #if DEBUG_OUTPUT OutputDebugString("Not a valid executable file.\n"); #endif goto error; } old_header = GET_NT_HEADER(data); if (old_header->Signature != IMAGE_NT_SIGNATURE) { #if DEBUG_OUTPUT OutputDebugString("No PE header found.\n"); #endif goto error; } // reserve memory for image of library result = (HMODULE)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE); if (result == NULL) // try to allocate memory at arbitrary position result = (HMODULE)VirtualAlloc(NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE); if (result == NULL) { SetLastError(ERROR_OUTOFMEMORY); goto error; } // XXX: is it correct to commit the complete memory region at once? // calling DllEntry raises an exception if we don't... VirtualAlloc(result, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE); // commit memory for headers headers = VirtualAlloc(result, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE); // copy PE header to code memcpy(headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders); // update position GET_NT_HEADER(result)->OptionalHeader.ImageBase = (DWORD)result; // copy sections from DLL file block to new memory location CopySections(data, old_header, result); // adjust base address of imported data locationDelta = (DWORD)((DWORD)result - old_header->OptionalHeader.ImageBase); if (locationDelta != 0) PerformBaseRelocation(result, locationDelta); // load required dlls and adjust function table of imports if (!BuildImportTable(result)) goto error; // mark memory pages depending on section headers and release // sections that are marked as "discardable" FinalizeSections(result); // Add loaded module to PEB if (!(loaderModule = InsertModuleInPEB(result, name, baseName, locationDelta))) goto error; // get entry point of loaded library if (GET_NT_HEADER(result)->OptionalHeader.AddressOfEntryPoint != 0) { // notify library about attaching to process DllEntry = (DllEntryProc)CALCULATE_REAL_ADDRESS(result, GET_NT_HEADER(result)->OptionalHeader.AddressOfEntryPoint); successfull = (*DllEntry)(result, DLL_PROCESS_ATTACH, 0); if (!successfull) { SetLastError(ERROR_DLL_INIT_FAILED); goto error; } loaderModule->Flags |= PROCESS_ATTACH_CALLED; } ModuleCount++; goto exit; error: // perform some cleanup... if (loaderModule != NULL) { if ((loaderModule->Flags & PROCESS_ATTACH_CALLED) != 0) (*DllEntry)(result, DLL_PROCESS_DETACH, 0); // remove from module chains loaderModule->InInitializationOrderModuleList.Flink->Blink = loaderModule->InInitializationOrderModuleList.Blink; loaderModule->InInitializationOrderModuleList.Blink->Flink = loaderModule->InInitializationOrderModuleList.Flink; loaderModule->InLoadOrderModuleList.Flink->Blink = loaderModule->InLoadOrderModuleList.Blink; loaderModule->InLoadOrderModuleList.Blink->Flink = loaderModule->InLoadOrderModuleList.Flink; loaderModule->InMemoryOrderModuleList.Flink->Blink = loaderModule->InMemoryOrderModuleList.Blink; loaderModule->InMemoryOrderModuleList.Blink->Flink = loaderModule->InMemoryOrderModuleList.Flink; // free memory for PEB structures HeapFree(GetProcessHeap(), 0, loaderModule->BaseDllName.Buffer); HeapFree(GetProcessHeap(), 0, loaderModule->FullDllName.Buffer); HeapFree(GetProcessHeap(), 0, loaderModule); } if (result != NULL) VirtualFree(result, 0, MEM_RELEASE); result = NULL; exit: return result; }