Systèmes d’Exploitation: TP 2 - Le Format Elf, Les segments mémoire

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.

Utilisation de Git

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:

Après chaque niveau il faut faire un commit.

Partie I: Reverse Engineering

Niveau 1

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();
}
  1. Dans quelle section ELF est stockée la chaîne secret (vous pouvez utiliser readelf ou objdump) ?
  2. Quel est le contenu de la chaîne secret ? Décrivez en un court paragraphe comment vous l’avez trouvé.
  3. Vérifiez votre réponse en appelant le programme de manière à afficher le message “You Win!”.

Niveau 2

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:

  1. À quelle adresse l’objet secret sera chargé à l’exécution ? Quelle est sa taille en octets ? Comment avez vous trouvé ?

  2. 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 ?

  3. À quelle position dans le binaire est stocké le contenu de l’objet secret ?

  4. 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
  1. Voyez vous une méthode plus rapide pour trouver le contenu de secret ?

Niveau 3

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();
}
  1. La fonction strcmp est elle chargée dynamiquement ou statiquement ?

  2. 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

  1. Créez un fichier source C et écrivez une version de 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
  1. À quoi sert l’option -shared dans la commande ci-dessus ? Pourquoi est-elle nécessaire ?
  2. À quoi sert l’option -fPIC dans la commande ci-dessus ? Pourquoi est-elle nécessaire ?

  3. Utilisez la variable d’environnement LD_PRELOAD pour charger votre version de strcmp et récupérez le secret.

Partie II: Débordements de Pile

Niveau 4

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(); 
}
  1. Où est stockée l’adresse de retour d’une fonction ?
  2. Utilisez 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 ()     
  1. À quoi correspond le premier mot sur la pile ?

  2. Où est alloué le tableau buffer ?

  3. Existe-t-il une entrée qui provoque une faute de segmentation pour ce programme ? Expliquez pourquoi ?

  4. 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 ?

  5. À quelle adresse est chargé le code de la fonction win() ?

  6. Pouvez vous exécuter la fonction win en changeant uniquement l’entrée du programme ?

Niveau 5

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.

  1. Quelle est cette différence (readelf -l peut vous aider) ?

  2. Qu’implique cette différence en termes de sécurité ?

  3. 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 ?

Bonus

  1. (Moyen) Lisez l’article fondateur d’Aleph 1 en 1996 (http://insecure.org/stf/smashstack.html)
  2. (Moyen) Investiguez les techniques modernes de protection de pile: canari de pile, NX bit et ASLR.
  3. (Dur) Implémentez la solution du Niveau 5 de manière à forker un shell système juste en changeant l’entrée du binaire.
All your base are belong to us

All your base are belong to us