Skip to content

Instantly share code, notes, and snippets.

@Geal
Created April 27, 2018 10:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Geal/1fa693b9189eefb456621658e5f0d2d8 to your computer and use it in GitHub Desktop.
Save Geal/1fa693b9189eefb456621658e5f0d2d8 to your computer and use it in GitHub Desktop.

fonctionnement de la stack

Les appels entre fonction sont supportés au niveau du processeur par l'utilisation de la pile (ou "stack"), une structure de donnée supportant les instructions "push" et "pop". C'est une structure "LIFO" (last in, first out): le dernier élément placé sur la pile (push) est le premier élément qu'on peut en retirer (pop).

Lorsque le processeur exécute du code, l'un de ses registres (EIP sur les processeurs X86 et X86_64) contient toujours l'adresse de l'instruction courante. Lorsque le code exécuté appelle une fonction, c'est généralement équivalent à appeler une instruction JMP pour "jump" vers une nouvelle adresse: le registre d'instruction EIP est alors mis à jour pour contenir l'adresse de la première instruction de la fonction, et le processeur peut continuer l'exécution de là.

A la fin de la fonction, il faut retourner à la fonction appelante, et du point de vue du processeur, il faut indiquer dans EIP l'adresse de l'instruction suivant celle qui appelait la fonction. Nous avons donc besoin de sauvegarder cette adresse quelque part avant d'appeler la fonction, et nous avons probablement besoin de sauvegarder plusieurs adresses, car chaque fonction peut en appeler une autre ensuite, etc. C'est pour cela que nous utilisons une stack. Juste avant d'appeler la fonction, nous allons sauvegarder l'adresse de retour sur la pile, comme ceci:

push EIP
jmp <adresse de la fonction>

la plupart des processeurs modernes effectuent ces deux opérations en une instruction, "CALL". Après ce "PUSH", le haut de la pile contient l'adresse de retour, et il suffit d'effectuer l'instruction "POP EIP" pour la sortir de la pile et recommencer l'exécution.

Pour aller plus loin, la pile peut aussi servir à stocker les arguments de la fonction, ainsi qu'à stocker les variables locales. Pour cette utilisation, nous avons deux autres registres, EBP et ESP. ESP contient l'adresse du haut de la pile, et est mis à jour à chaque instruction "PUSH" ou "POP". EBP contient l'adresse de la partie de la pile concernant la fonction appelante.

par exemple, avant d'appeler une fonction, vous pourrez voir ce genre d'opération:

push   ebp
mov    ebp,esp
sub    esp,0x1c
mov    DWORD PTR [esp+0x8],0x2a
mov    DWORD PTR [esp+0x4],0x8
mov    DWORD PTR [esp],0x4
call   0x8048394

Cette suite d'instruction effectue les opérations suivantes:

  • mettre la valeur de EBP sur la pile
  • mettre la valeur de ESP dans EBP (EBP indique maintenant le haut de la pile pour la fonction courante)
  • soustraire 0x1C à la valeur de ESP pour réserver de la place sur la pile (la pile part d'adresses haute, et grandit vers les adresses basses)
  • on ajoute des arguments sur la pile
  • puis on appelle la fonction

si on avait avant ce code, EBP contenant 0x00000100 et ESP contenant 0x00000090, on aurait à la fin comme contenu de la pile:

0x00000090: 0x00000100 0x2a 0x8 0x4 <adresse de l'instruction après call> 0x00000074:

EBP vaudrait 0x00000090 et ESP vaudrait 0x00000074

La fonction appelée pourra ensuite accéder à ces valeurs par leur adresse relative à la valeur dans ESP. La fonction appelée peut elle aussi réserver de l'espace sur la pile pour ses variables locales. L'ensemble des valeurs stockées dans la pile, entre les adresses indiquées par EBP et ESP, correspondent à la "stack frame", le contexte local de cette fonction.

Au retour, la fonction mettra à jour ESP pour retirer la mémoire allouée à ses variables locales et à ses arguments, puis effctuera l'opération "POP EBP" pour récupérer l'ancienne valeur de EBP qui était stockée sur la pile.

La pile contient donc toutes les informations sur la chaine de fonctions appelées, leurs arguments et leurs variables locales.

les "stack buffer overflows"

C'est un type de faille exploitant le fonctionnement de la pile. Si la fonction déclare un buffer comme variable locale, on pourrait avoir comme contenu de la pile:

0x00000090: 0x00000100 0x2a 0x8 0x4 <adresse de l'instruction après call> 0x00000074: fin du buffer de 16 octets | | | | 0x00000064 début du buffer de 16 octets

(comme la pile grandit vers les adresses basses, le buffer "grandit dans l'autre sens")

Si le code contient une erreur permettant l'écriture après la fin du buffer (par exemple, par une mauvaise manipulation de pointeurs), il est possible d'écrire dans la pile à la place de l'adresse de retour. Et lorque la fonction se terminera, le processeur emploiera cette adresse pour continuer l'exécution.

On peut ainsi modifier cette adresse pour appeler d'autres fonctions du programme. Mais on peut aller plus loin. Vu qu'on contrôle le contenu du buffer, on peut le remplir d'instructions valides, et indiquer à la place de l'adresse de retour, une adresse au milieu de notre buffer. On a ainsi inséré notre propre programme au milieu du programme existant. On appelle ces bouts de code passés par le buffer "shellcodes".

les heap buffer overflows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment