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;
//...
comments powered by Disqus