Définitions
En C, on spécifie des fonctions capables de prendre un nombre quelconque d’arguments de façon dynamique avec la syntaxe :
void* func(int argc, ...);Cas de printf
int main(){
printf("%d %s %f %lu %p %p %p %p");
}Par convention, chaque spécificateur de format fait référence au -ième argument concerné (ou qui devrait l’être). Ainsi, par convention, l’exécution du code ci-dessus donne lieu à l’affichage des six premiers registres généraux, puis du contenu de la pile. On se retrouve alors à afficher des données quelconques, faute de les avoir spécifiées.
Aperçu d’une faille
int main(){
char buf[64];
gets(buf);
printf(buf);
printf("\n");
return 0;
}CAUTION
Un tel programme laisse la possibilité d’afficher les valeurs des registres, et de la pile, sur une entrée contenant des spécificateurs de formats.
Par exemple, l’exécution ./bin %p %p %p ... %p révèle les emplacements mémoire susmentionnés. On peut s’en convaincre en utilisant GDB et en visualisant, entre autres, la stack avec la commande : hexdump byte $rsp --size 128.
Ainsi, %p permet de leak 8 octets, au détriment de 3 au sein du buffer. De cette façon, il est possible de leak saved rbp et saved rip.
Exploitation
Une fois saved rbp révélé, la taille des stack frames étant fixe et déterministe, on détermine l’offset à notre buffer au sein duquel on insère notre exploit (shellcode).
WARNING
Notre buffer ne se situe pas forcément au sommet de la stack.
Optimisation
On utilise les propriétés de formatage des spécificateurs de format afin d’améliorer l’exploit, en précisant l’index de l’argument désiré :
%29$pLe cas de %n
Le %n permet d’insérer dans le registre spécifié en paramètre de printf le nombre d’octets qui, pour l’instant, ont déjà été écrits dans le buffer. Il s’agit d’un attribut d’écriture.
De fait, si on souhaite réaliser une écriture arbitraire à une adresse donnée, on peut utiliser un payload comme suit :
aaaaaaa%7$n<lsbAddress>aaaa...aaaa%K$n<lsbAddress+1>...Le cas de %lc
Permet d’introduire un padding de taille l de façon à écrire exactement l octets, avec la valeur du char comprise.