Compare commits

...

3 commits

2 changed files with 668 additions and 0 deletions

View file

@ -0,0 +1,339 @@
---
title: "Sécurité système : introduction"
date: 2023-09-07
tags: ["bibliothèque", ".got"]
categories: ["Sécurité système", "Cours"]
---
## Définition : un exécutable
C'est un fichier contenant un programme et identifié par le système
d'exploitation en tant que tel ([définition par Wikipédia][l_execwiki]). Il
existe plusieurs format d'exécutable en fonction du système d'exploitation.
Il ne faut pas confondre programme et scripts, le premier contenant le code
**exécutable**, le second est **interprété** (par un programme) et passe
éventuellement par du code intermédiaire ou [bytecode][l_bytecode] : *Portable
Executable* Pour Microsoft Windows, *Match Object* pour Apple MacOS / iOS,
*Executable and Linkable Format* pour beaucoup de système Unix.
Pourquoi compiler du code? Plusieurs avantages :
* un programme compilé est moins consommateur de ressources et par conséquent
plus rapide à l'exécution;
* pour protéger sa propriété intellectuelle (par la possibilité d'obfuscation
du code machine généré);
* pour se passer de l'**interpréteur** et ainsi éviter une étape
(complexification de la pile d'exécution).
Le code interprété a aussi ses avantages :
* il est indépendant de la plate-forme d'exécution;
* en général il existe une multitude de bibliothèques utilisable rapidement
(coucou Python);
* le développement est simplifié et plus interactif, le langage est alors plus
accessible et le développement peut-être plus rapide (langage de plus haut
niveau)
[l_bytecode]: https://fr.wikipedia.org/wiki/Bytecode
[l_execwiki]: https://fr.wikipedia.org/wiki/Fichier_ex%C3%A9cutable
## La compilation
Un exécutable contient donc du code machine, le code source qui le défini est
alors **compilé** en code machine. Mais la compilation est en fait plus
compliquée que cette simple *"traduction"*. Elle se compose en fait de 4 grande
phases :
1. le préprocessing;
2. la compilation;
3. l'assemblage;
4. l'édition de liens
### le préprocessing
Chargé de modifier le code source avant la compilation. Ici le moteur de
préprocessing se charge de remplacer les macro (`#define`), les constantes
[^f_const], les inclusion d'entêtes (`#include`) par le code effectif;
[^f_const]: Voici quelques exemples de constantes de bases :
- `__FILE__` : affiche le nom du fichier source;
- `__LINE__` : affiche le numéro de la ligne atteinte;
- `__DATE__` : affiche la date de compilation du code source;
- `__TIME__` : affiche l'heure de compilation de la source.
### la compilation
C'est à cette étape que le code (`C`, `C++`, `Rust` etc.) est transforme en
assembleur. Cette étape se compose elle-même de plusieurs parties.
#### L'analyse lexicale
Lors de cette étape, le compilateur analyse le texte du code source. Il est
découpé en *mots* ou *tokens* définis par des **expressions rationnelles** et
**des automates à états finis**.
Il revient à **l'analyseur lexical** de réaliser cette opération. Lors de
cette étape, les *"bruits"* sont ignorés : commentaires et espaces. Les
*lexèmes* -- représentant une instance de la petite unité de signification --
sont ainsi définis. Si l'analyseur syntaxique rencontre un lexème inconnu, il
lèvera une erreur.
Il existe plusieurs analyseur lexicaux : flex, lex[^n_lex] etc.
Il ne revient pas à l'analyseur syntaxique de vérifier que l'enchainement des
lexème est correct, il transmets juste le résultat de son analyse à l'étape
suivante.
[^n_lex]: analyseur en *C* co-écrit par Eric Schmidt, cofondateur de Google.
#### L'analyse syntaxique
Réalisée par **l'analyseur syntaxique** qui traite les informations reçues par
l'analyseur lexical. Contrairement à l'étape précédente, il est question ici de
se focaliser sur la validités syntaxique *d'une phrase*.
C'est un processus itératif dont le résultat est un *Arbre Syntaxique Abstrait*
construit en fonction de la **grammaire du langage**.Il est construit au fur et
à mesure que l'analyseur avance dans le code source. Deux outils d'analyse
syntaxique sont principalement utilisés :
#### L'analyse sémantique
Lors de cette étape le compilateur vérifie la cohérence du code, certaines
erreurs relevant de la grammaire seront détectées comme par par exemple :
```c
// erreur sur les types
int my_integer = "a string";
// Accès à une variable en dehors de son scope
{ int x = 3;} x = 4;
```
Lors de l'analyse syntaxique, l'*Arbre Syntaxique Abstrait* est enrichi
d'information sur le sens des phrases du code.
#### La production de code intermédiaire
Cette étape, le code est transformé dans un langage intermédiaire entre le
langage de haut niveau (*C*, *C++*) et le langage machine. C'est à partir de ce
code que le compilateur appliquera des optimisations. C'est aussi lors de cette
étape que le code peut être offusqué afin de rendre son analyse plus complexe
(dans le logiciel privateur, ça va de soit).
#### Les optimisations
Ici, le compilateur réalise des optimisations sur le code intermédiaire. La
plupart de ces optimisations effectué sur le code de plus haut niveau le
rendraient moins compréhensible et pourrait se révéler plus fastidieux à écrire.
#### La génération de code assembleur
Ici le code intermédiaire optimisé est transformé en assembleur. Le compilateur
créé les fichiers `*.S`. On appelle aussi cette étape **génération de code
natif**. Lors de cette étape, le code généré est spécifique à une architecture
donnée et ce afin de profiter des instructions disponible sur celle-ci (*MMX*,
*SSE*, *NEON* etc.).
#### L'assemblage
Le compilateur créé un fichier objet `*.o` par fichier source. Ces fichiers
objets sont bien des binaires, il contienne déjà les segments nécessaires
(`.text`, `.data` ...) dans un format dépendant du système cible (PE pour
Microsoft Windows©, Mach-O pour Apple MacOS©, ELF pour Linux).
Lors de cette étape, le compilateur enlève tous les *labels*, mais afin de
pouvoir résoudre les symboles (fonctions, variables, etc.) il va créer la
**table des symboles**.
Cette table contenu dans chacun des fichiers objets générés fait référence à la
position des élément dans ce fichier. Le compilateur résout ces symboles **lors
de l'édition de liens**.
#### L'édition de liens
C'est le moment ou le compilateur agrège les différents fichiers objets en un
seul exécutable. Le *linker* responsable de cette étape doit résoudre les
symboles et les liers aux différents fichiers objets et bibliothèques.
C'est aussi lors de cette étape que les différents segments composants le
programme sont créés et que la **table de relocation** est créée (très utile
pour les mécanismes d'ALSR).
Il existe deux type d'édition de liens :
* **statique** : les éléments issus de bibliothèques sont inclus dans le
binaire final. Dans ce cas la résolution des symboles **est totale**;
* **dynamique** : les bibliothèques partagées ne sont pas incluses dans le
binaire. La résolution des symboles ne peut-être que partielle.
Dans le cas de la compilation **dynamique**, le *linker* doit identifier les
symboles appartenant aux bibliothèques partagées. Leur résolution se fera alors
au moment de l'exécution.
## Exécution
L'exécution d'un programme se passe en plusieurs étapes :
1. le noyau est averti que qu'il doit charger le fichier binaire;
2. l'élément chargé du chargement de notre binaire se charge de l'analyser;
3. le noyau alloue de la mémoire pour notre programme;
4. il *map* la mémoire allouée avec les éléments du programme;
5. il est maintenant temps de passer le flot d'exécutions au programme.
Bien évidement le binaire n'est pas chargé tel quel en mémoire, d'où l'étape
*d'analyse*. Sous Linux et les système Unix en général, le format d'exécutable
utilisé est ELF. ce format, largement documenté, sert de base pour le *loader*
### Segments et sections
C'est deux éléments qui composent un fichier binaire au format ELF sont souvent
source de confusion.
*Les segments* contiennent des éléments nécessaires à l'exécution alors que *les
sections* contiennent des informations utiles pour les liens, la relocation et la
résolution de symboles.
Les *sections* sont des composantes des *segments*. Prenons l'exemple du segment
`LOAD` qui contient des sections `.data`, `.got`, `.bss` ... Elles contiennent
soit des données brutes:
* `.text` : du code;
* `.data` : des données initialisées comme par exemple:
```c
int x = 42;
```
* `.bss` : des données non initialisées
* `.rodata` : des données en lecture seule comme les constantes statiques
## Le format ELF
Comme nous le disions, c'est le format binaire d'enregistrement de code compilé
utilisé par la plupart des systèmes de type *Unix*. Il commence par les nombres
magiques[^n_magic] `0x7fELF`. Il se compose ensuite d'un entête fixe. Celui ci
contient (entre autres) l'*endianess* (big ou little), l'ABI, le type
(bibliothèque, exécutable), l'architecture cible.
Après viens le *Program Header Table* qui contient des segments et informations
nécessaire à la création du processus en mémoire.
Ensuite la *Section Header Table* référençant et décrivant les sections
Enfin le fichier ELF contient les données référencées par ces deux tables.
[^n_magic]: dans notre cas, ensemble de caractères utilisés pour désigner un
format de fichier.
## ELF : Pratique
### Propriété de `ls`
Le plus simple pour obtenir des information est d'utiliser le programme `file`.
```shell
$ file /bin/ls
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked,
interpreter /usr/lib/ld-linux-x86-64.so.2,
BuildID[sha1]=4d53da289d128a5ccee3f944db35244bf91c7a99,
for GNU/Linux 3.10.0, not stripped
```
Nous apprenons donc que cet exécutable est un ELF 64bits pour uns architecture
*Intel x86_64*, lié dynamiquement, chargé par la bibliothèque `ld-linux-x86-64`.
Ici le binaire est *not stripped* : les différents *labels* ne sont pas
supprimés (ici pour NixOS).
Il est aussi possible de récolter plus d'informations avec `readelf` notamment
la version de la *libc* utilisée. Ce genre d'information peut permettre de mener
une attaque.
### les sections `.plt` et `.got`
Ces différentes sections permette la résolution de symboles. `.plt` signifie
*Procedure Linkag Table* et `.got` *Global Offset Table*. On parle alors de
relocation.
Pour la section `.plt`, toutes ses entrées sont initialisées pour pointr vers
l'editeur du lien et non l'adresse de la bonne fonction. C'est lors de premier
appel de la fonction en question que sont adresse sera résolue - elle st donc
effectuée au *runtime* - et mise à jour. On parle alors de *lazy symbols
binging*
La section `.got` fait plus office d'annuaire référençant juste les adresss de
fonctions mais aussi de variables globales des bibliothèques).
#### `.got` et `plt.got
Ces deux sections sont utilisés pour les symboles ayant besoin d'être résolus
**au moment du chargement du binaire en mémoire**.
#### `.plt` et `.got.plt`
Ces deux sections se chargent de la résolution fainéante.
### Segments et sections avec `realelf`
#### Le segment `GNU_STACK`
Il permet la configuration de la pile lorsque le binaire est chargé. Il sert par
exemple à la mise en place de protection (pile non exécutable par exemple).
#### Le segment `GNU_RELRO`
Il indique quelles régions de la mémoire doivent être marquées en lecture seule
une fois la résolution des symboles effectuée. Il existe deux type :
* **full**: `.got` et `.got.plt` sont passés en lecture seule
* **partiel**: seule la `.got` est en lecture seule.
*[ELF]: Executable and Linkable Format
## Le format PE
PE pour *Portable Execution* est le format de binaire utilisé par Microsoft
Windows. Comme pour Linux, un *loader* réalise l'analyse et le chargement du
binaire en mémoire. Dans ce format, la notion de segment n'existe pas, mais il
se compose de 3 entêtes :
* un reliquat de l'ère MS DOS avec comme *magic number* `MZ`. cette section
affiche un message d'erreur du style *Ce programme ne n'exécute pas en mode
MSDOS* lors de son exécusion en mode DOS;
* une entête *PE* avec comme *magic number* `PE`;
* une entête optionnelle.
Il se compose aussi:
* une partie *data directories*;
* une table des sections (`text`, `data`, etc.);
* les sections en questions (avec leurs entêtes).
### CFF explorer
Ce programme disponible uniquement pour Windows permet d'analyser les fichiers
binaires (exécutable, bibliothèques, etc.).
### Entête optionnelle
Elle est quasiment tout le temps présente et permet notamment de définir le
point d'entrée (*entrypoint*), d'activer les protections comme l'ASLR ou autre
mécanismes de sécurité.
### Entête *data directories*
Elle contient la localisation et la taille de chaque `directory`. Ces
*directories* sont de plusieurs natures :
* signatures cryptographiques;
* icônes;
* configurations;
* *export-table* : référençant les fonctions exportées par le binaire;
* *import-table* : référençant les DLL importées par le binaire, les fonctions
issues de ces DLL sont référencées dans l'*Imports Address Table*;
* *relocation table* : table référençant les relocations.
Plusieurs *directories* peuvent appartenir à une section.
### Entêtes de sections
Elles contiennent les information relatives à leurs sections respectives : nom,
adresse virtuelle, taille, droit d'accès.

View file

@ -0,0 +1,329 @@
---
title: "Sécurité système : initiation au reverse engeneering"
date: 2023-09-21
tags: ["ingénieurie inverse", "assembleur"]
categories: ["Sécurité système", "Cours", "TD"]
---
Nous avons revu dans le cours précédents les bases du fonctionnement d'un
binaire de la compilation à son exécution. Dans cette partie nous allons
appréhender les bases de l'ingénierie inverse. À l'issue de celle-ci nous
devrions être en mesure de réaliser de petits *crackme*.
L'étape de compilation est en quelque sorte destructrice : Il n'est plus
possible à partir du binaire compilé de retrouver le code source correspondant.
Mais il est tout de même possible de **désassembler** : on reconstitue un code
assembleur.
Ainsi afin de faire du reverse engineering, il est nécessaire de **comprendre la
compilation**, de savoir de quoi se **compose un binaire** et comment **il est
exécuté**.
Une compréhension de l'assembleur, des type de structures qu'il manipule est
primordiale. Il est aussi nécessaire de maîtriser les outils à notre
disposition, de savoir coder un minimum (en *C*, *Python*).
## La gestion de la mémoire
Les sections `.rodata`, `.bss` et `.data` contiennent des données mémoires. Deux
structures existent pour les manipuler : la pile et le tas.
### La pile
Elle manipule des données statiques. C'est une structure de type LIFO (*Last In
First Out) qui croit vers le bas : plus on descend plus les adresse augmentent.
Sa taille est alignées sur un *int32* ou *int64* en fonction de l'architecture
(32 ou 64 bits).
*Deux "fonctions"* permettent de la manipuler : `push()` afin de placer un
élément -- la pile descend alors d'une case -- et `pop()` pour reprendre un
élément -- la pile remonte d'un élément.
Elle se situe **dans l'espace mémoire d'un processus**, ainsi chaque processus a
sa pile. Elle a une structure linéaire; son accès est **rapide** (pas
d'allocation de mémoire à effectuer) mais la **taille des éléments que l'on peut
y stockée est limitée**.
### Le tas
Il manipule des données dynamiques représentées par une structure de données
hiérarchisée. Le tas est lui aussi positionné dans l'espace mémoire d'un
processus.
Il n'est pas limité en taille (enfin presque pas...) mais nécessite de coûteuses
allocations mémoire qui le rend plus lent. Le développeur peut lui même allouer
de la mémoire sur le tas avec la présence de fonctions comme `malloc()` (et ne
pas oublier de les libérer avec `free()`). L'implémentation de ces mécanismes
mémoire dépendent de **l'allocateur**.
## Initiation au reverse Engineering
### L'assembleur
C'est un langage de bas niveau mais encore compréhensible par un humain
(initié...). Il est dépendant de l'architecture, prenons par exemple le code *C*
suivant:
```c
int sum (int a, int b ){
return a +b;
}
```
Voici ce code en assembleur *x86_64* :
```asm
sum(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
pop rbp
ret
```
Et en assembleur *ARM64*:
```asm
sum(int, int):
sub sp, sp, #16
str w0, [sp, 12]
str w1, [sp, 8]
ldr w1, [sp, 12]
ldr w0, [sp, 8]
add w0, w1, w0
add sp, sp, 16
ret
```
On parle alors d'ISA pour *Instruction Set Architecture*, cela représente le jeu
d'instruction disponible. Nous y trouvons les opérations élémentaires :
addition, soustraction, multiplication, division, et / ou (exclusifs ou non)
etc. Ici mon manipule les registres et la mémoire directement. Une *ISA* ne
contient pas d'opérateur avancés comme les structures de contrôles que nous
pouvons trouver dans les langages de haut niveau (`while ...`, `if ...
else ...`, `for ...`).
### L'assembleur x86 (32 et 64 bits)
Tout comme pour le cours de sécurité logicielle, nous utiliserons principalement
l'assembleur *x86_32* pour ce cours (et quelques fois sa version 64). Mais par
contre nous utiliserons **la syntaxe *Intel*** majoritairement utilisée dans le
monde de l'ingénierie inverse. Cette syntaxe est plus simple :
* les suffixes de mnémoniques pour n'existes pas en syntaxe Intel, ainsi
`movl`, `movw` ou encore `movb` deviennent **`mov`**;
* les préfixes sur les registres et immédiats disparaissent : `%eax`, `$1`,
`$0x0ff` deviennent **`eax`, `1` et `0x0ff`**;
* l'ordre des opérandes est inversé : ce n'est plus `source, destination` mais
**`destination, source`**. `movl $1, %eax` devient **`mov eax, 1`**
* les accès indirect à la mémoire sont aussi plus simple : `(%eax)` devient
`[eax]` et `3(%eax)` devient `[eax + 3]` voir le cours [accès à la mémoire de
sécurité logicielle]({{<ref "secu_logicielle/4_acces_memoire/index.md#accès-indirects-à-la-mémoire">}})
Le vocablulaire reste le même que celui vu lors des cours [d'introduction de
sécurité logicielle]({{ref "secu_logicielle/1_introduction/index.md"}})
### L'assembleur x86 : les registres
LEs registres sont de petits espace memoire directement intégrés au processeur.
Ce sont les espace mémoires les plus rapides disponible sur un ordinateur, mais
aussi les plus petits. Il sont dédiés entre autres au stockage de données.
Certains de ces registres ont des **rôles bien déterminés** par exemples :
* `eip` pour *extended instruction pointer* pointe vers la prochaine
instruction à exécuter[^eip], il porte aussi le nom de *Compteur Ordinal*.
**il ne peut être modifié** contrairement à tous les autres registres;
* `ebp` pour `extended base pointeur` pointe vers le bas de la pile;
* `esp` pour `extented stack pointer` pointe lui vers le haut de la pile.
Tous ces registres sont noté *extended* dans leurs versions **32 bits**
Certains autres ont des spécificité, mais il est tout à fait possible pour le
programmeur de les utiliser à sa guise :
* `ecx` par exemple est utilisé comme compteur dans certaines instructions de
repétition(typiquement les boucles);
* `esi` et `edi` utilisés comme source (*extended source index*) et destination
(*extended destination index*) pour certaines instructions de copie.
Les registres peuvent être découpés en sous registres :
> `a` 8 bits -> `ax` 16 bits -> `eax` 32 bits -> `rax` 64 bits
Il est d'ailleurs possible de découper `ax` en deux registes de 8bits : `al`
(bits 0 à 7) et `ah` (bits 8 à 15). Ces découpes permettent certaines
optimisations comme par exemple le **stockage de deux entiers 16 bits** dans un
registre 32bits ou encore pour les instruction se basant sur **l'interprétation
du contenu** des registres.
Il existe aussi des registes d'états, mais nous les avons vu [en sécurité
logicielle]({{<ref "secu_logicielle/3_assembleur_approfondissement/index.md#flags-de-résultats">}})
DAs le cadre de ce cours, le *Zero Flag*, *Sign Flag* et *Carry flag* sont
importants. Le carry flag correspond à la présence d'une retenue [voir sur
Wikipedia][carry_flag].
Pour rappel, voici un exemple d'utilisation du *Zero flag* :
```asm
start:
mov eax, 1
dec eax ; décrémente eax
; comme le résultat de la précédente instruction est 0
; alors notre programme va forcément brancher...
jz hell
; [...]
hell:
```
[^eip]: en x86, dans certaine ISA, il pointe l'instruction en cours).
[carry_flag]:https://fr.wikipedia.org/wiki/Indicateur_de_retenue
## Pratique
### IDA Free
C'est un outils gratuit et multi plate-forme d'analyse de binaire. Il existe
aussi plusieurs versions payantes (pro, home, corporate ...). La version free
comporte un desassembleur, un décompilateur "cloud" pour x86_64 seulement. Il
permet la manipulation de binaire au formats différents (ELF, PE Mach-O). Il
s'interface aussi avec des débogueras notamment GDB.
C'est un outil puissant mais difficile à prendre en main.
### Étudions `ls` (binaire ELF)
Nous ouvrons notre binaire ls modifié avec `lift` lors du précédent TD pour
l'étudier avec *IDA Free*.
#### question b
`strcpy` se trouve dans la section `.dynsym`, section des symboles résolus
dynamiquement lors de son premier appel.
#### question c
Dans IDA, le code couleur utilisé entre autres dans la section `.dynsym`
correspond aux éléments fournis à l'extérieur de notre binaire, comme dans les
bibliothèques partagées.
#### question d
Les chiffres hexadécimaux dans ne nom de la fonction `sub_xxxxxx` correspondent
aus décalage (*offset*) de celle-ci par rapport à l'adresse de chargement de
notre binaire.
### Les raccourcis clavier
Ils sont une part importante de l'utilisation d'IDA, en voici quelques-uns:
| raccourcis | fonction | commentaires |
|------------|----------|--------------|
| crtl + w | sauvegarder la base de données | très utile cat IDA est instable
| ctrl + shitf + w | effectuer un snapshot de la base de données | CF au dessus
| ctrl + z | annuler | |
| n | renommer un objet (variable, fonction, etc.) | hexrays |
| y | modifier le type d'un objet (variable, fonction, etc.) | asm / hexrays |
| alt + a | interpréter l'objet comme une chaine de caractères | |
| h | interpréter la selection comme un décimal / hexadécimal | asm / hexrays |
| r | interpréter la selection comme un caractère | asm / hexrays |
| c | interpréter la portion comme du code asm | asm |
| d | interpréter la portion comme data | asm |
| u | retirer l'interprétation | asm |
| : ou ;|ajouter un commentaire au niveau de l'instruction | visualiseur |
| insert | ajouter un commentaire avant l'instruction | asm / hexrays |
| espace | cycle entre la vue graph et linéaire | hexrays |
| F5 | décompiler (si possible) | |
| entrée ou double-clic | su un symbole -> aller où pointe le symbole | asm / hexrays |
| echap | revenir à la position précédente - en arrière | asm / hexrays |
| ctrl + entrée | revenir à la position précédente - en avant | asm / hexrays |
| x | afficher les cross références *xref* d'un objet | asm / hexrays |
| alt + t | recherche de texte | |
| alt + b | recherche de motif binaire | |
| alt + i | rechetche un *immédiat* | |
| tab | passer de la vue *asm* à la vue hexrays | |
| ctrl + e | afficher les points d'entrées du binaire | |
| maj + F1 | afficher la fenêtre des types locaux | |
| maj + F12 | afficher la fenêtre des chaîne de caractères | |
Il faut voir IDA comme un bloc note accompagnant le travail d'ingénierie inverse
Il faut **absolument** documenter au fur et à mesure des investigations et faire
autant d'instantanés que possible (stabilité...).
### Analyse du binaire `mysecrets`
#### Question 1
Dans IDA, lorsque nous faisons `ctrl+e` nous atterrissons dans la liste des points
d'entrées du binaire chargé. Dans le cas de `mysecret`, nous atterrissons dans
la fonction `main:`. Ce qui est normal pour un binaire ELF.
#### Question 2
Pou trouver la section `.rodata`, il suffit de trouver la liste des segments
(mais pas au sens ELF...) avec le raccourci clavier `ctrl+s`. Cette section
contient les données en lecture seule, ici toutes les *strings* de notre
binaire.
#### Question 3-a
Dasn le code assembleur, il semble y avoir 3 arguments. IDA nous les donne en
commentaire :
```asm
ebp + arg0
ebp + arg4
ebp + arg8
```
#### Question 3-b
La convention d'appel utilisées ici est `__cdecl` (pour *C declaration*), c'est
la convention utilisés par le langage C et C++ -- la convention d'appel dépend
aussi de l'ABI système, du compilateur ). Ici les arguments sont placés sur la
pile par la fonction appelante (*caller*). Il sont placé dans l'ordre inverse,
prenons comme exemple le programme *C* suivant :
```c
int add_3 ( int a, int b, int c){
// [...]
}
main() {
add_3 (1, 2, 3);
}
```
Ce qui donnera en assembleur :
```asm
main:
push ebp ; backup current base pointer
mov ebp, esp ; get the new base pointer
push 3
push 2
push 1
call add_3
; [...]
```
#### Question 3-c
Les instructions `push epb` et `mov epb, esp` permettent de mettre en place le
*base pointer* après l'avoir sauvegardé pour assurer sa remise en place lors du
retour de notre fonction `secrets`.
#### Question 3-d
La décente de `0x18` dans la pile permet à la fonction apellée de mettre en
place un espace pour les variables locales.