La pile en assembleur x86
|
22-11-2012, 20h04
(Modification du message : 08-12-2012, 17h30 par ark.)
Message : #1
|
|
supersnail
Éleveur d'ornithorynques Messages : 1,613 Sujets : 72 Points: 466 Inscription : Jan 2012 |
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
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: 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 | Code : push argumentn 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: Notre pile aura alors cette allure: Code : | pile | 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:
J'espère que ce tuto vous aura aidé à mieux comprendre les spécificités de la pile sous l'asm x86
Mon blog
Code : push esp ; dec eax ; inc ebp ; and [edi+0x41],al ; dec ebp ; inc ebp "VIM est merveilleux" © supersnail |
|
23-11-2012, 18h32
Message : #2
|
|
spin
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 |
|
23-11-2012, 18h47
Message : #3
|
|
supersnail
Éleveur d'ornithorynques Messages : 1,613 Sujets : 72 Points: 466 Inscription : Jan 2012 |
RE: La pile en assembleur x86
J'ai modifié le tutoriel en tenant compte des remarques (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 )
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 : #4
|
|
phopho
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 : #5
|
|
ark
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 J'en suis pas sur a 100%, alors dites moi si je me suis trompé quelque part :) |
|
30-11-2012, 15h16
Message : #6
|
|
supersnail
Éleveur d'ornithorynques Messages : 1,613 Sujets : 72 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 : #7
|
|
gruik
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
|
|
« Sujet précédent | Sujet suivant »
|
Sujets apparemment similaires… | |||||
Sujet | Auteur | Réponses | Affichages | Dernier message | |
Le b.a.-ba de l'assembleur x86 — Partie I | spin | 0 | 761 |
02-11-2012, 17h29 Dernier message: spin |
Utilisateur(s) parcourant ce sujet : 3 visiteur(s)