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