unit PE;

interface

uses Windows, SysUtils, Classes, Dialogs;

type
  //////////////////////////////////////////////////////////////////////////////
  ///Lecture de la table des sections///////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  TSection = record
    Header : TImageSectionHeader;
    Donnees: pChar;
  end;

  TSectionArray = Array of TSection;

  TSectionTable = class
  private
    fSections: TSectionArray;

    procedure LireSections(const Fichier: String);

    function  GetSection(Index: Integer): TSection;
    procedure SetSection(Index: Integer; const Value: TSection);
    function  GetName(Index: Integer): String;
    function  GetCount: Integer;
    function  GetCharacteristics(Index: Integer): String;
  public
    constructor Create(Fichier: String);
    destructor  Destroy; override;
    function    SectionHasCharacteristic(Index: Integer; Charac: Cardinal): Boolean;

    property Sections[Index: Integer]  : TSection read GetSection write SetSection; default;
    property Name    [Index: Integer]  : String              read GetName;
    property Count                     : Integer             read GetCount;
    property StrCharacs[Index: Integer]: String              read GetCharacteristics;
  end;
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  ///Lecture de la table d'importation//////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  _IMAGE_IMPORT_DESCRIPTOR = record
    OriginalFirstThunk: DWORD;
    TimeDateStamp: DWORD;
    ForwarderChain: DWORD;
    Name1: DWORD;
    FirstThunk: DWORD;
  end;

  TImageImportDescriptor = _IMAGE_IMPORT_DESCRIPTOR;

  _IMAGE_IMPORT_BY_NAME = record
    Hint: WORD;
    Name: String;
  end;

  TImageImportByName = _IMAGE_IMPORT_BY_NAME;

  TImportedDLL = record
    Name: String;
    Functions: Array of TImageImportByName;
  end;

  TImportTable = class
  private
    fDLL: Array of TImportedDLL;
    fDLLCount: Integer;
    fFCount: Integer;

    procedure LireTable(Fichier: String);

    function GetDLL(Index: Integer): TImportedDLL;
    function GetFunction(DLL, Index: Integer): TImageImportByName;
    procedure SetDLL(Index: Integer; const Value: TImportedDLL);
    procedure SetFunction(DLL, Index: Integer; const Value: TImageImportByName);
  public
    constructor Create(Fichier: String);

    function IsFunctionImported(Name: String; out iDLL: Integer): Boolean;
    function IsDLLImported(DLL: String): Boolean;
    function FindDLLByName(Name: String): Integer;
    function FindFunctionByName(Name: String; iDLL: Integer): Integer;

    property DLLs[Index: Integer]: TImportedDLL read GetDLL write SetDLL;
    property Functions[DLL, Index: Integer]: TImageImportByName read GetFunction write SetFunction;
    property DLLCount     : Integer read fDLLCount write fDLLCount;
    property FunctionCount: Integer read fFCount   write fFCount;
  end;
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  ///Lecture de la table d'exportation//////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////

  TExportedFunction = record
    Name: String;
    Ordinal: Word;
    Address: Cardinal;
  end;

  type
    TExportTable = class
    private
      fFunctions: array of TExportedFunction;
    function GetCount: Integer;

      procedure LireTable(Fichier: String);

      function GetFunction(Index: Integer): TExportedFunction;
      procedure SetFunction(Index: Integer; const Value: TExportedFunction);
    public
      constructor Create(Fichier: String);

      function IsFunctionExported(Name: string; Ordinal: SmallInt = -1) : Boolean;
      function FindFunctionOrdinal(Name: String): SmallInt;
      function FindFunctionName(Ordinal: SmallInt): String;
      function GetFunctionIndex(Name: String; Ordinal: SmallInt = -1): Integer;

      property Functions[Index: Integer]: TExportedFunction read GetFunction write SetFunction; default;
      property Count                    : Integer           read GetCount;
    end;
  //////////////////////////////////////////////////////////////////////////////

resourcestring
  ///Erreurs/////////////////////////////////////////////////////////////////////
  sFileDoesntExists = 'Le fichier "%s" n''existe pas !';
  sNotAValidPE      = '"%s" n''est pas un fichier PE valide !';
  //////////////////////////////////////////////////////////////////////////////

  ///Caractéristiques d'une section//////////////////////////////////////////////
  sCntCode          = 'La section contient du code exécutable';
  sCntInitialized   = 'La section contient des données initialisées';
  sCntUnInitialized = 'La section contient des données non initialisées';
  sLnkComDat        = 'La section contient des données COMDAT';

  sAlign1Bytes      = 'Données alignées sur un octet';
  sAlign2Bytes      = 'Données alignées sur deux octets';
  sAlign4Bytes      = 'Données alignées sur quatre octets';
  sAlign8Bytes      = 'Données alignées sur huit octets';
  sAlign16Bytes     = 'Données alignées sur seize octets';
  sAlign32Bytes     = 'Données alignées sur 32 octets';
  sAlign64Bytes     = 'Données alignées sur 64 octets';
  sAlign128Bytes    = 'Données alignées sur 128 octets';
  sAlign256Bytes    = 'Données alignées sur 256 octets';
  sAlign512Bytes    = 'Données alignées sur 512 octets';
  sAlign1024Bytes   = 'Données alignées sur un kilo-octet';
  sAlign2048Bytes   = 'Données alignées sur deux kilo-octets';
  sAlign4096Bytes   = 'Données alignées sur quatre kilo-octets';
  sAlign8192Bytes   = 'Données alignées sur huit kilos-octets';

  sLnkNRelocOVFL    = 'La section contient des relocations étendues';
  
  sMemDiscardable   = 'La section peut être supprimée après utilisation';
  sMemNotCached     = 'La section ne peut être mise en cache';
  sMemNotPaged      = 'La section ne peut être placée en mémoire paginée';
  sMemShared        = 'La section peut être partagée en mémoire';
  sMemExecute       = 'La section peut être exécutée comme du code';
  sMemRead          = 'La section peut être lue';
  sMemWrite         = 'La section peut être écrite';
  //////////////////////////////////////////////////////////////////////////////


const
  ///Déclaration de certaines constantes manquantes pour les caractéristiques///
  ///d'une section (inutilisées selon windows.pas)//////////////////////////////
  IMAGE_SCN_ALIGN_128BYTES  = $00800000;
  IMAGE_SCN_ALIGN_256BYTES  = $00900000;
  IMAGE_SCN_ALIGN_512BYTES  = $00A00000;
  IMAGE_SCN_ALIGN_1024BYTES = $00B00000;
  IMAGE_SCN_ALIGN_2048BYTES = $00C00000;
  IMAGE_SCN_ALIGN_4096BYTES = $00D00000;
  IMAGE_SCN_ALIGN_8192BYTES = $00E00000;

  ///Tableau des caractéristiques d'une section/////////////////////////////////
  SectionCharacs: Array[0..25]Of Cardinal = (IMAGE_SCN_CNT_CODE,
                                             IMAGE_SCN_CNT_INITIALIZED_DATA,
                                             IMAGE_SCN_CNT_UNINITIALIZED_DATA,
                                             IMAGE_SCN_LNK_COMDAT,
                                             IMAGE_SCN_ALIGN_1BYTES,
                                             IMAGE_SCN_ALIGN_2BYTES,
                                             IMAGE_SCN_ALIGN_4BYTES,
                                             IMAGE_SCN_ALIGN_8BYTES,
                                             IMAGE_SCN_ALIGN_16BYTES,
                                             IMAGE_SCN_ALIGN_32BYTES,
                                             IMAGE_SCN_ALIGN_64BYTES,
                                             IMAGE_SCN_ALIGN_128BYTES,
                                             IMAGE_SCN_ALIGN_256BYTES,
                                             IMAGE_SCN_ALIGN_512BYTES,
                                             IMAGE_SCN_ALIGN_1024BYTES,
                                             IMAGE_SCN_ALIGN_2048BYTES,
                                             IMAGE_SCN_ALIGN_4096BYTES,
                                             IMAGE_SCN_ALIGN_8192BYTES,
                                             IMAGE_SCN_LNK_NRELOC_OVFL,
                                             IMAGE_SCN_MEM_DISCARDABLE,
                                             IMAGE_SCN_MEM_NOT_CACHED,
                                             IMAGE_SCN_MEM_NOT_PAGED,
                                             IMAGE_SCN_MEM_SHARED,
                                             IMAGE_SCN_MEM_EXECUTE,
                                             IMAGE_SCN_MEM_READ,
                                             IMAGE_SCN_MEM_WRITE);
  //////////////////////////////////////////////////////////////////////////////                                             

  ///Tableau des strings correspondantes////////////////////////////////////////
  StrSectionCharacs: Array[0..25]Of String = (sCntCode,
                                              sCntInitialized,
                                              sCntUnInitialized,
                                              sLnkComDat,
                                              sAlign1Bytes,
                                              sAlign2Bytes,
                                              sAlign4Bytes,
                                              sAlign8Bytes,
                                              sAlign16Bytes,
                                              sAlign32Bytes,
                                              sAlign64Bytes,
                                              sAlign128Bytes,
                                              sAlign256Bytes,
                                              sAlign512Bytes,
                                              sAlign1024Bytes,
                                              sAlign2048Bytes,
                                              sAlign4096Bytes,
                                              sAlign8192Bytes,
                                              sLnkNRelocOVFL,
                                              sMemDiscardable,
                                              sMemNotCached,
                                              sMemNotPaged,
                                              sMemShared,
                                              sMemExecute,
                                              sMemRead,
                                              sMemWrite);
  //////////////////////////////////////////////////////////////////////////////

  //Constante qui devrait être déclarée dans Windows.pas. Permet de tester la
  //nature des IMAGE_THUNK_DATA dans TImportTable.LireTable.
  IMAGE_ORDINAL_FLAG32 = $80000000;


////////////////////////////////////////////////////////////////////////////////
///Fonctions utiles/////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

(*
  Fonction vérifiant que le fichier dont le chemin est passé en paramètre est bien
  un fichier au format PE.
  S'il y a une erreur ou que ce n'est pas un fichier PE, la fonction renvoie False
*)
function IsValidPE(Fichier: String): Boolean;

(*
  Fonction de conversion d'une Adresse Virtuelle Relative (RVA) vers un offset
  de fichier valide.
  FichierPE est un stream contenant les données du fichier PE dans lequel
  retrouver l'offset de fichier à partir de RVA.
*)
function RVAToFileOffset(FichierPE: TStream; RVA: Cardinal): Cardinal;

(*
  IsZeroFilled renvoie True si la mémoire pointée par Mem est entièrement remplie
  de 0 (sur Taille octets), False sinon
*)
function IsZeroFilled(Mem: pByte; Taille: Integer): Boolean;

implementation

function IsValidPE(Fichier: String): Boolean;
var
  EnteteDOS: TImageDosHeader;  //Structure pour l'entête DOS MZ
  EntetePE : TImageNtHeaders;  //Structure pour l'entête PE
  FStream  : TFileStream;      //Stream de lecture
begin

  Result := True;

  If not FileExists(Fichier) then
    raise Exception.Create(Format(sFileDoesntExists, [Fichier]));

  FStream := TFileStream.Create(Fichier, fmOpenRead);
  try
    try
      //Lecture de l'entête DOS MZ
      FStream.ReadBuffer(EnteteDOS, SizeOf(EnteteDOS));

      //Comparaison de la signature du fichier avec la signature DOS
      If EnteteDOS.e_magic <> IMAGE_DOS_SIGNATURE then
        Result := False
      else
      begin
        //Déplacement jusqu'à l'entête PE dont l'offset est indiqué par _lfanew
        FStream.Seek(EnteteDOS._lfanew, soFromBeginning);
        //Lecture de l'entête PE
        FStream.ReadBuffer(EntetePE, SizeOf(EntetePE));

        //Comparaison de la signature de l'entête avec la signature PE
        If EntetePE.Signature <> IMAGE_NT_SIGNATURE then
          Result := False;
      end;
    except
      //En cas de problème, on renvoie False
      On Exception do
        Result := False;
    end;
  finally
    FStream.Free;
  end;
end;


function RVAToFileOffset(FichierPE: TStream; RVA: Cardinal): Cardinal;
var
  OrgPos: Int64;
  EnteteMZ: TImageDosHeader;
  EntetePE: TImageNtHeaders;
  Section : TImageSectionHeader;
  i, Nbr  : Integer;
begin
  (*
    Par défaut on renvoie la RVA donnée en paramètre, dans le cas où celle-ci
    pointe sur une zone qui n'est pas mappée en mémoire (et qui est donc déjà
    un offset de fichier)
  *)
  Result := RVA;

  //Sauvegarde de la position actuelle du stream
  OrgPos := FichierPE.Position;
  try
    FichierPE.Seek(0, soFromBeginning);
    FichierPE.Read(EnteteMZ, SizeOf(EnteteMZ));
    FichierPE.Seek(EnteteMZ._lfanew, soFromBeginning);
    FichierPE.Read(EntetePE, SizeOf(EntetePE));

    //Nombre de sections dans le fichier
    Nbr := EntetePE.FileHeader.NumberOfSections;

    if Nbr <> 0 then
      for i := 0 to Nbr - 1 do
      begin
        FichierPE.Read(Section, SizeOf(Section));

        //Si la RVA fournie se situe dans la section actuelle
        if (RVA >= Section.VirtualAddress) and (RVA < Section.VirtualAddress + Section.SizeOfRawData) then
        begin
          //On calcule l'offset de fichier à partir des informations d'adresses de la section
          Result := RVA - Section.VirtualAddress + Section.PointerToRawData;
          Break;
        end;
      end;
  finally
    //Retour à la position initiale
    FichierPE.Seek(OrgPos, soFromBeginning);
  end;
end;


function IsZeroFilled(Mem: pByte; Taille: Integer): Boolean;
begin
  Result := False;

  while (pByte(Integer(Mem) + Taille - 1)^ = 0) and (Taille >= 0) do
    Dec(Taille);

  if Taille = -1 then
    Result := True;
end;

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

{ TSectionTable }

constructor TSectionTable.Create(Fichier: String);
begin
  //Vérification de la validité du fichier
  If not IsValidPE(Fichier) then
    raise Exception.Create(Format(sNotAValidPE, [Fichier]));

  //Lecture et stockage des sections dans le tableau fSections
  LireSections(Fichier);
end;

function TSectionTable.GetSection(Index: Integer): TSection;
begin
  If (Index >= 0) and (Index < Length(fSections)) then
    Result := fSections[Index];
end;

procedure TSectionTable.SetSection(Index: Integer; const Value: TSection);
begin
  If (Index >= 0) and (Index < Length(fSections)) then
    fSections[Index] := Value;
end;

(*
  Le champ Name de TImageSectionHeader est un tableau d'octets où chaque octet
  représente un caractère du nom. La fonction GetName, getter de la propriété
  Name de la classe, permet de retourner directement ce nom sous forme de string 
*)
function  TSectionTable.GetName(Index: Integer): String;
var
  i: Integer;
begin
  Result := '';

  If (Index < 0) or (Index > Length(fSections)) then
    Exit;

  for i := 0 to Length(fSections[Index].Header.Name) - 1 do
    Result := Result + Chr(fSections[Index].Header.Name[i]);
end;

function TSectionTable.GetCount: Integer;
begin
  Result := Length(fSections);
end;

(*
  LireSections parcourt la table des sections du fichier PE fournit à la création
  de la classe et stocke dans le tableau fSections les informations de chaque
  section.
*)
procedure TSectionTable.LireSections(const Fichier: String);
var
  FichierPE : TFileStream;
  EnteteDOS : TImageDosHeader;  //Structure pour l'entête DOS MZ
  EntetePE  : TImageNtHeaders;  //Structure pour l'entête PE
  SectionNbr: Integer;
  i         : Integer;
begin
  FichierPE := TFileStream.Create(Fichier, fmOpenRead);

  Try
    //Lecture de l'entête PE
    FichierPE.Read(EnteteDOS, SizeOf(EnteteDOS));
    FichierPE.Seek(EnteteDOS._lfanew, soFromBeginning);
    FichierPE.Read(EntetePE, SizeOf(EntetePE));

    //On récupère le nombre de sections dans le fichier
    SectionNbr := EntetePE.FileHeader.NumberOfSections;

    If SectionNbr <> 0 then
    begin
      SetLength(fSections, SectionNbr);

      //Les sections sont à la suite dans le fichier, on les lit une à une
      for i := 0 to SectionNbr - 1 do
        FichierPE.Read(fSections[i].Header, SizeOf(fSections[i].Header));

      //Lecture et stockage des données de chaque section
      for i := 0 to SectionNbr - 1 do
      begin
        FichierPE.Seek(fSections[i].Header.PointerToRawData, soFromBeginning);

        GetMem(fSections[i].Donnees, fSections[i].Header.SizeOfRawData);
        FichierPE.Read(fSections[i].Donnees^, fSections[i].Header.SizeOfRawData);
      end;

    end;       
  Finally
    FreeAndNil(FichierPE);
  end;
end;

(*
  SectionHasCharacteristic renvoie True si la ième section de la table a la
  caractéristique Charac présente dans son champ Characteristics
*)
function TSectionTable.SectionHasCharacteristic(Index: Integer; Charac: Cardinal): Boolean;
begin
  Result := (fSections[Index].Header.Characteristics and Charac) <> 0;
end;

(*
  GetCharacteristics renvoie sous forme d'une string l'intégralité des caractéristiques
  de la ième section de la table.
*)
destructor TSectionTable.Destroy;
var
  i: Integer;
begin
  for i := 0 to Length(fSections) - 1 do
    FreeMem(fSections[i].Donnees, fSections[i].Header.SizeOfRawData);    

  inherited;
end;

function TSectionTable.GetCharacteristics(Index: Integer): String;
var
  i: Integer;
begin
  Result := '';

  for i := Low(SectionCharacs) to High(SectionCharacs) do
    if SectionHasCharacteristic(Index, SectionCharacs[i]) then
      Result := Result + StrSectionCharacs[i] + #13;
end;

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

{ TImportTable }

constructor TImportTable.Create(Fichier: String);
begin
  //Vérification de la validité du fichier
  If not IsValidPE(Fichier) then
    raise Exception.Create(Format(sNotAValidPE, [Fichier]));

  //Lecture de la table d'importations
  LireTable(Fichier);
end;

(*
  FindDLLByName renvoie l'index de la DLL nommée Name dans le tableau de DLL,
  afin de pouvoir y accéder directement par la propriété DLLs.
  La recherche n'est pas sensible à la casse.
  Name doit contenir l'extension du fichier.
*)
function TImportTable.FindDLLByName(Name: String): Integer;
var
  i: Integer;
begin
  Result := -1;

  for i := Low(fDLL) to High(fDLL) do
    if LowerCase(fDLL[i].Name) = LowerCase(Name) then
    begin
      Result := i;
      Break;
    end;
end;

(*
  FindFunctionByName renvoie l'index de la fonction nommée Name dans le
  tableau Functions de la DLL d'index iDLL.
  La recherche n'est pas sensible à la casse.
  Name peut contenir indifféremment un nom ou l'index d'exportation de la fonction.
  Par défaut la fonction comparera Name au nom d'exportation de la fonction, et à
  son index d'exportation (champ Hint) si le nom est vide.
*)
function TImportTable.FindFunctionByName(Name: String; iDLL: Integer): Integer;
var
  i: Integer;
begin
  Result := -1;

  for i := Low(fDLL[iDLL].Functions) to High(fDLL[iDLL].Functions) do
    if (fDLL[iDLL].Functions[i].Name <> '') and (LowerCase(fDLL[iDLL].Functions[i].Name) = Name) then
    begin
      Result := i;
      Break;
    end
    else if (fDLL[iDLL].Functions[i].Name = '') and (StrToIntDef(Name, -1) = fDLL[iDLL].Functions[i].Hint) then
    begin
      Result := i;
      Break;
    end;
end;

(*
  Renvoie True si un DLL du nom DLL est importée dans ce fichier PE.
*)
function TImportTable.IsDLLImported(DLL: String): Boolean;
begin
  Result := (FindDLLByName(DLL) <> -1);
end;

(*
  Renvoie True si une fonction de nom Name est importée dans ce fichier PE.
  Renvoie également dans iDLL l'index de la DLL dans laquelle la fonction a été
  trouvée.
*)
function TImportTable.IsFunctionImported(Name: String; out iDLL: Integer): Boolean;
var
  i, j: Integer;
begin
  Result := False;

  for i := Low(fDLL) to High(fDLL) do
    for j := Low(fDLL[i].Functions) to High(fDLL[i].Functions) do
      if (fDLL[i].Functions[j].Name <> '') and (LowerCase(fDLL[i].Functions[j].Name) = Name) then
      begin
        Result := True;
        iDLL   := i;
        Break;
      end
      else if (fDLL[i].Functions[j].Name = '') and (StrToIntDef(Name, -1) = fDLL[i].Functions[j].Hint) then
      begin
        Result := True;
        iDLL   := i;
        Break;
      end;
end;

procedure TImportTable.LireTable(Fichier: String);
var
  FichierPE  : TMemoryStream; //Utilisation d'un MemoryStream pour avoir le pointeur Memory
  EnteteMZ   : TImageDosHeader;
  EntetePE   : TImageNtHeaders;
  DataDir    : TImageDataDirectory;
  ImportDescs: Array of TImageImportDescriptor;
  i, j       : Integer;
  k          : Byte;
  Nom        : pChar;
  Hint       : WORD;
  ThunkData  : Cardinal;
begin
  FichierPE := TMemoryStream.Create;
  FichierPE.LoadFromFile(Fichier);
  try
    //On se rend à l'entête PE
    FichierPE.Read(EnteteMZ, SizeOf(EnteteMZ));
    FichierPE.Seek(EnteteMZ._lfanew, soFromBeginning);
    FichierPE.Read(EntetePE, SizeOf(EntetePE));

    //Dans l'entête facultatif, on récupère le deuxième Data Directory
    DataDir := EntetePE.OptionalHeader.DataDirectory[1];

    //Grâce à l'adresse contenue  dans le Data Directory on récupère tous les Import Descriptors
    FichierPE.Seek(RVAToFileOffset(FichierPE, DataDir.VirtualAddress), soFromBeginning);

    SetLength(ImportDescs, 1);
    FichierPE.Read(ImportDescs[0], SizeOf(ImportDescs[0]));

    //Lecture de tous les Import Descriptors. Un enregistrement vide indique la fin du tableau
    while not IsZeroFilled(@ImportDescs[High(ImportDescs)], SizeOf(ImportDescs[High(ImportDescs)])) do
    begin
      SetLength(ImportDescs, Length(ImportDescs) + 1);
      FichierPE.Read(ImportDescs[High(ImportDescs)], SizeOf(ImportDescs[High(ImportDescs)]));
    end;

    for i := 0 to High(ImportDescs) - 1 do
    begin
      FichierPE.Seek(RVAToFileOffset(FichierPE, ImportDescs[i].Name1), soFromBeginning);

      //Recherche de la taille du nom (= recherche du zéro terminal de la chaine)
      k := 0;
      while Byte(Pointer(Integer(FichierPE.Memory) + FichierPE.Position + k)^) <> 0 do
        Inc(k);

      GetMem(Nom, k + 1);
      FichierPE.Read(Nom^, k + 1); 

      //Ajout d'une DLL
      SetLength(fDLL, Length(fDLL) + 1);
      fDLL[High(fDLL)].Name := Nom;
      SetLength(fDll[High(fDLL)].Functions, 0);

      Inc(fDLLCount);

      FreeMem(Nom);
      
      j := 0;
      ThunkData := 1;

      while ThunkData <> 0 do
      begin
      	//On vérifie d'abord OriginalFirstThunk, et on n'utilise FirstThunk qu'à défaut.
        if ImportDescs[i].OriginalFirstThunk  <> 0 then
          FichierPE.Seek(RVAToFileOffset(FichierPE, ImportDescs[i].OriginalFirstThunk) + Cardinal(j) * SizeOf(ThunkData), soFromBeginning)
        else
          FichierPE.Seek(RVAToFileOffset(FichierPE, ImportDescs[i].FirstThunk) + Cardinal(j) * SizeOf(ThunkData), soFromBeginning);


        FichierPE.Read(ThunkData, SizeOf(ThunkData));

        if ThunkData = 0 then
          Break;

        //Exportation par ordinal seulement ?
        if ThunkData and IMAGE_ORDINAL_FLAG32 <> 0 then
        begin  
	  	    //Ajout d'une fonction
          SetLength(fDLL[High(fDLL)].Functions, Length(fDLL[High(fDLL)].Functions) + 1);
          fDLL[High(fDLL)].Functions[High(fDLL[High(fDLL)].Functions)].Hint := Word(ThunkData);
          fDLL[High(fDLL)].Functions[High(fDLL[High(fDLL)].Functions)].Name := '';

          Inc(fFCount);
        end
        else
          begin
            FichierPE.Seek(RVAToFileOffset(FichierPE, ThunkData), soFromBeginning);
            FichierPE.Read(Hint, SizeOf(Hint));

            //Recherche de la taille du nom (= recherche du zéro terminal de la chaine)
            k := 0;
            while Byte(Pointer(Integer(FichierPE.Memory) + FichierPE.Position + k)^) <> 0 do
              Inc(k);

            GetMem(Nom, k + 1);
            FichierPE.Read(Nom^, k + 1);

	    	    //Ajout d'une fonction
            SetLength(fDLL[High(fDLL)].Functions, Length(fDLL[High(fDLL)].Functions) + 1);
            fDLL[High(fDLL)].Functions[High(fDLL[High(fDLL)].Functions)].Hint := Hint;
            fDLL[High(fDLL)].Functions[High(fDLL[High(fDLL)].Functions)].Name := Nom;

            Inc(fFCount);

            FreeMem(Nom);
          end;

        Inc(j);
      end;
    end;
  finally
    SetLength(ImportDescs, 0);
    FichierPE.Free;
  end;
end;

function TImportTable.GetDLL(Index: Integer): TImportedDLL;
begin
  if (Index > High(fDLL)) then
    Exit;

  Result := fDLL[Index];
end;

function TImportTable.GetFunction(DLL, Index: Integer): TImageImportByName;
begin
  if (DLL > High(fDLL)) or (Index > High(fDLL[DLL].Functions)) then
    Exit;

  Result := fDll[DLL].Functions[Index];
end;

procedure TImportTable.SetDLL(Index: Integer; const Value: TImportedDLL);
begin
  fDLL[Index] := Value;
end;

procedure TImportTable.SetFunction(DLL, Index: Integer; const Value: TImageImportByName);
begin
  fDLL[DLL].Functions[Index] := Value;
end;

{ TExportTable }

constructor TExportTable.Create(Fichier: String);
begin
  //Vérification de la validité du fichier
  If not IsValidPE(Fichier) then
    raise Exception.Create(Format(sNotAValidPE, [Fichier]));

  //Lecture de la table d'exportations
  LireTable(Fichier);
end;

(*
  Renvoie le nom de la fonction exportée avec l'ordinal Ordinal. Renvoie une
  chaine vide si l'ordinal n'est pas trouvé.
*)
function TExportTable.FindFunctionName(Ordinal: SmallInt): String;
var
  i: Integer;
begin

  Result := '';

  for i := Low(fFunctions) to High(fFunctions) do
    if fFunctions[i].Ordinal = Word(Ordinal) then
    begin
      Result := fFunctions[i].Name;
      Break;
    end;

end;

(*
  Renvoie l'ordinal de la fonction exportée avec le nom Name. Si la fonction n'est
  pas trouvée, renvoie -1.
  La recherche n'est pas sensible à la casse.
*)
function TExportTable.FindFunctionOrdinal(Name: String): SmallInt;
var
  i: Integer;
begin

  Result := -1;

  for i := Low(fFunctions) to High(fFunctions) do
    if LowerCase(fFunctions[i].Name)  = LowerCase(Name) then
    begin
      Result := fFunctions[i].Ordinal;
      Break;
    end;

end;

(*
  Renvoie l'index de la fonction de nom Name ou d'ordinal Ordinal. Cet index est
  à utiliser avec la propriété Functions.
  La recherche par nom n'esdt pas sensible à la casse.
*)
function TExportTable.GetFunctionIndex(Name: String;
  Ordinal: SmallInt): Integer;
var
  i: Integer;
begin

  Result := -1;
        
  for i := Low(fFunctions) to High(fFunctions) do
    if ((Ordinal <> - 1) and (fFunctions[i].Ordinal = Word(Ordinal))) or (fFunctions[i].Name = Name) then
    begin
      Result := i;
      Break;
    end;
    
end;

(*
  Renvoie True si la fonction est exportée par le fichier PE, False sinon.
  La recherche peut se faire par nom ou par ordinal. La recherche par nom n'est
  pas sensible à la casse.
*)
function TExportTable.IsFunctionExported(Name: string; Ordinal: SmallInt = -1): Boolean;
var
  i: Integer;
begin

  Result := False;

  if Ordinal <> -1 then
  begin
    for i := Low(fFunctions) to High(fFunctions) do
      if fFunctions[i].Ordinal = Word(Ordinal) then
      begin
        Result := True;
        Break;
      end;
  end
  else
    Result := FindFunctionOrdinal(Name) <> -1; 
end;


(*
  Méthode de lecture des fonctions exportées, appelée par le constructeur.
*)
procedure TExportTable.LireTable(Fichier: String);
var
  Stream    : TMemoryStream;
  EnteteMZ  : TImageDosHeader;
  EntetePE  : TImageNtHeaders;
  ExportDir : TImageExportDirectory;
  i, NamePos: Cardinal;
  j         : Integer;
  Address   : Cardinal;
  Presente  : Boolean;
begin
  SetLength(fFunctions, 0);

  Stream := TMemoryStream.Create;
  try
    Stream.LoadFromFile(Fichier);

    //Récupération des entêtes MZ et PE
    Stream.Read(EnteteMZ, SizeOf(EnteteMZ));
    Stream.Seek(EnteteMZ._lfanew, soFromBeginning);
    Stream.Read(EntetePE, SizeOf(EntetePE));

    //On se rend au début de la table d'exportation
    Stream.Seek(RVAToFileOffset(Stream, EntetePE.OptionalHeader.DataDirectory[0].VirtualAddress), soFromBeginning);
    Stream.Read(ExportDir, SizeOf(ExportDir));

    //On retrouve toutes les fonctions
    if ExportDir.NumberOfFunctions <> 0 then
    begin
      Stream.Seek(RVAToFileOffset(Stream, Cardinal(ExportDir.AddressOfNames)), soFromBeginning);

      if ExportDir.NumberOfNames <> 0 then
        for i := 0 to ExportDir.NumberOfNames - 1 do
        begin
          //Nouvelle fonction
          SetLength(fFunctions, Length(fFunctions) + 1);

          //On se positionne dans le tableau de noms
          Stream.Seek(RVAToFileOffset(Stream, Cardinal(ExportDir.AddressOfNames)) + i * SizeOf(NamePos), soFromBeginning);

          //Lecture de la RVA du nom 
          Stream.Read(NamePos, SizeOf(NamePos));
          //Lecture du nom
          Stream.Seek(RVAToFileOffset(Stream, NamePos), soFromBeginning);

          //Recherche de la taille du nom (= recherche du zéro terminal de la chaîne) et lecture
          j := 0;
          while Byte(Pointer(Integer(Stream.Memory) + Stream.Position + j)^) <> 0 do
            Inc(j);

          SetLength(fFunctions[High(fFunctions)].Name, j);
          Stream.Read(fFunctions[High(fFunctions)].Name[1], j);

          //On récupère l'ordinal qui correspond (il se situe au même index que le nom)
          Stream.Seek(RVAToFileOffset(Stream, Cardinal(ExportDir.AddressOfNameOrdinals)) + i * SizeOf(Word), soFromBeginning);
          Stream.Read(fFunctions[High(fFunctions)].Ordinal, SizeOf(Word));
          fFunctions[High(fFunctions)].Ordinal := fFunctions[High(fFunctions)].Ordinal + ExportDir.Base;

          //Enfin, on récupère l'adresse de la fonction
          Stream.Seek(RVAToFileOffset(Stream, Cardinal(ExportDir.AddressOfFunctions)) + (fFunctions[High(fFunctions)].Ordinal - ExportDir.Base) * SizeOf(Cardinal), soFromBeginning);
          Stream.Read(fFunctions[High(fFunctions)].Address, SizeOf(Cardinal));
        end;

        //Cas des fonctions exportées par ordinal seulement :
        //chaque adresse de AddressOfFunctions n'ayant pas son correspondant dans le tableau Fonctions est ajoutée
        if ExportDir.NumberOfFunctions > ExportDir.NumberOfNames then
          for i := 0 to ExportDir.NumberOfFunctions do
          begin
            Stream.Seek(RVAToFileOffset(Stream, Cardinal(ExportDir.AddressOfFunctions)) + i * SizeOf(Cardinal), soFromBeginning);
        
            Stream.Read(Address, SizeOf(Address));

            //On cherche si l'adresse a déjà été récupérée
            Presente := False;
            for j := Low(fFunctions) to High(fFunctions) do
              if fFunctions[j].Address = Address then
              begin
                Presente := True;
                Break;
              end;

            //Si non et qu'elle n'est pas nulle, on ajoute la fonction
            if not Presente and (Address <> 0) then
            begin
              SetLength(fFunctions, Length(fFunctions) + 1);

              fFunctions[High(fFunctions)].Address := Address;
              fFunctions[High(fFunctions)].Name     := '';
              fFunctions[High(fFunctions)].Ordinal := i + ExportDir.Base;
            end;
          end;
    end;

  finally
    Stream.Free;
  end;
end;


function TExportTable.GetCount: Integer;
begin
  Result := Length(fFunctions);
end;

function TExportTable.GetFunction(Index: Integer): TExportedFunction;
begin
  if (Index >= Low(fFunctions)) and (Index < Length(fFunctions)) then
    Result := fFunctions[Index];
end;

procedure TExportTable.SetFunction(Index: Integer;
  const Value: TExportedFunction);
begin
  if (Index >= Low(fFunctions)) and (Index < Length(fFunctions)) then
    fFunctions[Index] := Value;
end;

end.
