PARTIE 5 : La table des sections
Date de publication : 08/10/2005
Par
Iczelion (Iczelion's Win32 Assembly Homepage)
Traduction et adaptation par Olivier Lance (Accueil)
Jusqu'à ce tutoriel, nous avons vu l'entête et l'entête . La table des sections est le sujet de ce nouveau tutoriel.
Article original :
Retour à
Tutoriel précédent :
Tutoriel suivant :
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 :
- Lire NumberOfSections dans l'entête fichier pour connaître le nombre de sections dans le fichier.
- Utiliser la valeur SizeOfHeaders comme l'offset fichier de la table des sections et y déplacer le pointeur de fichier.
- Parcourir le tableau de structures en examinant chaque membre.
- 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. A partir de là, on peut placer la section en mémoire et lui assigner les attributs spécifiés dans Characteristics.
- 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;
EntetePE : TImageNtHeaders;
SectionNbr: Integer;
i : Integer;
begin
FichierPE := TFileStream.Create(Fichier, fmOpenRead);
Try
FichierPE.Read(EnteteDOS, SizeOf(EnteteDOS));
FichierPE.Seek(EnteteDOS._lfanew, soFromBeginning);
FichierPE.Read(EntetePE, SizeOf(EntetePE));
SectionNbr := EntetePE.FileHeader.NumberOfSections;
If SectionNbr <> 0 then
begin
SetLength(fSections, SectionNbr);
for i := 0 to SectionNbr - 1 do
FichierPE.Read(fSections[i].Header, SizeOf(fSections[i].Header));
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


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 oeuvre intellectuelle protégée par les droits d'auteurs. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
Cette page est déposée à la
SACD.