0% found this document useful (0 votes)
40 views1 page

Reflective DLL Injection Explained

The document discusses Reflective DLL Injection, a method to load a DLL directly into a target process's memory without using the Windows DLL Loader. It outlines the steps required for reflective loading, including memory allocation, relocation, and correcting the Import Address Table, while also addressing potential indicators of compromise. Additionally, it covers enhancements to dynamic syscall functions and the execution of TLS callbacks before the DLL's entry point.

Uploaded by

peter
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views1 page

Reflective DLL Injection Explained

The document discusses Reflective DLL Injection, a method to load a DLL directly into a target process's memory without using the Windows DLL Loader. It outlines the steps required for reflective loading, including memory allocation, relocation, and correcting the Import Address Table, while also addressing potential indicators of compromise. Additionally, it covers enhancements to dynamic syscall functions and the execution of TLS callbacks before the DLL's entry point.

Uploaded by

peter
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

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)

You might also like