Our civil rights – including free speech and privacy – must be preserved on the electronic
frontier. At the same time, we must respect each other’s rights to privacy and free speech.
This means not writing viruses, breaking into another’s computer, or posting messages certain
to cause flame wars. Just as important, it means treating each other with civility, respect,
and tolerance.
Cliff Stoll, 1992
Ce TP est constitué de 5 niveaux. Pour chaque niveau un binaire est fourni, qui contient une fonction win()
. Pour passer le niveau il faut réussir a trouver une faille pour appeler la fonction win()
.
Les cinq binaires peuvent être récupérés avec la commande suivante
$ wget https://www.sifflez.org/lectures/SEA/tp2-binaires.tar.gz
Le but de ce TP est d’explorer le format Elf ainsi que les mécanismes d’allocation mémoire et d’appel de fonctions. Néanmoins ces aspects sont explorés à travers des techniques très basiques de désassemblage, reverse engineering, et à travers l’exploitation de vulnérabilités informatiques.
Le rendu du TP se fera avec GIT. Pour ce TP il faut travailler sur le répertoire TP2
que vous devez créer.
Pour ce TP il faut répondre à une série de questions et écrire un peu de code C.
Pour chaque niveau la procédure est la même, je prends ici l’exemple du niveau 1:
créer un fichier niveau01.txt
répondre aux questions à l’intérieur de ce fichier
Si des codes C sont demandés, tapez les dans des fichiers avec l’extension .c
et précisez dans le fichier niveau01.txt
le nom des fichiers .c
correspondants.
Après chaque niveau il faut faire un commit.
Le binaire niveau1
a été compilé à partir du code C suivant:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char secret[];
void fail() {
fprintf(stderr, "password not valid\n");
exit(1);
}
void win() {
printf("You Win!\n");
}
int main(int argc, char **argv) {
if (argc != 2) fail();
if (strcmp(argv[1], secret) != 0) fail();
win();
}
secret
(vous pouvez utiliser readelf
ou objdump
) ?secret
? Décrivez en un court paragraphe comment vous l’avez trouvé.Le binaire niveau2
a été compilé à partir du même code que secret1
, seul le secret est différent.
Pour passer ce niveau, trouvez le contenu de la chaîne secret
avec la méthode suivante:
À quelle adresse l’objet secret
sera chargé à l’exécution ? Quelle est sa taille en octets ? Comment avez vous trouvé ?
Utiliser readelf
pour obtenir la table des sections. Quelle section contient l’objet secret
? À quelle adresse la section sera elle chargée en mémoire ? À quelle position dans le binaire peut-on lire le contenu de la section ?
À quelle position dans le binaire est stocké le contenu de l’objet secret
?
Quel est le contenu de l’objet secret
?
Comme vous l’avez remarqué le contenu de l’objet secret
ne peut pas être saisi facilement au clavier. Pour appeler le programme niveau2
et obtenir le message “You Win!” vous pouvez utiliser la technique suivante:
# Si le contenu de secret est la valeur héxadécimale 0xba9876 vous pouvez faire
$ SECRET=$(echo -e "\xba\x98\x76")
$ ./niveau2 $SECRET
secret
?Le binaire niveau3
a été compilé à partir du code suivant:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char* getsecret() {
char* secret = malloc(7*sizeof(char));
// Code très secret et obfusqué qui remplit secret
return secret;
}
void fail() {
fprintf(stderr, "password not valid\n");
exit(1);
}
void win() {
printf("You Win!\n");
}
int main(int argc, char **argv) {
if (argc != 2) fail();
if (strcmp(argv[1], getsecret()) != 0) fail();
win();
}
La fonction strcmp
est elle chargée dynamiquement ou statiquement ?
Utilisez l’outil ldd
pour trouver depuis quel fichier est chargé le code de strcmp
.
On va utiliser une attaque connue sous le nom de LD_PRELOAD trick pour charger une autre version de strcmp
…
strcmp
qui vous permettrait de récupérer le secret. Soyez créatifs :)Compilez votre fonction dans une bibliothèque dynamique en utilisant les commandes suivantes:
$ gcc -fPIC -c evil_strcmp.c -o evil_strcmp.o
$ gcc -shared -o evil_strcmp.so evil_strcmp.o
-shared
dans la commande ci-dessus ? Pourquoi est-elle nécessaire ?À quoi sert l’option -fPIC
dans la commande ci-dessus ? Pourquoi est-elle nécessaire ?
Utilisez la variable d’environnement LD_PRELOAD
pour charger votre version de strcmp
et récupérez le secret.
Le binaire niveau4
a été compilé à partir du code suivant:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win() {
printf("Win !\n");
}
void check_credentials() {
char buffer[64];
gets(buffer);
}
int main(int argc, char **argv) {
check_credentials();
}
gdb
pour tracer une exécution normale de ce programme. Suivez l’exemple ci-dessous:echo "mon entree" > input
gdb ./niveau4
Reading symbols from /users/ens/pabdeoli/smash/niveau4/niveau4...done.
# Voici une execution normale du code
(gdb) r < input
Starting program: /users/ens/pabdeoli/smash/niveau4/niveau4 < input
Program exited with code 0210.
# On choisit de mettre un point d'arrêt à l'entrée de la fonction check_credentials
(gdb) b check_credentials
Breakpoint 1 at 0x40054b: file niveau4.c, line 12.
(gdb) r
Starting program: /users/ens/pabdeoli/smash/niveau4/niveau4 < input
Breakpoint 1, check_credentials () at test.c:12
12 gets(buffer);
# On passe en mode assembleur
(gdb) layout asm
# on est à l'entrée de la fonction check_credentials comme attendu
B+>│0x40054b <check_credentials+4> sub $0x40,%rsp
│0x40054f <check_credentials+8> lea -0x40(%rbp),%rax
│0x400553 <check_credentials+12> mov %rax,%rdi
│0x400556 <check_credentials+15> mov $0x0,%eax
│0x40055b <check_credentials+20> callq 0x400420 <gets@plt>
│0x400560 <check_credentials+25> nop
│0x400561 <check_credentials+26> leaveq
│0x400562 <check_credentials+27> retq
# L'instruction retq (pour retour) sort de la fonction check_credentials avant de retourner
dans main. On va mettre un point d'arrêt sur l'instruction ret.
(gdb) b *0x400562
# On continue l'exécution du programme
(gdb) c
#GDB s'arrête sur l'instruction ret, juste avant de sortir de la fonction check_credentials
# L'adresse de la pile est gardée dans un registre machine appellé sp (pour stack pointer)
# on va regarder quel est le contenu des deux premières case de la pile
(gdb) x/2 $sp
0x7fffffffe5c8: 0x0040057c 0x00000000
^^^ ^^^
voici l'addresse et voici la première case dans la pile
Il existe également des commandes plus haut-niveau dans gdb comme,
(gdb) info stack
#0 0x0000000000400562 in check_credentials ()
#1 0x000000000040057c in main ()
À quoi correspond le premier mot sur la pile ?
Où est alloué le tableau buffer
?
Existe-t-il une entrée qui provoque une faute de segmentation pour ce programme ? Expliquez pourquoi ?
Pouvez vous changer la première valeur sur la pile à autre chose que 0x0040057c (la valeur peut-être différente sur votre machine) en changeant uniquement l’entrée du programme ?
À quelle adresse est chargé le code de la fonction win()
?
Pouvez vous exécuter la fonction win
en changeant uniquement l’entrée du programme ?
Le binaire niveau5
est compilé à partir du même code source que niveau4
. Néanmoins il existe une petite différence au niveau des sections.
Quelle est cette différence (readelf -l
peut vous aider) ?
Qu’implique cette différence en termes de sécurité ?
Pouvez vous proposer (pas la peine de l’implémenter) une attaque vous permettant d’exécuter du code arbitraire en passant une entrée bien choisie à niveau5
?