Dans ce Tutorial, nous allons voir comment vérifier si un fichier donné est un PE File Valide.
Downloadez l'exemple.
Comment pouvez-vous vérifier si un fichier donné est PE file ? C'est difficile de répondre à cette question. Ça dépend jusqu'où vous voulez aller. Vous pouvez vérifier chaque structure de données définie dans le format du PE file ou bien vous pouvez vous satisfaire de vérifier seulement les éléments cruciaux. La plupart du temps, il est inutile de vérifier chaque structure appartenant aux fichiers. Si les structures cruciales sont valables, nous pouvons dire avec presque certitude que le fichier est un PE valide. Et nous nous contenterons de cette supposition.
La structure essentielle que nous vérifierons est le PE header lui-même. Donc nous avons besoin d'en savoir un peu plus sur sa programmation. Le PE header est en réalité une structure appelée IMAGE_NT_HEADERS. Sa définition est la suivante :
IMAGE_NT_HEADERS STRUCT
Signature dd ?
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS
Signature
est un dword qui contient la valeur 50h, 45h, 00h, 00h. En terme plus humain, ça représente le texte "PE" suivi par deux zéros de terminaison. Ce membre est la 'signature PE', donc nous nous en servirons pour vérifier si un fichier donné est ou n'est pas un PE valide.
FileHeader est une structure qui contient des informations sur la structure physique du PE file tels que son nombre de sections, la machine pour laquelle ce fichier est destinée et cetera.
OptionalHeader est une structure qui contient des informations sur la structure logique du PE file. Malgré qu'il soit "Optionel", il est toujours présent.
Notre but est maintenant clair. Si la valeur du membre 'signature' du IMAGE_NT_HEADERS est égale à "PE" suivi par deux zéros, alors le fichier est un fichier PE valide. En fait, justement dans le but de pouvoir comparer ça, Microsoft a défini une constante nommée IMAGE_NT_SIGNATURE que nous pouvons facilement employer.
IMAGE_DOS_SIGNATURE equ 5A4Dh
IMAGE_OS2_SIGNATURE equ 454Eh
IMAGE_OS2_SIGNATURE_LE equ 454Ch
IMAGE_VXD_SIGNATURE equ 454Ch
IMAGE_NT_SIGNATURE equ 4550h
La question est la suivante : comment pouvons-nous savoir où se situe le PE header ? La réponse est simple : le MZ header du DOS contient l'offset(de fichier) du PE header. Le MZ header est défini comme la structure IMAGE_DOS_HEADER. Vous pouvez le vérifier en regardant dans windows.inc. Le membre e_lfanew de la structure IMAGE_DOS_HEADER contient l'offset du PE header.
Les étapes sont les suivantes :
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
SEH struct
PrevLink dd ? ; adresse de la structure SEH ci-dessus
CurrentHandler dd ? ; adresse du 'exception handler'
SafeOffset dd ? ; endroit où est sauvegardé l'offset pour continuer l'exécution
PrevEsp dd ? ; l'ancienne valeur dans 'esp'
PrevEbp dd ? ; ancienne valeur dans 'ebp'
SEH ends
.data
AppName db "PE tutorial no.2",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
FileValidPE db "This file is a valid PE",0
FileInValidPE db "This file is not a valid PE",0
.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?
.code
start proc
LOCAL seh:SEH
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER
or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
.if eax!=INVALID_HANDLE_VALUE
mov hFile, eax
invoke CreateFileMapping, hFile, NULL,
PAGE_READONLY,0,0,0
.if eax!=NULL
mov hMapping, eax
invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
.if eax!=NULL
mov
pMapping,eax
assume
fs:nothing
push
fs:[0]
pop
seh.PrevLink
mov
seh.CurrentHandler,offset SEHHandler
mov
seh.SafeOffset,offset FinalExit
lea
eax,seh
mov
fs:[0], eax
mov
seh.PrevEsp,esp
mov
seh.PrevEbp,ebp
mov
edi, pMapping
assume
edi:ptr IMAGE_DOS_HEADER
.if
[edi].e_magic==IMAGE_DOS_SIGNATURE
add edi, [edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS
.if [edi].Signature==IMAGE_NT_SIGNATURE
mov ValidPE, TRUE
.else
mov ValidPE, FALSE
.endif
.else
mov ValidPE,FALSE
.endif
FinalExit:
.if
ValidPE==TRUE
invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
.else
invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
.endif
push
seh.PrevLink
pop
fs:[0]
invoke
UnmapViewOfFile, pMapping
.else
invoke
MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke CloseHandle,hMapping
.else
invoke MessageBox, 0,
addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
.endif
invoke CloseHandle, hFile
.else
invoke MessageBox, 0, addr FileOpenError,
addr AppName, MB_OK+MB_ICONERROR
.endif
.endif
invoke ExitProcess, 0
start endp
SEHHandler proc C uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
mov edx,pFrame
assume edx:ptr SEH
mov eax,pContext
assume eax:ptr CONTEXT
push [edx].SafeOffset
pop [eax].regEip
push [edx].PrevEsp
pop [eax].regEsp
push [edx].PrevEbp
pop [eax].regEbp
mov ValidPE, FALSE
mov eax,ExceptionContinueExecution
ret
SEHHandler endp
end start
Le programme ouvre un fichier et vérifie si le DOS header (le MZ) est valide, s'il l'est, on vérifie alors si le PE header est valide à son tour. S'il l'est également, alors on peut certifier que le fichier est un PE File valide. Dans cet exemple, j'emploie la 'Structure Exception Handling' (SEH) pour ne pas avoir à faire de vérifier chaque erreur possible : si une erreur se produit, on est sûr que c'est parce que le fichier n'est pas un PE File valide. Windows lui-même utilise largement le SEH dans ses routines de validation de paramètres. Si le SEH vous intéresse, lisez l'article de Jeremy Gordon.
Son programme affiche une fenêtre OPENFILE (la boîte de Dialog qui sert à ouvrir d'autres fichiers) et quand l'utilisateur choisit un fichier exécutable, il ouvre ce fichier et les 'Mappe' en mémoire. Avant qu'il ne commence sa vérification, il installe un SEH :
assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandler,offset SEHHandler
mov seh.SafeOffset,offset FinalExit
lea eax,seh
mov fs:[0], eax
mov seh.PrevEsp,esp
mov seh.PrevEbp,ebp
Nous commençons en assumant l'utilisation du register fs comme étant mis à zéro. On est obligé de faire comme ça parce que MASM assume l'utilisation du register fs à ERROR (par défaut). Ensuite nous stockons l'adresse du SEH Handler vu précédemment dans notre structure pour que Windows puisse l'utiliser. Nous stockons l'adresse de notre SEH Handler, l'adresse où l'exécution peut sans risque reprendre si une erreur arrive, les valeurs actuelles de esp et d'ebp pour que notre SEH Handler puisse récupérer l'état antérieur de la pile (comme en tant normale) avant que notre programme reprenne son exécution.
mov edi, pMapping
assume edi:ptr IMAGE_DOS_HEADER
.if [edi].e_magic==IMAGE_DOS_SIGNATURE
Après que nous en ayons finis avec l'installation du SEH, nous passons à la vérification. Nous mettons l'adresse du premier octet du fichier cible dans edi, qui est le premier octet du MZ header (du DOS). Pour le bien de la comparaison, nous disons à l'assembleur qu'il peut assumer edi en tant que pointeur sur la structure IMAGE_DOS_HEADER (Ce qui est la vérité). Nous comparons alors le premier WORD du MZ header avec la chaîne de caractères "MZ" qui est définie comme une constante dans Windows.inc sous le nom d'IMAGE_DOS_SIGNATURE. Si la comparaison est ok, nous passons au PE header. S'il n'est pas valide, nous mettons à FALSE la valeur dans ValidPE, signifiant ainsi que le fichier n'est pas PE File valide.
add edi, [edi].e_lfanew
assume edi:ptr IMAGE_NT_HEADERS
.if [edi].Signature==IMAGE_NT_SIGNATURE
mov ValidPE, TRUE
.else
mov ValidPE, FALSE
.endif
Pour arriver jusqu'au PE header, nous avons besoin de la valeur dans e_lfanew du MZ header. Ce paramètre contient l'offset du PE header. Ainsi nous ajoutons cette valeur à edi et nous arrivons au premier octet du PE header. C'est à cet endroit qu'une erreur peut se produire. Si le fichier n'est pas un vrai PE file, la valeur dans e_lfanew sera incorrecte et ainsi en l'utilisant on pointe vers une zone sauvage. Si nous n'employons pas le SEH, nous devons vérifier la valeur de e_lfanew avec la taille de ce sale fichier. Si tout va bien, nous comparons le premier dword du PE header avec la chaîne de caratères "PE". De nouveau il y a une constante bien pratique du nom de IMAGE_NT_SIGNATURE que nous pouvons utiliser. Si le résultat de la comparaison est TRUE, nous assumons que notre fichier est un PE File valide.
Si la valeur dans e_lfanew est incorrecte, une erreur peut se produire et notre SEH Handler reprendra le contrôle. Il rétablit simplement le pointer de pile, et reprend l'exécution à l'offset sauveqardé précédemment à l'endroit du label FinalExit.
FinalExit:
.if ValidPE==TRUE
invoke MessageBox, 0, addr FileValidPE,
addr AppName, MB_OK+MB_ICONINFORMATION
.else
invoke MessageBox, 0, addr FileInValidPE,
addr AppName, MB_OK+MB_ICONINFORMATION
.endif
Le code ci-dessus est d'une simplicité en lui-même. Il vérifie la valeur dans ValidPE et affiche un message à l'utilisateur en conséquence.
push seh.PrevLink
pop fs:[0]
Quand le SEH n'est plus employé, nous le dissocions de la chaîne du SEH.
[Iczelion's Win32 Assembly Homepage]