Update reverse engineering article
This commit is contained in:
parent
324179092e
commit
359c629860
1 changed files with 329 additions and 0 deletions
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