Guide des format string (partie 2)
Une compréhension générale du principe de stack est préférable pour suivre ce guide
Precisions sur les format strings
Dans ce guide, on va mettre au clair quelques points vus précédements sur les formats strings et leur lien avec la stack.
Pour cela, j'ai préparé un script très simple :
#include<stdio.h> int main() { char buf[64]; scanf("%s", buf); printf(buf); return 0; }
Ce code est vulnérable aux format string exploit, puisqu'on affiche avec printf l'entrée utilisateur sans utiliser de format specifier :
printf(buf);
On peut tester quelques formats :
terminalbash$- ./exemple > %lx-%lx-%lx-%lx-%lx-%lx-%lx-%lx-%lx a-0-0-a-ffffffff-2d786c252d786c25-2d786c252d786c25-2d786c252d786c25-2d786c252d786c25
On a deja vu qu'a partir d'un certain point, les données retournées par les format specifiers sont des données de la stack, mais il est important de comprendre dans quel ordre ces données sont retournées.
L'ordre des parametres est le suivant :
- Registre RSI
- Registre RDX
- Registre RCX
- Registre R8
- Registre R9
- Stack (soit valeur a RSP) Note: RSP est le Stack Pointer, c'est un pointeur vers le haut de la stack
- Parametre d'avant + 8 (soit valeur à RSP + 8)
- Parametre d'avant + 8 (soit valeur à RSP + 16)
- etc...
Note: Il n'est pas important de comprendre le principe de registre, on retiendra juste que ce sont comme des variables, présentes dans tous les executables Note: "valeur à RSP" veut dire "valeur à l'adresse contenue dans RSP" et non pas "valeur de RSP"
Dans notre exemple, cela correspond a :
- RSI = 0xA
- RDX = 0x0
- RCX = 0x0
- R8 = 0xA
- R9 = 0xffffffff
- $RSP = 0x2d786c252d786c25
- $RSP+8 = 0x2d786c252d786c25
- $RSP+16 = 0x2d786c252d786c25
- etc...
Note: $RSP veut dire "valeur à RSP"
Pour visualiser ca, on utilise le debugger GDB avec la commande :
terminalbash$- gdb ./exemple
Puis on execute le script et au moment du print, on regarde les valeurs des registres :
terminalbash(gdb) info registers rsi rsi 0xa (gdb) info registers rdx rdx 0x0 (gdb) info registers rcx rcx 0x0 (gdb) info registers r8 r8 0xa (gdb) info registers r9 r9 0xffffffff (gdb) x/x $rsp # x/x permet de lire une valeur a une adresse mémoire dans GDB 0x7fffffffda00: 0x2d786c252d786c25 # et $rsp retourne l'adresse contenue dans rsp (soit ici 0x7fffffffda00) (gdb) x/x $rsp + 8 # $rsp + 8 = 0x7fffffffda00 + 8 = 0x7fffffffda08 0x7fffffffda08: 0x2d786c252d786c25 (gdb) x/x $rsp + 16 # $rsp + 8 = 0x7fffffffda00 + 16 = 0x7fffffffda10 0x7fffffffda10: 0x2d786c252d786c25
A ce moment le haut de la stack ressemble donc à ça :
terminalbash(gdb) x/8x $rsp # x/8x permiet de lire 8 valeurs 0x7fffffffda00: 0x2d786c252d786c25 0x7fffffffda08: 0x2d786c252d786c25 0x7fffffffda10: 0x2d786c252d786c25 0x7fffffffda18: 0x2d786c252d786c25 0x7fffffffda20: 0x0000000000786c25 0x7fffffffda28: 0x0000000000000000 0x7fffffffda30: 0x0000000000000000 0x7fffffffda38: 0x00007ffff7fe5af0
On remarque également qu'une certaine valeur (0x2d786c252d786c25) se répete de nombreuses fois dans la stack. Par curiosité allons voir si elle correspond à une chaine de caracteres (en little endian) ou un entier connu :
Entrée
2d786c252d786c25Résultat
3276487635844492325Entrée
256c782d256c782dRésultat
%lx-%lx-L'entier 3276487635844492325 ne me dit rien du tout, mais la string "%lx-%lx-" est connue puisqu'il s'agit du début de celle passée en entrée. On en déduis donc que l'input de l'utilisateur passé dans le printf est stocké sur la stack.
Comme on sait que le haut de la stack est le 6eme parametre et que l'input de l'utilisateur est stockée en haut de la stack, on devrait pouvoir acceder à l'input directement avec (par exemple) le format specifier %6$p. Donc si on met une string connue en début d'input puis %6$p :
terminalbash$- ./myexe > aaaaaaaa%6$p aaaaaaaa0x6161616161616161
Le programme retourne bien notre entrée : aaaaaaaa suivi de 0x616161616161616
Entrée
6161616161616161Résultat
aaaaaaaaCela confirme donc notre hypothese, on peut passer n'importe quelle valeur en parametre de printf, du moment qu'elle est stockée sur la stack, et injecter de la donnée sur la stack. On peut donc passer une valeur et l'utiliser ensuite dans un format specifier, le tout en un seul payload.
Ces concepts peuvent sembler abstraits sans exemple d'éxploitation concret mais il est très important de les comprendre afin d'avoir de bonnes bases.
J'espere que je vous ait pas fait trop peur :)