Injection de librairie sous Linux
Injection de lib : à quoi ça sert et quand ? Il arrive parfois que l'on ai besoin lors de debugging ou de reverse de savoir quels sont les arguments passés à une fonction, et pouvoir accéder facilement à certaines infos sans se farcir un tas de ligne d'ASM à la main. Il suffit souvent d'injecter une librairie dans le processus pour « détourner » la fonction en question en modifiant son contenu pour faire certaines opérations comme de l'affichage de variable. Ca, c'est la bonne manière de l'utiliser. Mais on peut aussi très bien imaginer ajouter un bout de code malicieux dans une fonction appelée par un programme. Un appel system bien placé. Bien sûr en aucun cas je ne suis responsable de l'utilisation que vous ferez de cet article. Il faut savoir avant tout que le programme doit être linké « dynamiquement », c'est-à-dire que les librairies soient chargés comme des fichiers autres que le programme principal (souvent en .so pour shared-object). Dans le cas où le code des fonctions est directement intégré dans le programme, c'est tout de suite plus difficile (et cela sort du cadre de cet article). La plupart du temps (et par défaut), les programmes sont compilés avec linkage dynamique. Ne serait-ce que pour réduire considérablement la taille du programme résultant. Le code en question (exemple avec la fonction strcmp) Voici le code en question qui permet de détourner une fonction (ici strcmp) d'un programme. <code> #define _GNU_SOURCE #include <stdio.h> #include <stlib.h> #include <string.h> #include <dlfcn.h> static int (*next_strcmp)(const char *s1, const char *s2) = NULL; int strcmp(const char *s1, const char *s2) { printf(« String 1 = %s\n String 2 = %s\n », s1, s2) ; // Autre code if (next_strcmp == NULL) next_strcmp = dlsym(RTLD_NEXT, « strcmp ») ; return next_strcmp(s1, s2) ; } </code> On compile et on éxecute : <code> gcc -shared libstrcmp.c -o libstrcmp.so LD_PRELOAD=libstrcmp.so ./program </code> Explication pas à pas <code> #define _GNU_SOURCE #include <dlfcn.h > </code> Le #define est nécessaire pour utiliser la fonction dlsym qui permet de charger la vraie fonction strcmp (histoire que même si on a détourné la fonction, on fasse croire au programme que rien ne s'est passé). Le include permet d'appeler l'header pour la fonction dlsym. <code> #include <stdio.h> #include <stlib.h> #include <string.h> </code> On incluse les autres librairies dont on va avoir besoin. Pour strcmp, bien penser à string.h <code> static int (*next_strcmp)(const char *s1, const char *s2) = NULL; </code> Alors là c'est une partie un peu délicate. On déclare un pointeur de fonction qui nous permettra de stoquer l'adresse de notre réelle fonction strcmp. Int est le type de retour de la fonction, (*next_strmp) et le nom du pointeur et (const char *s1, const char *s2) sont les arguments utilisés par cette fonction. On initialise le pointeur à NULL (= NULL). Il faut savoir que l'on doit spécifier pour le pointeur exactement le même type de retour que la fonction à détourner (int) et les mêmes arguments ainsi que leurs types. Un man strcmp vous aidera. <code> int strcmp(const char *s1, const char *s2) </code> On déclare notre fonction. Ce bout de code doit être exactement le même que la fonction à détourner <code>
printf(« String 1 = %s\n String 2 = %s\n », s1, s2) ;
// Autre code </code> Ici notre code ajouté, ce qui nous permet de reverse. On affiche simplement les deux strings passées à la fonction. Libre à vous de rajouter ce que vous voulez à la place du printf. <code> if (next_strcmp == NULL) next_strcmp = dlsym(RTLD_NEXT, « strcmp ») ; </code> Ici est peut-être les instructions les plus interessantes. Tout d'abord on vérifie que notre pointeur de fonction ne pointe pas déjà sur la bonne fonction. Dans le cas contraire on utilise dlsym pour charger la vraie fonction. L'astuce est d'utiliser RTLD_NEXT qui permet de charger la fonction recherchée dans la prochaine librairie (.so) après la notre. Normalement c'est la vraie fonction. A moins que quelqu'un ai déjà injectée une autre librairie ;) <code> return next_strcmp(s1, s2) ; </code> On appele et retourne simplement le vrai strcmp via notre pointeur de fonction sur les chaines s1 et s2. Rien de bien compliqué. <code> gcc -shared libstrcmp.c -o libstrcmp.so </code> La commande de compilation pour gcc. -shared permet de dire que c'est une librairie dynamique. -o libstrcmp.so indique le fichier résultant et libstrcmp.c est notre code source. <code> LD_PRELOAD=libstrcmp.so ./program </code> Ici on appele notre programme ./program simplement. La différence est qu'on ajoute LD_PRELOAD avant. Cela permet de dire au programme qu'avant de charger les librairies nécessaires dans la mémoire du programme, on va ajouter la notre (libstrcmp.so) avant toutes les autres. Donc si la fonction strcmp est appelée par notre programme, on lui passera déjà celle dans notre librairie. On peut aussi utiliser export LD_PRELOAD=libstrcmp.so puis appeler notre programme ./program Cas particuliers d'injections Que faire si je veux détourner une fonction autre que strcmp ? Alors quatre choses à changer :
Vérifier qu'on a bien #include l'header correspondant à notre fonction à détourner
Modifier la définition de notre pointeur de fonction. C'est-à-dire modifier le type de retour, les arguments (et le nom du pointeur puis le modifier de partout).
Changer l'appel à dlsym. On change le nom de la fonction originel.
Le return, pour appeler notre fonction originel avec les bons arguments
Et si je veux cacher un peu plus l'appel à ma librairie (LD_PRELOAD) Plusieurs astuces peuvent être utilisées. Vous pouvez utiliser un export LD_PRELOAD plus tôt avant que le programme soit exécuté. L'ajouter dans le bashrc par exemple. Vous pouvez aussi créer un petit script bash du même nom que le programme (en ayant renommé le programme précedement) qui appel LD_PRELOAD dedans. Faites preuve d'imagination. Ma machine est une 64 bits mais mon programme est compilé en 32 bits Dans ce cas là, utilisez l'option -m32 dans gcc pour compiler notre librairie modifiée en 32 bits. <code> gcc -m32 -shared libstrcmp.c -o libstrcmp.so </code> Puis-je injecter directement la librairie dans le programme lorsque celui-ci tourne déjà ? Alors là, c'est tout de même rudement plus compliqué. Je vous laisse vous référer à cet article (qui propose même un outil pour automatiser le tout) : http://www.segmentationfault.fr/projets/injecso-injection-de-so-sous-linux/ Conclusion Voilà cet article touche à sa fin.Je vous ai présenté une technique simple et rapide qui permet de modifier facilement le comportement d'un programme. Pour toutes suggestions ou autre, veuillez me contacter à l'adresse < k1wy.mail (at) gmail (dot) com> (remplacez at par @ et dot par .). Bon détournement !