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 :-)

comments powered by Disqus