Loading a DLL from memory
Author: | Joachim Bauch |
---|---|
Contact: | mail@joachim-bauch.de |
Copyright: | Creative Commons License (by-sa) |
Overview
The default windows API functions to load external libraries into a program (LoadLibrary, LoadLibraryEx) only work with files on the filesystem. It's therefore impossible to load a DLL from memory. But sometimes, you need exactly this functionality (e.g. you don't want to distribute a lot of files or want to make disassembling harder). Common workarounds for this problems are to write the DLL into a temporary file first and import it from there. When the program terminates, the temporary file gets deleted.
In this tutorial, I will describe first, how DLL files are structured and will present some code that can be used to load a DLL completely from memory - without storing on the disk first.
Windows executables - the PE format
Most windows binaries that can contain executable code (.exe, .dll, .sys) share a common file format that consists of the following parts:
DOS header DOS stub |
PE header |
Section header |
Section 1 |
Section 2 |
. . . |
Section n |
All structures given below can be found in the header file winnt.h.
DOS header / stub
The DOS header is only used for backwards compatibility. It precedes the DOS stub that normally just displays an error message about the program not being able to be run from DOS mode.
Microsoft defines the DOS header as follows:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
PE header
The PE header contains informations about the different sections inside the executable that are used to store code and data or to define imports from other libraries or exports this libraries provides.
It's defined as follows:
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader;} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
The FileHeader describes the physical format of the file, i.e. contents, informations about symbols, etc:
typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics;} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
The OptionalHeader contains informations about the logical format of the library, including required OS version, memory requirements and entry points:
typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
The DataDirectory contains 16 (IMAGE_NUMBEROF_DIRECTORY_ENTRIES) entries defining the logical components of the library:
Index | Description |
---|---|
0 | Exported functions |
1 | Imported functions |
2 | Resources |
3 | Exception informations |
4 | Security informations |
5 | Base relocation table |
6 | Debug informations |
7 | Architecture specific data |
8 | Global pointer |
9 | Thread local storage |
10 | Load configuration |
11 | Bound imports |
12 | Import address table |
13 | Delay load imports |
14 | COM runtime descriptor |
For importing the DLL we only need the entries describing the imports and the base relocation table. In order to provide access to the exported functions, the exports entry is required.
Section header
The section header is stored after the OptionalHeader structure in the PE header. Microsoft provides the macro IMAGE_FIRST_SECTION to get the start address based on the PE header.
Actually, the section header is a list of informations about each section in the file:
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics;} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
A section can contain code, data, relocation informations, resources, export or import definitions, etc.
Loading the library
To emulate the PE loader, we must first understand, which steps are neccessary to load the file to memory and prepare the structures so they can be called from other programs.
When issuing the API call LoadLibrary, Windows basically performs these tasks:
- Open the given file and check the DOS and PE headers.
- Try to allocate a memory block of PEHeader.OptionalHeader.SizeOfImage bytes at position PEHeader.OptionalHeader.ImageBase.
- Parse section headers and copy sections to their addresses. The destination address for each section, relative to the base of the allocated memory block, is stored in the VirtualAddress attribute of the IMAGE_SECTION_HEADER structure.
- If the allocated memory block differs from ImageBase, various references in the code and/or data sections must be adjusted. This is called Base relocation.
- The required imports for the library must be resolved by loading the corresponding libraries.
- The memory regions of the different sections must be protected depending on the section's characteristics. Some sections are marked as discardable and therefore can be safely freed at this point. These sections normally contain temporary data that is only needed during the import, like the informations for the base relocation.
- Now the library is loaded completely. It must be notified about this by calling the entry point using the flag DLL_PROCESS_ATTACH.
In the following paragraphs, each step is described.
Allocate memory
All memory required for the library must be reserved / allocated using VirtualAlloc, as Windows provides functions to protect these memory blocks. This is required to restrict access to the memory, like blocking write access to the code or constant data.
The OptionalHeader structure defines the size of the required memory block for the library. It must be reserved at the address specified by ImageBase if possible:
memory = VirtualAlloc((LPVOID)(PEHeader->OptionalHeader.ImageBase), PEHeader->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE);
If the reserved memory differs from the address given in ImageBase, base relocation as described below must be done.
Copy sections
Once the memory has been reserved, the file contents can be copied to the system. The section header must get evaluated in order to determine the position in the file and the target area in memory.
Before copying the data, the memory block must get committed:
dest = VirtualAlloc(baseAddress + section->VirtualAddress, section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE);
Sections without data in the file (like data sections for the used variables) have a SizeOfRawData of 0, so you can use the SizeOfInitializedData or SizeOfUninitializedData of the OptionalHeader. Which one must get choosen depending on the bit flags IMAGE_SCN_CNT_INITIALIZED_DATA and IMAGE_SCN_CNT_UNINITIALIZED_DATA that may be set in the section`s characteristics.
Base relocation
All memory addresses in the code / data sections of a library are stored relative to the address defined by ImageBase in the OptionalHeader. If the library can't be imported to this memory address, the references must get adjusted => relocated. The file format helps for this by storing informations about all these references in the base relocation table, which can be found in the directory entry 5 of the DataDirectory in the OptionalHeader.
This table consists of a series of this structure
typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock;} IMAGE_BASE_RELOCATION;
It contains (SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2 entries of 16 bits each. The upper 4 bits define the type of relocation, the lower 12 bits define the offset relative to the VirtualAddress.
The only types that seem to be used in DLLs are
- IMAGE_REL_BASED_ABSOLUTE
- No operation relocation. Used for padding.
- IMAGE_REL_BASED_HIGHLOW
- Add the delta between the ImageBase and the allocated memory block to the 32 bits found at the offset.
Resolve imports
The directory entry 1 of the DataDirectory in the OptionalHeader specifies a list of libraries to import symbols from. Each entry in this list is defined as follows:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) }; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)} IMAGE_IMPORT_DESCRIPTOR;
The Name entry describes the offset to the NULL-terminated string of the library name (e.g. KERNEL32.DLL). The OriginalFirstThunk entry points to a list of references to the function names to import from the external library. FirstThunk points to a list of addresses that gets filled with pointers to the imported symbols.
When we resolve the imports, we walk both lists in parallel, import the function defined by the name in the first list and store the pointer to the symbol in the second list:
nameRef = (DWORD *)(baseAddress + importDesc->OriginalFirstThunk);symbolRef = (DWORD *)(baseAddress + importDesc->FirstThunk);for (; *nameRef; nameRef++, symbolRef++){ PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)(codeBase + *nameRef); *symbolRef = (DWORD)GetProcAddress(handle, (LPCSTR)&thunkData->Name); if (*funcRef == 0) { handleImportError(); return; }}
Protect memory
Every section specifies permission flags in it's Characteristics entry. These flags can be one or a combination of
- IMAGE_SCN_MEM_EXECUTE
- The section contains data that can be executed.
- IMAGE_SCN_MEM_READ
- The section contains data that is readable.
- IMAGE_SCN_MEM_WRITE
- The section contains data that is writeable.
These flags must get mapped to the protection flags
- PAGE_NOACCESS
- PAGE_WRITECOPY
- PAGE_READONLY
- PAGE_READWRITE
- PAGE_EXECUTE
- PAGE_EXECUTE_WRITECOPY
- PAGE_EXECUTE_READ
- PAGE_EXECUTE_READWRITE
Now, the function VirtualProtect can be used to limit access to the memory. If the program tries to access it in a unauthorized way, an exception gets raised by Windows.
In addition the section flags above, the following can be added:
- IMAGE_SCN_MEM_DISCARDABLE
- The data in this section can be freed after the import. Usually this is specified for relocation data.
- IMAGE_SCN_MEM_NOT_CACHED
- The data in this section must not get cached by Windows. Add the bit flag PAGE_NOCACHE to the protection flags above.
Notify library
The last thing to do is to call the DLL entry point (defined by AddressOfEntryPoint) and so notifying the library about being attached to a process.
The function at the entry point is defined as
typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
So the last code we need to execute is
DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint);(*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);
Afterwards we can use the exported functions as with any normal library.
Exported functions
If you want to access the functions that are exported by the library, you need to find the entry point to a symbol, i.e. the name of the function to call.
The directory entry 0 of the DataDirectory in the OptionalHeader contains informations about the exported functions. It's defined as follows:
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; // RVA from base of image DWORD AddressOfNames; // RVA from base of image DWORD AddressOfNameOrdinals; // RVA from base of image} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
First thing to do, is to map the name of the function to the ordinal number of the exported symbol. Therefore, just walk the arrays defined by AddressOfNames and AddressOfNameOrdinals parallel until you found the required name.
Now you can use the ordinal number to read the address by evaluating the n-th element of the AddressOfFunctions array.
Freeing the library
To free the custom loaded library, perform the steps
Call entry point to notify library about being detached:
DllEntryProc entry = (DllEntryProc)(baseAddress + PEHeader->OptionalHeader.AddressOfEntryPoint);(*entry)((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, 0);
Free external libraries used to resolve imports.
Free allocated memory.
MemoryModule
MemoryModule is a C-library that can be used to load a DLL from memory.
The interface is very similar to the standard methods for loading of libraries:
typedef void *HMEMORYMODULE;HMEMORYMODULE MemoryLoadLibrary(const void *);FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *);void MemoryFreeLibrary(HMEMORYMODULE);
Downloads
The latest development release can always be grabbed from my development subversion server at http://fancycode.com/viewcvs/MemoryModule/trunk/
All released versions can be downloaded from the list below.
- Version 0.0.2 (MPL release)
- http://www.joachim-bauch.de/tutorials/downloads/MemoryModule-0.0.2.zip
- Version 0.0.1 (first public release)
- http://www.joachim-bauch.de/tutorials/downloads/MemoryModule-0.0.1.zip
Known issues
- All memory that is not protected by section flags is gets committed using PAGE_READWRITE. I don't know if this is correct.
License
Since version 0.0.2, the MemoryModule library is released under the Mozilla Public License (MPL). Version 0.0.1 has been released unter the Lesser General Public License (LGPL).
It is provided as-is without ANY warranty. You may use it at your own risk.
Copyright
The MemoryModule library and this tutorial are Copyright (c) 2004-2006 by Joachim Bauch.
'Web' 카테고리의 다른 글
jQuery Form Plugin - Example (0) | 2013.06.05 |
---|---|
jQuery Form Plugin - Option (0) | 2013.06.05 |
jQuery Form Plugin - API (0) | 2013.06.05 |
CSS3 Transform (0) | 2012.07.19 |
비스타와 보안 (Vista & ActiveX) 관련(Memory Loading) (0) | 2008.02.14 |