Page 1 sur 2

Entete exécutable Win32 (Entête PE)

Publié : jeu. 19/févr./2009 15:00
par Cls
Salut à tous,

dans le cadre d'un projet perso, je me forme actuellement aux exécutables Windows et à leur structure. En faisant une recherche sur le forum (FR uniquement), je n'ai rien trouvé à ce sujet. Je poste donc un petit code convertit d'un code C++ trouvé sur le net permettant d'afficher l'entête PE d'un EXE.

Ce code est incomplet, il démontre seulement le principe de fonctionnement ;)

Code : Tout sélectionner

; --------------------------------------------------------------------------------------
; Auteur C++ : syncppfrance sur cppfrance.com
; Auteur PB : Cls
; Version : Février 2009
; --------------------------------------------------------------------------------------
; Source d'origine: http://www.cppfrance.com/codes/ANALYSE-FORMAT-ENTETE-PE_25561.aspx
; --------------------------------------------------------------------------------------
; Desc : analyse de l'entête d'un fichier executable sous Windows (win32)

;
;- Structures
Structure Misc
   PhysicalAddress.l
   VirtualSize.l 
EndStructure

Structure IMAGE_SECTION_HEADER
  Name.l
  Misc.Misc
  VirtualAddress.l
  SizeOfRawData.l
  PointerToRawData.l
  PointerToRelocations.l
  PointerToLinenumbers.l
  NumberOfRelocations.w
  NumberOfLinenumbers.w
  Characteristics.l
EndStructure

;
;- Tableaux
Dim DirectoryLabel.s(15)
  DirectoryLabel(0) = "IMAGE_DIRECTORY_ENTRY_EXPORT (0)"
  DirectoryLabel(1) = "IMAGE_DIRECTORY_ENTRY_IMPORT (1)"
  DirectoryLabel(2) = "IMAGE_DIRECTORY_ENTRY_RESOURCE (2)"
  DirectoryLabel(3) = "IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)"
  DirectoryLabel(4) = "IMAGE_DIRECTORY_ENTRY_SECURITY (4)"
  DirectoryLabel(5) = "IMAGE_DIRECTORY_ENTRY_BASERELOC (5)"
  DirectoryLabel(6) = "IMAGE_DIRECTORY_ENTRY_DEBUG (6)"
  DirectoryLabel(7) = "IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)"
  DirectoryLabel(8) = "IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)"
  DirectoryLabel(9) = "IMAGE_DIRECTORY_ENTRY_TLS (9)"
  DirectoryLabel(10) = "IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)"
  DirectoryLabel(11) = "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)"
  DirectoryLabel(12) = "IMAGE_DIRECTORY_ENTRY_IAT (12)"
  DirectoryLabel(13) = "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT (13)"
  DirectoryLabel(14) = "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR (14)"
  DirectoryLabel(15) = "??????? (15)"

;
;- Constantes
#IMAGE_SCN_CNT_CODE = $00000020
#IMAGE_SCN_CNT_INITIALIZED_DATA = $00000040
#IMAGE_SCN_CNT_UNINITIALIZED_DATA = $00000080
#IMAGE_SCN_MEM_SHARED = $10000000
#IMAGE_SCN_MEM_EXECUTE = $20000000
#IMAGE_SCN_MEM_READ = $40000000
#IMAGE_SCN_MEM_WRITE = $80000000


;
;- Début du programme
file.s = OpenFileRequester("Sélectionner un fichier", "", "Executable (*.exe)|*.exe|Tous les fichiers (*.*)|*.*", 0)

If file
  
  If ReadFile(0, file)
  
    Debug "Fichier sélectionné : " + file
  
    DOSHeader.IMAGE_DOS_HEADER
    ReadData(0, DOSHeader, SizeOf(IMAGE_DOS_HEADER))
    
    If DOSHeader\e_magic = $5A4D ; "MZ"
      
      ; Se rend à l'entête PE
      FileSeek(0, DOSHeader\e_lfanew)
      
      PEHeader.IMAGE_NT_HEADERS
      ReadData(0, PEHeader, SizeOf(IMAGE_NT_HEADERS))

      If PEHeader\Signature = $00004550 ; 00PE
        
        ; -----------------------------------------------------------------------------
        Debug ""
        Debug "[*] PE Header"
        
        Debug "Nombre de sections : " + Str(PEHeader\FileHeader\NumberOfSections)
        
        Debug "Taille du code: " + Str(PEHeader\OptionalHeader\SizeOfCode)
        Debug "Taille des donnees initialisees: " + Str(PEHeader\OptionalHeader\SizeOfInitializedData)
        Debug "Taille des donnees non initialisees: " + Str(PEHeader\OptionalHeader\SizeOfUninitializedData)
        Debug "Adresse du point d'entree dans le fichier: 0x" + Hex(PEHeader\OptionalHeader\AddressOfEntryPoint) + " Memoire: 0x" + Hex(PEHeader\OptionalHeader\AddressOfEntryPoint + PEHeader\OptionalHeader\ImageBase)
        Debug "Image base (position en memoire): 0x" + Hex(PEHeader\OptionalHeader\ImageBase)
        Debug "Alignement de " + Str(PEHeader\OptionalHeader\SectionAlignment) + " octets pour les sections"
        Debug "Alignement de " + Str(PEHeader\OptionalHeader\FileAlignment) + " octets pour le fichier"
        Debug "Taille du fichier en memoire: " + Str(PEHeader\OptionalHeader\SizeOfImage)
        Debug "Position de la 1ere section ds le fichier: 0x" + Hex(PEHeader\OptionalHeader\SizeOfHeaders)

        ; -----------------------------------------------------------------------------
        Debug ""
        Debug "[*] Directory"
        
        For x = 0 To 15
          
          If PEHeader\OptionalHeader\DataDirectory[x]\VirtualAddress <> 0 And PEHeader\OptionalHeader\DataDirectory[x]\Size > 0
            
            Debug "Nom : " + DirectoryLabel(x)
            Debug "Adresse virtuelle : " + Hex(PEHeader\OptionalHeader\DataDirectory[x]\VirtualAddress) + " Taille : " + Str(PEHeader\OptionalHeader\DataDirectory[x]\Size)
          
          EndIf

        Next
        
        ; -----------------------------------------------------------------------------
        Debug ""
        Debug "[*] Section"
        
        SectionHeader.IMAGE_SECTION_HEADER
        
        For x = 0 To PEHeader\FileHeader\NumberOfSections - 1
        
          sectionName.s
          ReadData(0, SectionHeader, SizeOf(IMAGE_SECTION_HEADER))
          sectionName.s = PeekS(@SectionHeader\Name)
          Debug ""
          Debug "Nom de la section : " + sectionName
          
          Debug "Adresse physique: 0x" + Hex(SectionHeader\Misc\PhysicalAddress)
          Debug "Taille virtuelle: " + Str(SectionHeader\Misc\VirtualSize) + " (0x" + Hex(SectionHeader\Misc\VirtualSize) + ")"
          Debug "Adresse en memoire: 0x" + Hex(SectionHeader\VirtualAddress) + " Reel: 0x" + Hex(PEHeader\OptionalHeader\ImageBase + SectionHeader\VirtualAddress)
          Debug "Taille dans le fichier: " + Hex(SectionHeader\SizeOfRawData)
          Debug "Adresse dans le fichier: 0x" + Hex(SectionHeader\PointerToRawData)

          If SectionHeader\Characteristics & #IMAGE_SCN_CNT_CODE
            Debug "Section de code"
          EndIf
          If SectionHeader\Characteristics & #IMAGE_SCN_CNT_INITIALIZED_DATA 
            Debug "Section de données"
          EndIf
          If SectionHeader\Characteristics & #IMAGE_SCN_CNT_UNINITIALIZED_DATA 
            Debug "Section de données non initialisées"
          EndIf
          If SectionHeader\Characteristics & #IMAGE_SCN_MEM_SHARED 
            Debug "Section partagée en mémoire"
          EndIf
          If SectionHeader\Characteristics & #IMAGE_SCN_MEM_EXECUTE 
            Debug "Section exécutable"
          EndIf
          If SectionHeader\Characteristics & #IMAGE_SCN_MEM_READ 
            Debug "Section en lecture"
          EndIf
          If SectionHeader\Characteristics & #IMAGE_SCN_MEM_WRITE 
            Debug "Section en écriture"
          EndIf
            
        Next
        
      Else
        Debug "Ce fichier n'est pas un éxecutable PE (win32 valide)"
      EndIf
      
    
    Else
      Debug "Ce fichier n'est pas au format DOS"
      Goto CloseFileAndQuit
    EndIf
    
    
    Goto CloseFileAndQuit
  Else
    Debug "Fichier introuvable"
  EndIf

EndIf

; Fin
End



CloseFileAndQuit:
CloseFile(0)
End

Publié : jeu. 19/févr./2009 15:18
par Anonyme
C'est intéressant , j'y connais à vrai dire pas grand chose au exécutables , que ce soit le format PE ou ELF...

Ton code m'amène à plein de questions :D

Comment est le code source dedans ? je veut dire par là , le code machine ?
peut on le récupéré à partir des infos du header ?

Si on maitrise la lecture du code exécutable , peut-on envisager la création d'un compilateur en pure basic ? j'imagine que oui , mais je ne vois pas comment écrire du code machine...


@+

Publié : jeu. 19/févr./2009 16:03
par Cls
Oui le code machine accessible. Il se trouve dans la section ".code" et ".text". Je ne suis pas encore assez calé pour t'en dire beaucoup plus. Je vais certainement faire un programme permettant de lire toutes les infos du programme et d'accéder aux sections directement (ce genre de programme existe déjà, LordPE par exemple ; mais le but est de se former ;)). *

Voici un lien si tu veux en savoir un peu plus : http://assembly.ifrance.com/pehader.htm
Et un autre : http://olance.developpez.com/articles/w ... -iczelion/
Et un autre : http://webster.cs.ucr.edu/Page_TechDocs/pe.txt

Mais je peux d'ores et déjà te dire qu'il doit être tout à fait possible d'écrire un compilateur en PB.

Publié : jeu. 19/févr./2009 17:07
par Anonyme
Oui , je me doute bien que c'est possible , mais comment transformé un langage en code exécutable ? tel est mon point d'interrogation :D

Publié : jeu. 19/févr./2009 17:39
par Cls
Voilà, je pense qu'il faut voir un exe comme un système de fichier, comme celui de Windows (c'est un des exemples pris dans l'un des liens au dessus). Il y a des répertoires, des sous répertoires et des fichiers. L'entête PE permet de savoir où se trouve telle chose ou telle chose.

Je ne suis pas expert en compilation mais je pense que le processus est le suivant (si quelqu'un s'y connait plus, n'hésitez pas à corriger) :

1. Le compilateur analyse le code source, vérifie s'il est correct et créé des tables temporaires contenant : constantes, variables, fonctions internes, appels systèmes, ressources, etc. Il les trie selon leur type.

2. Il "traduit" ensuite les sources (fonctions puis programme principal) en effectuant la correspondance en ASM.

Code : Tout sélectionner

If A = 5 Goto offset
deviendra

Code : Tout sélectionner

mov eax, [A]
cmp eax, 5
je offset:
Par exemple pour stocker une icône, il enregistre l'icône d'un coté et utilise sont adresse dans l'exe lorsqu'il souhaite l'afficher.

De même pour les appels systèmes et les fonctions internes, il stocke sur la pile les paramètres d'une fonction, l'adresse de retour de la fonction et saute ensuite vers l'adresse de la fonction (qui récupère ces paramètres, fait son traitement et retourne à l'adresse indiquée).

3. Il écrit le tout en les rangeant proprement dans des sections (une sorte de fichier interne en fait) selon leur type (une section pour le code, une section pour les constantes, une autre pour les ressources, etc.) en utilisant les tables créées en 1.

4. Il écrit ensuite un entête permettant de savoir où commence le code, où sont les sections de données, comment devra être structuré le programme quand il sera chargé en mémoire, etc.

5. Au final on se retrouve avec un ensemble de données, assez peu lisible à l'oeil nu mais organisé de manière très logique en fait : chaque chose est à sa place, l'entête constituant le mode d'emploi.

Bon c'est un domaine que je ne maitrise pas, ça se voit. Mais d'ici quelques jours voire quelques semaines, je serais surement en mesure d'être plus clair... :D

Publié : jeu. 19/févr./2009 17:57
par Anonyme
Je pense pas que le compilo transforme systématiquement en ASM ( je parle en générale , ex: gcc) mais en OPCODE

http://fr.wikipedia.org/wiki/Opcode

En tout cas , Topic très intéressant :)

Publié : jeu. 19/févr./2009 18:09
par Cls
Oui tu as raison, ce sont d'ailleurs ces opcodes qui différent en fonction du processeur. Mais tout opcode a sont équivalent en ASM. L'inverse n'étant vrai que pour un processeur donné.

Publié : jeu. 19/févr./2009 18:15
par Anonyme
Par ailleurs , une t'ite question ,

PureBasic ? n'est pas un langage autonome ?

PBCompiler , interprete le code .pb & .pbi , convertit en asm , puis balance l'asm à Fasm.exe pour la création de l'exécutable ?

Publié : jeu. 19/févr./2009 18:28
par Cls
Oui je crois que c'est ça.

Le compiler PB se "contente" de convertir le source en ASM et le transmets à Fasm qui s'occupe de créer le bytecode et de faire le linkage. Je suppose que le bytecode est ce qu'on retrouve dans les sections de l'exe final. La question est ensuite, en quoi consiste le linkage ?

Il parcours le bytecode et écrit les adresses qu'il n'a pas pu écrire avant ? Il créé l'entête PE et la table des imports ? Les deux ?

Publié : jeu. 19/févr./2009 18:43
par Anonyme
Bon , CLS , ta foutu ma soirée en l'air , sa me turlupine tout ça , faut que j'écrive un compilo en Purebasic maintenant ( c'est le p'tit bonhomme sur mon épaule qui me dit de le faire... ) :D

Publié : jeu. 19/févr./2009 18:45
par Ollivier
Salut Cls

Oui, intéressant cet exemple. Par contre, faire un compilo, c'est un peu chaud. Surtout quand Fred nous en file un sur un plateau (PbCompiler.EXE !), est-ce nécessaire de réinventer la roue?

Enfin, si c'est à titre passionnel, tu peux toujours gouter aux douces délices des instructions machines :

Code : Tout sélectionner

(Sous XP)
Clic [Démarrer]
Clic [Exécuter]
Tape « CMD » puis [Entrée]
Tape « DEBUG » puis [Entrée]
Tape « E 100 » puis [Entrée]
Tape « 0 » puis [Espace] puis « 0 » puis [Entrée]
Tape « U 100 » puis [Entrée]
Bravo voici, la toute première instruction de ton CPU :

Code : Tout sélectionner

XXXX:0100 0000 ADD [BX+SI], AL
XXXX:0100 c'est l'adresse en hexa
0000 c'est le code machine
ADD [BX+SI],AL c'est l'instruction en question
Elle prend la valeur des registres 16 bits de BX (2ème registre général, dit "base") et de SI (Source Index, c'est l'équivalent d'un pointeur *Pointeur en PB. SI est souvent destiné à l'importation des données de la mémoire vers le CPU). L'instruction additionne les contenus de ces 2 registres (BX+SI). Le résultat sert de pointeur. Le CPU importe la valeur sur 8 bits à l'endroit précisé par ce pointeur, l'additionne avec le registre de poids faible (AL) du registre général AX (dit "accumulateur"), et puis hop : une fois que l'addition est faite, retour à l'envoyeur (c'est-à-dire à la même adresse BX+SI).

C'est du 8 bits (donc il y a aussi le 16 bits) dans un mode 16 bits (donc il y a aussi le mode 32 bits, puis le mode 64 bits...). C'est sans retenue (donc il y a aussi l'instruction ADC qui gère la retenue de ton addition). Et puis pour finir, tu as des instructions qui font 8 bits de long, d'autres 16, d'autres 24, 32, 40, 48, 56, 64, et cela jusqu'à 88 bits de long !!!

L'instruction n° 2 : 0001 ADD [BX+DI], AL

Allez une "traduction" PB:

Code : Tout sélectionner


Structure X
   H.B
   L.B
EndStructure

Define A.X
Define B.X
Define DI.W

; Equivalent fonctionnel de l'instruction n°2 de la famille des x86
; (Code machine 16 bits 0x0001)
PokeB(B\X + DI, PeekB(B\X + DI) + A\L)
Alors, oui c'est possible de faire un compilateur, mais bon... Ce que c'est bon finalement de faire du PB et de se décharger de toutes ces opérations de compilation de fou... :D

Sinon, merci pour ce code, on peut détecter pas mal d'infos intéressantes grâce à ça.

Publié : jeu. 19/févr./2009 18:50
par Anonyme
Ollivier , t'a l'air calé en langage machine non ?

http://www.lirmm.fr/~ferber/Compilation/compil1.doc

Publié : jeu. 19/févr./2009 19:03
par Cls
Merci pour ton explication Olliver.

Mon projet n'est pas de faire un compilo ;) (Par contre Cpl.Bator va nous en pondre un d'ici demain matin ! :lol:). Mais c'est vrai qu'en fouillant dans le fonctionnement des EXE, on approche forcément le mécanisme de création de cet EXE, et c'est très intéressant ! :D

Publié : jeu. 19/févr./2009 19:11
par Kwai chang caine
C'est super interessant ce que vous parlez, meme si je comprend rien comme dab. :oops:

En tout cas CLS tu tripote pas qu'en WEB 8)

En parlant de compilateur de FRED, y'a un moyen de lui faire creer une DLL ou un exe sans entrer dans l'IDE.

Du style je créé un fichier texte, je l'enregistre en format texte, et je lui dit :

Code : Tout sélectionner

Compilateur c:\Texte.txt c:\Kcc.exe 

Publié : jeu. 19/févr./2009 19:17
par Anonyme2
J'ai travaillé un peu (pas trop tout de même) Le format PE avec les infos Microsoft pour ma librairie actuelle

A+

Le lien MS sur le format PE
http://www.microsoft.com/whdc/system/pl ... ECOFF.mspx

Un lien de téléchargement de MS sur un fichier donnant les explications du format PE
http://download.microsoft.com/download/ ... ff_v8.docx


Un article du magasine de MS
http://msdn.microsoft.com/en-us/magazine/cc301805.aspx