Cette méthode sera appliquée sur l'épreuve n°8 du wargame de la Nuit du hack 2010 qui est un exemple de format string. Cela fera l'objet d'un prochain article.
qu'est-ce que la vulnérabilité format string?
Cette vulnérabilité apparait quand il est possible d'injecter une chaine de formatage arbitraire dans la fonction printf(). Les chaînes de formatage sont identifiées par la notation %. Voici un exemple d'utilisation correcte dans lequel la chaine de formatage est associée à une chaine de caractère (string):
printf("%s", "hello");Voici un exemple de mauvaise utilisation. Un utilisateur malveillant peut injecter une chaîne de formatage dans la ligne suivante:
printf(argv[1]);Le détail des format strings pour la fonction printf peut être consulté ici: http://www.cplusplus.com/reference/clibrary/cstdio/printf/
Nous retiendrons:
%x affiche en hexadécimal. Prend en argument un entier non signé (placé sur la pile),
%s affiche une chaîne de caractères. Prend en argument un pointeur (placé sur la pile) vers la chaîne à afficher.
%n n'affiche rien. Prend en argument un pointeur (placé sur la pile) vers un entier signé où le nombre de caractères affichés jusque là est alors écrit.
Nous verrons que:
- %x nous permet de lire la pile,
- %s nous permet de lire n'importe où en mémoire,
- %n nous permet d'écrire en mémoire,
- $[nombre]%n ( associé à $[nombre]%x )nous permet d'écrire ce que nous voulons en mémoire. Nous reverrons cette notation un peu plus tard.
Lire la pile:
Avant l'appel de printf(), les arguments sont placés sur la pile. Par exemple, dans la ligne suivante, 0x00000000 et 0xfffffff sont placés sur la pile puis dépilés pour être affichés:printf(%x%x,0xffffffff,0x00000000)
Le problème est que si l'on "oublie" de mettre les arguments à l'appel de la fonction, printf() dépile quand même ce qu'elle doit afficher. Ainsi, la ligne suivant affichera les deux mots placés sur le dessus de la pile:
printf(%x%x)
Lire en mémoire:
La chaîne passée en argument à printf est placée sur la pile du dernier au premier caractère. Puis chacun de ces caractères est copié sur la pile pour être interprété successivement. Par exemplemain(){ printf("hello"); }+---------+
| o.... | <-- pile interne de printf():
+---------+ successivement "hell" puis "o\x00.."
| .... |
+---------+
| hell | <- pile de main()
+---------+
| o.... |
+---------+
Astuce: %[nombre]$s permet d'accéder au [nombre]ème mot de la pile sans rien dépiler
Nous pourrions dépiler un à un (grace à %x) la pile et ainsi atteindre notre chaine placée sur la pile par main().Il existe une méthode plus simple: Nous pouvons récupérer directement le [n]ième argument sur la pile avec l'écriture %[n]$s
par exemple, la ligne suivante accède directement au 8ème mot sur la pile. Dans ce cas, la pile n'est pas modifiée.
printf(%8$s)
Cette propriété vient en fait d'une fonctionnalité offerte par printf().
imaginons que nous voulions afficher 8 fois la meme chose. Par exemple:
"oh la! la! la! la! la! la! la! la!"En fait on peut soit écrire:
printf("oh %s %s %s %s %s %s %s %s; "la!","la!","la!","la!","la!","la!","la!","la!");soit écrire:
printf("oh %1$s %1$s %1$s %1$s %1$s %1$s %1$s %1$s; "la!");
Cette deuxième écriture est peu courante. Mais elle est intéressante pour l'exploitation des format strings car elle permet de lire autant de fois que nous voulons un mot arbitraire de la pile, et surtout sans le dépiler!
Maintenant, choisissons de mettre une adresse au début de notre chaîne, puis d'y accéder avec %s:
printf ( [address]%[n]$s )Main() place notre chaîne sur la pile: d'abord s, puis $, puis [n], puis %, et enfin sur le dessus de la pile: [address].
Main() empile ensuite d'autres mots par dessus lesquels il nous faudra passer pour accéder à notre chaîne
Printf() est appelé et va chercher successivement les arguments de notre chaîne. elle exécute alors:
- [address] : affiche [address],
- %[n]$s : affiche la chaîne pointée par l'adresse placée au [n]ième mot de la pile, en l'occurence [address].
Nous avons alors réussi à lire les caractères placés à [address] jusqu'à l'occurence d'un \0 qui signifie fin de chaîne.
écrire un entier en mémoire:
A présent, si nous remplacons %s par %n, alors un entier sera écrit à cette adresse. Cet entier sera égal au nombre de caractères écrits par printf() jusque là. Il nous faudra donc afficher avec printf() un nombre de caractères égal au nombre que nous souhaitons écrire en mémoire.Astuce: %[n]$[k]x affiche (printf output) [k] caractères
Par exemple, si [k] = 20 et "word" est placé sur le 7ème mot de la pile:
printf(%7$20x)(remarque: en fait les 0000000 sont des espaces. Nous avons écrit des zéros par commodité d'affichage et de compréhension)
0000000000000000word
écrire une adresse en mémoire
Nous allons écrire une chaîne de la forme[address+3][address+2][address+1][address]%1$[n1-16]x%[m+1]$n%1$[n2-n1]x%[m+2]$n%1$[n3-n2]x%[m+3]$n%1$[n4-n3]x%[m+4]$n[padding]Remarque: avec l'ajout de la notation %[nombre], rien n'est dépilé de la pile.
[m] est égal au nombre de mots sur la pile au dessus de [address+3]
[n1], [n2], [n3] et [n4] sont les valeurs décimales des caractères que nous souhaitons écrire en mémoire.
Voici d'une part ce qu'affiche chacun de ces arguments quand ils sont interprétés par printf(), et d'autre part ce qu'il se passe alors en mémoire.
[address+3][address+2][address+1][address] : affiche 16 caractères. La mémoire et la pile ne sont pas modifiés.
%1$[n1-16]x : affiche [ n1 - 16 ] caractères (des espaces et le premier mot sur la pile). n1 caractères ont alors été affichés jusque là. La pile n'est pas modifiée.
%[m+1]$n : n'affiche rien. Ecrit le nombre de caractères affichés jusqu'ici ( [n1] ) à l'adresse placée en [m+1]ème place dans la pile ( [address+3] )
%1$[n2-n1]x : ajoute [ n2 - n1] caractères. n2 caractères ont alors été affichés jusque là.
%[m+2]$n : n'affiche rien. Ecrit le nombre de caractères affichés jusqu'ici ( n2 ) à l'adresse placée en [m+2]ème place dans la pile ( [address+2] )
%1$[n3-n2]x : ajoute [ n3 - n2 ] caractères. n3 caractères ont alors été affichés jusque là.
%[m+3]$n : n'affiche rien. Ecrit le nombre de caractères affichés jusqu'ici ( n3 ) à l'adresse placée en [m+3]ème place dans la pile ( [address+1] )
%1$[n4-n3]x : ajoute [ n4 - n3 ] caractères. n4 caractères ont alors été affichés jusque là.
%[m+4]$n écrit le nombre de caractères affichés jusqu'ici ( n4 ) à l'adresse placée en [m+4]ème place dans la pile ( [address] ).
Astuce: ajouter du padding pour aligner les adresses sur la pile
Enfin, nous ajoutons [padding] à la fin de la chaîne:
[padding]: ajoute 0, 1, 2 ou 3 caractères à la fin de la chaine. Cela permet d'aligner les adresses sur la pile. En effet:
Voici la pile que nous voulons:
+----------------+
| |
| m words |
| |
+----------------+
|address+3|
+----------------+
|address+2|
+----------------+
|address+1|
+----------------+
| address |
+----------------+
| %... |
+----------------+
La chaine formatée %n prend en argument une adresse placée sur un mot en mémoire. Or il est possible que cette adresse ne soit pas alignée en mémoire.
Par exemple, supposons que notre adresse soit AAAA. Cette adresse peut se retrouver sur deux mots contigus de la pile. Nous aurons alors 4 cas possibles selon le nombre de caractères de notre chaîne modulo 4:
-41000000-00414141-
-41410000-00004141-
-41414100-00000041-
-41414141- <-- ce que nous souhaitons!
Donc: notre chaîne passée en argument devra comporter un padding permettant d'aligner les adresses.
Nous le placons à la fin de notre chaîne car:
- il doit être placé après le dernier %n pour ne pas affecter le nombre de caractères affichés,
- il doit être placé sous les adresses sur la pile pour permettre leur réalignement.
Astuce: La taille de chaque argument dans notre pile a une longueur multiple de 4 caractères
Une bonne habitude est de veiller à ce que tous nos arguments forment des multiples de 4 caractères. En effet, rappelons nous qu'un mot sur la pile égale 4 caractères.
Par exemple:
%1$[n1-16]x -> 8 caractères -> %1$ et x font 4 caractères, donc n1-16 = XXXX (4 caractères) (4+4 = 8)
%[m+1]$n -> 8 caractères -> %,$ et n font 3 caractères, donc m+1 = XXXXX (5 caractères) (3+5 = 8)
Astuce: ajouter des zeros à gauche des entiers pour avoir une longueur multiple de 4 octets
Il nous suffit de rajouter des 0 (zero) à gauche du nombre. Par exemple n1-16 = "0235" , m+1 = "00099"Ainsi, "00099" sera interprété comme "99" par printf(), et nous aurons écrit 5 caractères sur la pile.
pourquoi [address+3][address+2][address+1][address] ?
supposons que :- [source] est l'adresse de notre payload, par exemple, [source] = 0xaabbccdd,
- [address] est l'adresse dans laquelle nous souhaitons écrire. par exemple [address] = 0x8049654.
aa bb cc dd
^address
^address1
^address2
^address3
[address+3]= 0x8049657 -> aabbccdd
[address+2]= 0x8049656 -> ??aabbcc
[address+1]= 0x8049655 -> ????aabb
[address] = 0x8049654 -> ??????aa
Choix de [n1], [n2], [n3], [n4]:
si [source] = 0xaabbccdd
aa, bb, cc et dd doivent être des nombres croissants car ils seront égaux au nombre de caractères affichés successivement par printf (%n).
Donc nous avons besoin de
aa > bb > cc > dd
Par exemple:aa, bb, cc et dd doivent être des nombres croissants car ils seront égaux au nombre de caractères affichés successivement par printf (%n).
Donc nous avons besoin de
aa > bb > cc > dd
Astuce: ajoute des centaines à chaque octet de l'adresse source
[n1] = 0xdd = 221
[n2] = 0x1cc = 460
[n3] = 0x2bb = 699
[n4] = 0x3dd = 989
Lorsqu'ils seront écrits en mémoire, cela donnera successivement:
0x000000dd
0x0001ccdd
0x02bbccdd (le 1 de 1cc est écrasé)
0x3 0xaabbccdd (le 2 de 2bb est écrasé, et le 3 de 3aa est sur un autre mot)
conclusion
Nous avons vu que les format string permettent de lire la pile, lire en mémoire ou y écrire.références
tutoriel format string - http://www.bases-hacking.org/format-strings.htmlprintf - http://www.cplusplus.com/reference/clibrary/cstdio/printf/
Oh sympa, tu as séparé en 2 parties ton précédent article :).
RépondreSupprimerPlus facile à retrouver donc ;).
Sinon pour l'exploitation du level8 j'ai utilisé les short overwrite pour ma part.
Et le meilleur paper sur les formats strings que j'ai vu pour l'instant est celui de la team TESO :
http://julianor.tripod.com/bc/formatstring-1.2.pdf
Pas vu mieux.
Sur ce ;),