[French] How to Create a Shellcode on Linux x86

EDB-ID:

13873

CVE:

N/A


Platform:

Linux_x86

Published:

2010-06-14

        
	
	Title:	  How to create a shellcode on Linux x86 ?
	Author:   Jonathan Salwan <submit ! shell-storm.org>
	Web:	  http://www.shell-storm.org/ | http://twitter.com/jonathansalwan
	Date:	  2010-05-30
	Language: French 

	Original version: http://howto.shell-storm.org/files/howto-1.php




	I - Présentation des shellcodes
	===============================

	Un shellcode est une chaîne de caractères qui représente un code binaire exécutable capable de lancer
	n'importe quelle application sur la machine. La plupart  du temps un  shellcode ouvre un  shell pour
	avoir un accès complet sur la machine. Généralement, les shellcodes sont  injectés dans la mémoire de 
	la machine via l'exploitation d'une faille type dépassement de tampon (buffer overflow).


	II - Comprendre les bases
	=========================

	1 - Les appels systèmes
	
	Un appel système (en anglais, system call, abrégé en syscall)  est une fonction  fournie par le noyau 
	d'un système d'exploitation. Suivant les Os nos syscall seront appelés différemment.
	
	Exemple d'appel système fréquemment utilisé: 
	open, write, read, close, chmod, chown ...

	Sur la  majorité  des  systèmes  d'exploitations,  les  appels système peuvent être utilisés comme de 
	simples fonctions écrites en C. Par exemple pour l'appel système chown:

	extern int chown (__const char *__file, __uid_t __owner, __gid_t __group)

	Chaque appel  système  à une adresse, qui est attribuée par le système d'exploitation et qui lui  est
	propre.  Par  exemple  sous   linux  avec  le kernel 2.6.31 l'adresse  du  syscall  chown  est  0xb6.  
	Comment  connaître  cette  adresse ?  Une  simple  commande comme ci-dessous permet d'avoir l'adresse 
	du syscall. Les adresses dans unistd_x.h sont en decimales. 


	Pour un système 32 bits

	jonathan@archlinux [ shellcode ]# cat /usr/include/asm/unistd_32.h | grep chown
	#define __NR_lchown		 16
	#define __NR_fchown		 95
	#define __NR_chown		182
	#define __NR_lchown32		198
	#define __NR_fchown32		207
	#define __NR_chown32		212
	#define __NR_fchownat		298

	 
	Pour un système 64 bits

	jonathan@archlinux [ shellcode ]# cat /usr/include/asm/unistd_64.h | grep chown
	#define __NR_chown				92
	__SYSCALL(__NR_chown, sys_chown)
	#define __NR_fchown				93
	__SYSCALL(__NR_fchown, sys_fchown)
	#define __NR_lchown				94
	__SYSCALL(__NR_lchown, sys_lchown)
	#define __NR_fchownat				260
	__SYSCALL(__NR_fchownat, sys_fchownat)


	Comme vous pouvez le constater, si l'os est sous 32 ou 64 bits, l'adresse des syscalls change.



	III - Ecrire son premier shellcode
	==================================

	En premier lieu nous allons créer un shellcode simple, qui va nous  permettre d'effectuer une pause.
	Pour ça nous allons appeler la fonction _pause dont l'adresse est 29 ce qui donne 0x1d en hexadecimal
	(sous 32 bits).


	jonathan@archlinux [ ~ ]$ cat /usr/include/asm/unistd_32.h | grep pause
	#define __NR_pause		 29


	Une fois qu'on connait l'adresse du syscall, il nous reste plus qu'à connaître, ce qu'on doit mettre
	dans les registres.
	Pour cela référez vous à cette page => http://www.shell-storm.org/shellcode/files/syscalls.html

	Nous pouvons  constater  que pour _pause nous n'avons pas besoin de remplir les registres, juste un 
	appel suffit, ce qui va donc est très court à programmer.


	jonathan@archlinux [ shellcode ]$ cat pause.s
	xor %eax,%eax
	mov $29,%al	
	int $0x80	
	jonathan@archlinux [ shellcode ]$ as -o pause.o pause.s
	jonathan@archlinux [ shellcode ]$ ld -o pause pause.o
	ld: warning: cannot find entry symbol _start; defaulting to 08048054
	jonathan@archlinux [ shellcode ]$ ./pause 
	^C
	jonathan@archlinux [ shellcode ]$
 
	
	Expliquation
	============
	
	xor %eax,%eax	<= On met le registre eax à 0 pour éviter les segments faults
	mov $29,%al 	<= On place 29 (l'adresse du syscall) dans le registre al	
	int $0x80	<= On exécute


	Maintenant nous allons l'écrire C. Pour cela nous devons connaître l'équivalence des fonctions asm en 
	hexadecimales ce qui va par la suite être notre shellcode.

	Comment avoir les équivalences en hexadecimal ?
	C'est simple, nous utilisons tout simplement l'outil objdump, ce qui donne:


	jonathan@archlinux [ shellcode ]$ objdump -d ./pause

	pause:     file format elf32-i386


	Disassembly of section .text:

	08048054 <.text>:
	 8048054:	31 c0                	xor    %eax,%eax
	 8048056:	b0 1d                	mov    $0x1d,%al
	 8048058:	cd 80                	int    $0x80
	jonathan@archlinux [ shellcode ]$


	Et voilà, donc en C le code sera:


	jonathan@archlinux [ shellcode ]$ cat pause_c.c 
	#include<stdio.h>

	void main(void)
	{
	char shellcode[] = "\x31\xc0\xb0\x1d\xcd\x80";

	(*(void(*)()) shellcode)();

	}
	jonathan@archlinux [ shellcode ]$ gcc -o pause_c pause_c.c 
	jonathan@archlinux [ shellcode ]$ ./pause_c 
	^C
	jonathan@archlinux [ shellcode ]$

	Votre premier shellcode fonctionne correctement.

	Maintenant nous allons étudier la fonction _write. Référons nous encore au site que j'ai soumis
	plus haut.

	Info registre:
	==============
	%eax	   = 4 
	%ebx       = unsigned int 
	%ecx       = const char * 
	%edx       = size

	
	Nous allons tous simplement écrire jonathan, regardons ce que donne les sources:

	jonathan@ArchLinux [shellcode]$ cat write.s
	;_write	
	xor   %eax,%eax   <= Pour éviter les segmentfaults
	xor   %ebx,%ebx   <= 	//		//
	xor   %ecx,%ecx   <= 	//		//
	xor   %edx,%edx   <= 	//		//

	movb  $0x9,%dl    <= on place la taille de notre mot dans dl(edx) donc jonathan + \n |  8+1=9   
	pushl $0x0a       <= on commence à empiler notre line feed (\n) = 0x0a
	push  $0x6e616874 <= naht
	push  $0x616e6f6a <= onaj
	movl  %esp,%ecx   <= on envoie %esp dans %ecx le registre qui contient la constante char de _write
	movb  $0x1,%bl    <= ici 1 pour %ebx,
	movb  $0x4,%al    <= et ici le syscall de _write donc 4
	int   $0x80       <= on exécute

	;_exit
	xor   %ebx,%ebx    <= %ebx = 0
	movb  $0x1,%al     <= %eax = 1 (syscall de _exit)
	int   $0x80        <= on exécute

	
	Compilons et exécutons notre programme:
	
	jonathan@ArchLinux [shellcode]$ as -o write.o write.s
	jonathan@ArchLinux [shellcode]$ ld -o write write.o 
	ld: warning: cannot find entry symbol _start; defaulting to 08048054
	jonathan@ArchLinux [shellcode]$ ./write 
	jonathan
	jonathan@ArchLinux [shellcode]$

	Ecrivons notre shellcode en C pour cela, un petit objdump sera utile.

	jonathan@ArchLinux [shellcode]$ objdump -d write

	write:     file format elf32-i386


	Disassembly of section .text:

	08048054 <.text>:
	 8048054:	31 c0                	xor    %eax,%eax
	 8048056:	31 db                	xor    %ebx,%ebx
	 8048058:	31 c9                	xor    %ecx,%ecx
	 804805a:	31 d2                	xor    %edx,%edx
	 804805c:	b2 09                	mov    $0x9,%dl
	 804805e:	6a 0a                	push   $0xa
	 8048060:	68 74 68 61 6e       	push   $0x6e616874
	 8048065:	68 6a 6f 6e 61       	push   $0x616e6f6a
	 804806a:	89 e1                	mov    %esp,%ecx
	 804806c:	b3 01                	mov    $0x1,%bl
	 804806e:	b0 04                	mov    $0x4,%al
	 8048070:	cd 80                	int    $0x80
	 8048072:	31 db                	xor    %ebx,%ebx
	 8048074:	b0 01                	mov    $0x1,%al
	 8048076:	cd 80                	int    $0x80
	jonathan@ArchLinux [shellcode]$
	
	On retrouve bien à droite les sources de notre code en asm puis l'équivalence des instructions en 
	hexadecimal.

	jonathan@ArchLinux [shellcode]$ cat write_c.c 
	#include <stdio.h>

	void main(void)
	{
	char shellcode[] = 	"\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";

	fprintf(stdout,"Lenght: %d\n",strlen(shellcode));
	(*(void(*)()) shellcode)();
	}

	Compilons et exécutons notre shellcode.

	jonathan@ArchLinux [shellcode]$ gcc -o write_c write_c.c 
	jonathan@ArchLinux [shellcode]$ ./write_c 
	Lenght: 36
	jonathan
	jonathan@ArchLinux [shellcode]$

	Et voila cela fonctionne parfaitement. Shellcode _write(1,"jonathan\n",9) + _exit(0) pour une taille de 36 bytes.


	
	IV - Références
	===============
	
	[x] - http://www.shell-storm.org

	[1] - http://fr.wikipedia.org/wiki/Shellcode

	[2] - /usr/include/asm/unistd_32.h