Get DLL Exported Function Address From Memory
As promised, we will adapt our previous code grab an exported function directly from memory.
Serious advantage of this technique:
- We don't have to use
CreateToolHelp32Snapshot
anymore to enumerate modules and catch target module base address. - We don't need to parse PE Header from disk anymore, we will parse PE Header directly from memory.
Notice, it is still necessary to use
LoadLibrary
API to load desired DLL in memory. An alternative of LoadLibrary
would be to create our own PE Loader. We will cover that subject in a near future.
// Jean-Pierre LESUEUR (@DarkCoderSc)
//...
uses Windows;
//...
function GetExportAddressFromMemory(ALoadedModule : THandle {Returned by LoadLibrary(...)}; AFuncName : String) : Cardinal;
var dwBytesRead : SIZE_T;
AImageDosHeader : TImageDosHeader;
AImageNtHeaderSignature : DWORD;
x64Binary : Boolean;
AImageFileHeader : TImageFileHeader;
AImageOptionalHeader32 : TImageOptionalHeader32;
AImageOptionalHeader64 : TImageOptionalHeader64;
AExportAddr : TImageDataDirectory;
AExportDir : TImageExportDirectory;
I : Integer;
ACanCatchSection : Boolean;
AOffset : Cardinal;
pOffset : Pointer;
AExportName : AnsiString;
ALen : Cardinal;
AOrdinal : Word;
AFuncAddress : Cardinal;
hProcess : THandle;
function GetStringLength(AStartAtPos : Pointer) : Cardinal;
var ADummy : Byte;
begin
result := 0;
///
pOffset := Pointer(ALoadedModule + NativeUInt(AStartAtPos));
while True do begin
if NOT ReadProcessMemory(hProcess, Pointer(NativeUInt(pOffset) + result), @ADummy, SizeOf(Byte), dwBytesRead) then
Exit();
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;
///
if (ALoadedModule <= 0) then
Exit();
///
ACanCatchSection := False;
AFuncName := LowerCase(AFuncName);
hProcess := GetCurrentProcess();
pOffset := Pointer(ALoadedModule);
if NOT ReadProcessMemory(hProcess, pOffset, @AImageDosHeader, SizeOf(TImageDosHeader), dwBytesRead) then
Exit();
if (AImageDosHeader.e_magic <> IMAGE_DOS_SIGNATURE) then
Exit;
///
pOffset := Pointer(ALoadedModule + AImageDosHeader._lfanew);
if NOT ReadProcessMemory(hProcess, pOffset, @AImageNtHeaderSignature, SizeOf(DWORD), dwBytesRead) then
Exit();
if (AImageNTHeaderSignature <> IMAGE_NT_SIGNATURE) then
Exit;
///
pOffset := Pointer(ALoadedModule + AImageDosHeader._lfanew + sizeOf(DWORD));
if NOT ReadProcessMemory(hProcess, pOffset, @AImageFileHeader, SizeOf(TImageFileHeader), dwBytesRead) then
Exit();
ACanCatchSection := True;
x64Binary := (AImageFileHeader.Machine = IMAGE_FILE_MACHINE_AMD64);
pOffset := Pointer(NativeUInt(pOffset) + SizeOf(TImageFileHeader));
if x64Binary then begin
if NOT ReadProcessMemory(hProcess, pOffset, @AImageOptionalHeader64, AImageFileHeader.SizeOfOptionalHeader, dwBytesRead) then
Exit();
end else begin
if NOT ReadProcessMemory(hProcess, pOffset, @AImageOptionalHeader32, AImageFileHeader.SizeOfOptionalHeader, dwBytesRead) 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];
pOffset := Pointer(ALoadedModule + AExportAddr.VirtualAddress);
if NOT Assigned(pOffset) then
Exit;
if NOT ReadProcessMemory(hProcess, pOffset, @AExportDir, SizeOf(TImageExportDirectory), dwBytesRead) then
Exit();
if (AExportDir.NumberOfFunctions <= 0) then
Exit;
///
{
Enumerate Named Exported Functions
}
for I := 0 to AExportDir.NumberOfNames - 1 do begin
// Function Name
pOffset := Pointer(ALoadedModule + (AExportDir.AddressOfNames + (I * SizeOf(Cardinal))));
if NOT ReadProcessMemory(hProcess, pOffset, @AOffset, SizeOf(Cardinal), dwBytesRead) then
Exit();
pOffset := Pointer(AOffset);
ALen := GetStringLength(pOffset);
SetLength(AExportName, ALen);
if NOT ReadProcessMemory(hProcess, pOffset, @AExportName[1], ALen, dwBytesRead) then
Exit();
{
Checking if we found requested function
}
if LowerCase(AExportName) <> AFuncName then
continue;
// Ordinal
pOffset := Pointer(ALoadedModule + (AExportDir.AddressOfNameOrdinals) + (I * SizeOf(Word)));
if NOT ReadProcessMemory(hProcess, pOffset, @AOrdinal, SizeOf(Word), dwBytesRead) then
Exit();
// Function Address
pOffset := Pointer(ALoadedModule + (AExportDir.AddressOfFunctions) + (AOrdinal * SizeOf(Cardinal)));
if NOT ReadProcessMemory(hProcess, pOffset, @AFuncAddress, SizeOf(Cardinal), dwBytesRead) then
Exit();
///
result := AFuncAddress;
end;
end;
Bellow code doesn't change (GetProcAddress_ALT)
function GetProcAddress_ALT(hModule : HMODULE; lpProcName : LPCSTR) : Pointer;
var AFuncAddress : Cardinal;
begin
result := nil;
///
AFuncAddress := GetExportAddressFromMemory(hModule, lpProcName);
if (AFuncAddress > 0) then
result := Pointer(hModule + AFuncAddress);
end;
Finally, you can trigger your MessageBoxW :-)
//...
var hUser32 : THandle;
_MessageBoxW : function(hWnd: HWND; lpText, lpCaption: LPCWSTR; uType: UINT): Integer; stdcall;
//...
begin
hUser32 := LoadLibrary('user32.dll');
if (hUser32 = 0) then
Exit();
try
@_MessageBoxW := GetProcAddress_ALT(hUser32, 'MessageBoxW');
_MessageBoxW(0, 'Hello World', 'Hey', 0);
finally
FreeLibrary(hUser32);
end;
//...
Written the Nov. 23, 2020, 10:08 a.m. by Jean-Pierre LESUEUR
Updated: ago.