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$p

Le 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.