Compare commits
3 commits
2f023a33a5
...
359c629860
Author | SHA1 | Date | |
---|---|---|---|
359c629860 | |||
324179092e | |||
a17a401f00 |
2 changed files with 668 additions and 0 deletions
339
content/secu_systeme/1_introduction/index.md
Normal file
339
content/secu_systeme/1_introduction/index.md
Normal 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.
|
329
content/secu_systeme/2_initiation_re/index.md
Normal file
329
content/secu_systeme/2_initiation_re/index.md
Normal 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.
|
Loading…
Add table
Add a link
Reference in a new issue