Rédacteur : Trance
Date de création : 01/07/2006
Section : Sécurité > Shellcodes
Imprimer cet article : en noir et blanc ou en couleurs
Pour lire cet article, il est préférable d'avoir lu le premier article, intitulé "Les shellcodes". En effet, je m'adresse ici à ceux qui savent ce qu'est un shellcode et comment en concevoir manuellement. Dans cet article, nous verrons comment simplifier et automatiser la conception de shellcodes, en codant de petits outils très pratiques, qui nous éviteront pas mal de travail...
Maintenant que la conception de shellcodes vous est familière, notre but va être de passer "à la vitesse supérieure", c'est à dire d'augmenter le rendement... Vous connaissez les étapes menant à la réalisation d'un shellcode. Ici, puisque nous sommes fénéants, nous allons tenter d'en automatiser quelques unes, et même d'en sauter. Notre but va être la réalisation d'un outil qui, à partir d'un programme ASM donné, nous donne le shellcode correspondant, et le teste pour nous.
Nous supposons tout d'abord que nous savons coder en ASM notre programme. En effet, essayer d'automatiser l'étape C > ASM est relativement complexe, étant donné que toutes les fonctions C n'ont pas le même type d'équivalent en ASM. Nous allons donc sauter la première étape (coder le programme de base en C) ainsi que la troisième (recoder le programme en C avec syscall). En effet, la troisième étape est inutile et ne sert qu'a vérifier le focntionnement du syscall. La première n'est pas très utile non plus vu qu'elle ne nous permettra pas de générer directement le shellcode. En fait, elle ne sert que si l'on ne connaît pas à l'avance les numéro des syscalls. Nous commecerons donc par la programmation ASM du programme, avec syscall.
Nous allons nous baser sur l'exemple très original d'un shellcode exécutant un shell... Voici le code ASM correspondant, que nous avons déja vu dans le premier article (et auquel je vous renvoie si vous ne comprenez pas) :
Nous avons rajouté quelques petites choses. Nous verrons cela juste après. Comme nous l'avons dit, notre objectif est maintenant de générer le shellcode. Pour cela, nous allons coder un programme qui va effectuer toutes les tâches que nous avons à faire.
Codons le programme makeshellcode.c
qui va utiliser le programme que nous venons d'écrire :
On remarque que l'on adéfinit une fonction sh() qui n'a pas l'air de servir. Mais en fait, elle va nous servir lorsque nous
allons compiler le programme. En effet, elle porte le meme nom que l'étiquette se situant au dessus du programme asm.s
.
Nous allons compiler ces deux programmes ensemble, ce qui va remplacer le prototype de sh() par le contenu de notre programme ASM.
De même, l'instruction .string ""
du programme asm.s
sert à introduire un octet nul dans le shellcode,
ce qui indique sa fin.
Pour vérifier, nous pouvons tester :
Et voila notre shellcode ! Cette méthode est vraiment plus rapide que celle que nous avions vu dans l'article précédent.
Pourquoi avoir écrit du code C à l'écran ? Pour la suite, nous allons en fait rediriger la sortie du programme dans un fichier... Ceci afin d'automatiser encore plus le processus.
Nous allons reprendre la sortie de makeshellcode.c
afin de tester notre shellcode. Imaginons que cette sortie se
trouve dans un fichier "shellcode.h". Codons désormais un programme qui teste le shellcode ! Ce programme ressemblera comme deux
gouttes d'eau au programme de test que nous avons vu dans l'article précédent.
Maintenant, testons :
Impeccable, le programme fonctionne comme prévu. Maintenant, il ne nous reste plus qu'a automatiser l'exécution de ces deux programmes !
Cette fois çi, le plus dur est fait. Nous allons laisser tomber le C, nous n'en avons plus besoin. Comme tout ce que nous avons à faire est juste une automatisation de lancements de programmes, nous allons utiliser le shell Linux ! C'est parti :
Et voila ! Maintenant, pour réaliser un shellcode, vous n'aurez plus qu'à coder le programme en ASM et à lancer cette commande,
pour peu que makeshellcode.c
, testshellcode.c
et doall
soient dans le même répertoire.
Nous allons utiliser le dernier programme et re-vérifier son fonctionnement en créant un shellcode qui écrit "coucou" dans un fichier texte. On commence par coder le programme en C.
Il est vrai que nous aurions pu gagner de la place en supprimant l'appel à close. Mais le but est ici de comprendre, donc nous coderons proprement à but didactique :-). Maintenant, nous récupérons les appels systèmes.
Ceux qui nous intéressent sont les quatre derniers. On récupère leur numéro :
A-t-on tous les arguments de chaque syscall ? Non, car on ne sait pas combien valent les constantes O_WRONLY, O_CREAT, etc. Nous allons donc interroger le système pour le savoir.
C'est tout ce que nous voulions savoir. Maintenant, on recode le programme en assembleur.
Pourquoi ne pas avoir déclaré "coucou\n" de manière directe avec .string
? Parce que cette instruction colle
automatiquement un zéro terminale à la chaîne en question, et nous n'en voulons pas puisque la chaîne est au milieu du code,
et cela risquerait de couper notre shellcode !
Notre programme devrait marcher. Pour les sceptiques :
Mais le but est justement de laisser tester tous nos outils que nous venons de coder... Le plus dur est fait ; maintenant, il ne nous reste plus qu'à admirer le travail qui se fait tout seul ! Mais pour ce faire, n'oublions pas de copier les trois outils que nous avons codé au préalable dans le dossier courant.
Admirez le travail. En une seule commande, notre programme a été assemblé, inspecté, et le shellcode a été construit. Et il marche ! Vu sa taille, imaginez le travail que nous aurions du faire si nous ne disposions pas de nos petits outils...
Nous avons fait le tour des techniques d'automatisation de création de shellcodes. Vous savez désormais comment créer un shellcode d'une manière assez rapide. Ensuite, pour gagner encore plus de temps, il faut encore plus de pratique...