Get DLL Exported Function Address
This very small snippet is an adaptation of the previously released unit > UntEnumDLLExport.pas with just one goal, retrieve an exported function address by its name from any DLL (both 32 and 64bit).
This adaptation is also interesting because it remove the need of having both heavy units Generics.Collections
and SysUtils
to have a smaller binary.
Finally it is also quite interesting for tweaking our GetProcAddress
alternative (you will find here) and only have the necesarry code.
// Jean-Pierre LESUEUR (@DarkCoderSc)
//...
uses Windows;
//...
function GetExportAddress(ADLLFile, AFuncName : String) : Cardinal;
var hFile : THandle;
dwBytesRead : Cardinal;
AImageDosHeader : TImageDosHeader;
AImageNtHeaderSignature : DWORD;
x64Binary : Boolean;
AImageFileHeader : TImageFileHeader;
AImageOptionalHeader32 : TImageOptionalHeader32;
AImageOptionalHeader64 : TImageOptionalHeader64;
AExportAddr : TImageDataDirectory;
AExportDir : TImageExportDirectory;
I : Integer;
ACanCatchSection : Boolean;
AOffset : Cardinal;
AExportName : AnsiString;
ALen : Cardinal;
AOrdinal : Word;
AFuncAddress : Cardinal;
function RVAToFileOffset(ARVA : Cardinal) : Cardinal;
var I : Integer;
AImageSectionHeader : TImageSectionHeader;
ASectionsOffset : Cardinal;
begin
result := 0;
///
if (ARVA = 0) or (NOT ACanCatchSection) then
Exit();
///
ASectionsOffset := (
AImageDosHeader._lfanew +
SizeOf(DWORD) +
SizeOf(TImageFileHeader) +
AImageFileHeader.SizeOfOptionalHeader
);
for I := 0 to (AImageFileHeader.NumberOfSections -1) do begin
SetFilePointer(hFile, ASectionsOffset + (I * SizeOf(TImageSectionHeader)), nil, FILE_BEGIN);
if NOT ReadFile(hFile, AImageSectionHeader, SizeOf(TImageSectionHeader), dwBytesRead, 0) then
continue;
if (ARVA >= AImageSectionHeader.VirtualAddress) and (ARVA < AImageSectionHeader.VirtualAddress + AImageSectionHeader.SizeOfRawData) then
result := (ARVA - AImageSectionHeader.VirtualAddress + AImageSectionHeader.PointerToRawData);
end;
end;
function GetStringLength(AStartAtPos : Cardinal) : Cardinal;
var ADummy : Byte;
begin
result := 0;
///
if (hFile = INVALID_HANDLE_VALUE) then
Exit();
SetFilePointer(hFile, AStartAtPos, nil, FILE_BEGIN);
while True do begin
if NOT ReadFile(hFile, ADummy, SizeOf(Byte), dwBytesRead, nil) then
break;
///
if (ADummy = 0) then
break;
Inc(result);
end;
end;
// Ripped from SysUtils.pas
function LowerCase(const S: string): string;
var
I, Len: Integer;
DstP, SrcP: PChar;
Ch: Char;
begin
Len := Length(S);
SetLength(Result, Len);
if Len > 0 then
begin
DstP := PChar(Pointer(Result));
SrcP := PChar(Pointer(S));
for I := Len downto 1 do
begin
Ch := SrcP^;
case Ch of
'A'..'Z':
Ch := Char(Word(Ch) or $0020);
end;
DstP^ := Ch;
Inc(DstP);
Inc(SrcP);
end;
end;
end;
begin
result := 0;
///
ACanCatchSection := False;
AFuncName := LowerCase(AFuncName);
hFile := CreateFileW(PWideChar(ADLLFile), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
if hFile = INVALID_HANDLE_VALUE then
Exit;
///
try
if NOT ReadFile(hFile, AImageDosHeader, SizeOf(TImageDosHeader), dwBytesRead, 0) then
Exit;
///
if (AImageDosHeader.e_magic <> IMAGE_DOS_SIGNATURE) then
Exit;
///
SetFilePointer(hFile, AImageDosHeader._lfanew, nil, FILE_BEGIN);
if NOT ReadFile(hFile, AImageNtHeaderSignature, SizeOf(DWORD), dwBytesRead, 0) then
Exit;
///
if (AImageNTHeaderSignature <> IMAGE_NT_SIGNATURE) then
Exit;
///
SetFilePointer(hFile, (AImageDosHeader._lfanew + sizeOf(DWORD)), nil, FILE_BEGIN);
if NOT ReadFile(hFile, AImageFileHeader, SizeOf(TImageFileHeader), dwBytesRead, 0) then
Exit;
///
ACanCatchSection := True;
x64Binary := (AImageFileHeader.Machine = IMAGE_FILE_MACHINE_AMD64);
if x64Binary then begin
if NOT ReadFile(hFile, AImageOptionalHeader64, AImageFileHeader.SizeOfOptionalHeader, dwBytesRead, 0) then
Exit;
end else begin
if NOT ReadFile(hFile, AImageOptionalHeader32, AImageFileHeader.SizeOfOptionalHeader, dwBytesRead, 0) then
Exit;
end;
///
AExportAddr.VirtualAddress := 0;
AExportAddr.Size := 0;
if x64Binary then
AExportAddr := AImageOptionalHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]
else
AExportAddr := AImageOptionalHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
AOffset := RVAToFileOffset(AExportAddr.VirtualAddress);
if AOffset = 0 then
Exit;
SetFilePointer(hFile, AOffset, nil, FILE_BEGIN);
if NOT ReadFile(hFile, AExportDir, SizeOf(TImageExportDirectory), dwBytesRead, 0) then
Exit;
///
if (AExportDir.NumberOfFunctions <= 0) then
Exit;
///
{
Enumerate Named Exported Functions
}
for I := 0 to AExportDir.NumberOfNames - 1 do begin
// Function Name
AOffset := RVAToFileOffset(AExportDir.AddressOfNames) + (I * SizeOf(Cardinal));
SetFilePointer(hFile, AOffset, nil, FILE_BEGIN);
if NOT ReadFile(hFile, AOffset, SizeOf(Cardinal), dwBytesRead, 0) then
continue;
///
ALen := GetStringLength(RVAToFileOffset(AOffset));
SetLength(AExportName, ALen);
SetFilePointer(hFile, RVAToFileOffset(AOffset), nil, FILE_BEGIN);
if NOT ReadFile(hFile, AExportName[1], ALen, dwBytesRead, nil) then
continue;
{
Checking if we found requested function
}
if LowerCase(AExportName) <> AFuncName then
continue;
// Ordinal
AOffset := RVAToFileOffset(AExportDir.AddressOfNameOrdinals) + (I * SizeOf(Word));
SetFilePointer(hFile, AOffset, nil, FILE_BEGIN);
if NOT ReadFile(hFile, AOrdinal, SizeOf(Word), dwBytesRead, 0) then
continue;
///
// Function Address
AOffset := RVAToFileOffset(AExportDir.AddressOfFunctions) + (AOrdinal * SizeOf(Cardinal));
SetFilePointer(hFile, AOffset, nil, FILE_BEGIN);
if NOT ReadFile(hFile, AFuncAddress, SizeOf(Cardinal), dwBytesRead, 0) then
continue;
///
result := AFuncAddress;
end;
finally
CloseHandle(hFile);
end;
end;
GetProcAddress Alternative (Without UntEnumDLLExport.pas
)
function GetProcAddress_ALT(hModule : HMODULE; lpProcName : LPCSTR) : Pointer;
var AFuncAddress : Cardinal;
begin
result := nil;
///
AFuncAddress := GetExportAddress(GetModuleImagePath(hModule), lpProcName);
if (AFuncAddress > 0) then
result := Pointer(hModule + AFuncAddress);
end;
/!\ You will find the code of GetModuleImagePath
function Here
What about the future ?
I will adapat the function GetExportAddress()
to catch export table directly from process memory instead of reading file on disk.
This will remove the need of enumerating module to catch target module instance filename (function GetModuleImagePath
).
Less dependencies and API's you use and less likely your piece of code will get flagged. Especially while creating Malware while conducting red team exercice :-)
Written the Nov. 23, 2020, 10:10 a.m. by Jean-Pierre LESUEUR
Updated: 1 month, 4 weeks ago.