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