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,609 Sujets : 71 Points: 465 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 |
|
Messages dans ce sujet |
La pile en assembleur x86 - par supersnail - 22-11-2012, 20h04
RE: La pile en assembleur x86 - par spin - 23-11-2012, 18h32
RE: La pile en assembleur x86 - par supersnail - 23-11-2012, 18h47
RE: La pile en assembleur x86 - par phopho - 28-11-2012, 20h57
RE: La pile en assembleur x86 - par Ark - 30-11-2012, 13h44
RE: La pile en assembleur x86 - par supersnail - 30-11-2012, 15h16
RE: La pile en assembleur x86 - par gruik - 30-11-2012, 21h27
|
Sujets apparemment similaires… | |||||
Sujet | Auteur | Réponses | Affichages | Dernier message | |
Le b.a.-ba de l'assembleur x86 — Partie I | spin | 0 | 137 |
02-11-2012, 17h29 Dernier message: spin |
Utilisateur(s) parcourant ce sujet : 3 visiteur(s)