Structure du format de fichiers PE - Application avec le Notepad

(Remarque : des éléments de ce tut ont déjà été abordé dans d'autres Mementos. Ce tut aborde en détail et depuis le début les notions sur les exes.)

Note importante: Pour ne pas s'emméler les pinceaux, DANS TOUT LE TUT, tous les chiffres/nombres/valeurs en hexa seront précédés par un "0x". Le "0x" n'est pas un nombre, mais une information pour dire "Attention, ce qui suit juste après est en hexadécimal". Les nombres décimaux seront écrits normalement.

Exemple :
- Un bloc de 0x10 bytes = Un bloc contenant 16 (en décimal) bytes.
- Il y a 10 lignes dans ce bloc = Le bloc contient 10 (en décimal) lignes.

Aujourd'hui on va s'attaquer aux exes. Les utilisateurs de Mac nous reprochent toujours de devoir mettre la main dans le camboui en tant qu'utilisateurs de PC, et ça tombe bien car c'est ce qu'on va faire!!! Quant aux utilisateurs de Mac, qu'ils restent avec leurs belle boite bien chère tout fermée, et qu'ils continuent à faire ce qu'on leurs dit de faire et de ne surtout pas réfléchir... Et en plus ils s'en vantent :) les c...!

Bon en fait, je suis mauvaise langue car il y a quand même des rassemblements comme le MacHack (Macaque!?!) aux States, où il y a des gens qui n'ont rien à envier aux meilleurs coders,crackers.. de PC.
C'est de bonne guerre ;p

Go!

Matos nécessaire:
- Une tête bien fraîche et réveillée (Comme toujours !)
- On va travailler sur n'importe quel petit exe fournit par kro$oft, par exemple le notepad. Donc vous en faites une copie de sécurité que l'on va défoncer (yeaah!)
- Un éditeur hexa
- Procdump
- Un placard bien grand et bien solide :)

Tout d'abord vous enfermez la copine ( le copain s'il y a des filles qui s'initient au cracking! hello girls :) ), les parents, le frère, la soeur, le chien et le chat dans le placard. Et oui, jai dit bien grand et bien solide. Il y a du monde et il faut que ça tienne! Comme cela on a la paix, et on va pouvoir travailler tranquilloux.

On y va...

Tout bon crackeur doit très vite avoir une certaine notion des executables ne serait ce que pour savoir/comprendre ce qu'il faitquand il (elle) utilise Procdump par exemple! Je ne me prétend pas du tout être bon dans le domaine, mais je voudrais donner un minimum d'aperçu sur les exes.

Petit historique :)

Le système d'exploitation Windows NT version 3.1 a introduit un nouveau format de fichier executable appelé le format de fichier Portable Executable (PE). Ce nouveau format fut élaboré principalement à partir de la spécification du COFF (Common Object File Format - COFF entête et entête optionnel) en vigueur dans le monde Unix. De plus pour rester compatible avec les versions antérieures de MS-DOS et de Windows, le format PE comprend le familier en-tête MZ du MS-DOS.

Structure des fichiers PE

Le format de fichier PE est organisé comme un flux de données de manière linéaire. Il commence avec l'entête MS-DOS, le "program stub" en mode réel et la signature du PE. Ensuite, on trouve l'entête PE et le PE optionnel. Les sections sont déclarées dans un partie qui suit le PE optionnel.

On va aborder dans ce tut chacune des parties du PE (i.e. de l'executable) tels qu'elles s'enchainent quand on parcours de haut en bas l'executable sous un éditeur hexadécimal. Toute la structure et la composition d'un PE est donnée dans le fichier WINNT.H. Ce fichier est inclus dans toute application servant au développement d'applications windows.

On commence par ouvrir le notepad sous l'éditeur hexa. Je rappelle que tout ce tut ne traite QUE du notepad.
De même, toutes les données en chiffres sont HEXADECIMALES.

Ainsi quand on visualise un soft sous un éditeur hexa, il est bon de connaitre les differentes sections que l'on peut apercevoir. Voici un petit aperçu depuis son début jusqu'à la fin:

ENTETE / DEBUT DE PROGRAMME

00000000 4D5A 9000 0300 0000 0400 0000 FFFF 0000 MZ..............
00000010 B800 0000 0000 0000 4000 0000 0000 0000 ........@.......
00000020 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030 0000 0000 0000 0000 0000 0000 8000 0000 ................
00000040 0E1F BA0E 00B4 09CD 21B8 014C CD21 5468 ........!..L.!Th
00000050 6973 2070 726F 6772 616D 2063 616E 6E6F is program canno
00000060 7420 6265 2072 756E 2069 6E20 444F 5320 t be run in DOS
00000070 6D6F 6465 2E0D 0D0A 2400 0000 0000 0000 mode....$.......
00000080 5045 0000 4C01 0500 6591 4635 0000 0000 PE..L...e.F5....
00000090 0000 0000 E000 0E01 0B01 030A 0040 0000 .............@..
000000A0 0074 0000 0000 0000 CC10 0000 0010 0000 .t..............
000000B0 0050 0000 0000 4000 0010 0000 0010 0000 .P....@.........
000000C0 0400 0000 0000 0000 0400 0000 0000 0000 ................

=>Ce que l'on voit (en gros) au début de tout executable, avec la fameuse phrase
"This program cannot be run in DOS mode".

"PARTIE DE ZEROS"

(Je l'appelle comme ça, on vera après ce que c'est)

00000390 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003B0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003C0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003D0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003E0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000003F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000400 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000410 0000 0000 0000 0000 0000 0000 0000 0000 ................

=>Ca, ca apparait très souvent et c'est très pratique. Cela permet de bricoler, bricoler, bricoler :)) Ici cela apparait de manière flagrante sur plusieurs lignes, mais cela peut être aussi très court, du genre (là où c'est en rouge) :

6865 6C6C 6F2C 2077 6F72 6C64 0A00 0000

"PARTIE DE CODE"

(Là aussi, on verra après)

000017C0 00E8 320D 0000 E9E2 0000 003D FFFF 0000 ..2........=....
000017D0 7421 83F8 0274 1C83 F805 7417 83F8 0774 t!...t....t....t
000017E0 123D 0410 0000 7412 3D05 1000 000F 85BA .=....t.=.......
000017F0 0000 003D 0410 0000 7507 A1AC 5040 00EB ...=....u...P@..
00001800 05A1 5850 4000 6A10 6A00 50FF 3564 5040 ..XP@.j.j.P.5dP@
00001810 00FF 3500 5040 00E8 C10B 0000 E98C 0000 ..5.P@..........
00001820 00A1 5050 4000 8D8D C0FE FFFF 5051 FF15 ..PP@.......PQ..
00001830 D463 4000 C705 2850 4000 0100 0000 6850 .c@...(P@.....hP
00001840 5640 00E8 3036 0000 85C0 7433 6A01 8D85 V@..06....t3j...
00001850 C0FE FFFF 50FF 7508 E851 1900 0085 C074 ....P.u..Q.....t
00001860 1E8D 85C0 FEFF FF50 68A0 5640 00FF 15D4 .......Ph.V@....

=>Bon bien ici, c'est du code pur et dur, comme par exemple où l'on patch un saut quand on cracke.

"PARTIE DE FONCTIONS/DONNEES"

(Là aussi, on verra...)

00006640 6E64 436C 6F73 6500 A300 4669 6E64 4669 ndClose...FindFi
00006650 7273 7446 696C 6541 0000 DB02 6C73 7472 rstFileA....lstr
00006660 636D 7041 0000 3901 4765 7450 726F 6669 cmpA..9.GetProfi
00006670 6C65 5374 7269 6E67 4100 E702 6C73 7472 leStringA...lstr
00006680 6C65 6E41 0000 1702 5274 6C4D 6F76 654D lenA....RtlMoveM
00006690 656D 6F72 7900 E402 6C73 7472 6370 796E emory...lstrcpyn
000066A0 4100 BD01 4C6F 6361 6C52 6541 6C6C 6F63 A...LocalReAlloc
000066B0 0000 BC01 4C6F 6361 6C4C 6F63 6B00 B601 ....LocalLock...
000066C0 4C6F 6361 6C41 6C6C 6F63 0000 C001 4C6F LocalAlloc....Lo
000066D0 6361 6C55 6E6C 6F63 6B00 D002 5F6C 636C calUnlock..._lcl
000066E0 6F73 6500 D502 5F6C 7772 6974 6500 5F00 ose..._lwrite._.
000066F0 4465 6C65 7465 4669 6C65 4100 D102 5F6C DeleteFileA..._l

=>Là vous avez un passage "lisible". On reconnait des noms de fonctions/APIs...

"PARTIE DE FONCTIONS/DONNEES"

(Là aussi, on verra...)

0000A1D0 1B00 2600 5200 6500 7400 6F00 7500 7200 ..&.R.e.t.o.u.r.
0000A1E0 2000 E000 2000 6C00 6100 2000 6C00 6900 ... .l.a. .l.i.
0000A1F0 6700 6E00 6500 2000 6100 7500 7400 6F00 g.n.e. .a.u.t.o.
0000A200 6D00 6100 7400 6900 7100 7500 6500 0000 m.a.t.i.q.u.e...
0000A210 8000 2500 4300 2600 6800 6F00 6900 7300 ..%.C.&.h.o.i.s.
0000A220 6900 7200 2000 6C00 6100 2000 7000 6F00 i.r. .l.a. .p.o.
0000A230 6C00 6900 6300 6500 2E00 2E00 2E00 0000 l.i.c.e.........
0000A240 1000 2600 5200 6500 6300 6800 6500 7200 ..&.R.e.c.h.e.r.
0000A250 6300 6800 6500 0000 0000 0300 2600 5200 c.h.e.......&.R.
0000A260 6500 6300 6800 6500 7200 6300 6800 6500 e.c.h.e.r.c.h.e.

=>... ici, ce sont les menus du programme. Pour ceux qui codent, on remarque le "&" pour souligner une lettre pour les raccourcis...

"PARTIE DE FONCTIONS/DONNEES"

(Là aussi, on verra...)

0000AC30 6900 6F00 6E00 2E00 2000 5100 7500 6900 i.o.n... .Q.u.i.
0000AC40 7400 7400 6500 7A00 2000 7500 6E00 6500 t.t.e.z. .u.n.e.
0000AC50 2000 6F00 7500 2000 7000 6C00 7500 7300 .o.u. .p.l.u.s.
0000AC60 6900 6500 7500 7200 7300 2000 6100 7000 i.e.u.r.s. .a.p.
0000AC70 7000 6C00 6900 6300 6100 7400 6900 6F00 p.l.i.c.a.t.i.o.
0000AC80 6E00 7300 2000 7000 6F00 7500 7200 2000 n.s. .p.o.u.r. .
0000AC90 6C00 6900 6200 E900 7200 6500 7200 2000 l.i.b...r.e.r. .
0000ACA0 6400 6500 2000 6C00 6100 2000 6D00 E900 d.e. .l.a. .m...
0000ACB0 6D00 6F00 6900 7200 6500 2C00 2000 7000 m.o.i.r.e.,. .p.
0000ACC0 7500 6900 7300 2000 6500 7300 7300 6100 u.i.s. .e.s.s.a.
0000ACD0 7900 6500 7A00 2000 E000 2000 6E00 6F00 y.e.z. ... .n.o.
0000ACE0 7500 7600 6500 6100 7500 2E00 6A00 4C00 u.v.e.a.u...j.L.
0000ACF0 6500 2000 6600 6900 6300 6800 6900 6500 e. .f.i.c.h.i.e.

=>... et là, la partie de toutes les chaines de caractères qui sont manipulées par le soft. Elle se trouve à la fin en général.

Bon voilà, on a à peu près fait le tour. On va maintenant passer le soft en revue ligne par ligne et regarder en détail la signification.

On revient donc au début du soft.

00-7F : 1er bloc
------------

(Par 00-7F j'entend: "bloc allant de l'adresse 0x00 à l'adresse 0x7F". Pareil pour les parties suivantes)

00000000 4D5A 9000 0300 0000 0400 0000 FFFF 0000 MZ..............
00000010 B800 0000 0000 0000 4000 0000 0000 0000 ........@.......
00000020 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030 0000 0000 0000 0000 0000 0000 8000 0000 ................
00000040 0E1F BA0E 00B4 09CD 21B8 014C CD21 5468 ........!..L.!Th
00000050 6973 2070 726F 6772 616D 2063 616E 6E6F is program canno
00000060 7420 6265 2072 756E 2069 6E20 444F 5320 t be run in DOS
00000070 6D6F 6465 2E0D 0D0A 2400 0000 0000 0000 mode....$.......

Cette partie s'appelle l'entête MS-DOS. Elle porte aussi le nom d'entête Real-Mode. Elle est au début du fichier, car si celui-ci n'est pas executable sous DOS mais sous Windows uniquement, elle affiche "This program cannot be run in DOS mode" au lieu de mettre un truc barbare du style "The name specified is not recognized as an internal or external command, operable program or batch file". C'est ce qui se passerait en commençant directement avec l'entête PE (voir bloc suivante).

Dans tout ce bloc, le plus important à retenir est ceci :

Adrs Nom Description
00-01 e_magic MZ : Ces 2 lettres caractérisent un en-tête DOS
(i.e. un soft qui marche sous windows)
3C-3D e_lfanew 0x8000 : Lire 0x0080.
Cette adresse en 0x3C est un pointeur qui saute sur l'entête PE (voir section suivante).

Petite précision : bien évidemment, il ne suffit pas de mettre MZ (avec un éditeur hexa) au début d'un fichier pour qu'il "marche tout seul" sous windows...

MZ s'appelle le "Magic Number" est correspond à ce qu'on appelle le format d'image (d'un exe, dll, vxd...).

On définit ainsi plusieurs "magic numbers" :

IMAGE_DOS_SIGNATURE 0x4D5A MZ pour les fichiers exe DOS, Windows
IMAGE_OS2_SIGNATURE 0x4E45 NE pour les exes sous OS/2
IMAGE_OS2_SIGNATURE_LE 0x4C45 LE idem
IMAGE_VXD_SIGNATURE 0x454C LE pour les vxd
IMAGE_NT_SIGNATURE 0x50450000 PE00 pour l'entête PE des exes sous DOS, Windows

Il est à noter que les exes de OS/2 ont la même structure que les executables windows.
La syntaxe de programmation est donnée de la manière suivante :

typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

00000000 4D5A 9000 0300 0000 0400 0000 FFFF 0000 MZ..............
00000010 B800 0000 0000 0000 4000 0000 0000 0000 ........@.......
00000020 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030 0000 0000 0000 0000 0000 0000 8000 0000 ................
00000040 0E1F BA0E 00B4 09CD 21B8 014C CD21 5468 ........!..L.!Th
00000050 6973 2070 726F 6772 616D 2063 616E 6E6F is program canno

00000060 7420 6265 2072 756E 2069 6E20 444F 5320 t be run in DOS

00000070 6D6F 6465 2E0D 0D0A 2400 0000 0000 0000
mode....$.......

Ensuite vous avez la fameuse phase à 2 Francs "This program cannot be run in DOS mode" qui se termine en 0x73. Elle est suivit de 2E 0D 0D 0A 24 et puis des zéros. Vous avez ensuite la ligne 0x80 qui commence avec PE, mais ça c'est pour après avec la section suivante.

Entre MZ et la phrase "...DOS mode" il y a différentes variables que je n'aborderai pas ici hormis celle en 0x3C qui vaut 0x80. Il s'agit du pointeur (saut) qui indique ou se trouve l'entête PE dans le soft. Quand le programme est chargé en mémoire, l'ordi lit ce bloc, et arrivé en 0x3C il sait qu'il doit aller à l'adresse 0x80 pour lire le PE.

PADDING : ce terme est important. Il signifie "remplissage avec des 0"

C'est ce que l'on a a la ligne 0x70. Apès le 0x24 en 0x78, on termine la ligne par des "00 00". C'est ce dont je parlais plus haut dans la présentation des différents blocs. Ce remplissage peut faire quelques bytes ou bien des blocs entiers. Mais, me direz-vous, "pourquoi mettre des 00 au lieu de placer le code directement à la suite?". Bonne question mon cher Watson! Dans le cas de la ligne, c'est pour avoir une ligne complete et faire commencer le bloc suivant à la ligne. On peut ainsi traiter les blocs par adresse de multiple de 16 pour le mode 32 bits. Quant aux blocs entiers de 00, c'est pour remplir la place (la partie de code non occupée) entre la fin du code dans une section et le début de la section suivante. Et pourquoi ne pas rapprocher les blocs au lieu de mettre tant de 00 ? Et bien comme cela, nous (coders, reversers) avons un espace de liberté où nous pouvons nous exprimer! :o) Valà... De plus, le padding n'est pas qu' "ésthetique". Certaines sections ont besoins d'avoir une certaine taille pour marcher sous une version donnée de windows. Donc retenez que le remplissage avec des zéros pour garder une "structure" dans un exe s'appelle le padding.

80-FFF : 2e bloc
-------------

00000080 5045 0000 4C01 0500 6591 4635 0000 0000 PE..L...e.F5....
00000090 0000 0000 E000 0E01 0B01 030A 0040 0000 .............@..
000000A0 0074 0000 0000 0000 CC10 0000 0010 0000 .t..............
000000B0 0050 0000 0000 4000 0010 0000 0010 0000 .P....@.........
000000C0 0400 0000 0000 0000 0400 0000 0000 0000 ................
000000D0 00E0 0000 0004 0000 8918 0100 0200 0000 ................
000000E0 0000 1000 0010 0000 0000 1000 0010 0000 ................
000000F0 0000 0000 1000 0000 0000 0000 0000 0000 ................
00000100 0060 0000 8C00 0000 0070 0000 8C55 0000 .`.......p...U..
00000110 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000120 00D0 0000 3C09 0000 0000 0000 0000 0000 ....<...........
00000130 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000140 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000150 0000 0000 0000 0000 E062 0000 4002 0000 .........b..@...
00000160 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000170 0000 0000 0000 0000 2E74 6578 7400 0000 .........text...
00000180 9C3E 0000 0010 0000 0040 0000 0010 0000 .>.......@......
00000190 0000 0000 0000 0000 0000 0000 2000 0060 ............ ..`
000001A0 2E64 6174 6100 0000 4C08 0000 0050 0000 .data...L....P..
000001B0 0010 0000 0050 0000 0000 0000 0000 0000 .....P..........
000001C0 0000 0000 4000 00C0 2E69 6461 7461 0000 ....@....idata..
000001D0 E80D 0000 0060 0000 0010 0000 0060 0000 .....`.......`..
000001E0 0000 0000 0000 0000 0000 0000 4000 0040 ............@..@
000001F0 2E72 7372 6300 0000 0060 0000 0070 0000 .rsrc....`...p..
00000200 0060 0000 0070 0000 0000 0000 0000 0000 .`...p..........
00000210 0000 0000 4000 0040 2E72 656C 6F63 0000 ....@..@.reloc..
00000220 9C0A 0000 00D0 0000 0010 0000 00D0 0000 ................
00000230 0000 0000 0000 0000 0000 0000 4000 0042 ............@..B
00000240 0000 0000 0000 0000 0000 0000 0000 0000 ................
[...]
00000FE0 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000FF0 0000 0000 0000 0000 0000 0000 0000 0000 ................

De 0x240 à 0xFFF, il n'y a que des zéros . Il s'agit du padding jusqu'au début de la section suivante.

Bien. Nous sommes donc en 0x80 suite au saut en 0x3C de la section précédente. On lit à cette adresse " PE" qui signifie portable executable. On aborde là une notion très importante dans le monde des exes sous zindoz.

Le format de fichier PE est un format pour les exes et les dlls pour les systèmes d'exploitation de zindoz 95 à ultérieurs. On trouve ce nombre 0x 5045 (PE) sous l'appellation IMAGE_NT_SIGNATURE.

L'entête PE est au programme ce que la table des matières est au livre. Vous voyez dans le code ci-dessus les mots "text", "data", "idata", "rsrc" et "reloc". Ces mots sont en fait nommés avec un point devant ".text", ".data", ".idata", ".rsrc" et ".reloc", bien que le "." ne soit pas indispensable. Ils correspondent chacun à une section de l'exe. Il y a donc 5 sections dans cet exe. Parfois on en trouve plus, parfois moins. Cela dépend de chaque programme. De même, leur nom n'est pas figé dans l'appellation. On peut très bien le changer, où introduire une section avec un nom comme vous voulez. Il faut au maximum sept lettres (soit huit avec le "."), sinon on empiète sur les informations de la section et le programme ne va pas aimer! On va d'ailleurs traiter un petit exemple plus bas :)

Chaque section définie dans le PE est codée sur 0x28 bytes (Attention, on est en hexa! Je rappelle qu'un byte ça prend "00" en place). Par exemple pour la section .data, on a (en bleu) :


La manière la plus classique et facile pour visualiser ces informations, est d'utiliser procdump.
Lancez le, appuyez ensuite sur "PE EDITOR", choisissez votre exe (le notepad), appuyez ensuite sur "Sections" dans la nouvelle fenêtre qui vient de s'ouvrir. On obtient les informations de l'image ci-dessus.


Bien, voyons de plus près la signification de tout cela...

Sections Informations :

Name Virtual Size Virtual Offset Raw Size Raw Offset Characteristics
.text 00003E9C 00001000 00004000 00001000 60000020
.data 0000084C 00005000 00001000 00005000 C0000040
.idata 00000DE8 00006000 00001000 00006000 40000040
.rsrc 00006000 00007000 00006000 00007000 40000040
.reloc 00000A9C 0000D000 00001000 0000D000 42000040

Je traite l'exemple avec .text, c'est bien sûr valable pour toutes les sections.

La section commence au "Virtual Offset" en 0x1000 (vérifiez le sous un éditeur hexa en allant à cette adresse). Ensuite, on a la taille de cette section en "Virtual Size" qui vaut 0x3E9C. La taille indiquée ici correspond à la place effectivement occupée par du code. Mais comme il y a du padding entre les sections, la taille réelle de la section est plus grande. Cette taille réelle est donnée en "Raw Size". Il s'agit de la différence entre le début de la section suivante (.data en 0x5000) moins le début de notre section (.text en 0x1000) qui fait donc 0x4000. En dans ces 0x4000 bytes, on a 0x3E9C bytes (de la virtual size) qui sont du code, et le reste du padding, soit 0x4000 - 0x3E9C = 0x164 bytes de "00".

Donc, on retient les définitions ci-dessus, et le fait que la raw size = virtual size + padding. Donc la raw size est supérieure à la virtual size. On retrouve la propriété du padding où tous les nombres de la raw size sont "ronds" i.e. un multiple du 16, 32 ou 64 bits.

Pour la "Raw Offset", cela correspond dans le cas du Notepad à la "Virtual Offset". Mais ceci est rarement le cas, ce qui en général complique un peu les choses. Par exemple, si on regarde les sections de Procdump lui-même :

Sections Informations :

Name Virtual Size Virtual Offset Raw Size Raw Offset Characteristics
.text 000088B2 00001000 00008A00 00000400 60000020
.data 00008961 0000A000 00002800 00008E00 C0000040
.idata 00000A84 00013000 00000C00 0000B600 C0000040
.rsrc 00002AC0 00014000 00002C00 0000C200 40000040
.reloc 00001284 00017000 00001400 0000EE00 42000040


On remarque ici que l'on ne peut plus lire directement l'offset sous l'éditeur hexa à partir du dead listing
(0x4098A6 donne 0x98A6 qui est différent de 0x8CA6). On utilise en fait la formule suivante qui est applicable à tous les cas :

Offset = RVA - Virtual Offset + Raw Offset

L'adresse 0x8CA6 est dans la section .text. Il suffit de regarder la colonne Virtual Offset pour cela.
Pour l'obtenir (à l'ImageBase près), on fait donc :

0x408CA6 = 0x4098A6 - 0x1000 + 0x400

Et le 0x4098A6 que l'on lit sous wdasm ou bien sous softice est appelé le RVA (Relative Virtual Offset). Cette valeur est utilisée pour l'offset (du soft) en mémoire (dans la RAM) quand l'ImageBase est inconnue. Bon, bien maintenant que l'on a vu le petit calcul, ce n'est plus trés bien compliqué...

La dernière colonne "Characteristics" indique les propriétés de la section. Ces propriétés sont divisées en deux.

Execute, Read, Write
***************************

0x20000000 = IMAGE_SCN_MEM_EXECUTE : La section peut être executée comme du code
0x40000000 = IMAGE_SCN_MEM_READ : La section peut être lue
0x80000000 = IMAGE_SCN_MEM_WRITE : On peut écrire dans la section (lorsqu'elle est en mémoire)

On peut avoir des combinaisons de propriétés pour une section. Pour ceux d'entre vous qui ont l'habitude de la gestion des droits des fichiers sous Unix (avec 1,2,4), on a la même procédure ici mais en hexadécimal, par exemple :

20000000 20000000 20000000 40000000 40000000 80000000
  40000000 40000000   80000000  
    80000000      
-------- -------- -------- -------- -------- --------
20000000 60000000 E0000000 40000000 C0000000 80000000
           
Execute Execute Execute Read Read Write
  +Read + Read   +Write  
    + Write      

Code, Data
**************

0x00000020 = IMAGE_SCN_CNT_CODE La section contient du code executable
0x00000040 = IMAGE_SCN_CNT_INITIALIZED_DATA La section contient des données initialisées
0x00000080 = IMAGE_SCN_CNT_UNINITIALIZED_DATA La section contient des données non initialisées

Même calcul ici :

00000020 00000020 00000020 00000040 00000040 00000080
  00000040 00000040   00000080  
    00000080      
-------- -------- -------- -------- -------- --------
00000020 00000060 000000E0 00000040 000000C0 00000080
           
Code Code Code I-Data I-Data U-Data
  +I-Data +I-Data   +U-Data  
    +U-Data      

Mais ces quelques caractéristiques ne sont pas les seules qui existent.
Ce sont bien sûr les plus courantes dans le cas qui nous intérese, mais il en existe d'autres :

0x00000008 = IMAGE_SCN_TYPE_NO_PAD Pas de padding jusqu'à la prochaine limite
(pour les fichiers .obj seulement)
0x00000200 = IMAGE_SCN_LNK_INFO La section contient des infos ou des commentaires
(pour .obj seulement)
0x00000800 = IMAGE_SCN_LNK_REMOVE La section ne devient pas une partie de l'image
(pour .obj seulement)
0x00001000 = IMAGE_SCN_LNK_COMDAT La section contient des données COMDAT
(pour .obj seulement)
   
0x00@00000 = IMAGE_SCN_ALIGN_@BYTES Aligne les données sur une limite de @ byte(s) (@=1,2,4,8,16,32,64)
0x01000000 = IMAGE_SCN_LNK_OVFL La section contient des "extended relocations"
0x02000000 = IMAGE_SCN_MEM_DISCARDABLE La section peut être annulée à volonté
0x04000000 = IMAGE_SCN_MEM_NOT_CACHED La section ne peut être "cached" (processeur)
0x08000000 = IMAGE_SCN_MEM_NOT_PAGED La section ne peut être "pageable"
0x10000000 = IMAGE_SCN_MEM_SHARED La section peut être partagée en mémoire

De plus, il y a également des propriétés non encore utilisées mais déjà réservées pour le futur!! Elles ne sont donc pas attribuées...
0x00000000 = IMAGE_SCN_TYPE_REG
0x00000001 = IMAGE_SCN_TYPE_DSECT
0x00000002 = IMAGE_SCN_TYPE_NOLOAD
0x00000004 = IMAGE_SCN_TYPE_GROUP
0x00000010 = IMAGE_SCN_TYPE_COPY
0x00000100 = IMAGE_SCN_LNK_OTHER
0x00000400 = IMAGE_SCN_TYPE_OVER
0x00008000 = IMAGE_SCN_MEM_FARDATA
0x00020000 = IMAGE_SCN_MEM_PURGEABLE
0x00020000 = IMAGE_SCN_MEM_16BIT
0x00040000 = IMAGE_SCN_MEM_LOCKED
0x00080000 = IMAGE_SCN_MEM_PRELOAD

Si on veut donc se rajouter une section dans un exe (ou en modifier une existante), il sera avantageux de lui coller le maximum : un 0xE0000020 comme caractéristique.

Bien! On a fini pour les "characteristics", on va passer à notre petit exemple de ...bien plus haut :) (NDOracle (mise en page): Ouf!)

Il s'agit de rajouter une section à l'exe pour se faire la main en manipulant les données des sections. Pourquoi rajouter une section? Et bien parfois, on n'a pas assez de place dans les paddings d'un exe, et il est bon de se tailler soi-même sa place :) Autre avantage, quand on écrit du code dans le padding, on est par définition en dehors de la virtual size, donc si vous désassemblez le fichier, vous ne verrez pas votre code.

Pour rajouter une nouvelle section, on va faire cela d'abord "automatiquement" avec procdump. Puis, comme on est pas esclave d'un programme qui fait les choses à notre place, on va le faire "à la main" avec un éditeur hexa pour bien savoir comment cela marche!

Zou...

Vous avez la copie du notepad dans un repertoire. Renommez la N0.exe (par exemple, c'est ce que j'ai fait).
Celle-là, on n'y touche pas, elle va servir pour faire des comparaisons.
Faites en une copie, soit N1.exe, et une 2eme copie, soit N2.exe.

On va utiliser procdump pour N1.exe et on va faire N2.exe à la main.

Procdump
*************

Dans la fenêtre où les sections sont affichées, cliquer avec le bouton droit de la souris sur une section et choisir "Add section". Rentrer un nom (7 lettres max.) et appuyez sur OK. Procdump rajoute automatiquement la nouvelle section à la fin et caclule les virtual/raw offsets. La caractéristique par défaut est 0xC0000040. Il ne reste plus qu'à éditer la section pour y modifier ses valeurs. Au passage, la taille de l'exe passe de 56 à 60Ko. Voilà pour la méthode "automatique".

A la main
************

On part du principe qu'on ne sait rien, et qu'on veut apprendre. Comment rajoute-t-on une section? On peut déjà commencer à calquer notre section à la suite des autres. Ainsi, j'appelle ma section ".anubis", je la mets après la dernière section du notepad, soit .reloc. Son virtual offset sera donc de (= raw size + raw offset) E000. Et on va dire qu'elle fait 1000 en taille, donc de vitual size. Ici on n'a pas besoin de padding puisq'on a une section toute vide. Et pour les caractéristiques, on prendre les même que .rsrc et .idata soit 0x40000040. N'oubliez pas que la déclaration de la section se fait sur 0x28 bytes. On commence donc à l'offset 0x240.

En écrivant par analogie (par exemple avec .rsrc) dans le PE sous l'éditeur hexa, on a ceci :

000001F0 2E72 7372 6300 0000 0060 0000 0070 0000 .rsrc....`...p..
00000200 0060 0000 0070 0000 0000 0000 0000 0000 .`...p..........
00000210 0000 0000 4000 0040 2E72 656C 6F63 0000 ....@..@.reloc..
00000220 9C0A 0000 00D0 0000 0010 0000 00D0 0000 ................
00000230 0000 0000 0000 0000 0000 0000 4000 0042 ............@..B
00000240
2E61 6E75 6269 7300 0010 0000 00E0 0000 .anubis.........
00000250 0010 0000 00E0 0000 0000 0000 0000 0000 ................
00000260 0000 0000 4000 0040 0000 0000 0000 0000 ....@..@........

Maintenant, sauvez, et regarder N2.exe sous procdump... Rien! Ben oui, il ne s'agit pas d'écrire, il faut aussi dire au programme que quelquechose de nouveau est là. Très certainement il a quelque chose d'autre à changer dans l'en-tête PE.

Comment savoir? On va tout simplement comparer N0.exe et N1.exe, le fichier de référence et celui modifié par procdump.

Pour cela, pas besoin de se taper la comparaison byte à byte dans l'éditeur hexa. Kro$oft nous fournit les moyens rapides et efficaces de le faire. Sortez une fenêtre DOS, on va se taper des bonnes vieilles commandes. Héhé...

La commande DOS "FC" ou FILE COMPARE
*******************************************************

On peut utiliser cette commande de 2 manières simples :
1) La comparaison des 2 fichiers est de quelques bytes
=> tapez"fc nom_fichier1.exe nom_fichier2.exe | more"
(NDOracle : Appuyez sur Alt Gr + 6 pour faire le symbole " | " (barre verticale))
Et vous visualisez à l'écran le résultat.
S'il y a plus de données que la taille de l'écran, une pause est faite avec "| more"

2) La comparaison est de "beaucoup" de bytes
=> tapez"fc nom_fichier1.exe nom_fichier2.exe > ici.txt"

Et rien ne s'affiche à l'écran, mais tout est redirigé (>) dans le fichier ici.txt qui est automatiquement créé. Plus facile à lire par la suite :)

On ne pourra jamais se passer du DOS. C'est la meilleure et la pire des choses que kro$oft ait faite...
Et puis quelle analogie avec le shell (>, | more...), dès qu'on sort des DIR et compagnie!
Donc dans notre cas, on tape "fc N0.exe N1.exe > ici.txt" et on obtient ça :

Comparaison des fichiers N0.exe et N1.exe

0000007C: 00 63
0000007D: 00 91
0000007E: 00 3F
0000007F: 00 50
00000086: 05 06
000000D1: E0 F0
00000240: 00 2E
00000241: 00 61
00000242: 00 6E
00000243: 00 75
00000244: 00 62
00000245: 00 69
00000246: 00 73
00000249: 00 10
0000024D: 00 E0
00000251: 00 10
00000255: 00 E0
00000264: 00 40
00000267: 00 40
FC: N1.exe plus grand que N0.exe

...En plus c'est bien affiché, c'est pas beau ça?!

La commande FC nous affiche les premiers changements et après nous dit que les deux exes sont trop différents. On voit ainsi les changements suivants :

- de 0x7C à 0x7F : 4 bytes de modifiés. Pas d'explication pour le moment.
- en 0x86 : 0x05 à 0x06. Ne serait-ce pas l'annonce de notre 6ème section à l'ordi??
- en 0xD1 : 0xE0 à 0xF0. Pas d'explication pour le moment.
- 0x240 et + : On reconnait l'offset où on a déclaré notre ".anubis".

Je recopie ici le passage qui nous intéresse pour plus de clarté. Il s'agit du passage changé à la main et donc encore incomplet.

00000080 5045 0000 4C01 0500 6591 4635 0000 0000 PE ..L...e.F5....

00000090 0000 0000 E000 0E01 0B01 030A 0040 0000 .............@..
000000A0 0074 0000 0000 0000 CC10 0000 0010 0000 .t..............
000000B0 0050 0000 0000 4000 0010 0000 0010 0000 .P....@.........
000000C0 0400 0000 0000 0000 0400 0000 0000 0000 ................
000000D0 00E0 0000 0004 0000 8918 0100 0200 0000 ................
[...]
000001F0 2E72 7372 6300 0000 0060 0000 0070 0000 .rsrc....`...p..
00000200 0060 0000 0070 0000 0000 0000 0000 0000 .`...p..........
00000210 0000 0000 4000 0040 2E72 656C 6F63 0000 ....@..@.reloc..
00000220 9C0A 0000 00D0 0000 0010 0000 00D0 0000 ................
00000230 0000 0000 0000 0000 0000 0000 4000 0042 ............@..B
00000240 2E61 6E75 6269 7300 0010 0000 00E0 0000 .anubis.........
00000250 0010 0000 00E0 0000 0000 0000 0000 0000 ................
00000260 0000 0000 4000 0040 0000 0000 0000 0000 ....@..@........

Donc on reprend notre N2.exe et on change déjà 0x05 en 0x06 à l'offset 0x86. On regarde sous procdump, et... il ne veut pas :( Donc quelque chose manque encore! On va changer le 0xE0 en 0xF0, et si cela ne suffit pas on modifiera de 0x7C à 0x7F.

Cette fois ça a marché! Notre nouvelle section est correctement ajoutée.

Il ne nous reste plus qu'à ajouter mauellement la place que l'on a déclaré dans l'entête PE. On va tout à la fin de l'exe et on insère 0x1000 bytes de "00" (de 0xDFF0 à 0xEFF0). Mettez le curseur en 0xDFF0, allez dans le menu Edit>Insert et entrez 0x1000 bytes de "00". Sauvegardons, et l'exe passe de 56 à 60 Ko... CQFD! ou AQT! (pour les plus avancés ;) )

Le fait que la modification 0x0E à 0x0F fasse grinchonner procdump nous montre que c'est important, dès que l'on dit à l'ordi que notre programme a une nouvelle section (passage de 0x05 à 0x06). Que peut être cette valeur? 0xF0 - 0xE0 = 0x10. Et 0xF0 étant en 0xD1, il y a fort à parier que le 0x00 en 0xD0 fait aussi parti de cette valeur. Les bytes étant lus "à l'envers", on a donc 0xF000 - 0xE000 = 0x1000. Et la seule manière de rattacher ce 0x1000 à nos données et de le comparer à la taille de la section .anubis. Donc 0xE000 ne peut être que la taille de notre exe qui passe à 0xF000 avec l'ajout de la nouvelle section.

Ceci nous amène à finir les explications sur l'entête PE. On va regarder la signification des valeurs qui viennent après les lettres PE jusqu'au début de la déclaration des sections .text,... On s'intéresse donc au passage en jaune :

00000080 5045 0000 4C01 0600 6591 4635 0000 0000 PE..L...e.F5....
00000090 0000 0000 E000 0E01 0B01 030A 0040 0000 .............@..
000000A0 0074 0000 0000 0000 CC10 0000 0010 0000 .t..............
000000B0 0050 0000 0000 4000 0010 0000 0010 0000 .P....@.........
000000C0 0400 0000 0000 0000 0400 0000 0000 0000 ................
000000D0 00F0 0000 0000 0004 8918 0100 0200 0000 ................
000000E0 0000 1000 0010 0000 0000 1000 0010 0000 ................
000000F0 0000 0000 1000 0000 0000 0000 0000 0000 ................
00000100 0060 0000 8C00 0000 0070 0000 8C55 0000 .`.......p...U..
00000110 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000120 00D0 0000 3C09 0000 0000 0000 0000 0000 ....<...........
00000130 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000140 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000150 0000 0000 0000 0000
E062 0000 4002 0000 .........b..@...
00000160 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000170 0000 0000 0000 0000 2E74 6578 7400 0000 .........text...

Petit conseil : pour éviter d'avoir toujours des va-et-viens à faire entre le code ci-dessus et les explications ci-dessous, ouvrez le code ci- dessus dans une deuxième fenêtre du navigateur (Ctrl + N). Vous pouvez ensuite passez rapidos d'une fenêtre à l'autre (ALT + Tab) pour lire le code en couleur, et les explications qui vont avec ;o)

En 0x84, la valeur 0x4C01 (lire 0x014C) définie le type de système sur lequel est censé tourner le programme. Voici quelques valeurs pour cette variable qui porte le nom de "Machine" :

IMAGE_FILE_MACHINE_I386

0x014C = pour processeur Intel 80386 et plus (notre cas ici)
0x014D = pour processeur Intel 80486 et plus
0x014E = pour processeur Intel Pentium et plus

IMAGE_FILE_MACHINE_ALPHA

0x0184 = pour processeur DEC Alpha AXP

IMAGE_FILE_MACHINE_POWERPC

0x01F0 = pour IBM Power PC

Basiquement, les informations dans l'entête PE sont utilisées par le système pour déterminer comment traiter le fichier. Ainsi par exemple, si vous changez sous l'éditeur hexa la valeur d'origine 0x4C01 en 0x8401 (pour les processeurs Alpha), on obtient un joli message :

On remarque au passage la prose commerciale de kro$oft : "contacter un revendeur..."

En 0x86, on a vu que ce nombre correspond au nombre de sections du PE. Ici 0x06 (soit 0x0600),
car on a .anubis en plus. Dans un programme, le nombre maximal de section n'est pas déterminé.

En 0x88, il y a la date en "Time in seconds since UTC 1/1/70" : 768027063
Le TimeDateStamp est sur 4 bytes (6591 4635), et UTC signifie "Universal Time Coordinated" .

En 0x8C et jusqu'à la fin de la ligne en 0x8F on a le "PointerToSymbolTable". Il s'agit d'une valeur qui est la plupart du temps à zéro sur 4 bytes. De même, en 0x90 jusqu'à 0x93 (sur 4 bytes donc), on a le "NumberOfSymbols'". Egalement à zéro.

En 0x94, il y a le "SizeOfOptionalHeader" qui vaut 0xE000. Il s'agit de la taille de l'entête optionnel IMAGE_OPTIONAL_HEADER qui se trouve plus bas dans la section PE (dans laquelle on est maintenant).

En 0x96, on a les "Characteristics" sur 2 bytes avec 0x0E01.
Cette valeur est codé sur 16 bits à partir du mode binaire :

0 E 0 1
0000 1110 0000 0001

(bits: 15,14,13,12,...,2,1,0)

La signification du vecteur de base 16 étant :

Nom
Hexa
Bin
Utilité
 
IMAGE_FILE_RELOCS_STRIPPED
0x0001
00
Le fichier n'a pas d'infos de "relocation". Cas des executables.
IMAGE_FILE_EXECUTABLE_IMAGE
0x0002
01
Le fichier est executable (pas de dll par exemple).
IMAGE_FILE_LINE_NUMS_STRIPPED
0x0004
02
Les numéros de lignes ne sont pas dans le fichier.
IMAGE_FILE_LOCAL_SYMS_STRIPPED
0x0008
03
Les symbols locaux ne sont pas dans le fichier.
IMAGE_FILE_AGGRESIVE_WS_TRIM
0x0010
04
Intervention aggréssive du SE dans l'execution du fichier.
IMAGE_FILE_LARGE_ADDRESS_AWARE
0x0020
05
L'application peut manipuler plus de 2Gb d'adresses.
 
0x0040
06
IMAGE_FILE_BYTES_REVERSED_LO
0x0080
07
Inversion des bytes de "machine" (i.e. 0x4C01 plus haut). Voir bit 15.
IMAGE_FILE_32BIT_MACHINE
0x0100
08
Si l'ordinateur est un 32 bits.
IMAGE_FILE_DEBUG_STRIPPED
0x0200
09
Les infos de débugage ne sont pas dans l'exe, mais dans un fichier .dbg.
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP
0x0400
10
Si l'image est sur un media transportable (disquette, CDROM,...), elle est copiée et executée depuis un fichier swap.
IMAGE_FILE_NET_RUN_FROM_SWAP
0x0800
11
Si l'image est sur un réseau, elle est copiée et executée depuis un fichier swap.
IMAGE_FILE_SYSTEM
0x1000
12
Fichier système, driver.
IMAGE_FILE_DLL
0x2000
13
Le fichier est une DLL.
IMAGE_FILE_UP_SYSTEM_ONLY
0x4000
14
Le fichier doit être executé sur une machine n'ayant qu'un processeur.
IMAGE_FILE_BYTES_REVERSED_HI
0x8000
15
Inversion des bytes de "machine" (i.e. 0x4C01 plus haut). Voir bit 07.

SE : système d'exploitation

Dans le cas du notepad, on est en 0x0E01. On peut ou bien faire la somme des nombres appropriés de la colonne hexa (0x0001 + 0x0200 + 0x0400 + 0x0800 = 0x0E01) ou bien lire le vecteur et voir que les rangs 0, 9, 10, 11 sont allumés. Cela correspond aux caractéristiques suivantes :

IMAGE_FILE_RELOCS_STRIPPED Le fichier n'a pas d'infos de "relocation". Cas des executables.
IMAGE_FILE_DEBUG_STRIPPED Les infos de débugage ne sont pas dans l'exe, mais dans un fichier .dbg.
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP Si l'image est sur un media transportable (disquette, CDROM,...), elle est copiée et executée depuis un fichier swap.
IMAGE_FILE_NET_RUN_FROM_SWAP Si l'image est sur un réseau, elle est copiée et executée depuis un fichier swap.

Voilà pour ce qui se cache derrière ces quelques bits... :)

Jusqu'ici, on peut donc résumer l'entête PE à cela :

typedef struct _IMAGE_FILE_HEADER {
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Ensuite, dans l'entête PE il y a ce que l'on appelle l'entête PE optionnel. Cela n'a rien à voir d' "optionnel", bien au contraire. On va trouver dans cette partie des informations comme l'ImageBase, le PEP (Program Entry Point), la version du SE...

Décorticons tout cela :

L'entête optionnel est divisé en 2 parties. L'une dite standard et l'autre additionnelle pour NT.

typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

Regardons la partie standard (par analogie avec le code C++ ci-dessus) :

En 0x98, vous avez le Magic Number sur 2 bytes qui vaut 0x010B (il apparait comme 0x0B01) dans tous les executables, sauf preuve du contraire. Ce nombre n'a pas plus de signification particulière.

En 0x9A et 0x9B se trouvent 0x03 et 0x0A, les numéros des versions du linker lors de la compilation.

Ensuite on a 3 valeurs, sur 4 bytes chacune, qui valent :
- de 0x9C à 0x9F (SizeOfCode) = 0040 0000
- de 0xA0 à 0xA3 (SizeOfInitializedData) = 0074 0000
- de 0xA4 à 0xA7 (SizeOfUnitializedData) = 0000 0000
"SizeOfCode" est la taille de la section .text et vaut 0x4000.

En 0xA8 sur 4 bytes, on a LA valeur! Il s'agit ni plus ni moins que du PEP, le program entry point. C'est à partir de cette valeur là que le programme commence à s'executer. Cette valeur vaut ici 0x10CC (elle est écrite "à l'envers" comme tout le reste, CC10 0000). Si vous décompilez le notepad sous windasm et que vous appuyez sur le bouton PEP de la barre menu, vous atterrissez directement ici en 0x10CC. C'est comme cela que cette valeur est déterminée.

Les deux valeurs suivantes sont en AC et sur 4 bytes le "BaseOfCode" qui vaut 0010 0000 (lire 0x1000) et en B0, sur 4 bytes aussi, le "BaseOfData" qui vaut lui 0050 0000 (lire 0x5000). Le "BaseOfCode" est l'adresse (l'offset) du début de la section du code (c'est à dire la section .text), et "BaseOfData" l'offset du début de la section .data. Ce renseignement n'est pas très utile ici. Il vaut mieux utiliser les données de la déclaration des sections qui sont plus précises.

Ainsi se termine la partie standard, avec la déclaration du "program entry point" pour le plus important.

Pour la partie additionnelle NT, voyons ce qu'il y a dedans...

On commence en B4 sur 4 bytes avec l'"ImageBase" qui vaut 0x400000. Cette valeur est définie lors de la compilation par le linker. Elle peut être bien sur différente, mais elle vaut 0x400000 dans beaucoup de cas. Sa signification? Quand on lance le programme en mémoire (i.e. quand on le démarre), celui-ci va avoir une adresse relative (RVA ou Relativ Virtual Offset) qui est différente de son adresse (Virtual Offset) dans l'exe.

Je prends tout de suite un exemple. Vous avez déjà visualisé un soft sous windasm et sous un hexediteur. Sous windasm, toutes les adresses des lignes de code sont du type 0x40XXXX. Et quand on cracke par exemple, on se réferre dans la barre en bas de windasm pour avoir l'adresse équivalente sous l'éditeur hexa. La différence entre ces 2 valeurs est donnée par la formule citée (beaucoup) plus haut à l'"ImageBase" près.

Ensuite il y a des détails sur l'alignement des sections et du fichier de 0xB8 à 0xBF. Chacune valant 0x1000. On passe.

De même de 0xC0 à 0xCA , il y a tout un tas de renseignements sur les versions du SE, du programme (notepad)...

En 0xCB sur 4 bytes, il y a une valeur réservée.

La valeur suivante en 0xD0 sur 4 bytes qui vaut 00F0 0000 (lire 0xF000) est la taille de l'exe, c'est à dire la somme de la taille de toutes les sections (padding compris). Cette valeur s'appelle "SizeOfImage". Dans l'exercice d'ajout d'une nouvelle section plus haut (avec .anubis), on avait eu à changer cette valeur. Elle est définie ici.

En 0xD4 sur 4 bytes se trouve le "SizeOfHeaders" qui vaut 0004 0000 (lire 0x0400). Il correspond à la somme de tous les en-têtes.

En 0xD8, aussi sur 4 bytes, il y a le checksum 8918 0100 (lire 0x00011889) du fichier .exe. Seul le linker vérifie cette valeur. La changer n'influe pas sur le lancement de l'exe.

La valeur 0200 en 0xDC qui vaut 02 sur 2 bytes est le "Subsystem". Cela indique de quelle manière doit tourner le programme. Les différentes possibilités sont énumérées ci-dessous :

Valeur
Nom
Sur quoi ca tourne
0 IMAGE_SUBSYSTEM_UNKNOWN Subsystem inconnu.
1 IMAGE_SUBSYSTEM_NATIVE L'image n'a pas besoin de subsystem (cas des drivers).
2 IMAGE_SUBSYSTEM_WINDOWS_GUI L'image tourne sous un subsystem Windows GUI.
3 IMAGE_SUBSYSTEM_WINDOWS_CUI L'image tourne sous un subsystem de type Windows (i.e. en mode console aka DOS).
5 IMAGE_SUBSYSTEM_OS2_CUI L'image tourne sous un subsystem de type OS/2.
7 IMAGE_SUBSYSTEM_POSIX_CUI L'image tourne sous un subsystem de type Posix.
8 IMAGE_SUBSYSTEM_NATIVE_WINDOWS L'image est un driver "native" (d'origine?) Win9x.
9 IMAGE_SUBSYSTEM_WINDOWS_CE_GUI L'image tourne sous un subsystem Windows CE.

Donc comme notre valeur vaut 2, on tourne sous une application windows utilisant le Graphic User Interface (GUI). Ce qui est bien le cas avec le notepad. Ouf, on est sauvé, tout va bien. Et pour tout ce qui est application en mode DOS, on aura un 3 à la place du 2.

En 0xDE sur 2 bytes (0000) est ce qu'on appelle le "DllCharacteristics". Ce sont des propriétés pour les Dlls du programme :

Nom Valeur hexa
IMAGE_LIBRARY_PROCESS_INIT 0001
IMAGE_LIBRARY_PROCESS_TERM 0002
IMAGE_LIBRARY_THREAD_INIT 0004
IMAGE_LIBRARY_THREAD_TERM 0008


Ici rien n'est chargé, la valeur est à 0. On passe à la suite.

Ensuite on a 4 valeurs en rapport avec la pile lors de l'execution du soft par le processeur. Ces valeurs sont "SizeOfStackReserve", "SizeOfStackCommit", "SizeOfHeapReserve", "SizeOfHeapCommit". Elles font chacunes 4 bytes et sont localisées de 0xE0 à 0xEF.

En 0xF0 sur 4 bytes, il y a le LoaderFlags qui est une option pour le chargement du programme. Cette valeur est à 0000 si rien de particulier est demandé comme ici.

Ensuite en 0xF4 sur 4 bytes, la valeur "NumberOfRvaAndSizes" en F4 qui vaut 0x10 (soit 16 en décimal). Il s'agit de la longueur du tableau "DataDirectory" qui suit. Cette valeur permet de connaître la taille du tableau, mais ne renseigne pas sur les entrées valides du tableau.

Le "DataDirectory" est un tableau de 16 valeurs qui donnent diverses informations sur différentes parties de l'exe. Ce tableau s'étend de 0xF8 (inclus) à 0x17C (exclus), et toutes les entrées sont en WORD soit 4 bytes. Un rapide coup d'oeil sur les entrées du tableau donne :

Offset
Valeur
Nom de la variable
Signification
0xF8 0x0 IMAGE_DIRECTORY_ENTRY_EXPORT ExportDirectory VA
0xFC 0x0   ..ExportDirectory Size
0x100 0x6000 IMAGE_DIRECTORY_ENTRY_IMPORT ImportDirectory VA
0x104 0x8C   ..ImportDirectory Size
0x108 0x7000 IMAGE_DIRECTORY_ENTRY_RESOURCE ..ResourceDirectory VA
0x10C 0x558C   ..ResourceDirectorySize
0x110 0x0 IMAGE_DIRECTORY_ENTRY_EXCEPTION ExceptionDirectory VA
0x114 0x0   ..ExceptionDirectorySize
0x118 0x0 IMAGE_DIRECTORY_ENTRY_SECURITY ..SecurityDirectory VA
0x11C 0x0   ..SecurityDirectory Size
0x120 0xD000 IMAGE_DIRECTORY_ENTRY_BASERELOC ..BaseRelocationTable VA
0x124 0x93C   ..BaseRelocationTableSize
0x128 0x0 IMAGE_DIRECTORY_ENTRY_DEBUG DebugDirectory VA
0x12C 0x0   ..DebugDirectory Size
0x130 0x0 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE ArchitectureSpecificData VA
0x134 0x0   ..ArchitectureSpecificData ..Size
0x138 0x0 IMAGE_DIRECTORY_ENTRY_GLOBALPTR RVAofGP VA
0x13C 0x0   ..RVAofGP Size
0x140 0x0 IMAGE_DIRECTORY_ENTRY_TLS TLSDirectory VA
0x144 0x0   ..TLSDirectory Size
0x148 0x0 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG LoadConfigurationDirectory VA
0x14C 0x0   ..LoadConfigurationDirectory ..Size
0x150 0x0 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT BoundImportDirectoryinheaders VA
0x154 0x0   ..BoundImportDirectoryinheaders ..Size
0x158 0x62E0 IMAGE_DIRECTORY_ENTRY_IAT ImportAddressTable VA
0x15C 0x240   ..ImportAddressTable Size
0x160 0x0 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT DelayLoadImportDescriptors VA
0x164 0x0   ..DelayLoadImportDescriptors ..Size
0x168 0x0 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR COMRuntimedescriptor VA
0x16C 0x0   ..COMRuntimedescriptor Size
0x170 0x0 0 -
0x174 0x0 0 -

VA = Virtual address

Hormis IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR et la 16ème entrée qui est toujours à zéro, ce tableau est celui que nous donne procdump en cliquant sur "Directory" dans la partie "Structures editor" du PE editor.

Dans tous ces renseignements, il est important de voir que l'on trouve ici l'IAT ou ImportAddressTable en 0x62E0 sur 0x240 bytes. En comparant avec les valeurs des sections, on trouve que la IAT est inclue dans .idata.

L'IAT est une table qui contient des adresses pour appeler les fonctions dans les DLLd. Pour comprendre en détail la structure de l'IAT, il faut regarder en même temps la déclaration de la section .idata, l'import image data directory, l'entête optionnel et la section .idata elle-même.

Toutes les entrées non utilisées sont à zéro. Les champs renseignés et donc utilisés dans le cas du notepad sont:
ImportDirectory
ResourceDirectory
BaseRelocationTable
ImportAddressTable

On termine ici le "NT additional field". Juste après toutes ces valeurs du "DataDirectory" se trouve la déclaration des sections (.text, .data, ...)
que j'ai expliquées au début. Je donne quelques caractéristiques :

Le bloc de 0x28 bytes de la déclaration de la fonction est défini comme suit :

typedef struct _IMAGE_SECTION_HEADER {

UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];
union {
ULONG PhysicalAddress;
ULONG VirtualSize;
} Misc;
ULONG VirtualAddress;
ULONG SizeOfRawData;
ULONG PointerToRawData;
ULONG PointerToRelocations;
ULONG PointerToLinenumbers;
USHORT NumberOfRelocations;
USHORT NumberOfLinenumbers;
ULONG Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Pour définir une section, il y a donc dans l'ordre le nom (8 bytes), la Virtual Size (4 bytes), la VirtualAddress (4 bytes), la SizeOfRawData ou Raw Size (4 bytes), le PointerToRawData ou Raw Offset (4 bytes), le PointerToRelocations (4 bytes), le PointerToLinenumbers (4 bytes), le NumberOfRelocations (2 bytes), le NumberOfLinenumbers (2 bytes), les Characteristics (4 bytes). Soit au total 0x28 ou 40 bytes!

Comme on peut le voir aussi dans le cas du notepad, PointerToRelocations, PointerToLinenumbers, NumberOfRelocations et NumberOfLinenumbers ne sont jamais utilisés pour le PE d'un .exe, mais seulement celui d'un .obj.

Ensuite, on a du padding jusqu'au début de la première section. On peut y écrire n'importe quoi dedans sans qu'il y ait vraiment d'interférences dans l'execution du programme.

Voilà pour l'entête PE dans sa totalité!

Pour résumer, voici les points importants où il faut avoir l'oeil quand on regarde un exe dans un editeur hexadécimal (les offsets sont relatifs et dépendent du pointeur en 0x3C) :

Offset
Valeur
Signification
0x00 0x4D5A MZ - début de l'entête DOS
0x3C 0x80 Pointe sur l'entête PE - La valeur n'est pas toujours 0x80! Mais elle est toujours en 0x3C
0x80 0x5045 PE - début de l'entête PE
0x86 0xXX Number of Sections - Indique le nombre de sections dans l'exe. A changer si on rajoute une section à la main.
0xA8 0xXXXXXXXX PEP - Program Entry Point : là où commence le programme
0xB4 0x40XXXX ImageBase - Pour passer de l'offset relatif (sous windasm, softice...) à l'offset du fichier (dans un éditeur hexa)
0xD0 0xXXXX SizeOfImage - Taille de l'exe. A modifier si on rajoute ou modifie la taille d'une section à la main
0xF4 et +   Début du DataDirectory qui figure sous procdump. Il se termine là où commence la déclaration de la section .text, soit une longueur de 128 bytes.
0x158 0xXXXXXXXX IAT - Import Address Table

Bon, on pourrait bien sûr rajouter quelques valeurs, mais cela dépend de chacun et ce tableau est déjà bien complet :)

On va maintenant regarder les blocs que j'ai affichés en intro, à savoir les différentes sections de l'exe.

Il y a 9 sections de base. Elle ne figurent pas forcément toutes dans l'exe. Ensuite, on peut définir des sections "personnelles" (dans le cas d'un ajout de section à la main), des sections dûes à des compacteurs (UPX, ...), bref les possibilitées sont variées.

Les 9 sections de bases sont :
.text, .bss, .rdata, .data, .rsrc, .edata, .idata, .pdata, .debug

On va les voir en détail.

.text

Cette section est celle qui contient le code de l'executable et en principe, uniquement du code. Ce code est bien sûr executable dans ses propriétés (voir les "Characteristics" de cette section plus haut). Cette section porte parfois le nom .code ou CODE. Elle contient le PEP définit dans l'en-tête PE.

Pour illustrer la section, en voici un extrait tel que je l'ai montré au début de ce tut :

000017D0 7421 83F8 0274 1C83 F805 7417 83F8 0774 t!...t....t....t
000017E0 123D 0410 0000 7412 3D05 1000 000F 85BA .=....t.=.......
000017F0 0000 003D 0410 0000 7507 A1AC 5040 00EB ...=....u...P@..
00001800 05A1 5850 4000 6A10 6A00 50FF 3564 5040 ..XP@.j.j.P.5dP@
00001810 00FF 3500 5040 00E8 C10B 0000 E98C 0000 ..5.P@..........

Cette section commence en 0x1000 et se termine en 0x5000 (padding inclus). Elle doit être 0x60000020 en caractéristiques, cad : lue et executée (0x60000000) comme du code (0x00000020).

Les sections data

Il existe plusieurs types de sections data. Tout dépend du type de données qu'elles contiennent. Par rapport à la liste ci-dessus, il y a: .bss, .rdata, .data, .edata, .idata, .pdata

Je rappelle que toutes les adresses pour les début et fin de sections concernent le Notepad, fournit avec ce tut.

.data

Cette section peut aussi s'appeler DATA. Elle contient d'une manière générale toutes les données qui ne sont pas dans les autres "data" sections.

Dans le cas du notepad, on a les fonctions utilisées qui sont propres au notepad :

00005200 7900 0000 0000 0000 6C66 5069 7463 6841 y.......lfPitchA
00005210 6E64 4661 6D69 6C79 0000 0000 0000 0000 ndFamily........
00005220 6950 6F69 6E74 5369 7A65 0000 0000 0000 iPointSize......
00005230 6657 7261 7000 0000 6653 6176 6550 6167 fWrap...fSavePag
00005240 6553 6574 7469 6E67 7300 0000 0000 0000 eSettings.......

Cette section commence en 0x5000 et finit en 0x6000. Elle doit être 0xC0000040 en caractéristiques, cad : lecture et écriture (0xC0000000) de données initialisées (0x00000040).

.bss

Cette section concerne les données non initialisées (uninitialized data) comme par exemple "int i;". Elle peut aussi s'appeler BSS.

.rdata

Cette section concerne les données en lecture seule telles que les chaines littérales, les constantes ou les information de débugage. Le "r" signifie Read Only.

.edata

Section qui contient les données exportées (exported symbols). On trouve ces données plus généralement dans les Dlls plutôt que dans les executables (ce qui n'est quand même pas impossible). On trouvera dans cette section les entry point des fonctions exportées. Souvent, cette section est intégrée dans la section .data.

La structure de l'"export table" (IMAGE_EXPORT_DIRECTORY) comprends un entête et la donnée exportée.

typedef struct _IMAGE_EXPORT_DIRECTORY {

DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

typedef struct _IMAGE_IMPORT_BY_NAME {

WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

Il n'y a pas de section .edata dans le notepad.

.idata

Section contenant les "imported data". Ce sont des "import modules" et des "fonctions names", comprendre des Dlls et des APIs. Celles-ci sont d'abord représentés par leurs RVA (Relativ Virtual Address).

L'"import directory" devrait résider dans une section qui est "initialized data" et "readable".

C'est un tableau de IMAGE_IMPORT_DESCRIPTORs, une pour chaque Dll utilisée. La liste est terminée par un IMAGE_IMPORT_DESCRIPTOR, rempli de 00. Pour illustrer, un IMAGE_IMPORT_DESCRIPTOR possède la structure suivante :

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

union {
DWORD Characteristics; // 0 si l'import descriptor est terminé par zéro
DWORD OriginalFirstThunk; // RVA de l'IAT non liée (bound)(PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; // 0 si non lié,
// -1 si lié, avec le marquage vrai date\heure dans
// IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// Avec marquage date/heure de la DLL liée (Old BIND)
DWORD ForwarderChain; // -1 si pas de renvoi
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

Le nom des APIs Win32 figure toujours AVANT le nom de la Dll à laquelle elle est ratachée.

Exemple : dans la Dll GDI32.dll se trouve l'API "CreateDCA".

00006CD0 7445 7874 4578 0000 C600 4765 7454 6578 tExtEx....GetTex
00006CE0 7443 6861 7273 6574 0000 2100 4372 6561 tCharset..!.Crea
00006CF0 7465 4443 4100 4744 4933 322E 646C 6C00 teDCA.GDI32.dll.
00006D00 0400 436F 6D6D 446C 6745 7874 656E 6465 ..CommDlgExtende

Ces APIs sont utilisées par le notepad pour son fonctionnement (gestion des boites de dialogues, changement de la police...). La section commence en 0x6000 et fini en 0x7000, padding compris. Elle est en 0x40000040 en caractéristique, cad : la section peut être lue (0x40000000) et contient des données initialisées (0x00000040).

.pdata

Section non dévelopée dans le cas du notepad. Se reporter à .data.

.debug

Les informations relatives au débugage sont normalement placées dans cette section. Le format PE supporte aussi des fichiers débugages séparés. Ils ont alors l'extension ".DBG". La section .debug contient les informations de débugage, mais les "debug directories" sont dans la section .rdata sous IMAGE_DEBUG_DIRECTORY.

typedef struct _IMAGE_DEBUG_DIRECTORY {

ULONG Characteristics;
ULONG TimeDateStamp;
USHORT MajorVersion;
USHORT MinorVersion;
ULONG Type;
ULONG SizeOfData;
ULONG AddressOfRawData;
ULONG PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;

La section est divisée en plusieurs portions de données représentant différents types d'informations de débugage. Les différentes possibilités sont :

#define IMAGE_DEBUG_TYPE_UNKNOWN 0
#define IMAGE_DEBUG_TYPE_COFF 1
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
#define IMAGE_DEBUG_TYPE_FPO 3
#define IMAGE_DEBUG_TYPE_MISC 4

Il n'y a pas de section debug dans le cas du notepad.

.rsrc

(Cette section sera dévelopée plus en détail dans un prochain Mémento avec exemple à l'appui)

Cette section contient tout ce qui est appelé ressources. On entend par là tout ce qui est icones, menus, strings,... Cette section est en général assez longue, et est structurée sous forme d'arbre/arborescence. Chaque ressource est définie par son nom ou son ID, ainsi que par un numéro qui lui permet d'être atteinte dans l'arbre.

Au niveau des caractéristiques, cette section doit au moins être IMAGE_SCN_CNT_INITIALIZED_DATA et IMAGE_SCN_MEM_READ.

Pour décrire cette section, on doit considérer tous les éléments qui la constituent comme une arborescence.
Ces éléments sont les ressources. Ils possèdent un numéro d'identification (ID) qui est stocké dans une table : le Resource Directory qui se trouve en début de la section. Ainsi, le programme trouvera les élément dont il a besoin en regardant dans cette table pour aller chercher l'ID, puis va ensuite directement sur la ressource dont il a besoin.

Le Ressource Directory commence en 0x7000 (d'après la valeur ResourceDirectory VA en 0x108) et se termine en 0x7000 + 0x558C = 0xC58C (d'après la valeur ResourceDirectory Size en 0x10C). On remarque d'ailleurs un joli padding à la fin :

0000C550 6100 7200 4600 6900 6C00 6500 4900 6E00 a.r.F.i.l.e.I.n.
0000C560 6600 6F00 0000 0000 2400 0400 0000 5400 f.o.....$.....T.
0000C570 7200 6100 6E00 7300 6C00 6100 7400 6900 r.a.n.s.l.a.t.i.
0000C580 6F00 6E00 0000 0000 0C04 B004 5041 4444 o.n.........PADD
0000C590 494E 4758 5850 4144 4449 4E47 5041 4444 INGXXPADDINGPADD
0000C5A0 494E 4758 5850 4144 4449 4E47 5041 4444 INGXXPADDINGPADD
0000C5B0 494E 4758 5850 4144 4449 4E47 5041 4444 INGXXPADDINGPADD
0000C5C0 494E 4758 5850 4144 4449 4E47 5041 4444 INGXXPADDINGPADD
0000C5D0 494E 4758 5850 4144 4449 4E47 5041 4444 INGXXPADDINGPADD
0000C5E0 494E 4758 5850 4144 4449 4E47 5041 4444 INGXXPADDINGPADD
0000C5F0 494E 4758 5850 4144 4449 4E47 5041 4444 INGXXPADDINGPADD
0000C600 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000C610 0000 0000 0000 0000 0000 0000 0000 0000 ................

La racine et les branches de cette arborescence sont dictées par l'IMAGE_RESOURCE_DIRECTORY.

typedef struct _IMAGE_RESOURCE_DIRECTORY {

DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries; // IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

Pour prendre un exemple :

En 0x7324, il y a une entrée du Resource Directory... :

00007320 0000 0000 9052 2900 0400 0000 0000 0100 .....R).........
00007330 0C04 0000 6804 0000 7874 0000 2801 0000 ....h...xt..(...
00007340 E404 0000 0000 0000 A075 0000 E802 0000 .........u......

...qui correspond à l'icone déclarée en 0x7478 et dont le dessin commence en 0x74EA :

00007470 E404 0000 0000 0000 2800 0000 1000 0000 ........(.......
00007480 2000 0000 0100 0400 0000 0000 8000 0000 ................
00007490 0000 0000 0000 0000 0000 0000 0000 0000 ................
000074A0 0000 0000 0000 8000 0080 0000 0080 8000 ................
000074B0 8000 0000 8000 8000 8080 0000 C0C0 C000 ................
000074C0 8080 8000 0000 FF00 00FF 0000 00FF FF00 ................
000074D0 FF00 0000 FF00 FF00 FFFF 0000 FFFF FF00 ................
000074E0 0000 0000 0000 0000 0000 0777 7777 7780 ...........wwww.
000074F0 0000 0777 7777 7770 0000 0777 7777 FF70 ...wwwwp...www.p

Les entrées du Resource Directory commencent par "R)" dans la partie ascii de l'éditeur hexa. Comme resources facilement identifiables, il y a les icones qui sont la plupart du temps repérables grace aux "dessins symétriques" dans la partie ascii, et aux valeurs hexa "uniformes" dans la partie hexadécimale. En fait les dessins des icones sont codées par petits carrés, chacun avec un code hexa pour déterminer la couleur.

Toutes les ressources ont un entête qui commence avec "(" dans la partie ascii (soit 0x2800 dans la partie hexa comme en 0x7478). Ainsi pour repérer l'entrée dans le Resource Directory, il suffit de regarder l'offset de "(" (0x2800) et de faire une recherche de chaine hexa sur cette valeur. Obligatoirement, on tombe sur l'offset enregistré dans l'entrée du Resource Directory.

Par exemple, de 0x7000 à 0x8300, on a le Resource Directory suivit des icones. En 0x7324 se trouve le dernier "R)" qui déclare entre autres les icones. Est d'abord définie, l'offset (5e colonne avec 7874 pour 0x7478 comme 1ere valeur), puis la taille (2801 dans la 7e colonne, lire 0x0128, donc l'item suivant commence en 0x128 + 0x7478 = 0x75A0 (en dessous de 7874 dans la 5e colonne), puis la caractéristique de l'item avec la colonne de E404 dans la 1ere colonne.

00007320 0000 0000 9052 2900 0400 0000 0000 0100 .....R).........
00007330 0C04 0000 6804 0000 7874 0000 2801 0000 ....h...xt..(...
00007340 E404 0000 0000 0000 A075 0000 E802 0000 .........u......
00007350 E404 0000 0000 0000 8878 0000 E802 0000 .........x......
00007360 E404 0000 0000 0000 707B 0000 2801 0000 ........p{..(...
00007370 E404 0000 0000 0000 987C 0000 6806 0000 .........|..h...
00007380 E404 0000 0000 0000 0083 0000 A808 0000 ................
00007390 E404 0000 0000 0000 A88B 0000 A80E 0000 ................
000073A0 E404 0000 0000 0000 509A 0000 6805 0000 ........P...h...
000073B0 E404 0000 0000 0000 B89F 0000 6403 0000 ............d...
000073C0 E404 0000 0000 0000 84A3 0000 CE00 0000 ................
000073D0 E404 0000 0000 0000 54A4 0000 6A04 0000 ........T...j...
000073E0 E404 0000 0000 0000 C0A8 0000 B602 0000 ................
000073F0 E404 0000 0000 0000 78AB 0000 800C 0000 ........x.......
00007400 E404 0000 0000 0000 F8B7 0000 9002 0000 ................
00007410 E404 0000 0000 0000 88BA 0000 4606 0000 ............F...
00007420 E404 0000 0000 0000 D0C0 0000 4800 0000 ............H...
00007430 E404 0000 0000 0000 18C1 0000 6800 0000 ............h...
00007440 E404 0000 0000 0000 80C1 0000 2200 0000 ............"...
00007450 E404 0000 0000 0000 A4C1 0000 5A00 0000 ............Z...
00007460 E404 0000 0000 0000 00C2 0000 8C03 0000 ................
00007470 E404 0000 0000 0000 2800 0000 1000 0000 ........(.......

En 0x9FB8 commence la déclaration des menus (voir ci dessus en 0x73B8)... :

00009FA0 C000 0000 E000 3300 E000 6600 F000 9900 ......3...f.....
00009FB0 F001 CC00 F803 FF00 0000 0000 1000 2600 ..............&.
00009FC0 4600 6900 6300 6800 6900 6500 7200 0000 F.i.c.h.i.e.r...
00009FD0 0000 0900 2600 4E00 6F00 7500 7600 6500 ....&.N.o.u.v.e.
00009FE0 6100 7500 0000 0000 0A00 2600 4F00 7500 a.u.......&.O.u.
00009FF0 7600 7200 6900 7200 2E00 2E00 2E00 0000 v.r.i.r.........

...suivis des strings à partir de 0xA384 jusqu'en 0xC0D0... :

0000A570 8000 4F00 7200 6900 6500 6E00 7400 6100 ..O.r.i.e.n.t.a.
0000A580 7400 6900 6F00 6E00 0000 0000 0900 0350 t.i.o.n........P
0000A590 0000 0000 1000 5200 3400 0C00 2004 FFFF ......R.4... ...
0000A5A0 8000 5000 2600 6F00 7200 7400 7200 6100 ..P.&.o.r.t.r.a.
0000A5B0 6900 7400 0000 0000 0900 0050 0000 0000 i.t........P....
[...]
0000BFC0 2000 4200 6C00 6F00 6300 2D00 6E00 6F00 .B.l.o.c.-.n.o.
0000BFD0 7400 6500 7300 2E00 0A00 5600 6F00 7500 t.e.s.....V.o.u.
0000BFE0 6C00 6500 7A00 2D00 7600 6F00 7500 7300 l.e.z.-.v.o.u.s.
0000BFF0 2000 7500 7400 6900 6C00 6900 7300 6500 .u.t.i.l.i.s.e.
0000C000 7200 2000 5700 6F00 7200 6400 5000 6100 r. .W.o.r.d.P.a.
0000C010 6400 2000 7000 6F00 7500 7200 2000 6C00 d. .p.o.u.r. .l.

En 0xC200 se trouve les infos "maison" du programme (qui l'a fait, quelle version, date, copyright...).

Bien entendu, si vous utilisez un soft genre exescope, vous avez acces à toutes ces infos et vous pouvez les modifier à loisir. Tous les changements d'offsets sont en principe recalculés par exescope & co pour que l'exe soit toujours "cohérent".

Quelques détails sur la structure elle-même du Resource Directory :

Le Resource Directory consiste en deux comptes. Le premier est le nombre d'entrées au début du tableau ayant un nom associé avec chaque entrées. Le second est le nombre d'entrées qui suit directement l'entrée nommée. Ce second compte identifie le nombre d'entrées qui ont des IDs de 16 bits en tant que nom.

Cette structure permet de rechercher rapidement un item par nom ou nombre (le ou étant exclusif, cad qu'on doit chercher par l'un ou l'autre, mais pas par les deux).

Les champs NumberOfNamedEntries et NumberOfIdEntries indiquent le nombre d'entrées attachées au Directory. Les entrées nommées apparaissent en premier en ordre alphabétique croissant, suivies des IDs en ordre numérique croissant. Une entrée est représentée par les deux champs suivant :

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {

ULONG Name;
ULONG OffsetToData;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

L'ID dépend de la position de l'entrée dans l'arborescence. Si c'est un nombre, son bit haut est vide, sinon c'est un nom et son bit haut est allumé. Dans ce cas-là, les 31 bits bas représentent l'offset du début de la raw data de la section ressource.

Une fois les renseignements de la ressource atteints, on a ses caractéristiques suivantes :

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {

ULONG OffsetToData; // position de la ressource (en RVA)
ULONG Size; // taille
ULONG CodePage;
ULONG Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;

Puis, étant dans le répertoire racine, on a une représentation de ce type (ces ressources sont prédéfinies) :

#define RT_CURSOR MAKEINTRESOURCE(1)
#define RT_BITMAP MAKEINTRESOURCE(2)
#define RT_ICON MAKEINTRESOURCE(3)
#define RT_MENU MAKEINTRESOURCE(4)
#define RT_DIALOG MAKEINTRESOURCE(5)
#define RT_STRING MAKEINTRESOURCE(6)
#define RT_FONTDIR MAKEINTRESOURCE(7)
#define RT_FONT MAKEINTRESOURCE(8)
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
#define RT_RCDATA MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE MAKEINTRESOURCE(11)

Tout autre nombre est défini par l'utilisateur. A un niveau plus bas dans l'arborescence, l'ID est son nom ou son nombre.

Pour décrypter les IDs, il faut les couper en deux groupes de bits de 0 à 9 et de 10 à 15. Les valeurs sont définies dans le fichier "winresrc.h".

La section ressources commence en 0x7000 et termine en 0xD000. Elle est en 0x40000040 en caractéristique, cad : la section peut être lue (0x40000000) et contient des données initialisées (0x00000040).

.reloc

Quelques mots sur cette section... Son nom signifie relocation. Cette section contient les données nécessaires pour permettre au loader de charger l'exe en mémoire à une adresse autre que celle définie en ImageBase (recalculation des offsets).

Cette section est définie aussi dans le DataDirectory. Elle commence en 0xD000 et termine en 0xE000. Elle est en 0x42000040 en caractéristique, cad : la section peut être lue (0x40000000) et contient des données initialisées (0x00000040). Deplus, la section peut être annulée à volonté (0x02000000).

(--- fin des sections ---)

En résumé, on peut retenir les points suivants importants pour les sections :

.text Contient du code brut et le PEP.
.data Contient les fonctions propres au notepad (.data), les données non initialisées (.bss), en lecture seule (.rdata), les données exportées depuis la Dll (.edata) et les fonctions appelées dans les DLLs (.idata) ainsi que la IAT.
.debug Contient les infos acquises lors d'un débugage (voilà pourquoi un exe en mode debug lors de la compilation est si gros...).
.rsrc Contient les ressources (menus, icones, strings....).
.reloc Permet de reloader l'exe à une adresse autre que celle définie de préférence si l'exe n'est pas loadable.

Pour finir, voici un schéma de la structure du Notepad, et plus généralement d'un .exe :


Voilà !

Vous devriez y voir plus clair maintenant. Regardez par exemple sous wdasm le debut du dead listing du notepad jusqu'au PEP (program entry point). Il n'y a pas tant que ça à lire, et vous allez voir que beaucoup d'informations sont bien mieux "lisibles"...

A partir de ce tut, vous avez une connaissance générale sur la structure d'un exe sous windows. Dans les prochains tuts, lorsqu'on bidouillera dans les offsets, ajoutera des sections, recalculera certains élément pour reconstruire le nouvel .exe après transformation, vous saurez à quoi correspond les termes utilisés et où les trouver.

Il y a énormément à dire sur le PE en général. Plutôt que de vraiment entrer dans les détails et d'écrire un bouquin (qui existe déjà du reste), j'ai voulu à travers ce tut donner une vision globale de la structure d'un exe tel qu'on le visualise sous un éditeur hexadécimal. Les parties plus spécifiques seront développées dans des prochains tuts avec des exemples détaillés.

Je voudrais terminer ce tut par une petite note mondaine :) On touche là un domaine de la programmation "windows" qui a été merveilleusement bien décrite par Matt Pietrek dans son livre Windows 95 System Programming Secrets (IDG Books, 1995). Ce livre est aujourd'hui épuisé et plus ou moins introuvable. En quelques mots, ce livre explique *comment* marche le systeme d'exploitation Windows en explorant les dll, les vxd, la structure des fichiers.... Ce n'est pas un livre qui explique la programmation sous windows.

Matt Pietrek était (entre autres) directeur de projet à Numega Technologies. Voici un petit extrait de son parcours :

"I joined NuMega in 1993, after being laid off from Borland. I certainly was not one of the founders, but was well within the first 10 people.

When I joined, there were the two founders, two programmers (one of them off-site), an office manager, and a sales/marketing guy. A few more great people joined shortly after I did, but it was still pretty small for the first two years.

The two founders, Jim and Frank, are great guys. I still see them semi-regularly, and live in the same small town as they do. I wasn’t even formally interviewed. I just talked to them on the phone on two separate occasions, with most of conversation being about what I’d do to improve SoftIce. I was totally shocked when they called with a job offer. I certainly felt under qualified.

The great thing about the early days of NuMega was that it was a very family oriented company. It seemed that all the families knew each other, and we were always doing things together. We didn’t work horrible hours either, like most startups do these days. We were all pretty good at what we did, and we worked pretty normal hours (say, 9-5). If you have good products, and a high barrier to entry, you don’t have the same pressures that it seems everybody’s under today. As I recall, we didn’t even bother to track vacation time. Everybody just intrinsically knew that as long as the job got done, having a lot of formal procedures just got in the way.

When I joined, the primary products were SoftIce for DOS & Windows, and BoundsChecker for DOS. I jumped right into the middle of the first BoundsChecker for Windows release (you have to recall, in 1993, this would have been for 16 bit Windows 3.1) My first big job was to write the parameter validation system. The team was basically Frank, the SoftIce for Windows guru, and myself. You wouldn’t believe how much of the code was stolen from the DOS products."

Sa page web est là : http://www.wheaty.net

Il y a des trucs de fous dessus!!
Regarder aussi les liens chez ses collègues qui sont tous plus graves les uns que les autres... 8o)

Valaaaaa, le prochain coup on se tappera un vxd ou quelque chose d'équivalent :)

Si vous êtes encore debout après ce tut, ou bien vous êtes venu lire directement la fin, ou bien je vous conseille d'aller mettre la tête dans le congélateur pour vous raffraichir...


Merci au MaLaDe pour la relecture de ce tut