MemDll - Load DLL from memory

Share your advanced PureBasic knowledge/code with the community.
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

MemDll - Load DLL from memory

Post by luis »

Found this on my HD, thought it was a shame to not make it public since it's some pretty hackish stuff, so here it is.

QNA (Questions Nobody Asked)

Q: If there is MemoryModule already available, why did you write this ?

A: It's true, but this is in PB, also I wanted lo learn something about the PE format and it's easier to debug and experiment with this one from inside a PB program instead of calling a C library.

Code: Select all

; *****************************************************************************
; MemDll.pb
; by Luis
;
; Load a DLL from memory.
; OS: Tested on Windows 7, Windows 10, x86 and x64 with PB 6.00 
;
; 1.10, 2021, PB 5.73
; Converted into a module.
;
; 1.01, 2017, PB 5.60
; Fixed a bug: SizeOfRawData is not VirtualSize !
; https://www.purebasic.fr/english/viewtopic.php?p=508606#p508606
;
; 1.00, 2016, PB 5.60
; First release.
;
; With this module is possible to load a DLL from memory instead of a file.
; Tested on Windows 7 and Windows 10 with just *ONE* DLL both in 32 and 64 bit flavours.
; Feel free to report if a DLL doesn't work for you and I'll try to improve the code if I can.
;
; Thanks to:
;
; - Joachim Bauch (http://www.joachim-bauch.de) for the original code I studied to learn something about what a loader should do.
; - The user "shebaw" (http://www.rohitab.com forum) for some nice C snippets I converted to PB.
; - Countless Microsoft articles and text files describing the PE format.
; - The x64dbg debugger (http://x64dbg.com) to enable me to track a problem.
;
; What this loader does:
;
; 1) Check if the image in memory looks like a valid DLL.
; 2) Reserve enough virtual address space to map the DLL to it, at the preferred address specified in the DLL if possible, else at a different one.
; 3) Map the DLL to virtual space, copies the headers and the sections in memory to the appropriate aligned starting offsets.
; 4) Process the import table, load the required DLLs (dependencies) and patch the table with the loaded function addresses.
; 5) If any of functions is forwarded to another DLL, the target DLL module is retrieved if the DLL is already in memory, else the required DLL is loaded.
; 6) If the DLL has not been mapped to the preferred location, the relocation table is processed and relocations are applied.
; 7) The protection flags specified in the section headers are applied to the sections in memory.
; 8) The #DLL_PROCESS_ATTACH event is signaled to the DLL and its handle is returned.
;
; Limitations:
;
; It's not possible to load from memory a DLL "A" depending on a DLL "B" if "B" is also embedded in memory and not available in the filesystem.
; In other words you can:
; - Load all the DLLs you want from memory as long they are not dependent on each other.
; - Load all the DLLs you want from memory as long their dependencies are available in the file system (tipically OS DLLs : GDI32, KERNEL32, MSVCRT, ...)
;
; The idea is to embed just 1 or 2 DLLs in your program since sometimes may be annoying to have to distribute them separately, 
; For example For a small tech-demo, intro, utility, etc.
; If your program requires many DLLs I think is better to just distribute them as external files along with your program.
;
; Usage example:
;
; IncludeFile "MemDll.pb"
;
; DataSection
;  mydll:
;  IncludeBinary "mydll.dll"
; EndDataSection
;
; handle = MemDll::LoadLibrary(?mydll)
; 
; If handle
;     Prototype.i foo()
;     Global foo.foo = MemDll::GetProcAddress(handle, "foo")
;     foo()
;     MemDll::FreeLibrary(handle)
; EndIf
;
; *****************************************************************************

DeclareModule MemDll 
Declare.i   LoadLibrary (*image)
Declare.i   GetProcAddress (hdll, func$)
Declare     FreeLibrary (hdll)
EndDeclareModule

Module MemDll

EnableExplicit 

#DEBUG_VERBOSE = 0 ; only for testing *DO NOT ENABLE*

#DEBUG_FORCE_RELOCATION = 0 ; only for testing *DO NOT ENABLE*

;{ Some annotations about the PE structure 

; +- [IMAGE_DOS_HEADER] --------------------------------------------------------+
; +     e_magic.w ($5A4D) 'MZ'
; +     e_cblp.w
; +     e_cp.w
; +     e_crlc.w
; +     e_cparhdr.w
; +     e_minalloc.w
; +     e_maxalloc.w
; +     e_ss.w
; +     e_sp.w
; +     e_csum.w
; +     e_ip.w
; +     e_cs.w
; +     e_lfarlc.w
; +     e_ovno.w
; +     e_res.w[4]
; +     e_oemid.w
; +     e_oeminfo.w
; +     e_res2.w[10]
; +     e_lfanew.l -> points to IMAGE_NT_HEADERS signature
; +-----------------------------------------------------------------------------+

; +                                DOS STUB
; +-----------------------------------------------------------------------------+
; + DOS executable printing "This program cannot be run in DOS mode"             
; +-----------------------------------------------------------------------------+

;                       PE HEADER = IMAGE_NT_HEADERS =
;           Signature + IMAGE_FILE_HEADER + IMAGE_OPTIONAL_HEADER

; +- [IMAGE_NT_HEADERS] --------------------------------------------------------+
; +     Signature.l ; $00004550 'PE00'
; +- [IMAGE_FILE_HEADER] -------------------------------------------------------+
; +     Machine.w
; +     NumberOfSections.w ; This indicates the size of the section table, which immediately follows the headers.
; +     TimeDateStamp.l
; +     PointerToSymbolTable.l
; +     NumberOfSymbols.l
; +     SizeOfOptionalHeader.w
; +     Characteristics.w ; Flags that indicate attributes of the object or image file.
; +- [IMAGE_OPTIONAL_HEADER] ---------------------------------------------------+
; + (COFF FIELDS) 
; +     Magic.w
; +     MajorLinkerVersion.b
; +     MinorLinkerVersion.b
; +     SizeOfCode.l
; +     SizeOfInitializedData.l ; Size of the initialized data section, or the sum of all such sections if there are multiple data sections.
; +     SizeOfUninitializedData.l ; Size of the uninitialized data section, or the sum of all such sections if there are multiple sections.
; +     AddressOfEntryPoint.l ; VRA of the entry point (optional for DLLs)
; +     BaseOfCode.l
; +     BaseOfData.l
; + (WINDOWS FIELDS)
; +     ImageBase.i ; The preferred address of the first byte of image when loaded into memory (in other words, of a module)
; +     SectionAlignment.l ; The alignment of sections when they are loaded in memory. It must be greater than or equal to FileAlignment. The default is the page size.
; +     FileAlignment.l ; The alignment used for sections in the image file. If the SectionAlignment is less than page size, then FileAlignment must match SectionAlignment.
; +     MajorOperatingSystemVersion.w
; +     MinorOperatingSystemVersion.w
; +     MajorImageVersion.w
; +     MinorImageVersion.w
; +     MajorSubsystemVersion.w
; +     MinorSubsystemVersion.w
; +     Win32VersionValue.l
; +     SizeOfImage.l  ; The amount of space to reserve in the virtual address space for the loaded executable image. It should be a multiple of SectionAlignment.
; +     SizeOfHeaders.l ; How much space in the file is used for all the file headers, including the MS-DOS header, PE file header, PE optional header, and the sections headers.
; +     CheckSum.l
; +     Subsystem.w
; +     DllCharacteristics.w
; +     SizeOfStackReserve.i
; +     SizeOfStackCommit.i
; +     SizeOfHeapReserve.i
; +     SizeOfHeapCommit.i
; +     LoaderFlags.l
; +     NumberOfRvaAndSizes.l ; The number of the following data directory entries (should always be 16, but could change in the future).
; +- [IMAGE_DATA_DIRECTORY] ----------------------------------------------------+ repeated x [IMAGE_OPTIONAL_HEADER].NumberOfRvaAndSizes
; +     VirtualAddress.l -> RVA of the first structure associated with this specific directory entry (IMAGE_EXPORT_DIRECTORY, IMAGE_IMPORT_DESCRIPTOR, etc.)
; +     Size.l
; +-----------------------------------------------------------------------------+

;                              SECTION HEADERS 
; +- [IMAGE_SECTION_HEADER] ----------------------------------------------------+ repeated x [IMAGE_FILE_HEADER].NumberOfSections 
; +     SecName.b[8]
; +     StructureUnion
; +      PhysicalAddr.l
; +      VirtualSize.l ; The total size of the section when loaded into memory. If this value is greater than SizeOfRawData, the section is zero-padded. 
; +     EndStructureUnion
; +     VirtualAddress.l ; For executable images, the RVA of the first byte of the section.
; +     SizeOfRawData.l ; The size of the initialized data on disk. When a section contains only uninitialized data, this field should be zero.
; +     PointerToRawData.l ; The file pointer to the first page of the section. For executable images, this must be a multiple of FileAlignment from the optional header.
; +     PointerToRelocations.l
; +     PointerToLinenumbers.l
; +     NumberOfRelocations.w
; +     NumberOfLinenumbers.w
; +     Characteristics.l
; +-----------------------------------------------------------------------------+

;                                  SECTIONS 
; +-----------------------------------------------------------------------------+ repeated x [IMAGE_FILE_HEADER].NumberOfSections 
; + Various sections follow here, the contents of the raw data is depending on 
; + the section's type.
; + Some of the most notable are listed below ...
; +-----------------------------------------------------------------------------+

;                                IMPORT SECTION 
;                         (MS usually call it .idata)
; +- [IMAGE_IMPORT_DESCRIPTOR] -------------------------------------------------+ repeated x n times (zero terminated)
; +  OriginalFirstThunk.l -> points to an array of IMAGE_THUNK_DATA
; +  EndStructureUnion
; +  TimeDateStamp.l
; +  ForwarderChain.l
; +  Name.l
; +  FirstThunk.l
; +
; + The array is terminated by a IMAGE_IMPORT_DESCRIPTOR containing all zeroes.
; +-----------------------------------------------------------------------------+

;                                EXPORT SECTION 
;                          (MS usually call it .edata)
; +-----------------------------------------------------------------------------+
;                                     ...
; +-----------------------------------------------------------------------------+

;                              RELOCATION SECTION 
;                          (MS usually call it .reloc)
; +-----------------------------------------------------------------------------+
;                                     ...
; +-----------------------------------------------------------------------------+

;}

;- [ PRIVATE ] 

CompilerIf Defined(ALIGN_ADDRESS_MACRO, #PB_Constant) = 0
 Macro ALIGN_ADDRESS (a, b) 
  (((a + (b - 1)) / b) * b)
 EndMacro
 #ALIGN_ADDRESS_MACRO = 1 
CompilerEndIf

Structure IMAGE_IMPORT_DESCRIPTOR ; from winnt.h
 OriginalFirstThunk.l    
 TimeDateStamp.l
 ForwarderChain.l
 Name.l
 FirstThunk.l
EndStructure

Structure IMAGE_THUNK_DATA ; from winnt.h
 StructureUnion
  ForwarderString.i
  Function.i
  Ordinal.i
  AddressOfData.i
 EndStructureUnion        
EndStructure

Structure IMAGE_IMPORT_BY_NAME ; from winnt.h
 Hint.w ; some linkers may set this value to 0
 Name.a[0]
EndStructure

Structure IMAGE_BASE_RELOCATION ; from winnt.h
 VirtualAddress.l
 SizeOfBlock.l
EndStructure

#IMAGE_DOS_SIGNATURE = $5A4D        ; MZ (Mark Zbikowski's DOS format)
#IMAGE_NT_SIGNATURE =  $00004550    ; PE00 (PE format)

#IMAGE_NT_OPTIONAL_HDR32_MAGIC = $010B ; 32 bit PE
#IMAGE_NT_OPTIONAL_HDR64_MAGIC = $020B ; 64 bit PE

#IMAGE_REL_BASED_ABSOLUTE = 0
#IMAGE_REL_BASED_HIGH = 1
#IMAGE_REL_BASED_LOW = 2
#IMAGE_REL_BASED_HIGHLOW = 3
#IMAGE_REL_BASED_HIGHADJ = 4
#IMAGE_REL_BASED_MIPS_JMPADDR = 5
#IMAGE_REL_BASED_DIR64 = 10

; file header characteristics 
#IMAGE_FILE_RELOCS_STRIPPED	= 1
#IMAGE_FILE_EXECUTABLE_IMAGE = 2
#IMAGE_FILE_LINE_NUMS_STRIPPED = 4
#IMAGE_FILE_LOCAL_SYMS_STRIPPED	= 8
#IMAGE_FILE_AGGRESIVE_WS_TRIM = 16
#IMAGE_FILE_LARGE_ADDRESS_AWARE	= 32
#IMAGE_FILE_BYTES_REVERSED_LO = 128
#IMAGE_FILE_32BIT_MACHINE = 256
#IMAGE_FILE_DEBUG_STRIPPED = 512
#IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 1024
#IMAGE_FILE_NET_RUN_FROM_SWAP = 2048
#IMAGE_FILE_SYSTEM = 4096
#IMAGE_FILE_DLL = 8192
#IMAGE_FILE_UP_SYSTEM_ONLY = 16384
#IMAGE_FILE_BYTES_REVERSED_HI = 32768

; data directories
#IMAGE_DIRECTORY_ENTRY_EXPORT = 0
#IMAGE_DIRECTORY_ENTRY_IMPORT = 1
#IMAGE_DIRECTORY_ENTRY_RESOURCE	= 2
#IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3
#IMAGE_DIRECTORY_ENTRY_SECURITY	= 4
#IMAGE_DIRECTORY_ENTRY_BASERELOC = 5
#IMAGE_DIRECTORY_ENTRY_DEBUG = 6
#IMAGE_DIRECTORY_ENTRY_COPYRIGHT = 7 ; both are 7
#IMAGE_DIRECTORY_ENTRY_ARCHITECTURE	= 7 ; both are 7
#IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8
#IMAGE_DIRECTORY_ENTRY_TLS = 9
#IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10
#IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT	= 11
#IMAGE_DIRECTORY_ENTRY_IAT = 12
#IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT	= 13
#IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14

#IMAGE_SCN_CNT_CODE = 32
#IMAGE_SCN_CNT_INITIALIZED_DATA = 64
#IMAGE_SCN_CNT_UNINITIALIZED_DATA = 128
#IMAGE_SCN_MEM_DISCARDABLE = $2000000
#IMAGE_SCN_MEM_NOT_CACHED = $4000000
#IMAGE_SCN_MEM_NOT_PAGED = $8000000
#IMAGE_SCN_MEM_SHARED = $10000000                       
#IMAGE_SCN_MEM_EXECUTE = $20000000
#IMAGE_SCN_MEM_READ = $40000000
#IMAGE_SCN_MEM_WRITE = $80000000

CompilerIf (#PB_Compiler_Processor = #PB_Processor_x86)
#IMAGE_ORDINAL_FLAG = $80000000
CompilerElse   
#IMAGE_ORDINAL_FLAG = $8000000000000000
CompilerEndIf

Structure MEMDLL_IMPORTED_LIBRARY
 ImportedDllName$
 ImportedDllHandle.i
EndStructure

CompilerIf Defined(DllMain, #PB_Prototype) = 0
 Prototype.i DllMain (hDll, fdwReason, lpvReserved)
CompilerEndIf

Structure MEMDLL_MODULE
 *EntryPoint ; entry point for the specific module 
 List ImportedList.MEMDLL_IMPORTED_LIBRARY() ; list of the DLL imported for the specific module
EndStructure 

Structure OBJ_MEMDLL
 Map ModulesMap.MEMDLL_MODULE() ; map of all the modules loaded with MemDll::LoadLibrary() 
EndStructure : Global OBJ_MEMDLL.OBJ_MEMDLL

CompilerIf #DEBUG_VERBOSE = 1

Procedure ListAllExportedByName (*module)
 Protected *DOS_Header.IMAGE_DOS_HEADER
 Protected *PE_Header.IMAGE_NT_HEADERS  
 Protected *IDD_Export.IMAGE_DATA_DIRECTORY
 Protected *ExportDirectory.IMAGE_EXPORT_DIRECTORY
 Protected *FuncTable, *OrdinalTable, *FuncAddr, *NameTableThunk
 Protected i
 
 *DOS_Header = *module  
 *PE_Header = *module + *DOS_Header\e_lfanew
  
 *IDD_Export = @*PE_Header\OptionalHeader\DataDirectory[#IMAGE_DIRECTORY_ENTRY_EXPORT]  
 *ExportDirectory = *module + *IDD_Export\VirtualAddress  

 *FuncTable = *DOS_Header + *ExportDirectory\AddressOfFunctions
 *OrdinalTable = *DOS_Header + *ExportDirectory\AddressOfNameOrdinals
 *NameTableThunk = *DOS_Header + *ExportDirectory\AddressOfNames
  
 For i = 0 To *ExportDirectory\NumberOfNames - 1        
    *FuncAddr = *module + PeekL(*FuncTable + PeekW(*OrdinalTable + i * SizeOf(Word)) * SizeOf(Long))
    Debug PeekS(*module + PeekL(*NameTableThunk + i * SizeOf(Long)), -1, #PB_Ascii) + " = $"  + Hex(*FuncAddr)
 Next 
EndProcedure
CompilerEndIf

Procedure.i LookForSectionRawData (*module)
 Protected *DOS_Header.IMAGE_DOS_HEADER = *module
 Protected *PE_Header.IMAGE_NT_HEADERS = *module + *DOS_Header\e_lfanew  
 Protected *SectionHeader.IMAGE_SECTION_HEADER = *PE_Header + SizeOf(IMAGE_NT_HEADERS)
 Protected *IDD_BaseReloc.IMAGE_DATA_DIRECTORY = *PE_Header\OptionalHeader\DataDirectory[#IMAGE_DIRECTORY_ENTRY_BASERELOC] 
 Protected i, *raw, *rva = *IDD_BaseReloc\VirtualAddress
 
 ; Looks through all the available sections to see where the passed RVA is pointing. 
 ; Once the related section is found returns an offset from the start of the base address of the file
 ; to the start of the raw data for that section.
 
 For i = 1 To *PE_Header\FileHeader\NumberOfSections 
    If (*rva >= *SectionHeader\VirtualAddress) And (*rva < *SectionHeader\VirtualAddress + *SectionHeader\VirtualSize)
        *raw = *SectionHeader\PointerToRawData + (*rva - *SectionHeader\VirtualAddress)
        Break
    EndIf
    *SectionHeader + SizeOf(IMAGE_SECTION_HEADER)
 Next
  
 ProcedureReturn *raw
EndProcedure

Procedure.i CopySections (*module, *image)
 Protected *DOS_Header.IMAGE_DOS_HEADER = *module
 Protected *PE_Header.IMAGE_NT_HEADERS = *module + *DOS_Header\e_lfanew  
 Protected *DestSection, PaddingLenght, i
 Protected *FirstSectionHeader.IMAGE_SECTION_HEADER = *PE_Header + SizeOf(IMAGE_NT_HEADERS) 
 Protected *SectionHeader.IMAGE_SECTION_HEADER = *FirstSectionHeader  
 Protected NumberOfSections 
 Protected SectionAlignment 

 SectionAlignment = *PE_Header\OptionalHeader\SectionAlignment  
 NumberOfSections = *PE_Header\FileHeader\NumberOfSections 

CompilerIf #DEBUG_VERBOSE = 1  
 Debug "DataDirectory array size = " + *PE_Header\OptionalHeader\NumberOfRvaAndSizes
 Debug "Number of sections = " + NumberOfSections
 Debug ""
 Debug "The copying of the sections to the allocated module in memory starts here ..."
 Debug ""
CompilerEndIf

 For i = 1 To NumberOfSections
 
   CompilerIf #DEBUG_VERBOSE = 1 
    Protected output$ : output$ = ""
    
     Debug "Section n." + i + " = " + PeekS(@*SectionHeader\SecName, SizeOf(*SectionHeader\SecName), #PB_Ascii)
     If *SectionHeader\Characteristics & #IMAGE_SCN_CNT_CODE
        output$ + "CODE "
     EndIf
     If *SectionHeader\Characteristics & #IMAGE_SCN_CNT_INITIALIZED_DATA
        output$ + "INITIALIZED_DATA "
     EndIf
     If *SectionHeader\Characteristics & #IMAGE_SCN_CNT_UNINITIALIZED_DATA
        output$ + "UNITIALIZED_DATA "
     EndIf    
     If *SectionHeader\Characteristics & #IMAGE_SCN_MEM_SHARED
        output$ + "SHAREABLE "
     EndIf
     If *SectionHeader\Characteristics & #IMAGE_SCN_MEM_DISCARDABLE
        output$ + "DISCARDABLE "
     EndIf
     If *SectionHeader\Characteristics & #IMAGE_SCN_MEM_EXECUTE
        output$ + "EXECUTABLE "
     EndIf
     If *SectionHeader\Characteristics & #IMAGE_SCN_MEM_READ
        output$ + "READABLE "
     EndIf
     If *SectionHeader\Characteristics & #IMAGE_SCN_MEM_WRITE
        output$ + "WRITEABLE "
     EndIf
     If output$
        Debug Left(output$, Len(output$)-1)
     EndIf
        
     Debug "VirtualSize = " + *SectionHeader\VirtualSize
     Debug "SizeOfRawData = " + *SectionHeader\SizeOfRawData
     Debug "SectionAlignment = " + SectionAlignment
     Debug "RVA = $" +  Hex(*SectionHeader\VirtualAddress)
     Debug "RAW = $" + Hex(*module + *SectionHeader\VirtualAddress)
   CompilerEndIf
    
    *DestSection = *module + *SectionHeader\VirtualAddress ; This field holds the RVA to where the loader should map the section. 

    If *SectionHeader\SizeOfRawData = 0 ; if zero it's a section of uninitialized data
        ; copy the section data to the appropriate offset in the module
        If *PE_Header\OptionalHeader\SectionAlignment > 0 
            ; minimum size if unitialized is then SectionAlignment           
            FillMemory(*DestSection, *PE_Header\OptionalHeader\SectionAlignment, 0)
        EndIf        
    EndIf
    
    If *SectionHeader\SizeOfRawData > 0           
        CopyMemory(*image + *SectionHeader\PointerToRawData, *DestSection, *SectionHeader\SizeOfRawData) ; 1.01 changed VirtualSize to SizeOfRawData                
                
        ; if VirtualSize > SizeOfRawData the remaining bytes after SizeOfRawData should be preferably set to zero
        If *SectionHeader\VirtualSize > *SectionHeader\SizeOfRawData 
            PaddingLenght = ALIGN_ADDRESS (*SectionHeader\VirtualSize, SectionAlignment) - *SectionHeader\SizeOfRawData                 
            FillMemory(*DestSection + *SectionHeader\SizeOfRawData, PaddingLenght, 0)
            
           CompilerIf #DEBUG_VERBOSE = 1 
            Debug "Padding = " + PaddingLenght
           CompilerEndIf
        EndIf    
    EndIf    
        
    *SectionHeader + SizeOf(IMAGE_SECTION_HEADER)   

   CompilerIf #DEBUG_VERBOSE = 1 
    Debug ""
   CompilerEndIf    

 Next
EndProcedure

Procedure.i AddToImportedLibraries (*module, DllName$, hdll)
 If FindMapElement(OBJ_MEMDLL\ModulesMap(), Str(*module)) = 0 ; module not found in the map
    If AddMapElement(OBJ_MEMDLL\ModulesMap(), Str(*module)) = 0 ; but trying to add it fails
        Goto FAIL
    EndIf
 EndIf
       
 If AddElement(OBJ_MEMDLL\ModulesMap()\ImportedList()) = 0 ; add element to the list of dlls for the specific module
    Goto FAIL
 EndIf
 
 OBJ_MEMDLL\ModulesMap()\ImportedList()\ImportedDllName$ = DllName$
 OBJ_MEMDLL\ModulesMap()\ImportedList()\ImportedDllHandle = hdll  

 ProcedureReturn 1
 
FAIL:
CompilerIf #DEBUG_VERBOSE = 1
 Debug "AddToImportedLibraries() failed !"   
CompilerEndIf
 ProcedureReturn 0 
EndProcedure

Procedure FreeImportedLibraries (*module)
 If FindMapElement(OBJ_MEMDLL\ModulesMap(), Str(*module))
    ForEach OBJ_MEMDLL\ModulesMap()\ImportedList() ; cycle through the list of dlls for the specific module    
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug "Unloading " + OBJ_MEMDLL\ModulesMap()\ImportedList()\ImportedDllName$ + " from $" + Hex(OBJ_MEMDLL\ModulesMap()\ImportedList()\ImportedDllHandle)
       CompilerEndIf       
        If FreeLibrary_(OBJ_MEMDLL\ModulesMap()\ImportedList()\ImportedDllHandle) = 0         
            CallDebugger ; this should never happen
        EndIf
    Next
    ClearList(OBJ_MEMDLL\ModulesMap()\ImportedList()) ; empty the list
    DeleteMapElement(OBJ_MEMDLL\ModulesMap()) ; remove module
 EndIf
EndProcedure

Procedure.i GetProcAddressHelp (*module, func$, ordinal)
 Protected *DOS_Header.IMAGE_DOS_HEADER = *module
 Protected *PE_Header.IMAGE_NT_HEADERS = *module + *DOS_Header\e_lfanew 
 Protected *IDD_Export.IMAGE_DATA_DIRECTORY = *PE_Header\OptionalHeader\DataDirectory[#IMAGE_DIRECTORY_ENTRY_EXPORT]  
 Protected *ExportDirectory.IMAGE_EXPORT_DIRECTORY = *module + *IDD_Export\VirtualAddress  
 Protected *FuncTable, *OrdinalTable, *FuncAddr, *NameTableThunk
 Protected OrdinalBaseValue, i, dot
 Protected DllFwdFull$, DllFwdLib$, DllFwdFunc$, DllFwdHandle
        
 *FuncTable = *DOS_Header + *ExportDirectory\AddressOfFunctions
 *OrdinalTable = *DOS_Header + *ExportDirectory\AddressOfNameOrdinals
 *NameTableThunk = *DOS_Header + *ExportDirectory\AddressOfNames
  
 *FuncAddr = 0

 If ordinal 
    OrdinalBaseValue = *ExportDirectory\Base
    If ordinal < OrdinalBaseValue Or ordinal > OrdinalBaseValue + *ExportDirectory\NumberOfFunctions
        ProcedureReturn 0
    EndIf    
    *FuncAddr = *module + PeekL(*FuncTable + ((ordinal - OrdinalBaseValue) * SizeOf(Long)))
 Else
    
    For i = 0 To *ExportDirectory\NumberOfNames - 1        
        If func$ = PeekS(*module + PeekL(*NameTableThunk + i * SizeOf(Long)), -1, #PB_Ascii)
            *FuncAddr = *module + PeekL(*FuncTable + PeekW(*OrdinalTable + i * SizeOf(Word)) * SizeOf(Long))
            Break
        EndIf
    Next    
 EndIf
 
 ; if the function address is inside the export directory range it can't be a real function pointer
 ; and that means we have found a forwarded function
 
 If (*FuncAddr >= *ExportDirectory) And (*FuncAddr < *ExportDirectory + *IDD_Export\Size)
    DllFwdFull$ = PeekS(*FuncAddr,-1,#PB_Ascii)
   
   CompilerIf #DEBUG_VERBOSE = 1
    Debug " >> " + func$ + " is being forwarded to " + DllFwdFull$
   CompilerEndIf
   
    dot = FindString(DllFwdFull$, ".")
    If dot
        DllFwdLib$ = Left(DllFwdFull$, dot-1)
        DllFwdFunc$ = Mid(DllFwdFull$, dot+1)
    EndIf
   
    DllFwdHandle = GetModuleHandle_(DllFwdLib$) ; check if already there
    If DllFwdHandle 
        *FuncAddr = GetProcAddressHelp(DllFwdHandle, DllFwdFunc$, 0)        
        Goto exit
    EndIf

    DllFwdHandle = LoadLibrary_(DllFwdLib$) ; if not already there, load it 
    If DllFwdHandle 
        *FuncAddr = GetProcAddressHelp(DllFwdHandle, DllFwdFunc$, 0) 
        If AddToImportedLibraries(*module, DllFwdLib$, DllFwdHandle) = 0 
            *FuncAddr = 0 ; signal failure
        EndIf
        Goto exit
    EndIf
    
    *FuncAddr = 0    
 EndIf

exit:

CompilerIf #DEBUG_VERBOSE = 1
 If *FuncAddr = 0 
    If ordinal = 0   
        Debug "Address not found for " + func$
    Else
        Debug "Address not found for ordinal " + ordinal
    EndIf    
 EndIf 
CompilerEndIf
    
 ProcedureReturn *FuncAddr
EndProcedure

Procedure.i LoadFromImportTable (*module)
 Protected *DOS_Header.IMAGE_DOS_HEADER = *module
 Protected *PE_Header.IMAGE_NT_HEADERS = *module + *DOS_Header\e_lfanew  
 Protected *IDD_Import.IMAGE_DATA_DIRECTORY = *PE_Header\OptionalHeader\DataDirectory[#IMAGE_DIRECTORY_ENTRY_IMPORT]  
 Protected *FirstImportDesc.IMAGE_IMPORT_DESCRIPTOR = *module + *IDD_Import\VirtualAddress
 Protected *ImportDesc.IMAGE_IMPORT_DESCRIPTOR = *FirstImportDesc
 Protected *NameTableThunk.IMAGE_THUNK_DATA, *AddressTableThunk.IMAGE_THUNK_DATA, *Thunk.IMAGE_THUNK_DATA
 Protected *ImportByName.IMAGE_IMPORT_BY_NAME
 Protected DllName$, func$, hdll, ordinal

CompilerIf #DEBUG_VERBOSE = 1         
 Debug "The loading of the imported DLLs and the patching of function addresses starts here ..."
CompilerEndIf
   
 While IsBadReadPtr_(*ImportDesc, SizeOf(IMAGE_IMPORT_DESCRIPTOR)) = 0 And *ImportDesc\Name        
    DllName$ = PeekS(*module + *ImportDesc\Name, -1, #PB_Ascii)
        
    hdll = LoadLibrary_(DllName$)
                    
    If hdll = 0
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug "Unable to load library " + DllName$ + ". Abort."
       CompilerEndIf            
        Goto FAIL            
    EndIf
    
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug ""
    Debug "Loaded " + DllName$ + " = $" + Hex(hdll)
   CompilerEndIf
    
    If AddToImportedLibraries(*module, DllName$, hdll) = 0
        Goto FAIL
    EndIf
    
    *NameTableThunk  = *module + *ImportDesc\OriginalFirstThunk
    *AddressTableThunk  = *module + *ImportDesc\FirstThunk
    
    If *ImportDesc\OriginalFirstThunk
        *Thunk = *NameTableThunk
    ElseIf *AddressTableThunk
        *Thunk = *AddressTableThunk
    EndIf               
    
    If *Thunk = 0
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug "Both the name table and the addres table looks empty for " + DllName$ + ". Abort."       
       CompilerEndIf
        Goto FAIL
    EndIf
    
    While *Thunk\AddressOfData
        ordinal = 0
        
        If *Thunk\Ordinal & #IMAGE_ORDINAL_FLAG ; this is an ordinal entry (unlikely)
            ordinal = *Thunk\Ordinal & $ffff 
            func$ = Hex(ordinal)
        Else
            *ImportByName = *module + *Thunk\AddressOfData ; this is an import by name entry
            func$ = PeekS(@*ImportByName\Name[0], -1, #PB_Ascii)               
        EndIf
                        
        *AddressTableThunk\Function = GetProcAddressHelp(hdll, func$, ordinal)
                
       CompilerIf #DEBUG_VERBOSE = 1 
        If ordinal
            Debug " (" + ordinal + ") -> $" + Hex(*AddressTableThunk\Function)
        Else
            Debug " " + func$ + " -> $" + Hex(*AddressTableThunk\Function)
        EndIf
       CompilerEndIf
 
        *Thunk + SizeOf(IMAGE_THUNK_DATA)
        *AddressTableThunk + SizeOf(IMAGE_THUNK_DATA)
    Wend
 
    *ImportDesc + SizeOf(IMAGE_IMPORT_DESCRIPTOR)        
 Wend
 
 ProcedureReturn 1
 
FAIL: 
 FreeImportedLibraries(*module)
 ProcedureReturn 0
EndProcedure
 
Procedure PatchRelocationTable (*module, *BaseReloc.IMAGE_BASE_RELOCATION, *PreferredBase)
 Protected *DOS_Header.IMAGE_DOS_HEADER = *module
 Protected *PE_Header.IMAGE_NT_HEADERS = *module + *DOS_Header\e_lfanew  
 Protected *IDD_BaseReloc.IMAGE_DATA_DIRECTORY = *PE_Header\OptionalHeader\DataDirectory[#IMAGE_DIRECTORY_ENTRY_BASERELOC] 
 Protected *CurReloc.IMAGE_BASE_RELOCATION = *BaseReloc  
 Protected *RelocEnd.IMAGE_BASE_RELOCATION = *BaseReloc + *IDD_BaseReloc\Size
 Protected *CurWordEntry.Word, *TargetRelocItem.Integer, *SectionBase
 Protected Delta = *module - *PreferredBase
 Protected RelocationsCount, RelocTypeWord, i

CompilerIf #DEBUG_VERBOSE = 1 
 Protected output$
 Protected total_expected, total_valid, partial_valid
 Debug "Actual module base = $" + Hex(*module)
 Debug "Preferred image base = $" + Hex(*PreferredBase) 
 Debug "Delta = " + Delta
CompilerEndIf

 While (*CurReloc < *RelocEnd) And *CurReloc\VirtualAddress
    RelocationsCount = (*CurReloc\SizeOfBlock - SizeOf(IMAGE_BASE_RELOCATION)) / SizeOf(Word) ; relocations count
    
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "" 
    Debug "Start of a new relocation block." 
    Debug "Size of block = " + *CurReloc\SizeOfBlock
    Debug "Expected relocation entries = " + RelocationsCount     
   CompilerEndIf
   
    *CurWordEntry = *CurReloc + SizeOf(IMAGE_BASE_RELOCATION)
    *SectionBase = *module + *CurReloc\VirtualAddress
        
   CompilerIf #DEBUG_VERBOSE = 1
    total_expected + RelocationsCount
    partial_valid = 0
   CompilerEndIf
        
    For i = 1 To RelocationsCount
        RelocTypeWord = (*CurWordEntry\w >> 12) & $000F ; binary shifted word instead of the arithmetic one (mask out the high 12 bits after the shift)
            
       CompilerIf #DEBUG_VERBOSE = 1 
        output$ = Str(i) +" RVA ($" + Hex(*CurReloc\VirtualAddress + *CurWordEntry\w & $0fff) + "), "
        If (RelocTypeWord) = #IMAGE_REL_BASED_HIGHLOW
            output$ + "IMAGE_REL_BASED_HIGHLOW"
        ElseIf (RelocTypeWord) = #IMAGE_REL_BASED_DIR64
            output$ + "IMAGE_REL_BASED_DIR64"
        Else
            output$ + Str(RelocTypeWord)
        EndIf
       CompilerEndIf
       
        If (RelocTypeWord = #IMAGE_REL_BASED_HIGHLOW) Or (RelocTypeWord = #IMAGE_REL_BASED_DIR64) 
           CompilerIf #DEBUG_VERBOSE = 1        
            total_valid + 1
            partial_valid + 1
           CompilerEndIf
            
            *TargetRelocItem = *SectionBase + (*CurWordEntry\w & $0fff) ; only the low 12 bits of the word            
            *TargetRelocItem\i + Delta
            
           CompilerIf #DEBUG_VERBOSE = 1 
            output$ + ", $" + Hex(*TargetRelocItem\i + Delta)
           CompilerEndIf
        EndIf
                
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug output$
       CompilerEndIf
        
        *CurWordEntry + SizeOf(Word)
    Next
    
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "Valid relocation entries processed = " + partial_valid
   CompilerEndIf
    
    *CurReloc = *CurReloc + *CurReloc\SizeOfBlock    
 Wend

CompilerIf #DEBUG_VERBOSE = 1 
 Debug ""
 Debug "Total expected relocation entries = " + total_expected
 Debug "Total valid relocation entries processed = " + total_valid 
 Debug ""
CompilerEndIf

EndProcedure

Procedure.i SectorToVirtualMemProtectionFlags (secp)
 Protected vmp
 Protected executable, readable, writable
 Protected Dim vmflags(1,1,1)
 
 vmflags(0,0,0) = #PAGE_NOACCESS
 vmflags(0,0,1) = #PAGE_WRITECOPY
 vmflags(0,1,0) = #PAGE_READONLY
 vmflags(0,1,1) = #PAGE_READWRITE

 vmflags(1,0,0) = #PAGE_EXECUTE
 vmflags(1,0,1) = #PAGE_EXECUTE_WRITECOPY
 vmflags(1,1,0) = #PAGE_EXECUTE_READ
 vmflags(1,1,1) = #PAGE_EXECUTE_READWRITE

 If (secp & #IMAGE_SCN_MEM_EXECUTE) 
    executable = #True
 EndIf
 
 If (secp & #IMAGE_SCN_MEM_READ) 
    readable = #True 
 EndIf
 
 If (secp & #IMAGE_SCN_MEM_WRITE) 
    writable = #True
 EndIf 
 
 vmp = vmflags(executable, readable, writable)
 
 If (secp & #IMAGE_SCN_MEM_NOT_CACHED)
    vmp = vmp | #PAGE_NOCACHE
 EndIf
 
 ProcedureReturn vmp
EndProcedure
 
Procedure.i SetProtectionFlagsForSections (*module)
 Protected *DOS_Header.IMAGE_DOS_HEADER = *module  
 Protected *PE_Header.IMAGE_NT_HEADERS = *module + *DOS_Header\e_lfanew
 Protected *SectionHeader.IMAGE_SECTION_HEADER
 Protected *Section, OldProtect, NewProtect, i, SectionSize
 
 ; set PE header to readonly
 VirtualProtect_(*module, *PE_Header\OptionalHeader\SizeOfHeaders, #PAGE_READONLY, @OldProtect)
 
 *SectionHeader = *PE_Header + SizeOf(IMAGE_NT_HEADERS)
 
 For i = 1 To *PE_Header\FileHeader\NumberOfSections 
 
    *Section = *module + *SectionHeader\VirtualAddress    
    
    SectionSize = *SectionHeader\VirtualSize ; I think this should be VirtualSize and not SizeOfRawData (cfr. Joachim Bauch)
    
    If *SectionHeader\Characteristics & #IMAGE_SCN_MEM_DISCARDABLE ; let's throw away any temporary section
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug "Discarding section " + PeekS(@*SectionHeader\SecName, SizeOf(*SectionHeader\SecName), #PB_Ascii) + " (size = " + SectionSize + ")"
       CompilerEndIf

        VirtualFree_(*Section, SectionSize, #MEM_DECOMMIT) 
        *SectionHeader + SizeOf(IMAGE_SECTION_HEADER) ; do not forget this !
        Continue        
    EndIf
    
    ; translate section protection to VAS protections
    NewProtect = SectorToVirtualMemProtectionFlags (*SectionHeader\Characteristics) 

    If VirtualProtect_(*Section, SectionSize, NewProtect, @OldProtect)    
       CompilerIf #DEBUG_VERBOSE = 1
        Protected output$
        output$ = "Protecting section " + PeekS(@*SectionHeader\SecName, SizeOf(*SectionHeader\SecName), #PB_Ascii) + " (" 
        Select NewProtect
            Case #PAGE_NOACCESS
                output$ + "PAGE_NOACCESS"
            Case #PAGE_WRITECOPY
                output$ + "PAGE_WRITECOPY"
            Case #PAGE_READONLY
                output$ + "PAGE_READONLY"    
            Case #PAGE_READWRITE
                output$ + "PAGE_READWRITE"
            Case #PAGE_EXECUTE
                output$ + "PAGE_EXECUTE"    
            Case #PAGE_EXECUTE_WRITECOPY
                output$ + "PAGE_EXECUTE_WRITECOPY"           
            Case #PAGE_EXECUTE_READ
                output$ + "PAGE_EXECUTE_READ"    
            Case #PAGE_EXECUTE_READWRITE
                output$ + "PAGE_EXECUTE_READWRITE"
            Default
                output$ + "???"    
        EndSelect    
        Debug output$ + ")"
       CompilerEndIf
    Else    
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug "Error in VirtualProtect_() for section n. " + i
       CompilerEndIf
        Goto FAIL
    EndIf
    
    *SectionHeader + SizeOf(IMAGE_SECTION_HEADER)
 Next
 
 ProcedureReturn 1
 
FAIL: 
 ProcedureReturn 0
EndProcedure
  
;- [ PUBLIC ] 

Procedure.i LoadLibrary (*image)
 Protected *DOS_Header.IMAGE_DOS_HEADER
 Protected *PE_Header.IMAGE_NT_HEADERS        
 Protected *BaseReloc.IMAGE_BASE_RELOCATION
 Protected *PreferredBase, *module 
 Protected RetCode, SizeOfImage, SizeOfHeaders, OffsetToRawDataOfRelocationTable, flgRelocationRequired
 Protected DllMain.DllMain
 
 ;****************************************************************************************************
 ; check if all it is as it should be ...
 ;****************************************************************************************************
 
 If *image = #Null
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "The passed memory address is null. Abort."
   CompilerEndIf    
    Goto FAIL
 EndIf
 
 *DOS_Header = *image ; the image of the DLL starts with the old DOS header
 
 If *DOS_Header\e_magic <> #IMAGE_DOS_SIGNATURE
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "DOS header signature is missing. Abort."
   CompilerEndIf
    Goto FAIL
 EndIf
 
 *PE_Header = *image + *DOS_Header\e_lfanew ; follows the link to the PE header

 ; check for a valid PE 
 If *PE_Header\Signature <> #IMAGE_NT_SIGNATURE
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "PE header signature is missing. Abort."  
   CompilerEndIf
    Goto FAIL
 EndIf
 
 If *PE_Header\FileHeader\Characteristics & #IMAGE_FILE_DLL = 0 ; check if this is a valid DLL
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "This is not a DLL. Abort."
   CompilerEndIf     
    Goto FAIL
 EndIf
  
CompilerIf (#PB_Compiler_Processor = #PB_Processor_x86)
 If *PE_Header\OptionalHeader\Magic <> #IMAGE_NT_OPTIONAL_HDR32_MAGIC ; we expect a 32 bit DLL
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "This is not a 32 bit DLL. Abort."
   CompilerEndIf    
    Goto FAIL
 EndIf
CompilerElse   
 If *PE_Header\OptionalHeader\Magic <> #IMAGE_NT_OPTIONAL_HDR64_MAGIC ; we expect a 64 bit DLL
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "This is not a 64 bit DLL. Abort."
   CompilerEndIf    
    Goto FAIL
 EndIf
CompilerEndIf
 
 *PreferredBase = *PE_Header\OptionalHeader\ImageBase           
 SizeOfImage = *PE_Header\OptionalHeader\SizeOfImage 
 SizeOfHeaders = *PE_Header\OptionalHeader\SizeOfHeaders 
 
CompilerIf #DEBUG_VERBOSE = 1 
 Debug "DLL preferred base address = $" + Hex(*PreferredBase)
CompilerEndIf
  
 ;****************************************************************************************************
 ; allocate the module in virtual address space (VAS)
 ;****************************************************************************************************
 
CompilerIf #DEBUG_FORCE_RELOCATION = 1 
 Protected *PreferredBaseForcedToReserved = VirtualAlloc_(*PreferredBase, SizeOfImage, #MEM_RESERVE, #PAGE_READWRITE) 
CompilerEndIf 
 
 ; we first try to allocate it to the preferred base address
 *module = VirtualAlloc_(*PreferredBase, SizeOfImage, #MEM_RESERVE, #PAGE_READWRITE)
 
 If *module = #Null ; that space must be already reserved for something... no luck
    flgRelocationRequired = 1 ; take note of it
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "The preferred base address is already in use, relocation is required."
   CompilerEndIf
    ; do we have the relocation table ?
    If (*PE_Header\OptionalHeader\DataDirectory[#IMAGE_DIRECTORY_ENTRY_BASERELOC]\Size = 0) Or (*PE_Header\FileHeader\Characteristics & #IMAGE_FILE_RELOCS_STRIPPED)                
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug "... and the module is not relocatable. Abort."
       CompilerEndIf
        Goto FAIL
    EndIf        
 EndIf
 
 If flgRelocationRequired 
    ; we try to allocate it somewhere else
    *module = VirtualAlloc_(#Null, SizeOfImage, #MEM_RESERVE, #PAGE_READWRITE)    
    If *module = #Null
       CompilerIf #DEBUG_VERBOSE = 1 
        Debug "An error happened trying to allocate the module at a new address. Abort."
       CompilerEndIf
       Goto FAIL
     EndIf    
 EndIf
 
 ; now we have a good address range all for ourself, let's commit the memory
 *module = VirtualAlloc_(*module, SizeOfImage, #MEM_COMMIT, #PAGE_READWRITE) 
 
CompilerIf #DEBUG_VERBOSE = 1 
 Debug "Module loaded at $" + Hex(*module)
 Debug "End of module at $" + Hex(*module + SizeOfImage)
 Debug "Size of the Module = $" + Hex(SizeOfImage) 
 If (SizeOfImage % *PE_Header\OptionalHeader\SectionAlignment) <> 0
    Debug "WARNING: module's size is not a multiple of SectionAlignment."
 EndIf
CompilerEndIf
 
 ;****************************************************************************************************
 ; copy DOS header, PE header and the section table arrays in one shot to the allocated module
 ;****************************************************************************************************
 
 CopyMemory(*image, *module, SizeOfHeaders) ; a simple CopyMemory() is enough
  
 ;****************************************************************************************************
 ; copy the sections each one at its appropriate starting address
 ;****************************************************************************************************
 
 CopySections(*module, *image) 

CompilerIf #DEBUG_VERBOSE = 1 
 Debug ""
 Debug "List of all functions exported by name ..."
 Debug ""
 ListAllExportedByName(*module) ; just a convenience, could be useful to have
 Debug ""
CompilerEndIf

 ;****************************************************************************************************
 ; load imports (the other DLLs required by this one) and patch the function addresses
 ;****************************************************************************************************
 
 If LoadFromImportTable(*module) = 0 
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "An error happened while processing the import table. Abort."
   CompilerEndIf
    Goto FAIL 
 EndIf
 
 ;****************************************************************************************************
 ; update the relocation table with new addresses if it is required
 ;****************************************************************************************************
 
 If flgRelocationRequired
    OffsetToRawDataOfRelocationTable = LookForSectionRawData (*module)  ; This is the RVA to the first entry of type IMAGE_BASE_RELOCATION in the relocation table.   
    *BaseReloc = *image + OffsetToRawDataOfRelocationTable
    
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug ""
    Debug "The relocation of entries in the relocation table starts here ..."
    Debug ""
    Debug "Offset to relocation table data = $" +  Hex(OffsetToRawDataOfRelocationTable)   
    Debug "Absolute start of the relocation table data = $" + Hex(*BaseReloc)
   CompilerEndIf
        
    PatchRelocationTable(*module, *BaseReloc, *PreferredBase)
 EndIf
    
CompilerIf #DEBUG_VERBOSE = 1    
 If flgRelocationRequired = 0
    Debug ""
    Debug "Relocation is not required, DLL was loaded at the preferred address."
    Debug ""
 EndIf    
CompilerEndIf   
 
 ;****************************************************************************************************
 ; cycle through all sections and apply the appropriate protection settings
 ;****************************************************************************************************
 
 If SetProtectionFlagsForSections(*module) = 0
   CompilerIf #DEBUG_VERBOSE = 1 
    Debug "An error happened while setting section protections. Abort."
   CompilerEndIf
    Goto FAIL 
 EndIf

 ;****************************************************************************************************
 ; If there is a valid entry point, send a #DLL_PROCESS_ATTACH notification
 ;****************************************************************************************************

 If *PE_Header\OptionalHeader\AddressOfEntryPoint
    DllMain = *module + *PE_Header\OptionalHeader\AddressOfEntryPoint
    OBJ_MEMDLL\ModulesMap(Str(*module))\EntryPoint = DllMain
    
    RetCode = DllMain(*module, #DLL_PROCESS_ATTACH, 0)
    
   CompilerIf #DEBUG_VERBOSE = 1     
    Debug ""
    Debug "DllMain -> #DLL_PROCESS_ATTACH = " + RetCode
   CompilerEndIf

    If RetCode = 0        
        Goto FAIL
    EndIf
 EndIf

CompilerIf #DEBUG_VERBOSE = 1 
 Debug ""
CompilerEndIf        

CompilerIf #DEBUG_FORCE_RELOCATION = 1 
 If *PreferredBaseForcedToReserved
    VirtualFree_(*PreferredBaseForcedToReserved, 0, #MEM_RELEASE)
 EndIf   
CompilerEndIf 

 ProcedureReturn *module ; all done
 
FAIL:  
 If *module
    VirtualFree_(*module, 0, #MEM_RELEASE)
    FreeImportedLibraries(*module)        
 EndIf
 
CompilerIf #DEBUG_VERBOSE = 1 
 Debug ""
CompilerEndIf        

CompilerIf #DEBUG_FORCE_RELOCATION = 1 
 If *PreferredBaseForcedToReserved
    VirtualFree_(*PreferredBaseForcedToReserved, 0, #MEM_RELEASE)
 EndIf   
CompilerEndIf 
 
 ProcedureReturn 0 ; something went wrong 
EndProcedure
 
Procedure.i GetProcAddress (*module, func$)
 ProcedureReturn GetProcAddressHelp (*module, func$, 0)
EndProcedure

Procedure FreeLibrary (*module) 
 Protected *DOS_Header.IMAGE_DOS_HEADER = *module
 Protected *PE_Header.IMAGE_NT_HEADERS = *module + *DOS_Header\e_lfanew
 Protected DllMain.DllMain = OBJ_MEMDLL\ModulesMap(Str(*module))\EntryPoint
 
 If DllMain
    CompilerIf #DEBUG_VERBOSE = 1 
     Debug ""   
     Debug "DllMain -> #DLL_PROCESS_DETACH"
    CompilerEndIf                
 
    DllMain(*module, #DLL_PROCESS_DETACH, 0)
 EndIf
    
 If VirtualFree_(*module, 0, #MEM_RELEASE) = 0    
    CallDebugger ; this should never happen
 EndIf
 
 FreeImportedLibraries(*module)
EndProcedure

EndModule
Last edited by luis on Wed Apr 12, 2023 11:28 am, edited 19 times in total.
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Load DLL from memory

Post by luis »

example of verbose debug output with a DLL loaded at its preferred base address

Code: Select all

[21:23:53] DLL preferred base address = $6E5C0000
[21:23:53] Module loaded at $6E5C0000
[21:23:53] End of module at $6E5E4000
[21:23:53] Size of the Module = $24000
[21:23:53] DataDirectory array size = 16
[21:23:53] Number of sections = 17
[21:23:53] 
[21:23:53] The copying of the sections to the allocated module in memory starts here ...
[21:23:53] 
[21:23:53] Section n.1 = .text
[21:23:53] CODE INITIALIZED_DATA EXECUTABLE READABLE
[21:23:53] VirtualSize = 61652
[21:23:53] SizeOfRawData = 61952
[21:23:53] SectionAlignment = 4096
[21:23:53] RVA = $1000
[21:23:53] RAW = $6E5C1000
[21:23:53] 
[21:23:53] Section n.2 = .data
[21:23:53] INITIALIZED_DATA READABLE WRITEABLE
[21:23:53] VirtualSize = 32
[21:23:53] SizeOfRawData = 512
[21:23:53] SectionAlignment = 4096
[21:23:53] RVA = $11000
[21:23:53] RAW = $6E5D1000
[21:23:53] 
[21:23:53] Section n.3 = .rdata
[21:23:53] INITIALIZED_DATA READABLE
[21:23:53] VirtualSize = 5108
[21:23:53] SizeOfRawData = 5120
[21:23:53] SectionAlignment = 4096
[21:23:53] RVA = $12000
[21:23:53] RAW = $6E5D2000
[21:23:53] 
[21:23:53] Section n.4 = /4
[21:23:53] INITIALIZED_DATA READABLE
[21:23:53] VirtualSize = 3504
[21:23:53] SizeOfRawData = 3584
[21:23:53] SectionAlignment = 4096
[21:23:53] RVA = $14000
[21:23:53] RAW = $6E5D4000
...
[21:23:53] Section n.17 = /91
[21:23:53] INITIALIZED_DATA DISCARDABLE READABLE
[21:23:53] VirtualSize = 200
[21:23:53] SizeOfRawData = 512
[21:23:53] SectionAlignment = 4096
[21:23:53] RVA = $23000
[21:23:53] RAW = $6E5E3000
[21:23:53] 
[21:23:53] 
[21:23:53] List of all functions exported by name ...
[21:23:53] 
[21:23:53] glfwCreateCursor = $6E5C2BCB
[21:23:53] glfwCreateStandardCursor = $6E5C2C5C
[21:23:53] glfwCreateWindow = $6E5C41DF
[21:23:53] glfwDefaultWindowHints = $6E5C45BE
[21:23:53] glfwDestroyCursor = $6E5C2D33
...
[21:23:53] glfwWaitEvents = $6E5C5414
[21:23:53] glfwWindowHint = $6E5C4697
[21:23:53] glfwWindowShouldClose = $6E5C4AC2
[21:23:53] 
[21:23:53] The loading of the imported DLLs and the patching of function addresses starts here ...
[21:23:53] 
[21:23:53] Loaded GDI32.dll = $769C0000
[21:23:53]  CreateBitmap -> $769D5D53
...
[21:23:53]  SetPixelFormat -> $76A0594C
[21:23:53]  SwapBuffers -> $76A059FB
[21:23:53] 
[21:23:53] Loaded KERNEL32.dll = $76820000
[21:23:53]  >> DeleteCriticalSection is being forwarded to NTDLL.RtlDeleteCriticalSection
[21:23:53]  DeleteCriticalSection -> $77254625
[21:23:53]  >> EnterCriticalSection is being forwarded to NTDLL.RtlEnterCriticalSection
[21:23:53]  EnterCriticalSection -> $772422C0
[21:23:53]  FreeLibrary -> $76833468
...
[21:23:53]  VirtualQuery -> $768343FA
[21:23:53]  WideCharToMultiByte -> $768316DD
[21:23:53] 
[21:23:53] Loaded msvcrt.dll = $764A0000
[21:23:53]  _strdup -> $764C47AD
[21:23:53] 
[21:23:53] Loaded msvcrt.dll = $764A0000
[21:23:53]  __dllonexit -> $764AF509
[21:23:53]  __mb_cur_max -> $76543148
...
[21:23:53]  wcscpy -> $764BD4F8
[21:23:53]  wcslen -> $764BD335
[21:23:53] 
[21:23:53] Loaded SHELL32.DLL = $75520000
[21:23:53]  DragAcceptFiles -> $75641C31
[21:23:53]  DragFinish -> $757352E9
[21:23:53]  DragQueryFileW -> $7573552D
[21:23:53]  DragQueryPoint -> $75735286
[21:23:53] 
[21:23:53] Loaded USER32.dll = $76170000
[21:23:53]  AdjustWindowRectEx -> $761951E2
[21:23:53]  BringWindowToTop -> $76197B3B
...
[21:23:53]  WaitMessage -> $761AF5A9
[21:23:53]  WindowFromPoint -> $761AED12
[21:23:53] 
[21:23:53] Relocation is not required, DLL was loaded at the preferred address.
[21:23:53] 
[21:23:53] Protecting section .text (PAGE_EXECUTE_READ)
[21:23:53] Protecting section .data (PAGE_READWRITE)
[21:23:53] Protecting section .rdata (PAGE_READONLY)
[21:23:53] Protecting section /4 (PAGE_READONLY)
[21:23:53] Protecting section .bss (PAGE_READWRITE)
[21:23:53] Protecting section .edata (PAGE_READONLY)
[21:23:53] Protecting section .idata (PAGE_READWRITE)
[21:23:53] Protecting section .CRT (PAGE_READWRITE)
[21:23:53] Protecting section .tls (PAGE_READWRITE)
[21:23:53] Discarding section .reloc (size = 2224)
[21:23:53] Discarding section /14 (size = 120)
[21:23:53] Discarding section /29 (size = 6418)
[21:23:53] Discarding section /41 (size = 876)
[21:23:53] Discarding section /55 (size = 889)
[21:23:53] Discarding section /67 (size = 356)
[21:23:53] Discarding section /80 (size = 3206)
[21:23:53] Discarding section /91 (size = 200)
[21:23:53] 
[21:23:53] ModuleEntryPoint -> #DLL_PROCESS_ATTACH = 1
[21:23:53] 
[21:23:53] 3.1.2 Win32 WGL MinGW DLL
[21:23:53] Unloading GDI32.dll
[21:23:53] Unloading KERNEL32.dll
[21:23:53] Unloading msvcrt.dll
[21:23:53] Unloading msvcrt.dll
[21:23:53] Unloading SHELL32.DLL
[21:23:53] Unloading USER32.dll
Last edited by luis on Thu Jun 21, 2018 11:32 pm, edited 2 times in total.
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Load DLL from memory

Post by luis »

example of verbose debug output with a DLL NOT loaded at its preferred base address and so manual relocation is applied

Code: Select all

[21:26:36] DLL preferred base address = $6E5C0000
[21:26:36] The preferred base address is already in use, relocation is required.
[21:26:36] Module loaded at $3D0000
[21:26:36] End of module at $3F4000
[21:26:36] Size of the Module = $24000
[21:26:36] DataDirectory array size = 16
[21:26:36] Number of sections = 17
[21:26:36] 
[21:26:36] The copying of the sections to the allocated module in memory starts here ...
[21:26:36] 
[21:26:36] Section n.1 = .text
[21:26:36] CODE INITIALIZED_DATA EXECUTABLE READABLE
[21:26:36] VirtualSize = 61652
[21:26:36] SizeOfRawData = 61952
[21:26:36] SectionAlignment = 4096
[21:26:36] RVA = $1000
[21:26:36] RAW = $3D1000
[21:26:36] 
[21:26:36] Section n.2 = .data
[21:26:36] INITIALIZED_DATA READABLE WRITEABLE
[21:26:36] VirtualSize = 32
[21:26:36] SizeOfRawData = 512
[21:26:36] SectionAlignment = 4096
[21:26:36] RVA = $11000
[21:26:36] RAW = $3E1000
[21:26:36] 
[21:26:36] Section n.3 = .rdata
[21:26:36] INITIALIZED_DATA READABLE
[21:26:36] VirtualSize = 5108
[21:26:36] SizeOfRawData = 5120
[21:26:36] SectionAlignment = 4096
[21:26:36] RVA = $12000
[21:26:36] RAW = $3E2000
[21:26:36] 
[21:26:36] Section n.4 = /4
[21:26:36] INITIALIZED_DATA READABLE
[21:26:36] VirtualSize = 3504
[21:26:36] SizeOfRawData = 3584
[21:26:36] SectionAlignment = 4096
[21:26:36] RVA = $14000
[21:26:36] RAW = $3E4000
...
[21:26:36] Section n.17 = /91
[21:26:36] INITIALIZED_DATA DISCARDABLE READABLE
[21:26:36] VirtualSize = 200
[21:26:36] SizeOfRawData = 512
[21:26:36] SectionAlignment = 4096
[21:26:36] RVA = $23000
[21:26:36] RAW = $3F3000
[21:26:36] 
[21:26:36] 
[21:26:36] List of all functions exported by name ...
[21:26:36] 
[21:26:36] glfwCreateCursor = $3D2BCB
[21:26:36] glfwCreateStandardCursor = $3D2C5C
[21:26:36] glfwCreateWindow = $3D41DF
...
[21:26:36] glfwWaitEvents = $3D5414
[21:26:36] glfwWindowHint = $3D4697
[21:26:36] glfwWindowShouldClose = $3D4AC2
[21:26:36] 
[21:26:36] The loading of the imported DLLs and the patching of function addresses starts here ...
[21:26:36] 
[21:26:36] Loaded GDI32.dll = $769C0000
[21:26:36]  CreateBitmap -> $769D5D53
[21:26:36]  CreateDCW -> $769DE743
...
[21:26:36]  SetPixelFormat -> $76A0594C
[21:26:36]  SwapBuffers -> $76A059FB
[21:26:36] 
[21:26:36] Loaded KERNEL32.dll = $76820000
[21:26:36]  >> DeleteCriticalSection is being forwarded to NTDLL.RtlDeleteCriticalSection
[21:26:36]  DeleteCriticalSection -> $77254625
[21:26:36]  >> EnterCriticalSection is being forwarded to NTDLL.RtlEnterCriticalSection
[21:26:36]  EnterCriticalSection -> $772422C0
...
[21:26:36]  VirtualQuery -> $768343FA
[21:26:36]  WideCharToMultiByte -> $768316DD
[21:26:36] 
[21:26:36] Loaded msvcrt.dll = $764A0000
[21:26:36]  _strdup -> $764C47AD
[21:26:36] 
[21:26:36] Loaded msvcrt.dll = $764A0000
[21:26:36]  __dllonexit -> $764AF509
[21:26:36]  __mb_cur_max -> $76543148
...
[21:26:36]  wcscpy -> $764BD4F8
[21:26:36]  wcslen -> $764BD335
[21:26:36] 
[21:26:36] Loaded SHELL32.DLL = $75520000
[21:26:36]  DragAcceptFiles -> $75641C31
[21:26:36]  DragFinish -> $757352E9
[21:26:36]  DragQueryFileW -> $7573552D
[21:26:36]  DragQueryPoint -> $75735286
[21:26:36] 
[21:26:36] Loaded USER32.dll = $76170000
[21:26:36]  AdjustWindowRectEx -> $761951E2
[21:26:36]  BringWindowToTop -> $76197B3B
...
[21:26:36]  WaitMessage -> $761AF5A9
[21:26:36]  WindowFromPoint -> $761AED12
[21:26:36] 
[21:26:36] The relocation of entries in the relocation table starts here ...
[21:26:36] 
[21:26:36] Offset to relocation table data = $13A00
[21:26:36] Absolute start of the relocation table data = $44C570
[21:26:36] Actual module base = $3D0000
[21:26:36] Preferred image base = $6E5C0000
[21:26:36] Delta = -1847525376
[21:26:36] 
[21:26:36] Start of a new relocation block.
[21:26:36] Size of block = 104
[21:26:36] Expected relocation entries = 48
[21:26:36] 1 RVA ($1006), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5000
[21:26:36] 2 RVA ($1010), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5004
[21:26:36] 3 RVA ($1025), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5000
...
[21:26:36] 46 RVA ($1E94), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5008
[21:26:36] 47 RVA ($1F75), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F229C
[21:26:36] 48 RVA ($1FDC), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F22C4
[21:26:36] Valid relocation entries processed = 48
[
[21:26:36] Start of a new relocation block.
[21:26:36] Size of block = 384
[21:26:36] Expected relocation entries = 188
[21:26:36] 1 RVA ($50D0), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F257C
[21:26:36] 2 RVA ($50F6), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5008
[21:26:36] 3 RVA ($5130), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5008
...
[21:26:36] 186 RVA ($5D6D), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5AAC
[21:26:36] 187 RVA ($5D8F), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F26A3
[21:26:36] 188 RVA ($5E9E), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F26C0
[21:26:36] Valid relocation entries processed = 188
[21:26:36] 
[21:26:36] Start of a new relocation block.
[21:26:36] Size of block = 80
[21:26:36] Expected relocation entries = 36
[21:26:36] 1 RVA ($6037), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F26E0
[21:26:36] 2 RVA ($606C), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F270A
[21:26:36] 3 RVA ($65D6), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F270A
...
[21:26:36] Start of a new relocation block.
[21:26:36] Size of block = 56
[21:26:36] Expected relocation entries = 24
[21:26:36] 1 RVA ($D018), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F2F6A
[21:26:36] 2 RVA ($D087), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F2F5C
[21:26:36] 3 RVA ($D14D), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F3108
...
[21:26:36] 23 RVA ($D807), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F3140
[21:26:36] 24 RVA ($DC2E), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F314C
[21:26:36] Valid relocation entries processed = 24
[21:26:36] 
[21:26:36] Start of a new relocation block.
[21:26:36] Size of block = 36
[21:26:36] Expected relocation entries = 14
[21:26:36] 1 RVA ($E0DC), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F3158
[21:26:36] 2 RVA ($E0F8), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F314C
[21:26:36] 3 RVA ($E150), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F315C
...
[21:26:36] 13 RVA ($ED16), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F8350
[21:26:36] 14 RVA ($ED91), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F5058
[21:26:36] Valid relocation entries processed = 14
[21:26:36] 
[21:26:36] Start of a new relocation block.
[21:26:36] Size of block = 136
[21:26:36] Expected relocation entries = 64
[21:26:36] 1 RVA ($F078), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F59A0
[21:26:36] 2 RVA ($F098), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F59A0
[21:26:36] 3 RVA ($F0B6), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F59A0
...
[21:26:36] Start of a new relocation block.
[21:26:36] Size of block = 16
[21:26:36] Expected relocation entries = 4
[21:26:36] 1 RVA ($1A004), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921FA001
[21:26:36] 2 RVA ($1A008), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921FA01C
[21:26:36] 3 RVA ($1A00C), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F501C
[21:26:36] 4 RVA ($1A010), IMAGE_REL_BASED_HIGHLOW, $FFFFFFFF921F9004
[21:26:36] Valid relocation entries processed = 4
[21:26:36] 
[21:26:36] Total expected relocation entries = 1028
[21:26:36] Total valid relocation entries processed = 1023
[21:26:36] 
[21:26:36] Protecting section .text (PAGE_EXECUTE_READ)
[21:26:36] Protecting section .data (PAGE_READWRITE)
[21:26:36] Protecting section .rdata (PAGE_READONLY)
[21:26:36] Protecting section /4 (PAGE_READONLY)
[21:26:36] Protecting section .bss (PAGE_READWRITE)
[21:26:36] Protecting section .edata (PAGE_READONLY)
[21:26:36] Protecting section .idata (PAGE_READWRITE)
[21:26:36] Protecting section .CRT (PAGE_READWRITE)
[21:26:36] Protecting section .tls (PAGE_READWRITE)
[21:26:36] Discarding section .reloc (size = 2224)
[21:26:36] Discarding section /14 (size = 120)
[21:26:36] Discarding section /29 (size = 6418)
[21:26:36] Discarding section /41 (size = 876)
[21:26:36] Discarding section /55 (size = 889)
[21:26:36] Discarding section /67 (size = 356)
[21:26:36] Discarding section /80 (size = 3206)
[21:26:36] Discarding section /91 (size = 200)
[21:26:36] 
[21:26:36] ModuleEntryPoint -> #DLL_PROCESS_ATTACH = 1
[21:26:36] 
[21:26:36] 3.1.2 Win32 WGL MinGW DLL
[21:26:37] Unloading GDI32.dll
[21:26:37] Unloading KERNEL32.dll
[21:26:37] Unloading msvcrt.dll
[21:26:37] Unloading msvcrt.dll
[21:26:37] Unloading SHELL32.DLL
[21:26:37] Unloading USER32.dll
Last edited by luis on Thu Jun 21, 2018 11:34 pm, edited 2 times in total.
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
skywalk
Addict
Addict
Posts: 3972
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Load DLL from memory

Post by skywalk »

Cool luis! Intense post. Long time no hear?
Happy New Year!
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: Load DLL from memory

Post by Mistrel »

Thank you for sharing. I made an attempt at this some years ago and failed:

http://www.purebasic.fr/english/viewtop ... 13&t=44254

It's worth noting that, although this is extremely clever and interesting, it's also an anti-pattern for the original use case of shared libraries. :wink:
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Load DLL from memory

Post by davido »

@luis,
Nice to see you back after 19 months even if its just a brief visit! :)
Thanks for sharing some excellent code.
DE AA EB
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Load DLL from memory

Post by luis »

@Skywalk

Yep. Long time. Thanks and happy new year to you too.
Nice hair. I think I know where the eagle took them from -> https://www.youtube.com/watch?v=L32bvp2on2g

@Mistrel

Ouch. I completely missed that thread at the time. BTW, this is not a direct conversion of that library, if you look at it it's quite different, not entirely sure it's better and I would not be surprised if it's flawed somewhere. These things are not really documented.
I mean the data structures are but the internal steps followed by the loader used by the OS are not (AFAIK).
As I wrote it worked for me so far, but maybe someone will spot some problem in some specific case and enhance it.

You are right this is a little silly because is going against the ideas behind a dll but I wrote this because linking static libs to PB is often a PITA (for various reasons) whilst a dll usually work without problems and more importantly keeps working over time even when compilers change. Embedding a dll was the second best thing when you would like to statically link a library.

@Davido
Yes it's a brief visit, nice to hear from you too.

Bye guys
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
idle
Always Here
Always Here
Posts: 5050
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Load DLL from memory

Post by idle »

Nice new years gift Luis.
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Load DLL from memory

Post by Lunasole »

Definitely nice stuff in addition to "run exe from memory" I found on forum previously, thanks ^^
Checked it with some my DLLs, works fine.
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Load DLL from memory

Post by Kwai chang caine »

Works very well !!!
Like LUNASOLE say EXE and DLL in memory, it's two powerfull Tools :shock:
Thanks a lot for this precious gift of begining year 8)
ImageThe happiness is a road...
Not a destination
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: Load DLL from memory

Post by ts-soft »

Image thx for sharing!
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Load DLL from memory

Post by SeregaZ »

i am trying to attach to exe file bass.dll. and main dll is fine. but when i try to attach bassmix.dll and bassenc.dll - they are wants to see base.dll near exe file. they cant use dll inside exe file. can you help with this?
https://www.dropbox.com/s/97i3u81b2ru98 ... e.zip?dl=1
(base.dll lays in another folder, becouse near final exe it will no have lay this base.dll. that is why path is "..\bass\bass.dll")
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Load DLL from memory

Post by luis »

SeregaZ wrote:i am trying to attach to exe file bass.dll. and main dll is fine. but when i try to attach bassmix.dll and bassenc.dll - they are wants to see base.dll near exe file. they cant use dll inside exe file. can you help with this?
Hi, it's listed in the limitations:
comments from the include wrote: ; It's not possible to load from memory a DLL "A" depending on a DLL "B" if "B" is also embedded in memory and not available in the filesystem.
; It's possible to enhance the code to support even that but it's a little extreme and I don't need it.
I'm sorry but I don't think I'll ever add support for it, because in a case like yours, I would simply drop the three interdependent DLLs as external files.
If I'm not mistaken believe it can be done, since when a DLLs is scanned by my code, I also manually load the other DLLs required by it.
It should "just" require to keep track of the DLLs already loaded from memory and to resolve the required functions without trying to load that DLL from the filesystem.
Who knows, maybe in a month I'll change my mind and try to add it, just don't count on it because I'm not sure it's such a good idea to push this too far.

PS: I'm saving your zip just in case :D


BTW: I saw your annotation in your test file

Code: Select all

; it have ima, but i change VirtualSize into SizeOfRawData and starts work fine
; CopyMemory(*image + *SectionHeader\PointerToRawData, *DestSection, *SectionHeader\SizeOfRawData);VirtualSize) ; I think this should be VirtualSize and not SizeOfRawData
That's interesting, thank you for mentioning it.
As I wrote in the comment I'm not entirely sure about that line, I'll look again into it.
"Have you tried turning it off and on again ?"
A little PureBasic review
SeregaZ
Enthusiast
Enthusiast
Posts: 617
Joined: Fri Feb 20, 2009 9:24 am
Location: Almaty (Kazakhstan. not Borat, but Triple G)
Contact:

Re: Load DLL from memory

Post by SeregaZ »

probably it is my fault, becouse Windows XP 32bit and PB5.31, not as you mark higher PB version. with newer probably fine :)
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Load DLL from memory

Post by luis »

@Seregaz

I re-read some docs about the PE structure, and this time I understood it should have been SizeOfRawData and not VirtualSize.
Don't know why I understood something different at the time.
Anyway, I changed it and updated the first post.

About loading interdependent DLLs from memory: I modified my local copy of the code, and it almost worked, but just almost.
I was able to resolve the functions to the DLL already present in memory keeping track of which is which and where, but the program then starts complaining with a message box mentioning the first of the functions required by the DLL B (and present in the DLL A) is "not found".

The functions are correctly linked, I checked using the DLLs from the filesystem instead of memory and the memory map at the end of loading is the same, the addresses are the same, and it still doesn't work.

Probably some small step is still missing which is required but I don't know what it could be, problem is this stuff is not officially documented.

Anyway, as I said this was thought for a more simple scenario. With the single DLL I used with this thing up to this moment it worked perfectly and it's enough for me.

BTW: when I tried your BASS library test code I was getting sometimes an IMA when freeing the library with my MemDll module.

I tried and tried and was happening 10% of the times.

After trying everything I could think of, I tried with OpenLibrary()/CloseLibrary(), same code but loading from file.
The IMA happens there too, but it seem more rare. Once every 20...25 runs.
So evidently it's not a problem in MemDll code as I initially thought.
I tried with the MemoryModule library (the C library posted here in the forum) and it does the same.
So maybe there is something wrong with the prototypes, or with the BASS library itself. I have no idea.

The code I used in my reduced test program was simply

BASS_Init(-1,44100,0,0,0)

which returns 1, and so success.

Code: Select all

hb = OpenLibrary(#PB_Any, "bass.dll")

If hb
    Debug hb
    
    ;bass.dll
    Prototype.l BASS_Init(device.l, freq.l, flags.l, *win, *dsguid)  
    Prototype.l BASS_ErrorGetCode()
  
    Global BASS_Init.BASS_Init = GetFunction(hb, "BASS_Init")
    Global BASS_ErrorGetCode.BASS_ErrorGetCode = GetFunction(hb, "BASS_ErrorGetCode")

    r = BASS_Init(-1,44100,0,0,0)     
    ;MessageRequester("", Str(r))
    
    ;r = BASS_ErrorGetCode()
    ;MessageRequester("", Str(r))
        
    CloseLibrary(hb)
EndIf
on the 15th run

Code: Select all

[00:19:00] Waiting for executable to start...
[00:19:00] Executable type: Windows - x86  (32bit, Unicode, Purifier)
[00:19:00] Executable started.
[00:19:00] [ERROR] test_with_openlibrary.pb (Line: 20)
[00:19:00] [ERROR] Invalid memory access. (write error at address 20)
[00:19:00] The Program execution has finished.
PS: the line number 20 is just a coincidence, does not has anything to do with the IMA at 20 :)

The random problem seems to happen only with the debugger enabled.

Disabling it and enabling the message requester

r = BASS_Init(-1,44100,0,0,0)
MessageRequester("", Str(r))

I was able to execute the program successfully for 50+ times, then I got tired.

Just thought of mentioning it even if it's not strictly related to the thread.
Last edited by luis on Thu Jun 21, 2018 10:42 am, edited 3 times in total.
"Have you tried turning it off and on again ?"
A little PureBasic review
Post Reply