Windows Delphi / Reflective DLL injection by DarkCoderSc
Created the Tuesday 21 November 2023. Updated 6 months, 2 weeks ago.
Description:
Here is a short example demonstrating the reflective loading of a Dynamic Link Library (DLL) into memory, whether sourced from disk or memory (supporting streams). This approach supports both 32-bit (PE) and 64-bit (PE+) DLLs. The technique enables the loading of exported functions either by their ordinal value or by the exported function name.
Code
program DLLReflector;
// DLL Reflection with both 32 and 64-bit support.
// www.unprotect.it
// @DarkCoderSc
uses
Winapi.Windows,
System.Classes,
System.SysUtils;
const
IMAGE_REL_BASED_DIR64 = 10;
type
TImageBaseRelocation = record
VirtualAddress : DWORD;
SizeOfBlock : DWORD;
end;
PImageBaseRelocation = ^TImageBaseRelocation;
TImageOptionalHeader =
{$IFDEF WIN64}
TImageOptionalHeader64
{$ELSE}
TImageOptionalHeader32
{$ENDIF};
PImageOptionalHeader = ^TImageOptionalHeader;
TImageThunkData =
{$IFDEF WIN64}
TImageThunkData64
{$ELSE}
TImageThunkData32
{$ENDIF};
PImageThunkData = ^TImageThunkData;
PRelocationInfo =
{$IFDEF WIN64}
PCardinal
{$ELSE}
PWord
{$ENDIF};
TNTSignature = DWORD;
PNTSignature = ^TNTSignature;
TPEHeader = record
pImageBase : Pointer;
// Main Headers
_pImageDosHeader : PImageDosHeader;
_pNTSignature : PNTSignature;
_pImageFileHeader : PImageFileHeader;
_pImageOptionalHeader : PImageOptionalHeader;
_pImageSectionHeader : PImageSectionHeader;
// Sections Headers
SectionHeaderCount : Cardinal;
pSectionHeaders : array of PImageSectionHeader;
end;
TPEHeaderDirectories = record
_pImageExportDirectory : PImageExportDirectory;
end;
{ _.RVAToVA }
function RVAToVA(const pImageBase : Pointer; const ARelativeVirtualAddress : NativeUInt) : Pointer;
begin
result := Pointer(NativeUInt(pImageBase) + ARelativeVirtualAddress);
end;
{ _.IdentifyPEHeader }
function IdentifyPEHeader(const pImageBase : Pointer) : TPEHeader;
var
pOffset : Pointer;
_pImageSectionHeader : PImageSectionHeader;
I : Cardinal;
procedure IncOffset(const AIncrement : Cardinal);
begin
pOffset := Pointer(NativeUInt(pOffset) + AIncrement);
end;
begin
ZeroMemory(@result, SizeOf(TPEheader));
///
if not Assigned(pImageBase) then
Exit();
result.pImageBase := pImageBase;
pOffset := result.pImageBase;
// Read and validate Library PE Header
result._pImageDosHeader := pOffset;
if (result._pImageDosHeader.e_magic <> IMAGE_DOS_SIGNATURE) then
Exit();
IncOffset(result._pImageDosHeader^._lfanew);
if (PNTSignature(pOffset)^ <> IMAGE_NT_SIGNATURE) then
Exit();
IncOffset(SizeOf(TNTSignature));
result._pImageFileHeader := pOffset;
IncOffset(SizeOf(TImageFileHeader));
result._pImageOptionalHeader := pOffset;
IncOffset(SizeOf(TImageOptionalHeader));
// Read and register section headers
result.SectionHeaderCount := result._pImageFileHeader^.NumberOfSections;
SetLength(result.pSectionHeaders, result.SectionHeaderCount);
for I := 0 to result.SectionHeaderCount -1 do begin
_pImageSectionHeader := pOffset;
try
result.pSectionHeaders[I] := _pImageSectionHeader;
finally
IncOffset(SizeOf(TImageSectionHeader));
end;
end;
end;
{ _.IdentifyPEHeaderDirectories }
function IdentifyPEHeaderDirectories(const APEHeader : TPEHeader) : TPEHeaderDirectories;
var AVirtualAddress : Cardinal;
begin
ZeroMemory(@result, SizeOf(TPEHeaderDirectories));
///
// Identify Export Directory
AVirtualAddress := APEHeader._pImageOptionalHeader^.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
result._pImageExportDirectory := Pointer(NativeUInt(APEHeader.pImageBase) + AVirtualAddress);
end;
{ _.ResolveImportTable }
procedure ResolveImportTable(const APEHeader : TPEHeader);
var _pImageDataDirectory : PImageDataDirectory;
_pImageImportDescriptor : PImageImportDescriptor;
hModule : THandle;
_pImageOriginalThunkData : PImageThunkData;
_pImageFirstThunkData : PImageThunkData;
pFunction : Pointer;
pProcName : Pointer;
function RVA(const Offset : NativeUInt) : Pointer;
begin
result := Pointer(NativeUInt(APEHeader.pImageBase) + Offset);
end;
begin
_pImageDataDirectory := @APEHeader._pImageOptionalHeader^.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if _pImageDataDirectory^.Size = 0 then
Exit();
_pImageImportDescriptor := RVA(_pImageDataDirectory^.VirtualAddress);
while _pImageImportDescriptor^.Name <> 0 do begin
try
hModule := LoadLibraryA(RVA(_pImageImportDescriptor^.Name));
if hModule = 0 then
continue;
try
if _pImageImportDescriptor^.OriginalFirstThunk <> 0 then
_pImageOriginalThunkData := RVA(_pImageImportDescriptor^.OriginalFirstThunk)
else
_pImageOriginalThunkData := RVA(_pImageImportDescriptor^.FirstThunk);
_pImageFirstThunkData := RVA(_pImageImportDescriptor^.FirstThunk);
if not Assigned(_pImageOriginalThunkData) then
continue;
while _pImageOriginalThunkData^.AddressOfData <> 0 do begin
try
if (_pImageOriginalThunkData^.Ordinal and IMAGE_ORDINAL_FLAG) <> 0 then
pProcName := MAKEINTRESOURCE(_pImageOriginalThunkData^.Ordinal and $FFFF)
else
pProcName := RVA(_pImageOriginalThunkData^.AddressOfData + SizeOf(Word));
pFunction := GetProcAddress(
hModule,
PAnsiChar(pProcName)
);
if not Assigned(pFunction) then
continue;
_pImageFirstThunkData^._Function := NativeUInt(pFunction);
finally
Inc(_pImageOriginalThunkData);
Inc(_pImageFirstThunkData);
end;
end;
finally
FreeLibrary(hModule);
end;
finally
Inc(_pImageImportDescriptor);
end;
end;
end;
{ _.PerformBaseRelocation }
procedure PerformBaseRelocation(const APEHeader: TPEHeader; const ADelta: NativeUInt);
var
I : Cardinal;
_pImageDataDirectory : PImageDataDirectory;
pRelocationTable : PImageBaseRelocation;
pRelocationAddress : Pointer;
pRelocInfo : PRelocationInfo;
pRelocationType : Integer;
pRelocationOffset : NativeUInt;
ARelocationCount : Cardinal;
const
IMAGE_SIZEOF_BASE_RELOCATION = 8;
IMAGE_REL_BASED_HIGH = 1;
IMAGE_REL_BASED_LOW = 2;
IMAGE_REL_BASED_HIGHLOW = 3;
begin
_pImageDataDirectory := @APEHeader._pImageOptionalHeader^.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if _pImageDataDirectory^.Size = 0 then
Exit();
pRelocationTable := RVAToVA(APEHeader.pImageBase, _pImageDataDirectory^.VirtualAddress);
while pRelocationTable^.VirtualAddress > 0 do begin
pRelocationAddress := RVAToVA(APEHeader.pImageBase, pRelocationTable^.VirtualAddress);
pRelocInfo := RVAToVA(pRelocationTable, IMAGE_SIZEOF_BASE_RELOCATION);
ARelocationCount := (pRelocationTable^.SizeOfBlock - SizeOf(TImageBaseRelocation)) div SizeOf(Word);
for I := 0 to ARelocationCount -1 do begin
pRelocationType := (pRelocInfo^ shr 12);
pRelocationOffset := pRelocInfo^ and $FFF;
case pRelocationType of
IMAGE_REL_BASED_HIGHLOW, IMAGE_REL_BASED_DIR64:
Inc(PNativeUInt(NativeUInt(pRelocationAddress) + pRelocationOffset)^, ADelta);
IMAGE_REL_BASED_HIGH:
Inc(PNativeUInt(NativeUInt(pRelocationAddress) + pRelocationOffset)^, HiWord(ADelta));
IMAGE_REL_BASED_LOW:
Inc(PNativeUInt(NativeUInt(pRelocationAddress) + pRelocationOffset)^, LoWord(ADelta));
end;
Inc(pRelocInfo);
end;
///
pRelocationTable := Pointer(NativeUInt(pRelocationTable) + pRelocationTable^.SizeOfBlock);
end;
end;
{ _.ReflectLibraryFromMemory }
function ReflectLibraryFromMemory(const pSourceBuffer : Pointer; const ABufferSize : UInt) : Pointer;
var pOffset : Pointer;
ASourcePEHeader : TPEHeader;
ADestPEHeader : TPEHeader;
pImageBase : Pointer;
_pImageSectionHeader : PImageSectionHeader;
I : Cardinal;
ADelta : UInt64;
begin
result := nil;
///
ASourcePEHeader := IdentifyPEHeader(pSourceBuffer);
{$IFDEF WIN64}
if (ASourcePEHeader._pImageFileHeader^.Machine <> IMAGE_FILE_MACHINE_AMD64) then
{$ELSE}
if (ASourcePEHeader._pImageFileHeader^.Machine <> IMAGE_FILE_MACHINE_I386) then
{$ENDIF}
raise Exception.Create('You must load a DLL with same architecture as current process!');
// Create a memory region that will contain our Library code
// We then patch our TPEHeader structure with new image base
pImageBase := VirtualAlloc(nil, ASourcePEHeader._pImageOptionalHeader^.SizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if not Assigned(pImageBase) then
Exit();
// Write Library headers to allocated region
CopyMemory(pImageBase, pSourceBuffer, ASourcePEHeader._pImageOptionalHeader.SizeOfHeaders);
// Write Library sections code to allocated region
for I := 0 to ASourcePEHeader.SectionHeaderCount -1 do begin
_pImageSectionHeader := ASourcePEHeader.pSectionHeaders[I];
pOffset := Pointer(NativeUInt(pImageBase) + _pImageSectionHeader^.VirtualAddress);
// Pad new allocated region with zeros
ZeroMemory(pOffset, _pImageSectionHeader^.Misc.VirtualSize);
// Copy section content from buffer to freshly allocated region
CopyMemory(
pOffset,
Pointer(NativeUInt(pSourceBuffer) + _pImageSectionHeader^.PointerToRawData),
_pImageSectionHeader^.SizeOfRawData
);
end;
// Calculate the distance between default library expected image base and mapped library image base
// Used for relocation
ADelta := NativeUInt(pImageBase) - NativeUInt(ASourcePEHeader._pImageOptionalHeader.ImageBase);
// Point to new image header
ADestPEHeader := IdentifyPEHeader(pImageBase);
// Patch new image header image base value
ADestPEHeader._pImageOptionalHeader^.ImageBase := NativeUInt(pImageBase);
// Resolve import table, load required libraries and exported functions
ResolveImportTable(ADestPEHeader);
// Perform Image Base Relocation since it differs from target library PE Header expectation
if ADelta <> 0 then
PerformBaseRelocation(ADestPEHeader, ADelta);
///
result := pImageBase;
end;
{ _.GetReflectedProcAddress }
function GetReflectedProcAddress(const pImageBase : Pointer; const AFunctionOrOrdinal : String) : Pointer;
var APEHeader : TPEHeader;
APEHeaderDirectories : TPEHeaderDirectories;
I : Cardinal;
pOffset : PCardinal;
pOrdinal : PWord;
pFuncAddress : PCardinal;
pAddrOfNameOrdinals : Pointer;
pAddrOfFunctions : Pointer;
pAddrOfNames : Pointer;
ACurrentName : String;
AOrdinalCandidate : Integer;
ACurrentOrdinal : Word;
AResolveByName : Boolean;
begin
result := nil;
///
if not Assigned(pImageBase) then
Exit();
APEHeader := IdentifyPEHeader(pImageBase);
APEHeaderDirectories := IdentifyPEHeaderDirectories(APEHeader);
for I := 0 to APEHeaderDirectories._pImageExportDirectory^.NumberOfNames -1 do begin
pAddrOfNameOrdinals := Pointer(NativeUInt(APEHeader.pImageBase) + APEHeaderDirectories._pImageExportDirectory^.AddressOfNameOrdinals);
pAddrOfFunctions := Pointer(NativeUInt(APEHeader.pImageBase) + APEHeaderDirectories._pImageExportDirectory^.AddressOfFunctions);
pAddrOfNames := Pointer(NativeUInt(APEHeader.pImageBase) + APEHeaderDirectories._pImageExportDirectory^.AddressOfNames);
AResolveByName := False;
if not TryStrToInt(AFunctionOrOrdinal, AOrdinalCandidate) then
AResolveByName := True;
if (AOrdinalCandidate < Low(Word)) or (AOrdinalCandidate > High(Word)) and not AResolveByName then
AResolveByName := True;
// Function Name
pOffset := Pointer(NativeUInt(pAddrOfNames) + (I * SizeOf(Cardinal)));
ACurrentName := String(PAnsiChar(NativeUInt(pImageBase) + pOffset^));
// Ordinal
ACurrentOrdinal := PWord(NativeUInt(pAddrOfNameOrdinals) + (I * SizeOf(Word)))^;
if AResolveByName then begin
if (String.Compare(ACurrentName, AFunctionOrOrdinal, True) <> 0) then
continue;
end else begin
if (ACurrentOrdinal + APEHeaderDirectories._pImageExportDirectory^.Base) <> AOrdinalCandidate then
continue;
end;
// Resolve Function Address
pFuncAddress := PCardinal(NativeUInt(pAddrOfFunctions) + (ACurrentOrdinal * SizeOf(Cardinal)));
result := Pointer(NativeUInt(pImageBase) + pFuncAddress^);
break;
end;
end;
{ _.ReflectLibraryFromFile }
function ReflectLibraryFromFile(const AFileName : String) : Pointer;
var AFileStream : TFileStream;
pBuffer : Pointer;
ASize : Int64;
begin
result := nil;
///
AFileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
try
AFileStream.Position := 0;
///
ASize := AFileStream.Size;
GetMem(pBuffer, ASize);
AFileStream.ReadBuffer(PByte(pBuffer)^, ASize);
result := ReflectLibraryFromMemory(pBuffer, ASize);
finally
if Assigned(AFileStream) then
FreeAndNil(AFileStream);
end;
end;
{ _.ReflectFromMemoryStream }
function ReflectFromMemoryStream(const AStream : TMemoryStream) : Pointer;
begin
result := nil;
///
if not Assigned(AStream) then
Exit();
if AStream.Size = 0 then
Exit();
result := ReflectLibraryFromMemory(AStream.Memory, AStream.Size);
end;
// Example (Update Code Accordingly)
var pReflectedModuleBase : Pointer;
pReflectedMethod : procedure(); stdcall;
begin
pReflectedModuleBase := ReflectLibraryFromFile('test.dll');
// Through Function Name
@pReflectedMethod := GetReflectedProcAddress(pReflectedModuleBase, 'ModuleAction');
if Assigned(pReflectedMethod) then
pReflectedMethod();
// Through Exported Function Ordinal
@pReflectedMethod := GetReflectedProcAddress(pReflectedModuleBase, '3');
if Assigned(pReflectedMethod) then
pReflectedMethod();
end.