PARTIE 5 : La table des sections

Iczelion

Traducteur

Traduction et adaptation par Olivier Lance

Jusqu'à ce tutoriel, nous avons vu l'entête DOS MZ et l'entête PE. La table des sections est le sujet de ce nouveau tutoriel.
Article original : Tutorial 5: Section Table

Retour à la liste des tutoriels

Tutoriel précédent : Entête facultatif
Tutoriel suivant : La table d'importation

- Titre : PARTIE 5 : La table des sections

- Auteur : Iczelion

- Traducteur : Traduction et adaptation par Olivier Lance

- Parution : 8 octobre 2005

- Mise à jour : 6 janvier 2020

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2005 Olivier Lance. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Théorie

- Exemple

- Téléchargements

- Liens

Théorie

La table des sections est en fait un tableau de structures suivant immédiatement l'entête PE. Le nombre de membres dans ce tableau est donné par le champ NumberOfSections dans l'entête fichier (TImageFileHeader). La structure en question est appelée TImageSectionHeader.

En voici la déclaration dans Windows.pas :

_IMAGE_SECTION_HEADER = packed record
Name: packed array[0..IMAGE_SIZEOF_SHORT_NAME-1] of Byte;
Misc: TISHMisc;
VirtualAddress: DWORD;
SizeOfRawData: DWORD;
PointerToRawData: DWORD;
PointerToRelocations: DWORD;
PointerToLinenumbers: DWORD;
NumberOfRelocations: Word;
NumberOfLinenumbers: Word;
Characteristics: DWORD;
end;

TImageSectionHeader = _IMAGE_SECTION_HEADER;

Encore une fois, tous les champs ne sont pas utiles. Voici la description de ceux qui sont vraiment intéressants :

Champ

Contenu

Name

Ce champ contient le nom de la section. Notez que la taille maximale est de 8 octets (IMAGE_SIZEOF_SHORT_NAME = 8). Le nom n'est qu'une "étiquette", rien de plus. Vous pouvez utiliser n'importe quel nom, voire laisser ce champ vide. Notez également qu'il n'y a aucune mention de caractère terminal nul. Le nom n'est pas une chaîne à zéro terminal, donc ne vous attendez pas à trouver un 0 à sa fin.

VirtualAddress

La RVA de la section. Le PE Loader vérifie et utilise la valeur de ce champ lorsqu'il place une section en mémoire. Ainsi si la valeur de ce champ est $1000 et que le fichier PE est chargé à l'adresse $400000, la section sera chargée à l'adresse $401000.

SizeOfRawData

La taille des données de la section, arrondie au prochain multiple de FileAlignment (voir l'entête fichier). Le PE Loader vérifie ce champ pour savoir combien d'octets de la section il va devoir placer en mémoire.

PointerToRawData

L'offset fichier du début de la section. Le PE Loader utilise la valeur de ce champ pour trouver les données de la section dans le fichier.

Characteristics

Contient des drapeaux indiquant par exemple si la section contient du code exécutable, des données non initialisées, si elle dispose d'un accès en écriture ou en lecture.

Maintenant que nous connaissons la structure TImageSectionHeader, voyons comment on pourrait émuler le travail du PE Loader :

1. Lire NumberOfSections dans l'entête fichier pour connaître le nombre de sections dans le fichier.

2. Utiliser la valeur SizeOfHeaders comme l'offset fichier de la table des sections et y déplacer le pointeur de fichier.

3. Parcourir le tableau de structures en examinant chaque membre.

4. Pour chaque membre, récupérer la valeur de PointerToRawData et déplacer le pointeur de fichier à cet offset. Ensuite, lire la valeur de SizeOfRawData pour connaître le nombre d'octets à placer en mémoire. Lire la valeur VirtualAddress et y ajouter la valeur ImageBase pour obtenir l'adresse virtuelle où la section est censée débuter. À partir de là, on peut placer la section en mémoire et lui assigner les attributs spécifiés dans Characteristics.

5. Parcourir le tableau jusqu'à avoir examiné toutes les sections.

Notez que nous n'avons pas utilisé le champ Name : il n'est pas vraiment nécessaire.

Exemple

L'exemple suivant permet d'ouvrir un fichier PE et d'afficher les informations des sections qu'il contient. Le programme est basé sur l'utilisation d'une classe TSectionTable qui permet de lire toutes les sections d'un fichier exécutable et d'y donner accès au moyen d'une propriété de type tableau. Nous ne détaillerons pas toute la classe mais donnerons seulement sa déclaration et la méthode qui nous intéresse le plus : LireSections.

Déclaration de TSectionTable

type
TSection = record
Header : TImageSectionHeader;
Donnees: pChar;
end;

TSectionArray = Array of TSection;

TSectionTable = class
private
FichierPE: TFileStream;
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;

On remarque en premier lieu la déclaration du type TSection. Il associe simplement aux headers d'une section les données qui lui correspondent, sous forme d'un pChar pointant vers une zone mémoire qui sera allouée au besoin.

Les propriétés publiques de cette classe sont les suivantes :

- Sections : Le tableau des sections du fichier exécutable ouvert par la classe.

- Name : Le tableau des noms de chacune des sections de l'exécutable.

- Count : Le nombre de sections dans l'exécutable.

- StrCharacs : Un tableau de chaînes de caractères donnant une interprétation lisible du champ characteristics de chaque section.

La méthode LireSections est appelée dans le constructeur, auquel on passe le fichier à ouvrir. Le constructeur se charge de vérifier que le fichier fourni est bien un fichier PE en utilisant la fonction vue en deuxième partie puis exécute LireSections :

Méthode de lecture des sections

procedure TSectionTable.LireSections(const Fichier: String);
var
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;

Après s'être rendu à la table des sections grâce aux informations contenues dans les différents entêtes, la méthode ajoute chaque section au tableau fSections (qui sera accessible par la propriété Sections) puis parcourt le fichier pour retrouver les données de chacune et les enregistrer en mémoire.

Une fois la lecture terminée, le fichier est fermé et toutes les informations sont accessibles sans relecture ultérieure.
Il suffit alors d'accéder aux éléments de Sections pour obtenir toutes les informations voulues.

Téléchargements

Vous pouvez télécharger le projet utilisant cette classe ici :
miroir 1
miroir 2 (si le miroir 1 ne fonctionne pas)

Vous aurez également besoin du fichier PE.pas :
miroir 1
miroir 2 (si le miroir 1 ne fonctionne pas)

Placez PE.pas dans un répertoire de votre choix et dézippez l'application dans un sous-répertoire de celui-ci pour pouvoir compiler.

Liens

- MSDN : Image Help Library

- MSDN : IMAGE_SECTION_HEADER

Tutoriel suivant : La table d'importation