[Reverse Engineering] Cracker des programmes avec GDB

 

Salut à tous,

 

Nous entrons là dans un sujet plutôt technique, qui vous permettra de trouver des codes, des serials, ...

La plupart de mes articles sont "vulgarisés" et "courts" pour permettre à une majorité de personnes de comprendre de quoi je parle.
Une minorité me réclame régulièrement des articles un peu plus pointus ..
Et pour ces personnes-là, de temps en temps, j'écris un article un peu plus long ...
Aujourd'hui c'est pour ces personnes que j'écris cet article.
Je tiens à préciser avant de vous voir tous fuir, que j'ai énormément vulgarisé / simplifié cet article plutôt technique et complexe, et j'espère que vous l’apprécierez à sa juste valeur.

Introduction

Avant d'entrer dans le vif du sujet, quelques bases s'imposent...
Lorsque vous utilisez un logiciel, un programme, ou une application, celui-ci va créer des données,  des configurations / réglages...
Toutes ces données volatiles seront stockées dans la mémoire "temporaire" de votre ordinateur.

Bien entendu, ça n'est pas un simple fichier texte où vous pourriez lire :

 

Après la mise à jour, redémarrer l'ordinateur : Oui
Le fond d'écran de votre ordinateur est : c:\Photo2Voiture.jpg

 

C'est bien plus compliqué que ça ^^
Mais dans le paragraphe suivant, vous allez mieux comprendre comment ça marche !

 

 

Vulgarisons

La mémoire temporaire est une espèce de meuble géant, où chaque programme va chercher un tiroir de libre lorsqu'il souhaite y stocker une donnée.
Quand un programme range une donnée, il récupère un numéro de tiroir (une adresse) ... clairement c'est assez moche ...
Comme vous pouvez l'imaginer votre ordinateur, lorsqu'il est en marche, fait tourner un certain nombre de programmes (assez impressionnant), visible ou en tâche de fond ...

Tout ça pour dire que notre meuble "géant" ne va pas seulement contenir  2/3 tiroirs, mais plusieurs milliers ...
Donc les numéros de ces fameux tiroirs vont être vraiment longs et moches ...

0x0000000000400804

Et si vous avez besoin d'une image pour mieux comprendre, ça ressemble à ça !

 

 

Attention !
Un tiroir appartient à un programme ...
Aucun autre (dans un cadre normal d'utilisation) n'a le droit d'accéder aux données qui sont dedans.

Aussi, lorsqu'un Hacker cherche à trouver une donnée "cachée" d'un programme, c'est là-dedans, qu'il va aller fouiller ...

Alors pour cela il existe des outils comme GDB !

GDB est un outil pour les développeurs, il permet de déboguer une application ...
Vous pourrez par exemple, mettre ce que l'on appelle des "break point" ... traduction, des "points d'arrêt"
Ainsi, lorsque vous exécuterez votre programme avec GDB, un développeur pourra savoir exactement par où passe son programme (quelle fonction), et ce qu'il fait !

 

Mais cet outil permet également de connaitre le contenu des fameux tiroirs !

 

Un peu de théorie

GDB est aussi bien disponible sous Windows que sous Linux, je vais dans ce "chapitre", vous exposer quelques notions concernant son utilisation.

Il faut savoir qu'un programme informatique, est un ensemble de fonctions, chaque fonction contient un certain nombre d'actions.

Comme vous pouvez le constater dans l'exemple ci-dessus, notre "programme", contient bien un ensemble de fonctions, qui peuvent s'appeler entre elles

Une fois compilé, le "code source du programme" est transformé en binaire.
À ce stade, le programme n'est plus vraiment lisible ...

 

 

GDB permet d'afficher en assembleur ce binaire, et donc d'afficher non pas le code source d'origine, mais son équivalent en instructions-machines...

L'assembleur est un langage "Bas niveau", donc pas ultra-simple à lire, mais tout de même compréhensible ...

 

Ci-dessus, un simple petit programme avec une fonction "main"
Une fois compilé, j'affiche le contenu de la fonction "main" avec l'outil GDB.
Si, vous faites un peu attention, vous constaterez, que l'on voit bien les nom des "fonctions utilisées", en l’occurrence une seule, la fonction "puts" (qui permet d'afficher notre texte "Hello World").

Pour utiliser GDB il suffit de saisir :

gdb NomDeVotreProgramme

Exemple :

gdb ./helloWorld

Et une fois dans GDB, si vous souhaitez consulter le contenu d'une fonction, il vous faudra utiliser "disas" ou "disassemble"

disas fonctionAConsulter

Par exemple, si nous souhaitons consulter la fonction "main" de notre code

disas main

GDB nous retournera alors le contenu de la fonction en assembleur ...
Ci-dessous l'assembleur de la fonction "main"

0x000000000040050c <+0>: push %rbp
0x000000000040050d <+1>: mov %rsp,%rbp
0x0000000000400510 <+4>: mov $0x4005dc,%edi
0x0000000000400515 <+9>: callq 0x4003e0 <puts@plt>
0x000000000040051a <+14>: mov $0x0,%eax
0x000000000040051f <+19>: pop %rbp
0x0000000000400520 <+20>: retq

Alors OK, je vous l'accorde, ce n'est pas méga méga compréhensible ...
Sur la gauche, en bleu, ce sont des adresses, ce qui vient ensuite ce sont les instructions "assembleur".
Grosso merdo, ces instructions sont là pour modifier / déplacer / supprimer une ou plusieurs valeurs en mémoire. Les déplacer d'un tiroir à un autre par exemple.
(cf. partie vulgarisation)

Si l'on revient sur le code source

 puts("Hello World");

On se rend compte que "Hello World" n’apparaît nulle part dans l'assembleur que nous avons sous les yeux !

C'est normal, c'est une donnée qui est stockée dans la mémoire de votre ordinateur ...
Aussi nous allons essayer de la récupérer, et de l'afficher !

 

Pour cela , il nous faudra utiliser la partie "Debug" de GDB !

Par debug j'entends mettre des points d'arrêt !
D'ailleurs, avant d'aller plus loin, je vais détailler ce qu'est un point d'arrêt, au risque de perdre ceux qui n'y connaissent rien ^^

Un point d'arrêt c'est lorsque vous demandez à votre programme de se mettre en pause à un endroit précis ...

On met des points d'arrêt sur des adresses ...
Pour mettre un point d'arrêt avec GDB on utilise la commande "b * adresse"

 

Ici, j'ai mis un point d'arrêt (breakpoint), juste avant l'affichage (puts) du Hello World !

b * 0x00000000000400510

Je vais donc démarrer le programme depuis GDB, pour voir si celui-ci s'arrête bien sur mon point d'arrêt !!!

Mais avant cela, quelques petites commandes basiques à connaitre :

  • r : Démarre le programme
  • c : Reprend le programme, si celui-ci est en pause (point d'arrêt)

 

La commande qui nous intéresse c'est "r" (Running)

Après avoir démarré le programme, celui-ci s'arrête bien à l'endroit où je souhaite, sur le point d'arrêt que nous avons placé !!!
Nous allons donc maintenant fouiller la mémoire avec la commande "x/s"

Pour rendre mes futures exemples plus simples, j'ai réalisé un petit changement du code source

int main(){
	char *str="Hello World";
	puts(str);
	return 0;
}

 

 

Voilà, donc ici, je présente "grossièrement les différentes étapes"
Dès lors que nous avons un programme ... ici c'est "helloWorld"

  1. Nous allons le désassembler ...
  2. Mettre un point d'arrêt ...
  3. Démarrer le programme
  4. Rechercher notre ou nos donnée(s) (ici nous cherchons où est "Hello World")

Comme vous le voyez dans mon exemple, j'utilise "x/s" sur la "variable "$rax"

x/s $rax

Alors c'est assez simple de reconnaître les "variables" ...

Admirez mon beau surligneur "jaune"

Vous avez trouvé le point commun de ce qui était surligné ?
Oui, pour simplifier, tout ce qui ressemble à une suite de caractères, précédé d'un "%" est une "variable" (ou la référence vers une adresse mémoire ;) ) ...
Pour l'utiliser, il faudra remplacer le "%" par un "$" ...
"%rax" deviendra $rax ...

"x/s" permet également de se déplacer en mémoire (de bouger le curseur)  ...

En utilisant "-" ou "+"

x/s $variable-curseur

Exemple, si je souhaite me déplacer de 4 position avant  $variable

x/s  $variable-4

 

Dans cet exemple il ne sera pas nécessaire de déplacer le curseur, mais je pense que pour mon petit "challenge" ci-après, vous aurez besoin de cette info ;)

 

Un peu de pratique

 

Désormais, vous partez avec un peu de théorie ... suffisamment en tout cas pour résoudre ce petit "CrackMe" (crack moi), et trouver le "serial" qui changera tout le temps, et donc ne sera jamais le même  ;)

Je précise, que je vous ai mis la solution à la fin de l'article pour les plus flemmards !

 

/*
*	CrackMe N°1
*	Dyrk 2017
*
*/
#include <stdlib.h>
/*
*
*	Afficher un caractère
*/
void put_char(int c){
        write(1, &c, 1);
}
/*
*	Afficher une chaine de caractère
*
*/
void put_str(char *str){
        int i = 0;
        while (str[i] != 0){
                put_char(str[i++]);
        }
}
/*
*
*	Récupérer la taille d'une chaine de caractère
*/
int str_len(char *str){
        int i = 0;
        while (str[i] != 0) i++;
        return i;
}
/*
*
*	Vérifier si 2 chaines de caractère sont identiques
*/
int str_cmp(char *str1, char *str2){
        int i = 0;
        while (str1[i] != 0 && str2[i] != 0) {
                if (str1[i] != str2[i]){
                        return -1;
                }
                i++;
        }
        return 1;
}

/*
*
*	Convertir un Char[] en  char*
*
*/
char *copy_str(char str[]){
        char *newStr = malloc(sizeof(char) * str_len(str));
        int i = 0;
        while (str[i]  != 0)
                newStr[i] = str[i++];
        newStr[i] = 0;
        return newStr;
}

/*
*
*	Générer un nombre aléatoire entre Min et Max
*/
int random_int(int min, int max){
        return  min + rand() % (max+1 - min);
}

/*
*
*	Crackme - c'est là que vous devrez trouver le code
*	Attention celui-ci change tout le temps
*
*/
int crackme(){
        char upassword[12], *password, *upass;
        int i = 0, length_pass;
        password  = malloc(sizeof(char) * 12);
        while(i <= 10){
                password[i++] = random_int('A','Z');
        }
        password[i] = 0;
        put_str("Serial : ");
        length_pass = read(0, upassword, 12);
        upassword[length_pass] = 0;
        upass = copy_str(upassword);
        if (str_cmp(upass, password) == 1){
                put_str("Good Serial\n");
                return 1;
        }
        put_str("Bad Serial\n");
        return -1;
}
/*
*	
*	Main Code
*
*/
void main(){
        crackme();
}

 

 

Solution

 

 

Attention ... spoiler ...
Tu ne devrais pas consulter la solution, avant d'avoir cherché !

 

Je suis sur que tu n'as même pas fait chauffer le moindre neurone !!!!

 

Bon Ok ... je vais être sympa et te la donner quand même cette solution ....

 

 

Conclusion

 

L'utilisation de GDB est une solution pour cracker des programmes, il en existe de nombreuses autres.
Pour les curieux, je vous invite à consulter le blog de Deamon Crack, qui a été mon inspiration pendant de longues années dans le domaine du Reverse Engineering.

 

Partagez ce contenu

Laisser une réponse

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *