• STATISTIQUES
  • Il y a eu un total de 1 membres et 26840 visiteurs sur le site dans les dernières 24h pour un total de 26 841 personnes!


    Membres: 2 604
    Discussions: 3 579
    Messages: 32 816
    Tutoriels: 78
    Téléchargements: 38
    Sites dans l'annuaire: 58


  • ANNUAIRE
  • [FR] PHP France
    Pour tout savoir sur le PHP, en français. Vous trouverez des tutoriels, des exemples, des astuces, toute la do...
    Hacking
    [FR] Kalkulators
    Ce projet a plusieurs buts, le premier étant l’étude de toutes formes cryptographiques, le cot&ea...
    Cryptographie
    [FR] Comment ca marche
     Gratuit et accessible à tous, ce site de communauté permet de se dépanner, se faire aider ...
    Webmaster
    [FR] Le top web
    Nous offrons une sélection la plus large possible de resources webmaster gratuites, hébergement gratuit...
    Webmaster
    [FR] Root-Me
    Notre équipe se base sur un constat : à l'heure actuelle ou l'information tend à devenir...
    Hacking
    [EN] Rosecode
    Programming: 36, Math: 29, Probability: 5, Sequence: 7, Crypto: 4, Brainf**k: 13, TimeRace: 4, Hack: 9
    Challenges
    [EN] w3challs
    Ce site propose différents types de défis informatiques: piratage, craquage, cryptographie, stég...
    Hacking

  • DONATION
  • Si vous avez trouvé ce site internet utile, nous vous invitons à nous faire un don du montant de votre choix via Paypal. Ce don servira à financer notre hébergement.

    MERCI!




La pile en assembleur x86

Ce tutoriel est susceptible de contenir quelques erreurs, so n'hésitez pas à me les remonter que je les corrige Wink

Ce tutoriel a pour but de présenter quelques aspects fréquemment utilisés de la pile sous un processeur x86 (je ne parlerai que du mode protégé 32bits ici). Je présenterai d'abord quelques généralités que tout le monde connaît (ou presque), avant d'étudier un peu plus en profondeur l'utilisation de la pile lors des appels de fonctions. Ensuite, pour les courageux (et suite aux réclamations/protestations sur IRC), j'ai écrit un petit pavé vite fait sur les conventions d'appel.

I- Introduction / Généralités

La pile est une zone de la mémoire, qui est beaucoup utilisée en assembleur x86. Comprendre son fonctionnement est ainsi primordial pour développer en assembleur, ou encore pour le reverse-engineering. Ce tutoriel ne parlera que du mode protégé en 32bit, utilisé par la plupart des systèmes d'exploitation (le mode réel est très peu utilisé, et je n'ai pas encore étudié l'assembleur x86_64).

La pile est délimitée par deux registres: EBP (pour "Base Pointer"), et ESP (pour "Stack Pointer"), qui contiennent les adresses mémoire de la pile. EBP permettra en général d'accéder à des données stockées sur la pile (les paramètres passés à une fonction par exemple, ou encore les variables locales; ce qu'on étudiera plus en détail dans la suite de ce tuto).

Pour placer des éléments, on peut utiliser l'instruction "push". Cette instruction placera la donnée à l'adresse pointée par esp, avant de décrémenter esp de 4 octets (la plupart des OS modernes ont une pile qui grandit des adresses hautes, ie "grandes", vers les adresses basses, ie "petites"). Ceci est valable quelque soit la taille des données pushées sur la pile (par exemple un "push 42h" stockera en réalité 00000042h sur la pile). L'instruction "pop" elle, place la donnée pointée par esp dans le registre spécifié (ou à l'adresse spécifiée), avant d'incrémenter de 4 octets ESP.

II- La pile et les paramètres de fonctions

Comme je l'ai dit précédemment, on peut passer des paramètres aux fonctions par la pile. Cependant, on ne peut pas accéder directement aux arguments directement avec un "mov eax,[esp]". En effet, le processeur "push" sur la pile l'adresse de retour de la fonction, c'est-à-dire
l'adresse des instructions à exécuter lorsqu'on sera sorti de la fonction (à noter que l'exploitation de buffer overflows consiste juste à écraser cette adresse de retour pour rediriger l'exécution vers un code injecté par l'attaquant). Ainsi, il faut utiliser "mov eax,[esp+4]" pour accéder au premier argument de la fonction (rappelez-vous, on décrémente la pile chaque fois qu'on push quelque chose).

Cependant, si on a besoin de push/pop des éléments à l'intérieur de notre fonction, il nous faudra à chaque fois recalculer les offsets pour accéder aux arguments, ce qui n'est guère pratique. La solution pour pallier ce problème est d'envelopper notre fonction de la sorte:

Code ASM :
mafonction:
  push ebp
  mov ebp, esp

  ... corps de la fonction ...

  leave
  ret


Ici on place la sauvegarde de ebp sur la pile, puis on place la valeur de esp dans ebp. On pourra ainsi accéder à notre premier argument via [ebp+8] (et oui, on a encore push un truc sur la pile :') ). L'instruction "leave" permet de restaurer le registre esp à la valeur du registre ebp, puis de restaurer ebp (ce qui revient à un "mov esp, ebp - pop ebp". Bref, notre pile ressemble à ça après le prologue de fonction:
Code :
|        pile        |
+--------------------+ <--- ebp = esp
| sauvegarde de EBP  |
+--------------------+ <--- ebp+4
| addresse de retour |
+--------------------+ <--- ebp+8
|     argument 1     |
+--------------------+ <--- ebp+0ch
|         ...        |
+--------------------+ <--- ebp+4+4*n
|     argument n     |
+--------------------+
Ainsi, durant l'exécution de la fonction, on pourra accéder aux arguments relativement à ebp, qui n'est pas censé bouger pendant l'exécution de la fonction (contrairement à esp). On passera ainsi les arguments à notre fonction comme ceci:

Code :
push argumentn
....
push argument1
call mafonction
Pour ceux qui aiment les termes techniques, on a mis en place une "stack frame" pour notre fonction.

III- Variables locales aux fonctions

Notre fonction a désormais un "stack frame" en place, ce qui va nous permettre de définir assez facilement des variables locales (visibles uniquement pour la fonction). Il nous suffit de soustraire à esp la taille dont on a besoin pour stocker les arguments, avec ce code:

Code ASM :
 mafonction:
  push ebp
  mov ebp, esp
  sub esp, 0x40 ; on réserve 64 octets pour les variables locales
  ... corps de la fonction ...
  leave
  ret

 

Notre pile aura alors cette allure:

Code :
|        pile        |
+--------------------+ <--- esp
|                    |
| variables locales  |
|        ...         |
|                    |
+--------------------+ <--- ebp
| sauvegarde de EBP  |
+--------------------+ <--- ebp+4
| addresse de retour |
+--------------------+ <--- ebp+8
|     argument 1     |
+--------------------+ <--- ebp+0ch
|         ...        |
+--------------------+ <--- ebp+4+4*n
|     argument n     |
+--------------------+
Et on accèdera aux variables par [ebp-x], et les données que l'on aura besoin de push seront au-dessus des variables locales.
Lorsqu'on sortira de la fonction, les variables resteront présentes sur le "cadavre" de la pile, mais pas directement accessibles, et attendant d'être réécrites (quelle triste fin :') ).
IV- Bonus track: les conventions d'appel

Cette partie (bonus \o/) se concentre sur les différentes manières dont une fonction peut être appelée. En effet, une fonction peut "choisir" de prendre ses paramètres via les registres ou la pile, ou encore de nettoyer la pile, c'est-à-dire d'enlever tous les arguments passés à la fonction lors de la pile. Je vais donc énumérer les conventions les plus courantes:
  • stdcall: c'est la convention d'appel par défaut des APIs win32 (exceptés celles du runtime C, msvcrt.dll): les arguments sont passés sur la pile, et la fonction nettoie la pile avant de revenir à la routine appelante.
  • cdecl: c'est la convention par défaut utilisée par la stdlib: les arguments sont eux aussi passés par la pile, mais c'est à la fonction appelante de nettoyer la pile après l'appel de la fonction (en incrémentant esp du nombre d'octets pushed sur la pile). Cette convention est utilisée dans la stdlib C
  • fastcall/pascal: ces conventions récupèrent les arguments d'abord via les registres, puis récupèrent les arguments supplémentaires (si besoin) sur la pile. La pile est nettoyée par la fonction appelée. On retrouve cette convention d'appel dans les programmes écrits en Pascal (ie Delphi, etc), ou encore lors de l'appel d'un syscall Linux.
Pour ceux qui voudraient plus de détails, je vous renvoie rtfm wikipedia (http://en.wikipedia.org/wiki/X86_calling_conventions ) pour plus d'informations.

J'espère que ce tuto vous aura aidé à mieux comprendre les spécificités de la pile sous l'asm x86 Wink
23-11-2012, 18h32
Message : #1
spin Hors ligne
Contributeur
*****



Messages : 325
Sujets : 15
Points: 38
Inscription : Nov 2011
RE: La pile en assembleur x86
Salut, je trouve le tuto assez clair mais j'ai quelques remarques.

Dans la section I : que veut dire « la pile est délimitée par ESP et EBP » ? Que doivent contenir au juste ces deux registres ? Et on peut se demander aussi pourquoi on alloue 4 octets ; c'est systématique ou ça dépend de la taille de ce qu'on veut passer en argument, ou encore autre chose ? Les lecteurs pourraient se le demander.

Dans la section II, dans le code d'exemple, il faut dire à quoi correspond l'instruction 'leave' au cas où on ne la connaîtrait pas, je pense.

Dans la section III, tu nous dis comment faire des variables locales dans une fonction. Maisle lecteur peut se demander ce qu'il advient de cette zone pour les variables locales quand on quitte la fonction.

Et pour les conventions d'appel avec le C, rien de mieux que de présenter directement l'output des compilos :þ

Bon tutoriel sinon Wink
23-11-2012, 18h47
Message : #2
supersnail Hors ligne
Éleveur d'ornithorynques
*******



Messages : 1,608
Sujets : 71
Points: 466
Inscription : Jan 2012
RE: La pile en assembleur x86
J'ai modifié le tutoriel en tenant compte des remarques Wink (sauf la partie IV, je laisse le lecteur intéressé se renseigner par lui-même, cette partie servant étant un peu "à côté" du sujet Wink )
Mon blog

Code :
push esp ; dec eax ; inc ebp ; and [edi+0x41],al ; dec ebp ; inc ebp

"VIM est merveilleux" © supersnail
28-11-2012, 20h57
Message : #3
phopho Hors ligne
Newbie
*



Messages : 10
Sujets : 1
Points: 0
Inscription : Nov 2012
RE: La pile en assembleur x86
Tuto très bien expliqué pour moi.
Merci supersnail
30-11-2012, 13h44
Message : #4
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Sujets : 48
Points: 317
Inscription : Sep 2011
RE: La pile en assembleur x86
Pour ceux que ça intéresse, je me suis pencher sur l’état de la stack au moment de l'appel de la fonction main() telle que "préparée" par gcc, par contre c'est en 64bits. (je sais pas si c'est différent en 32...)

Code :
Highest address

+-----------------------------+
| Unknown stuff...            | // Also contain the Argv's strings
|                             |
+-----------------------------+
| End of elf auxiliary        |
|                vectors      |
+-----------------------------+
| Start of elf auxiliary      |
|                vectors      |
+-----------------------------+
| env pointer                 | <-- env + i // while(*(env + i)) {++i;}
|        and every env entry  |
+-----------------------------+
| Argv last pointer           | <-- argv + argc
|           (argv[argc])      |
+-----------------------------+
| Argv first pointer          | <-- argv
|           (argv[0])         |
+-----------------------------+
| argc value                  | <-- &argc
|                             |
+-----------------------------+
| return address              |
|                             |
+-----------------------------+
| rbp                         | <-- rsp && rbp (after call)
|                             |
+-----------------------------+
|                             |
|                             |
v                             v

Lower address

J'en suis pas sur a 100%, alors dites moi si je me suis trompé quelque part :)
30-11-2012, 15h16
Message : #5
supersnail Hors ligne
Éleveur d'ornithorynques
*******



Messages : 1,608
Sujets : 71
Points: 466
Inscription : Jan 2012
RE: La pile en assembleur x86
Comme je l'ai dit, en 64bits c'est sûrement différent, j'ai aucune compétence là-dedans...

Bref on peut rencontrer ce genre de trucs en 64bits, mais pas en 32 imo
Mon blog

Code :
push esp ; dec eax ; inc ebp ; and [edi+0x41],al ; dec ebp ; inc ebp

"VIM est merveilleux" © supersnail
30-11-2012, 21h27
Message : #6
gruik Hors ligne
gouteur de savon
*



Messages : 757
Sujets : 44
Points: 482
Inscription : Oct 2012
RE: La pile en assembleur x86
c'est la meme chose en 32 et en 64 pour le coup, juste le nom des registres qui change

New Project News White Hat Hacker V2.3
Accueil | Challenges | Tutoriels | Téléchargements | Forum