Tutorial 5: Table des Sections
(Section Table)

Downloadez l'exemple.

Théorie:

Jusqu'à maintenant, nous avons vu le DOS header, et le PE header. Ce qui reste c'est la table des sections. Une table des sections est en réalité un tableau regroupant plusieurs structures, ce tableau se trouve immédiatement à la suite du PE header. Le nombre de membres du tableau est déterminé par le paramètre NumberOfSections du File Header (IMAGE_FILE_HEADER). Cette structure (la Table des Sections) est appelée IMAGE_SECTION_HEADER.

IMAGE_SIZEOF_SHORT_NAME equ 8

IMAGE_SECTION_HEADER STRUCT
   Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
   union Misc
      PhysicalAddress dd ?
      VirtualSize dd ?
   ends
   VirtualAddress dd ?
   SizeOfRawData dd ?
   PointerToRawData dd ?
   PointerToRelocations dd ?
   PointerToLinenumbers dd ?
   NumberOfRelocations dw ?
   NumberOfLinenumbers dw ?
   Characteristics dd ?
IMAGE_SECTION_HEADER ENDS

Une fois encore, tous les membres ne sont pas utiles. Je décrirai seulement ceux qui sont vraiment importants.

Paramètres Signification
Name1 En réalité le nom de ce paramètre est "name" mais le mot "name" est un mot-clé de MASM donc nous devons utiliser "Name1" à la place. Ce membre contient le nom de la section. Notez que la longueur maximale est de 8 octets. Le nom n'est qu'un label, rien plus. Vous pouvez employer n'importe quel nom ou laisser même ce paramètre vide. Notez qu'il n'y a aucun besoin d'un NULL de terminaison. Ce nom n'est pas une chaîne de caractères ASCIIZ donc ne vous attendez pas à ce qu'il se termine par un null.
VirtualAddress est le RVA de la section. Le PE Loader examine et emploie la valeur de ce paramètre lorsqu'il Mappe la section en mémoire. Ainsi si la valeur dans ce paramètre est 1000h et que le PE file à été chargé à partir de l'adresse 400000h, alors la section en question sera chargée à partir de 401000h.
SizeOfRawData représente la taille des données de la section arrondies au multiple suivant de l'alignement de fichiers. Le PE Loader examine la valeur dans ce paramètre, il sait ainsi combien d'octets de la section doivent êtres mappés en mémoire.
PointerToRawData est l'offset(de fichier) où commence la section. Le PE Loader utilise la valeur de ce paramètre pour retrouver où les données de la section sont placées dans le fichier.
Characteristics contient des flags de la même façon que si cette section contenait du code exécutable, des données initialisées, des données non initialisées, ils peuvent être accessible en écriture aussi bien qu'en lecture.

Maintenant que nous connaissons la structure IMAGE_SECTION_HEADER, on va voir comment nous pouvons simuler le travail du PE loader :

  1. On lit la valeur de NumberOfSections dans l'IMAGE_FILE_HEADER, ainsi on sait combien de sections sont présente dans le fichier.
  2. Utilisez la valeur de SizeOfHeaders en tant qu'offset(de fichier) de la table des sections et déplacez le pointer de fichier sur cet offset.
  3. Promenez-vous à l'intérieur du tableau des structures, en examinant chaque membre.
  4. Pour chaque structure, nous obtenons la valeur dans PointerToRawData et nous déplaçons le pointer de fichier sur cet offset. Ensuite nous lisons la valeur dans SizeOfRawData et comme ça on sait combien d'octets on doit mapper en mémoire. On lit la valeur dans VirtualAddress et on y ajoute la valeur de l'ImageBase pour obtenir l'adresse virtuelle à laquelle la section doit commencer. Maintenant nous sommes prêts à mapper la section en mémoire et à donner ses attributs (à cette section) en mémoire d'après les flags contenus dans Characteristics.

Notez qu'on ne s'est pas servit du nom de la section : ce n'est pas vraiment nécessaire.

Exemple:

Cet exemple ouvre un PE file (un fichier Win32 exécutable) et se balade dans sa table des sections, il affiche les informations à propos de ses sections dans un 'listview control' (un objet prédéterminé par Windows (ici une simple page de listes de noms, de tailles, caractéristiques...)).

.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
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

IDD_SECTIONTABLE equ 104
IDC_SECTIONLIST equ 1001

SEH struct
PrevLink dd ? ; adresse de la structure SEH ci-dessus
CurrentHandler dd ? ; adresse du nouvel '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.5",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
FileInValidPE db "This file is not a valid PE",0
template db "%08lx",0
SectionName db "Section",0
VirtualSize db "V.Size",0
VirtualAddress db "V.Address",0
SizeOfRawData db "Raw Size",0
RawOffset db "Raw Offset",0
Characteristics db "Characteristics",0

.data?
hInstance dd ?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?
NumberOfSections dd ?

.code
start proc
LOCAL seh:SEH
   invoke GetModuleHandle,NULL
   mov hInstance,eax
   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:
               push seh.PrevLink
               pop fs:[0]
               .if ValidPE==TRUE
                  call ShowSectionInfo
               .else
                  invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION
               .endif
               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
  invoke InitCommonControls
start endp

SEHHandler proc C uses 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

DlgProc proc uses edi esi hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
   LOCAL lvc:LV_COLUMN
   LOCAL lvi:LV_ITEM
   .if uMsg==WM_INITDIALOG
      mov esi, lParam
      mov lvc.imask,LVCF_FMT or LVCF_TEXT or LVCF_WIDTH or LVCF_SUBITEM
      mov lvc.fmt,LVCFMT_LEFT
      mov lvc.lx,80
      mov lvc.iSubItem,0
      mov lvc.pszText,offset SectionName
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,0,addr lvc inc lvc.iSubItem
      mov lvc.fmt,LVCFMT_RIGHT
      mov lvc.pszText,offset VirtualSize
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,1,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset VirtualAddress
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,2,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset SizeOfRawData
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,3,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset RawOffset
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,4,addr lvc
      inc lvc.iSubItem
      mov lvc.pszText,offset Characteristics
      invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,5,addr lvc
      mov ax, NumberOfSections
      movzx eax,ax
      mov edi,eax      
      mov lvi.imask,LVIF_TEXT
      mov lvi.iItem,0
      assume esi:ptr IMAGE_SECTION_HEADER
      .while edi>0
         mov lvi.iSubItem,0
         invoke RtlZeroMemory,addr buffer,9
         invoke lstrcpyn,addr buffer,addr [esi].Name1,8
         lea eax,buffer
         mov lvi.pszText,eax
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].Misc.VirtualSize
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].VirtualAddress
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].SizeOfRawData
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].PointerToRawData
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         invoke wsprintf,addr buffer,addr template,[esi].Characteristics
         lea eax,buffer
         mov lvi.pszText,eax
         inc lvi.iSubItem
         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi
         inc lvi.iItem
         dec edi
         add esi, sizeof IMAGE_SECTION_HEADER
      .endw
   .elseif
      uMsg==WM_CLOSE
         invoke EndDialog,hDlg,NULL
   .else
      mov eax,FALSE
      ret
   .endif
   mov eax,TRUE
   ret
DlgProc endp

ShowSectionInfo proc uses edi
   mov edi, pMapping
   assume edi:ptr IMAGE_DOS_HEADER
   add edi, [edi].e_lfanew
   assume edi:ptr IMAGE_NT_HEADERS
   mov ax,[edi].FileHeader.NumberOfSections
   movzx eax,ax
   mov NumberOfSections,eax
   add edi,sizeof IMAGE_NT_HEADERS
   invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi
   ret
ShowSectionInfo endp
end start

Analyse:

Cet exemple réutilise le code de l'exemple du Tutorial 2. Après qu'il vérifie que le fichier soit bien un PE valide, il appelle une fonction, ShowSectionInfo.

ShowSectionInfo proc uses edi
   mov edi, pMapping
   assume edi:ptr IMAGE_DOS_HEADER
   add edi, [edi].e_lfanew

   assume edi:ptr IMAGE_NT_HEADERS

Nous nous servons d'edi en tant que pointer de données dans le PE file. D'abord, nous l'initialisons à la valeur 'pMapping' lequel est l'adresse du DOS header. Ensuite nous lui ajoutons la valeur d'e_lfanew, de cette façon il contient maintenant l'adresse du PE header.

   mov ax,[edi].FileHeader.NumberOfSections
   mov NumberOfSections,ax

Puisque nous avons besoin de la table des sections, il nous faut obtenir le nombre de sections dans le fichier. C'est la valeur du membre NumberOfSections du File header. N'oubliez pas que ce membre est d'une taille d'un WORD.

   add edi,sizeof IMAGE_NT_HEADERS

edi contient actuellement l'adresse du PE header. L'addition de la taille du PE header à l'adresse de début du PE Header, fera qu'on pointera sur la table des sections.

   invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi

On appelle DialogBoxParam pour afficher la boîte de dialogue contenant le 'listview control'. Remarquez que nous passons l'adresse de la table des sections en tant que son dernier paramètre. Cette valeur sera disponible dans lParam pendant le message WM_INITDIALOG.

Dans la procédure servant à traiter les messages de la DialogBox, en réponse au message WM_INITDIALOG, nous stockons la valeur d'lParam (l'adresse de la table des sections) dans esi, le nombre de sections dans edi et ensuite on crée le 'listview control'. Quand tout est prêt, nous entrons dans une boucle qui insérera les renseignements de chaque section dans le 'listview control'. Cette partie est très simple.

      .while edi>0
         mov lvi.iSubItem,0

Met cette chaîne de caractères dans la première colonne.

         invoke RtlZeroMemory,addr buffer,9
         invoke lstrcpyn,addr buffer,addr [esi].Name1,8
         lea eax,buffer
         mov lvi.pszText,eax

Nous affichons le nom de la section, mais nous devons d'abord la convertir en une chaîne de caractères ASCIIZ.

         invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi

Ainsi nous l'affichons dans la première colonne.
Nous continuons avec ce schéma tant que la dernière valeur que nous souhaitons montrer concernant cette section soit affichée. Ensuite nous devons nous déplacer jusqu'à la structure suivante.

         dec edi
         add esi, sizeof IMAGE_SECTION_HEADER
      .endw

On fait décroître la valeur dans edi à chaque fois qu'une section à été traitée. Et nous ajoutons la taille d'IMAGE_SECTION_HEADER à esi, ainsi il contient l'adresse de la structure IMAGE_SECTION_HEADER suivante.

Les étapes de notre petite balade dans la table des sections sont les suivantes:

  1. On vérifie que le fichier est un PE valide
  2. On se place au début du PE header
  3. On récupère le nombre de sections grâce au paramètre NumberOfSections dans le File header.
  4. On arrive à la table des sections soit en ajoutant ImageBase et SizeOfHeaders soit en ajoutant l'adresse du PE header et la taille du PE header. (La table des sections suit immédiatement le PE header). Si vous n'utilisez pas le procédé de 'File Mapping', vous devez déplacer le pointer de fichier sur la table des sections en employant SetFilePointer. L'offset(de fichier) de la table des sections est dans SizeOfHeaders.(SizeOfHeaders est un membre d'IMAGE_OPTIONAL_HEADER)
  5. On épie chaque structure IMAGE_SECTION_HEADER.

[Iczelion's Win32 Assembly Homepage]


Traduit par Morgatte