unit CnSMRPEUtils;

interface

{$I CnWizards.inc}

uses
  SysUtils, Windows, Classes;

procedure GetImportTable(ss: TStrings; const PEFile: String);
procedure GetDelayImportTable(ss: TStrings; const PEFile: String);
procedure GetExportTable(ss: TStrings; const PEFile: String);

implementation

{$RANGECHECKS OFF}
{$IFDEF DELPHI7_UP}
  {$WARN SYMBOL_PLATFORM OFF}
  {$WARN UNIT_PLATFORM OFF}
{$ENDIF}

const
  IMAGE_ORDINAL_FLAG = DWORD($80000000);
  IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   = 11;
  IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   = 13;

type
  PIMAGE_IMPORT_BY_NAME = ^IMAGE_IMPORT_BY_NAME;
  {$EXTERNALSYM PIMAGE_IMPORT_BY_NAME}
  _IMAGE_IMPORT_BY_NAME = record
    Hint: Word;
    Name: array [0..0] of Char;
  end;
  {$EXTERNALSYM _IMAGE_IMPORT_BY_NAME}
  IMAGE_IMPORT_BY_NAME = _IMAGE_IMPORT_BY_NAME;
  {$EXTERNALSYM IMAGE_IMPORT_BY_NAME}
  TImageImportByName = IMAGE_IMPORT_BY_NAME;
  PImageImportByName = PIMAGE_IMPORT_BY_NAME;

  PIMAGE_THUNK_DATA32 = ^IMAGE_THUNK_DATA32;
  {$EXTERNALSYM PIMAGE_THUNK_DATA32}
  _IMAGE_THUNK_DATA32 = record
    case Integer of
      0: (ForwarderString: PBYTE);
      1: (Function_: PDWORD);
      2: (Ordinal: DWORD);
      3: (AddressOfData: PIMAGE_IMPORT_BY_NAME);
  end;
  {$EXTERNALSYM _IMAGE_THUNK_DATA32}
  IMAGE_THUNK_DATA32 = _IMAGE_THUNK_DATA32;
  {$EXTERNALSYM IMAGE_THUNK_DATA32}
  TImageThunkData32 = IMAGE_THUNK_DATA32;
  PImageThunkData32 = PIMAGE_THUNK_DATA32;

  TIIDUnion = record
    case Integer of
      0: (Characteristics: DWORD);
      1: (OriginalFirstThunk: PIMAGE_THUNK_DATA32);
  end;

  PIMAGE_IMPORT_DESCRIPTOR = ^IMAGE_IMPORT_DESCRIPTOR;
  {$EXTERNALSYM PIMAGE_IMPORT_DESCRIPTOR}
  _IMAGE_IMPORT_DESCRIPTOR = record
    Union: TIIDUnion;
    TimeDateStamp: DWORD;                  
    ForwarderChain: DWORD;
    Name: DWORD;
    FirstThunk: PIMAGE_THUNK_DATA32;         
  end;
  {$EXTERNALSYM _IMAGE_IMPORT_DESCRIPTOR}
  IMAGE_IMPORT_DESCRIPTOR = _IMAGE_IMPORT_DESCRIPTOR;
  {$EXTERNALSYM IMAGE_IMPORT_DESCRIPTOR}
  TImageImportDescriptor = IMAGE_IMPORT_DESCRIPTOR;
  PImageImportDescriptor = PIMAGE_IMPORT_DESCRIPTOR;

  {$EXTERNALSYM ImgDelayDescr}
  ImgDelayDescr = packed record
    grAttrs: DWORD;                 
    szName: DWORD;                  
    phmod: PDWORD;                  
    pIAT: TImageThunkData32;          
    pINT: TImageThunkData32;          
    pBoundIAT: TImageThunkData32;     
    pUnloadIAT: TImageThunkData32;    
    dwTimeStamp: DWORD;                                    
  end;
  TImgDelayDescr = ImgDelayDescr;
  PImgDelayDescr = ^ImgDelayDescr;

  PloadedImage = ^TLoadedImage;
  {$EXTERNALSYM _LOADED_IMAGE}
  _LOADED_IMAGE = record
    ModuleName: LPSTR;
    hFile: THandle;
    MappedAddress: PChar;
    FileHeader: PImageNtHeaders;
    LastRvaSection: PImageSectionHeader;
    NumberOfSections: ULONG;
    Sections: PImageSectionHeader;
    Characteristics: ULONG;
    fSystemImage: ByteBool;
    fDOSImage: ByteBool;
    Links: TListEntry;
    SizeOfImage: ULONG;
  end;
  {$EXTERNALSYM LOADED_IMAGE}
  LOADED_IMAGE = _LOADED_IMAGE;
  LoadedImage = _LOADED_IMAGE;
  TLoadedImage = _Loaded_IMAGE;

type
  TFWOrdinalData = record
    FuncName: ShortString;
    Ordinal: DWORD;
    Addr: String[10];
  end;
  TFWOrdinalDataArray = array of TFWOrdinalData;

var
  OrdinalData: TFWOrdinalDataArray;

function MapAndLoad(ImageName, DllPath: LPSTR; LoadedImage: PLoadedImage;
  DotDll, ReadOnly: Bool): Bool; stdcall; external 'Imagehlp.dll';

function UnMapAndLoad(LoadedImage: PLoadedImage): Bool; stdcall;
  external 'Imagehlp.dll';

function FieldOffset(const Struc; const Field): Cardinal;
begin
  Result := Cardinal(@Field) - Cardinal(@Struc);
end;

function IMAGE_FIRST_SECTION(NtHeader: PImageNtHeaders): PImageSectionHeader;
begin
  Result := PImageSectionHeader(Cardinal(NtHeader) +
    FieldOffset(NtHeader^, NtHeader^.OptionalHeader) +
    NtHeader^.FileHeader.SizeOfOptionalHeader);
end;

function ImageRvaToSection(NtHeaders: PImageNtHeaders; Base: Pointer;
  Rva: ULONG): PImageSectionHeader;
var
  NtSection: PImageSectionHeader;
  I: Integer;
begin
  Result := nil;
  NtSection := IMAGE_FIRST_SECTION(NtHeaders);
  for I := 0 to NtHeaders^.FileHeader.NumberOfSections - 1 do
    if (Rva >= NtSection.VirtualAddress) and
      (Rva < NtSection.VirtualAddress + NtSection.SizeOfRawData) then
    begin
      Result := NtSection;
      Exit;
    end
    else
      Inc(NtSection);
end;

function ImageRvaToVa(NtHeaders: PImageNtHeaders; Base: Pointer;
  Rva: ULONG; var LastRvaSection: PImageSectionHeader): Pointer;
var
  NtSection: PImageSectionHeader;
begin
  Result := nil;
  NtSection := LastRvaSection;
  if (LastRvaSection = nil) or (Rva < NtSection^.VirtualAddress) or
    (Rva < NtSection^.VirtualAddress + NtSection^.SizeOfRawData) then
    NtSection := ImageRvaToSection(NtHeaders, Base, Rva);
  if NtSection = nil then Exit;
  if LastRvaSection <> nil then
    LastRvaSection := NtSection;
  Result := Pointer(DWORD(Base) + (Rva - NtSection^.VirtualAddress) +
    NtSection^.PointerToRawData);
end;

function ImageDirectoryEntryToData(Base: Pointer; MappedAsImage: ByteBool;
  DirectoryEntry: Word; var Size: ULONG): Pointer;
const
  IMAGE_NT_OPTIONAL_HDR32_MAGIC = $10B;
  IMAGE_NT_OPTIONAL_HDR64_MAGIC = $20B;
  IMAGE_ROM_OPTIONAL_HDR_MAGIC  = $107;
var
  DOSHeader: PImageDosHeader;
  NTHeader: PImageNtHeaders;
  FileHeader: TImageFileHeader;
  NtSection: PImageSectionHeader;
  OptionalHeader: TImageOptionalHeader;
  DirectoryAddress: DWORD;
  I: Integer;
begin
  Result := nil;

  if (DWORD(Base) and 1) = 1 then
    Base := Pointer((DWORD(Base) and not 1));

  DOSHeader := PImageDosHeader(Base);
  if IsBadReadPtr(Pointer(Base), SizeOf(TImageNtHeaders)) then Exit;
  if (DOSHeader^.e_magic <> IMAGE_DOS_SIGNATURE) then Exit;
  NTHeader := PImageNtHeaders(DWORD(DOSHeader) + DWORD(DOSHeader^._lfanew));
  if NTHeader^.Signature <> IMAGE_NT_SIGNATURE then Exit;
  FileHeader := NTHeader^.FileHeader;
  if FileHeader.Machine <> IMAGE_FILE_MACHINE_I386 then Exit;
  OptionalHeader := NTHeader^.OptionalHeader;
  if OptionalHeader.Magic <> IMAGE_NT_OPTIONAL_HDR32_MAGIC then Exit;

  if DirectoryEntry >= OptionalHeader.NumberOfRvaAndSizes then
  begin
    Size := 0;
    Exit;
  end;

  DirectoryAddress :=
    OptionalHeader.DataDirectory[DirectoryEntry].VirtualAddress;
  if DirectoryAddress = 0 then
  begin
    Size := 0;
    Exit;
  end;

  Size := OptionalHeader.DataDirectory[DirectoryEntry].Size;
  if MappedAsImage and (DirectoryAddress < OptionalHeader.SizeOfHeaders) then
  begin
    Result := Pointer(DWORD(Base) + DirectoryAddress);
    Exit;
  end;

  NtSection := ImageRvaToSection(NTHeader, Base, DirectoryAddress);
  if NtSection = nil then
  begin
    Size := 0;
    Exit;
  end;

  for I := 0 to FileHeader.NumberOfSections - 1 do
    if (DirectoryAddress >= NtSection^.VirtualAddress) and
      (DirectoryAddress < NtSection^.VirtualAddress + NtSection^.SizeOfRawData) then
    begin
      Result := Pointer(DWORD(Base) +
        (DirectoryAddress - NtSection^.VirtualAddress) + NtSection^.PointerToRawData);
      Break;
    end
    else
      Inc(NtSection);
end;

procedure MapAndLoadModuleForReadOrdinalFuncName(const ModuleName: String);
type
  PDWORDArray = ^TDWORDArray;
  TDWORDArray = array [0..MaxInt div SizeOf(DWORD) - 1] of DWORD;
var
  liImageInfo: LoadedImage;
  pExportDirectory: PImageExportDirectory;
  nDirSize: Cardinal;
  pDummy: PImageSectionHeader;
  I: Cardinal;
  pNameRVAs: PDWORDArray;
  FuncName, FuncAddr: String;
  Ordinal: PWORD;
begin
  SetLength(OrdinalData, 0);
  if MapAndLoad(PAnsiChar(AnsiString(ModuleName)), nil, @liImageInfo, True, True) then
  begin
    try
      pExportDirectory := ImageDirectoryEntryToData(liImageInfo.MappedAddress,
        False, IMAGE_DIRECTORY_ENTRY_EXPORT, nDirSize);
      if (pExportDirectory <> nil) then
      begin
        pDummy := nil;
        pNameRVAs := ImageRvaToVa(liImageInfo.FileHeader,
          liImageInfo.MappedAddress,
          DWORD(pExportDirectory^.AddressOfNames), pDummy);
        pDummy := nil;
        Ordinal := ImageRvaToVa(liImageInfo.FileHeader,
          liImageInfo.MappedAddress,
          DWORD(pExportDirectory^.AddressOfNameOrdinals), pDummy);
        
        for I := 0 to pExportDirectory^.NumberOfNames - 1 do
        begin
          SetLength(OrdinalData, Length(OrdinalData) + 1);
          
          pDummy := nil;
          FuncName := PChar(ImageRvaToVa(liImageInfo.FileHeader,
            liImageInfo.MappedAddress, pNameRVAs^[i], pDummy));
          FuncAddr := PChar('0x' + IntToHex(Integer(pNameRVAs^[I]), 8));
          
          OrdinalData[Length(OrdinalData) - 1].FuncName := FuncName;
          OrdinalData[Length(OrdinalData) - 1].Addr := FuncAddr;
          OrdinalData[Length(OrdinalData) - 1].Ordinal := Ordinal^ + pExportDirectory^.Base;
          Inc(Ordinal);
        end;
      end;
    finally
      UnMapAndLoad(@liImageInfo);
    end;
  end;
end;

function ReadOrdinalFuncName(const Ordinal: DWORD): String;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to Length(OrdinalData) - 1 do
    if OrdinalData[I].Ordinal = Ordinal then
    begin
      Result := OrdinalData[I].FuncName;
      Break;
    end;
end;

function ReadOrdinalFuncAddr(const Ordinal: DWORD): String;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to Length(OrdinalData) - 1 do
    if OrdinalData[I].Ordinal = Ordinal then
    begin
      Result := OrdinalData[I].Addr;
      Break;
    end;
end;

procedure GetImportTable(ss: TStrings; const PEFile: String);
var
  ImageInfo: LOADED_IMAGE;
  DirSize: Cardinal;
  pDummy: PImageSectionHeader;
  Image: PIMAGE_IMPORT_DESCRIPTOR;
  ModuleName: PAnsiChar;
  Thunk: PImageThunkData32;
begin
  ss.BeginUpdate;
  try
    if MapAndLoad(PAnsiChar(AnsiString(PEFile)), nil, @ImageInfo, True, True) then
    begin
      try
        Image := ImageDirectoryEntryToData(ImageInfo.MappedAddress,
          False, IMAGE_DIRECTORY_ENTRY_IMPORT, DirSize);
        if (Image <> nil) then
        begin
          while Image^.Name <> 0 do
          begin
            pDummy := nil;

            ModuleName := ImageRvaToVa(ImageInfo.FileHeader,
              ImageInfo.MappedAddress, Image^.Name, pDummy);

            ss.Add(ModuleName);

            if Image^.Union.OriginalFirstThunk <> nil then

              Thunk := PImageThunkData32(ImageRvaToVa(ImageInfo.FileHeader,
                ImageInfo.MappedAddress, Image^.Union.Characteristics, pDummy))
            else
              Thunk := PImageThunkData32(ImageRvaToVa(ImageInfo.FileHeader,
                ImageInfo.MappedAddress, DWORD(Image^.FirstThunk), pDummy));

            if Thunk <> nil then
            try
              while Thunk^.Function_ <> nil do
              begin
                if (DWORD(Thunk^.Function_) and IMAGE_ORDINAL_FLAG) = IMAGE_ORDINAL_FLAG then
                begin
                  if Length(OrdinalData) = 0 then
                    MapAndLoadModuleForReadOrdinalFuncName(ModuleName);
                end;

                ss.Add(ModuleName);
                Inc(Thunk);
              end;
              Inc(Image);
            finally
              SetLength(OrdinalData, 0);
            end;
          end;
        end;
      finally
        UnMapAndLoad(@ImageInfo);
      end;
    end;
  finally
    ss.EndUpdate;
  end;
end;

procedure GetDelayImportTable(ss: TStrings; const PEFile: String);
var
  ImageInfo: LOADED_IMAGE;
  DirSize: Cardinal;
  Image: PImgDelayDescr;
  ModuleName: PAnsiChar;
  Thunk: PImageThunkData32;

  function RvaToVaWithAmendment(Value: DWORD): Pointer;
  var
    pDummy: PImageSectionHeader;
  begin
    if (Value > ImageInfo.SizeOfImage) and
      (Value > ImageInfo.FileHeader^.OptionalHeader.ImageBase) then
      Dec(Value, ImageInfo.FileHeader^.OptionalHeader.ImageBase);
    pDummy := nil;
    Result := ImageRvaToVa(ImageInfo.FileHeader, ImageInfo.MappedAddress,
      Value, pDummy);
  end;

begin
  ss.BeginUpdate;
  try
    if MapAndLoad(PAnsiChar(AnsiString(PEFile)), nil, @ImageInfo, True, True) then
    begin
      try
        Image := ImageDirectoryEntryToData(ImageInfo.MappedAddress,
          False, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, DirSize);
        if (Image <> nil) then
        begin
          while Image^.szName <> 0 do
          begin
            ModuleName := RvaToVaWithAmendment(DWORD(Image^.szName));

            ss.Add(ModuleName);
            Thunk := PImageThunkData32(RvaToVaWithAmendment(DWORD(Image^.pINT.AddressOfData)));
            if Thunk <> nil then
            try
              while Thunk^.Function_ <> nil do
              begin
                if (DWORD(Thunk^.Function_) and IMAGE_ORDINAL_FLAG) = IMAGE_ORDINAL_FLAG then
                begin
                  if Length(OrdinalData) = 0 then
                    MapAndLoadModuleForReadOrdinalFuncName(ModuleName);
                end;

                ss.Add(ModuleName);
                Inc(Thunk);
              end;
            Inc(Image);
            finally
              SetLength(OrdinalData, 0);
            end;
          end;
        end;
      finally
        UnMapAndLoad(@ImageInfo);
      end;
    end;
  finally
    ss.EndUpdate;
  end;
end;

procedure GetExportTable(ss: TStrings; const PEFile: String);
type
  PDWORDArray = ^TDWORDArray;
  TDWORDArray = array [0..MaxInt div SizeOf(DWORD) - 1] of DWORD;
var
  liImageInfo: LoadedImage;
  pExportDirectory: PImageExportDirectory;
  nDirSize: Cardinal;
  pDummy: PImageSectionHeader;
  I: Integer;
  pNameRVAs: PDWORDArray;
  FuncName, FuncAddr: String;
begin
  ss.BeginUpdate;
  try
    if MapAndLoad(PAnsiChar(AnsiString(PEFile)), nil, @liImageInfo, True, True) then
    begin
      try
        pExportDirectory := ImageDirectoryEntryToData(liImageInfo.MappedAddress,
          False, IMAGE_DIRECTORY_ENTRY_EXPORT, nDirSize);
        if (pExportDirectory <> nil) then
        begin
          pDummy := nil;
          pNameRVAs := ImageRvaToVa(liImageInfo.FileHeader,
            liImageInfo.MappedAddress,
            DWORD(pExportDirectory^.AddressOfNames), pDummy);
          
          for I := 0 to pExportDirectory^.NumberOfNames - 1 do
          begin
            pDummy := nil;
            FuncName := PChar(ImageRvaToVa(liImageInfo.FileHeader,
              liImageInfo.MappedAddress, pNameRVAs^[i], pDummy));
            FuncAddr := PChar('0x' + IntToHex(Integer(pNameRVAs^[I]), 8));

            ss.Add(FuncName);
          end;
        end;
      finally
        UnMapAndLoad(@liImageInfo);
      end;
    end;
  finally
    ss.EndUpdate;
  end;
end;

end.

