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:

// 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: 5 months ago.