[French] How to Create an ASCII Shellcode

EDB-ID:

13963

CVE:

N/A


Platform:

Linux

Published:

2010-06-21

	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