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 gameIl est même possible d'avoir un graphe à la IDA. Ici le graphe de la fonction main :
#!/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.