Mega Code Archive

 
Categories / Delphi / System
 

Load and run embedded DLL in EXE from memory

Title: Load and run embedded DLL in EXE from memory Question: Loads and calls DLL functions and methods from memory from a resource within the EXE. Replaces typical use whereby the DLL is compiled as a resource and at runtime is saved to a file, then loaded and executed. Works in a similar fashion to Delphi LoadLibrary(),GetProcAddress() and FreeLibrary() functions except the DLL is loaded from resource to memory. It is best to call the functions in a try..except structure Answer: unit MW_MemoryDll; interface {$REGION 'Documentation'} // ================================================================================================= // Memory DLL 32 Bit functions // // Loads and calls DLL functions and methods from memory from a resource within the EXE. // Replaces typical use whereby the DLL is compiled as a resource and at runtime is saved // to a file, then loaded and executed. // Works in a similar fashion to Delphi LoadLibrary(),GetProcAddress() and FreeLibrary() // functions except the DLL is loaded from resource to memory. // It is best to call the functions in a try..except structure // // Mike Heydon 2010 // // RESOURCE Creation example // ------------------------------------------------------------- // Create a resource file (eg. testdll.rc) // Add following line ... // DEMODLL RCDATA test.dll // Compile the .rc file (eg. brcc32 testdll.rc) // This creates a .res file (eg. testdll.res) // Include it in source code after {$R *.dfm} // eg. {$R testdll.res} // // Simplistic Example (proc Setup() is in test.dll) // DLL resource name in .res is DEMODLL // ------------------------------------------------------------- // procedure TForm4.Button1Click(Sender: TObject); // type // TSetup = procedure(AMessage : PChar); stdcall; // // var pDll : pointer; // pSetup : TSetup; // // begin // pDll := nil; // // try // pDll := MemoryLoadLibrary('DEMODLL'); // except // on E : Exception do begin // ShowMessage(E.Message); // Halt; // end; // end; // // try // @pSetup := MemoryGetProcAddress(pDll,'Setup'); // if @pSetup nil then pSetup('Demo DLL Call'); // finally // MemoryFreeLibrary(pDll); // end; // end; // // ================================================================================================= {$ENDREGION} uses Windows, SysUtils, Classes; // Prototype Definitions function MemoryLoadLibrary(const ADllResName : string) : pointer; function MemoryGetProcAddress(AModule : pointer; const AName : string; ACaseSensitive : boolean = false): pointer; stdcall; procedure MemoryFreeLibrary(AModule : pointer); stdcall; // ------------------------------------------------------------------------------------------------ implementation {$REGION 'Types and Constants'} // ------------------------------------------------------------------------------------------------- type TDllEntryProc = function(hinstdll : THandle; fdwReason : DWORD; lpReserved : pointer) : BOOL; stdcall; PBTMemoryModule = ^TBTMemoryModule; _BT_MEMORY_MODULE = packed record Headers : PImageNtHeaders; CodeBase : pointer; Modules : pointer; NumModules : integer; Initialized : boolean; end; {$EXTERNALSYM _BT_MEMORY_MODULE} TBTMemoryModule = _BT_MEMORY_MODULE; BT_MEMORY_MODULE = _BT_MEMORY_MODULE; {$EXTERNALSYM BT_MEMORY_MODULE} // Missing Windows API Definitions PImageBaseRelocation = ^TImageBaseRelocation; _IMAGE_BASE_RELOCATION = packed record VirtualAddress : DWORD; SizeOfBlock : DWORD; end; {$EXTERNALSYM _IMAGE_BASE_RELOCATION} TImageBaseRelocation = _IMAGE_BASE_RELOCATION; IMAGE_BASE_RELOCATION = _IMAGE_BASE_RELOCATION; {$EXTERNALSYM IMAGE_BASE_RELOCATION} PImageImportDescriptor = ^TImageImportDescriptor; _IMAGE_IMPORT_DESCRIPTOR = packed record OriginalFirstThunk : DWORD; TimeDateStamp : DWORD; ForwarderChain : DWORD; Name : DWORD; FirstThunk : DWORD; end; {$EXTERNALSYM _IMAGE_IMPORT_DESCRIPTOR} TImageImportDescriptor = _IMAGE_IMPORT_DESCRIPTOR; IMAGE_IMPORT_DESCRIPTOR = _IMAGE_IMPORT_DESCRIPTOR; {$EXTERNALSYM IMAGE_IMPORT_DESCRIPTOR} PImageImportByName = ^TImageImportByName; _IMAGE_IMPORT_BY_NAME = packed record Hint: Word; Name: array[0..255] of byte; end; {$EXTERNALSYM _IMAGE_IMPORT_BY_NAME} TImageImportByName = _IMAGE_IMPORT_BY_NAME; IMAGE_IMPORT_BY_NAME = _IMAGE_IMPORT_BY_NAME; {$EXTERNALSYM IMAGE_IMPORT_BY_NAME} const IMAGE_SIZEOF_BASE_RELOCATION = 8; {$EXTERNALSYM IMAGE_SIZEOF_BASE_RELOCATION} IMAGE_REL_BASED_HIGHLOW = 3; {$EXTERNALSYM IMAGE_REL_BASED_HIGHLOW} IMAGE_ORDINAL_FLAG32 = DWORD($80000000); {$EXTERNALSYM IMAGE_ORDINAL_FLAG32} {$ENDREGION} {$REGION 'Internal Calls'} // ------------------------------------------------------------------------------------------------- function _GetFieldOffset(const AStruc; const AField) : cardinal; stdcall; inline; begin Result := cardinal(@AField) - cardinal(@AStruc); end; function _GetImageFirstSection(ANtHeader : PImageNtHeaders) : PImageSectionHeader; stdcall; inline; begin Result := PImageSectionHeader(cardinal(ANtHeader) + _GetFieldOffset(ANtHeader^,ANtHeader^.OptionalHeader) + ANtHeader^.FileHeader.SizeOfOptionalHeader); end; function _GetHeaderDictionary(AModule : PBTMemoryModule; AIdx : integer): PImageDataDirectory; stdcall; inline; begin Result := PImageDataDirectory(@(AModule.headers.OptionalHeader.DataDirectory[AIdx])); end; function _GetImageOrdinal(AOrdinal : DWORD) : word; stdcall; inline; begin Result := AOrdinal and $FFFF; end; function _GetImageSnapByOrdinal(AOrdinal : DWORD): boolean; stdcall; inline; begin Result := ((AOrdinal and IMAGE_ORDINAL_FLAG32) 0); end; procedure _CopySections(const AData : pointer; const AOldHeaders : TImageNtHeaders; AModule : PBTMemoryModule); stdcall; var iSize,i : integer; pCodebase,pDest : pointer; pSection : PImageSectionHeader; begin pCodebase := AModule.CodeBase; pSection := _GetImageFirstSection(AModule.Headers); for i := 0 to AModule.Headers.FileHeader.NumberOfSections - 1 do begin // Section doesn't contain data in the dll itself, but may define uninitialized data if (pSection.SizeOfRawData = 0) then begin iSize := AOldHeaders.OptionalHeader.SectionAlignment; if iSize 0 then begin pDest := VirtualAlloc(pointer(cardinal(pCodebase) + pSection.VirtualAddress), iSize,MEM_COMMIT,PAGE_READWRITE); pSection.Misc.PhysicalAddress := cardinal(pDest); ZeroMemory(pDest,iSize); end; inc(longword(pSection),sizeof(TImageSectionHeader)); Continue; end; // Commit memory block and copy data from DLL pDest := VirtualAlloc(pointer(cardinal(pCodebase) + pSection.VirtualAddress), pSection.SizeOfRawData,MEM_COMMIT,PAGE_READWRITE); CopyMemory(pDest, pointer(longword(AData) + pSection.pointerToRawData),pSection.SizeOfRawData); pSection.Misc.PhysicalAddress := cardinal(pDest); // IMAGE_SIZEOF_SECTION_HEADER inc(longword(pSection),sizeof(TImageSectionHeader)); end; end; procedure _PerformBaseRelocation(AModule : PBTMemoryModule; ADelta : cardinal); stdcall; var i : cardinal; pCodebase,pDest : pointer; pDirectory : PImageDataDirectory; pRelocation : PImageBaseRelocation; pRelInfo : ^word; pPatchAddrHL : ^DWORD; iType,iOffset : integer; begin pCodebase := AModule.CodeBase; pDirectory := _GetHeaderDictionary(AModule,IMAGE_DIRECTORY_ENTRY_BASERELOC); if pDirectory.Size 0 then begin pRelocation := PImageBaseRelocation(cardinal(pCodebase) + pDirectory.VirtualAddress); while pRelocation.VirtualAddress 0 do begin pDest := pointer((cardinal(pCodebase) + pRelocation.VirtualAddress)); pRelInfo := pointer(cardinal(pRelocation) + IMAGE_SIZEOF_BASE_RELOCATION); for i := 0 to (trunc(((pRelocation.SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2)) - 1) do begin // Upper 4 bits define the type of relocation iType := (pRelInfo^ shr 12); // Lower 12 bits define the offset iOffset := pRelInfo^ and $FFF; if iType = IMAGE_REL_BASED_HIGHLOW then begin // Change complete 32 bit address pPatchAddrHL := pointer(cardinal(pDest) + cardinal(iOffset)); pPatchAddrHL^ := pPatchAddrHL^ + ADelta; end; inc(pRelInfo); end; pRelocation := pointer(cardinal(pRelocation) + pRelocation.SizeOfBlock); end; end; end; function _BuildImportTable(AModule : PBTMemoryModule; out AErrStr : string) : boolean; stdcall; var pCodeBase : pointer; pDirectory : PImageDataDirectory; pImportDesc : PImageImportDescriptor; pThunkRef,pFuncRef : ^DWORD; iHandle : HMODULE; iTemp : integer; rThunkData : TImageImportByName; begin Result := true; pCodeBase := AModule.CodeBase; pDirectory := _GetHeaderDictionary(AModule,IMAGE_DIRECTORY_ENTRY_IMPORT); if (pDirectory.Size 0) then begin pImportDesc := PImageImportDescriptor(cardinal(pCodeBase) + pDirectory.VirtualAddress); while (not IsBadReadPtr(pImportDesc, sizeof(TImageImportDescriptor))) and (pImportDesc.Name 0) do begin iHandle := LoadLibraryA(PAnsiChar(cardinal(pCodeBase) + pImportDesc.Name)); if (iHandle = INVALID_HANDLE_VALUE) or (iHandle = 0) then begin AErrStr := 'BuildImportTable: can''t load library: ' + PAnsiChar(cardinal(pCodeBase) + pImportDesc.Name); Result := false; exit; end; // ReallocMemory crashes if "AModule.Modules = nil" if AModule.Modules = nil then AModule.Modules := AllocMem(1); AModule.Modules := ReallocMemory(AModule.Modules,((AModule.NumModules + 1) * (sizeof(HMODULE)))); if AModule.Modules = nil then begin AErrStr := 'BuildImportTable: ReallocMemory failed'; Result := false; exit; end; iTemp := (sizeof(cardinal) * (AModule.NumModules)); inc(cardinal(AModule.Modules),iTemp); cardinal(AModule.Modules^) := iHandle; dec(cardinal(AModule.Modules),iTemp); AModule.NumModules := AModule.NumModules + 1; if pImportDesc.OriginalFirstThunk 0 then begin pThunkRef := pointer(cardinal(pCodeBase) + pImportDesc.OriginalFirstThunk); pFuncRef := pointer(cardinal(pCodeBase) + pImportDesc.FirstThunk); end else begin // No hint table pThunkRef := pointer(cardinal(pCodeBase) + pImportDesc.FirstThunk); pFuncRef := pointer(cardinal(pCodeBase) + pImportDesc.FirstThunk); end; while pThunkRef^ 0 do begin if _GetImageSnapByOrdinal(pThunkRef^) then pFuncRef^ := cardinal(GetProcAddress(iHandle,PAnsiChar(_GetImageOrdinal(pThunkRef^)))) else begin CopyMemory(@rThunkData, pointer(cardinal(pCodeBase) + pThunkRef^), sizeof(TImageImportByName)); pFuncRef^ := cardinal(GetProcAddress(iHandle,PAnsiChar(@(rThunkData.Name)))); end; if pFuncRef^ = 0 then begin AErrStr := 'BuildImportTable: GetProcAddress failed'; Result := false; break; end; inc(pFuncRef); inc(pThunkRef); end; inc(longword(pImportDesc),sizeof(TImageImportDescriptor)); end; end; end; function _GetSectionProtection(ASectionHeader : cardinal) : cardinal; stdcall; var iResult : cardinal; begin iResult := 0; if (ASectionHeader and IMAGE_SCN_MEM_NOT_CACHED) 0 then iResult := iResult or PAGE_NOCACHE; // E - Execute, R - Read , W - Write if (ASectionHeader and IMAGE_SCN_MEM_EXECUTE) 0 //E ? then if (ASectionHeader and IMAGE_SCN_MEM_READ) 0 //ER ? then if (ASectionHeader and IMAGE_SCN_MEM_WRITE) 0 //ERW ? then iResult := iResult or PAGE_EXECUTE_READWRITE else iResult := iResult or PAGE_EXECUTE_READ else if (ASectionHeader and IMAGE_SCN_MEM_WRITE) 0 //EW? then iResult := iResult or PAGE_EXECUTE_WRITECOPY else iResult := iresult or PAGE_EXECUTE else if (ASectionHeader and IMAGE_SCN_MEM_READ) 0 // R? then if (ASectionHeader and IMAGE_SCN_MEM_WRITE) 0 //RW? then iResult := iResult or PAGE_READWRITE else iResult := iResult or PAGE_READONLY else if (ASectionHeader and IMAGE_SCN_MEM_WRITE) 0 //W? then iResult := iResult or PAGE_WRITECOPY else iResult := iResult or PAGE_NOACCESS; Result := iResult; end; procedure _FinalizeSections(AModule : PBTMemoryModule); stdcall; var i : integer; pSection : PImageSectionHeader; iProtect,iOldProtect,iSize : cardinal; begin pSection := _GetImageFirstSection(AModule.Headers); for i := 0 to AModule.Headers.FileHeader.NumberOfSections - 1 do begin if (pSection.Characteristics and IMAGE_SCN_MEM_DISCARDABLE) 0 then begin // Section is not needed any more and can safely be freed VirtualFree(pointer(pSection.Misc.PhysicalAddress),pSection.SizeOfRawData,MEM_DECOMMIT); inc(longword(pSection),sizeof(TImageSectionHeader)); continue; end; iProtect := _GetSectionProtection(pSection.Characteristics); if (pSection.Characteristics and IMAGE_SCN_MEM_NOT_CACHED) 0 then iProtect := (iProtect or PAGE_NOCACHE); // Determine size of region iSize := pSection.SizeOfRawData; if iSize = 0 then begin if (pSection.Characteristics and IMAGE_SCN_CNT_INITIALIZED_DATA) 0 then iSize := AModule.Headers.OptionalHeader.SizeOfInitializedData else begin if (pSection.Characteristics and IMAGE_SCN_CNT_UNINITIALIZED_DATA) 0 then iSize := AModule.Headers.OptionalHeader.SizeOfUninitializedData; end; if iSize 0 then begin if not VirtualProtect(pointer(pSection.Misc.PhysicalAddress),pSection.SizeOfRawData, iProtect,@iOldProtect) then raise Exception.Create('FinalizeSections: VirtualProtect failed'); end; end; inc(longword(pSection),sizeof(TImageSectionHeader)); end; end; // ======================================== // Load Library into memory structure // ======================================== function _MemoryLoadLibary(AData : pointer) : PBTMemoryModule; stdcall; var sErrStr : string; pResult : PBTMemoryModule; rDosHeader : TImageDosHeader; rOldHeader : TImageNtHeaders; pCode,pHeaders : pointer; iLocationDelta : cardinal; rDllEntry : TDllEntryProc; bSuccessfull : boolean; begin sErrStr := ''; pResult := nil; try CopyMemory(@rDosHeader,AData,sizeof(_IMAGE_DOS_HEADER)); if (rDosHeader.e_magic IMAGE_DOS_SIGNATURE) then raise Exception.Create('MemoryLoadLibary: DLL DOS header is not valid'); CopyMemory(@rOldHeader, pointer(longint(AData) + rDosHeader._lfanew), sizeof(_IMAGE_NT_HEADERS)); if rOldHeader.Signature IMAGE_NT_SIGNATURE then raise Exception.Create('MemoryLoadLibary: IMAGE_NT_SIGNATURE is not valid'); // Reserve memory for image of library pCode := VirtualAlloc(pointer(rOldHeader.OptionalHeader.ImageBase), rOldHeader.OptionalHeader.SizeOfImage,MEM_RESERVE,PAGE_READWRITE); // Try to allocate memory at arbitrary position if pCode = nil then pCode := VirtualAlloc(nil,rOldHeader.OptionalHeader.SizeOfImage,MEM_RESERVE,PAGE_READWRITE); if pCode = nil then raise Exception.Create('MemoryLoadLibary: VirtualAlloc failed'); // Alloc space for the result record pResult := PBTMemoryModule(HeapAlloc(GetProcessHeap(), 0, sizeof(TBTMemoryModule))); pResult.CodeBase := pCode; pResult.NumModules := 0; pResult.Modules := nil; pResult.Initialized := false; // xy: is it correct to commit the complete memory region at once? // Calling DllEntry raises an exception if we don't... VirtualAlloc(pCode,rOldHeader.OptionalHeader.SizeOfImage,MEM_COMMIT,PAGE_READWRITE); // Commit memory for headers pHeaders := VirtualAlloc(pCode,rOldHeader.OptionalHeader.SizeOfHeaders, MEM_COMMIT,PAGE_READWRITE); // Copy PE header to code CopyMemory(pHeaders,AData,(cardinal(rDosHeader._lfanew) + rOldHeader.OptionalHeader.SizeOfHeaders)); pResult.Headers := PImageNtHeaders(longint(pHeaders) + rDosHeader._lfanew); // Update position pResult.headers.OptionalHeader.ImageBase := cardinal(pCode); // Copy sections from DLL file block to new memory location _CopySections(AData,rOldHeader,pResult); // Adjust base address of imported data iLocationDelta := cardinal(cardinal(pCode) - rOldHeader.OptionalHeader.ImageBase); if iLocationDelta 0 then _PerformBaseRelocation(pResult,iLocationDelta); // Load required dlls and adjust function table of imports if not _BuildImportTable(pResult,sErrStr) then raise Exception.Create(sErrStr + ' MemoryLoadLibary: BuildImportTable failed'); // Mark memory pages depending on section headers and release // sections that are marked as "discardable" _FinalizeSections(pResult); // Get entry point of loaded library if (pResult.Headers.OptionalHeader.AddressOfEntryPoint) 0 then begin @rDllEntry := pointer(cardinal(pCode) + pResult.Headers.OptionalHeader.AddressOfEntryPoint); if @rDllEntry = nil then raise Exception.Create('MemoryLoadLibary: Get DLLEntyPoint failed'); bSuccessfull := rDllEntry(cardinal(pCode),DLL_PROCESS_ATTACH,nil); if not bSuccessfull then raise Exception.Create('MemoryLoadLibary: Can''t attach library'); pResult.Initialized := true; end; except MemoryFreeLibrary(pResult); raise; end; Result := pResult; end; {$ENDREGION} {$REGION 'User Calls'} // ------------------------------------------------------------------------------------------------- // ======================================================== // Load the library from RESOURCE NAME in EXE into memory // ======================================================== function MemoryLoadLibrary(const ADllResName : string) : pointer; var oMs : TMemoryStream; oRs : TResourceStream; iDllDataSize : int64; pDllData : pointer; pResult : PBTMemoryModule; begin pResult := nil; if FindResource(hInstance,PWideChar(ADllResName),RT_RCDATA) 0 then begin oRs := TResourceStream.Create(hInstance,AdllResName,RT_RCDATA); oMs := TMemoryStream.Create; try oMs.LoadFromStream(oRs); oMs.Position := 0; iDllDataSize := oMs.Size; pDllData := GetMemory(iDllDataSize); oMs.Read(PDllData^,iDllDataSize); pResult := pointer(_MemoryLoadLibary(pDllData)); finally oMs.Free; oRs.Free; end; end; Result := pResult; end; // =============================================== // Find the Funcion/Procedure Entry point // =============================================== function MemoryGetProcAddress(AModule : pointer; const AName : string; ACaseSensitive : boolean = false): pointer; stdcall; var sName : AnsiString; pFoundName,pName : PAnsiChar; sFoundName : string; pCodeBase : pointer; iIdx : integer; i : DWORD; pNameRef,pTemp : ^DWORD; pOrdinal : ^word; pExports : PImageExportDirectory; pDirectory : PImageDataDirectory; pModule : PBTMemoryModule; begin pModule := PBTmemoryModule(AModule); pCodeBase := pModule.CodeBase; iIdx := -1; pDirectory := _GetHeaderDictionary(pModule,IMAGE_DIRECTORY_ENTRY_EXPORT); if pDirectory.Size = 0 then raise Exception.Create('MemoryGetProcAddress: no export table found'); pExports := PImageExportDirectory(cardinal(pCodeBase) + pDirectory.VirtualAddress); if ((pExports.NumberOfNames = 0) or (pExports.NumberOfFunctions = 0)) then raise Exception.Create('MemoryGetProcAddress: DLL doesn''t export anything'); // Search function name in list of exported names pNameRef := pointer(cardinal(pCodeBase) + cardinal(pExports.AddressOfNames)); pOrdinal := pointer(cardinal(pCodeBase) + cardinal(pExports.AddressOfNameOrdinals)); for i := 0 to pExports.NumberOfNames - 1 do begin if not ACaseSensitive then begin // Ignore Case pFoundName := PAnsiChar(cardinal(pCodeBase) + pNameRef^); sFoundName := string(pFoundName); if SameText(AName,sFoundName) then begin iIdx := pOrdinal^; break; end; end else begin // Match Case sName := AnsiString(AName); pName := @sName[1]; if StrComp(pName,PAnsiChar(cardinal(pCodeBase) + pNameRef^)) = 0 then begin iIdx := pOrdinal^; break; end; end; inc(pNameRef); inc(pOrdinal); end; if (iIdx = -1) then raise Exception.Create('MemoryGetProcAddress: exported symbol not found'); if (cardinal(iIdx) pExports.NumberOfFunctions - 1) then raise Exception.Create('MemoryGetProcAddress: name - ordinal number don''t match'); // AddressOfFunctions contains the RVAs to the "real" functions pTemp := pointer(cardinal(pCodeBase) + cardinal(pExports.AddressOfFunctions) + cardinal((iIdx * 4))); Result := pointer(cardinal(pCodeBase) + pTemp^); end; // ====================================== // Free the Library if allocated // ====================================== procedure MemoryFreeLibrary(AModule : pointer); stdcall; var pModule1,pModule2 : PBTMemoryModule; i : integer; iTemp : integer; rDllEntry : TDllEntryProc; begin pModule1 := PBTmemoryModule(AModule); pModule2 := PBTmemoryModule(AModule); if pModule1 nil then begin if pModule1.Initialized then begin @rDllEntry := pointer(cardinal(pModule1.CodeBase) + pModule1.Headers.OptionalHeader.AddressOfEntryPoint); rDllEntry(cardinal(pModule1.CodeBase), DLL_PROCESS_DETACH, nil); pModule1.Initialized := false; // Free previously opened libraries for i := 0 to pModule1.NumModules - 1 do begin iTemp := (sizeof(cardinal) * (i)); inc(cardinal(pModule1.Modules),iTemp); if cardinal(pModule2.Modules^) INVALID_HANDLE_VALUE then FreeLibrary(cardinal(pModule2.Modules^)); dec(cardinal(pModule1.Modules),iTemp); end; FreeMemory(pModule1.Modules); if pModule1.CodeBase nil then VirtualFree(pModule1.CodeBase,0,MEM_RELEASE); HeapFree(GetProcessHeap(),0,pModule2); pointer(pModule2) := nil; end; end; end; {$ENDREGION} end.