lundi 13 juin 2011

Metasm for fun

Durant mes pérégrinations sur le net je suis tombé sur un challenge intéressant : un programme setuid bit avec une sorte de buffer overflow à exploiter. Jusque là, rien de bien nouveau, mais la vulnérabilité se trouve dans la fonction d'enregistrement de l'utilisateur, celle là seulement appelée si on termine le mini-jeu proposé avant...

Là de suite ça devient plus compliqué pour automatiser l'exploit surtout que le mini-jeu utilise largement les fonctions de génération de nombres pseudo aléatoires... Heureusement le programme laisse la possibilité de passer en paramètre la valeur qui initialisera srand...

Tout devient donc forcément plus simple, il suffira de trouver le paramètre qui va générer le mini-jeu de sorte qu'on puisse le terminer facilement. Tout appel ultérieur au programme avec le même paramètre générera donc le même mini-jeu. Pour rappel, si on initialise le générateur pseudo-aléatoire avec la même valeur (srand), tous les appels aux fonctions rand produiront la même suite de nombre (il ne sera pas possible de prédire la suite de valeur, mais ce sera toujours la même).

Pour trouver cette suite qui nous intéresse, on pourrait très bien faire un petit programme appelant srand, puis faisant les appels successifs aux fonctions rand, celà conviendrait très bien et serait probablement bien plus simple. Mais l'objectif ici n'est pas la résolution du problème, c'est l'apprentissage en douceur de quelques fonctionnalités de metasm. On va donc utiliser cet outil en tant que débogueur, pour mettre les points d'arrêt aux endroits qui nous intéressent en affichant les bonnes valeurs. Tout cela sera accompagné d'un petit script ruby pour automatiser le tout, car metasm est écrit en ruby. Mais j'ai ouïe dire qu'un outil du même type va bientôt voir le jour, et lui écrit en python, je pense que ça va faire des heureux :p

Pour commencer, le programme... Je ne vais pas utiliser le programme du challenge dont je parle mais d'un programme qui a juste pour objectif d'illustrer mon propos.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define SIDE 10
 
#define HEROE 'H'
#define TRAP 'O'
#define TREASURE '*'
#define EMPTY '.'
 
#define NB_TRAPS 25
 
#define POS(i,j) (((i)*SIDE)+(j))
#define I(pos) ((pos)/SIDE)
#define J(pos) ((pos)%SIDE)
 
unsigned char g_map[SIDE*SIDE];
 
void set_element(const unsigned int e) {
 while(1) {
  unsigned int pos;
  pos = rand() % (SIDE*SIDE);
  //printf("%i\n",pos);
  if(g_map[pos] == EMPTY) {
   g_map[pos] = e;
   break;
  }
 }
}
 
void print_map(void) {
 unsigned int i,j;
 for(i=0;i<SIDE;i++) {
  for(j=0;j<SIDE;j++) {
   printf("%c ",g_map[POS(i,j)]);
  }
  printf("\n");
 }
}
 
void game(void) {
 // TODO
}
 
void instruction() {
 char r;
 printf("Voulez vous lire les instructions (o/n) ? ");
 r = getchar();
 if(r == 'o') printf("blabla\n");
 
}
 
 
int main(int argc, const char **argv) {
 unsigned int seed,i;
 
 if(argc < 2) {
  fprintf(stderr,"Usage: %s seed\n",argv[0]);
  exit(EXIT_FAILURE);
 }
 
 seed = strtoul(argv[1],NULL,10);
 
 // On initialise le générateur de nombre pseudo-aléatoire
 srand(seed);
 
 instruction();
 
 // Initialisation de la map
 memset(g_map,(int)EMPTY,SIDE*SIDE);
 
 // On place le héros
 set_element(HEROE);
 
 // On place le trésor
 set_element(TREASURE);
 
 // On place les pièges
 for(i=0; i<NB_TRAPS; i++) {
  set_element(TRAP);
 }
 
 //print_map();
 
 game();
 
 return EXIT_SUCCESS;
}
# gcc -Wall -o game game.c

Un petit programme simple, qui génère une carte de 10 sur 10 dans laquelle se trouve un trésor et 25 pièges. Il faut se déplacer sur la carte pour trouver le trésor, en évitant les pièges. Le programme s'assure quand il génère la carte de toujours placer un élément sur une case vide.

L'idée ici va être de trouver la bonne seed pour que notre héros se retrouve juste à coté du trésor et ce dès le démarrage (forcément...).

On commence par installer metasm, comme expliqué dans la documentation via mercurial :
# hg clone http://metasm.cr0.org/hg/metasm/

On configure l'interpréteur ruby pour qu'il puisse trouver les librairies de metasm :
# export RUBYLIB=$RUBYLIB:/metasm

Et on fait un petit test (il est nécessaire d'avoir la libgtk2-ruby pour le GUI) :
# ruby /metasm/samples/disassemble-gui.rb game
Il est même possible d'avoir un graphe à la IDA. Ici le graphe de la fonction main :


On place un breakpoint dans la fonction set_element, juste après le calcul du rand() % (SIDE*SIDE). Au premier breakpoint, on mémorise la valeur, ca sera la position du héros et au deuxième breakpoint, on trouvera la valeur du trésor. Il ne manquera plus qu'à faire la vérification : Si on est à coté du trésor c'est fini, sinon on tente avec une nouvelle graine.

#!/usr/bin/ruby1.9.1
# encoding: utf-8

require 'metasm'
include Metasm

file=ARGV[0];

# Fonction appelée pour afficher à l'écran
# Réouvre stdout juste pour le message
def print_data(d,out,stdout_bck)
 STDOUT.reopen(stdout_bck);
 print "#{d}";
 STDOUT.reopen(out); 
end

# On clone stdout, pour pouvoir l'utiliser par la suite
out = STDOUT.clone;

# On utilise un pipe pour gérer stdin
r,w = IO.pipe;
STDIN.reopen(r);
STDOUT.reopen(w);

side = 10;

100.times do |i|
 heroe_pos = -1;

 dbg = LinOS.create_debugger(file + " #{i+1}");

 # Breakpoint sur le résultat du rand % (SIDE*SIDE)
 dbg.bpx(0x80485d7) {
  if heroe_pos == -1 then
   heroe_pos = dbg.get_reg_value(:edx);
   dbg.continue_wait;
  else
   treasure_pos = dbg.get_reg_value(:edx);
   dbg.kill;
   print_data("#{i+1} => Heros (#{heroe_pos/side},#{heroe_pos%side}), Trésor (#{treasure_pos/side},#{treasure_pos%side})\n",w,out);
   if ((heroe_pos%side == treasure_pos%side) and ((heroe_pos/side)-(treasure_pos%side)).abs == 1) or ((heroe_pos%side-treasure_pos%side).abs == 1 and (heroe_pos/side == treasure_pos/side)) then
    print_data("SUCCEED ! Seed = #{i+1}\n",w,out);
    exit(0);
   else
    next;
   end
  end
 }

 # On répond à la question comme quoi on veut pas lire les instructions
 w.write("n\n");

 dbg.run_forever;
end

Le programme est très simple. Il y a une petite subtilité pour gérer la question posée par le programme au sujet des instructions.

# ruby break_srand.rb ./game
1 => Heros (8,3), Trésor (8,6)
2 => Heros (9,0), Trésor (1,9)
3 => Heros (4,6), Trésor (8,5)
4 => Heros (0,1), Trésor (8,3)
[...]
40 => Heros (0,6), Trésor (8,9)
41 => Heros (4,5), Trésor (6,6)
42 => Heros (6,6), Trésor (4,0)
43 => Heros (7,2), Trésor (7,1)
SUCCEED ! Seed = 43

Voilà un petit exemple des fonctionnalités disponibles et de la simplicité d'utilisation de metasm. La documentation n'étant pas très abondante, je pense que ce petit exemple pourra être utile.