Reverse Shell – Garder le shell ouvert

Un problème récurrent

Il vous est surement arrivé de devoir exploiter un code semblable à celui ci-dessous afin d’ouvrir un shell et ainsi récupérer un flag.

// programme.exe

#include <stdio.h>
#include <stdlib.h>

int main(){
    ....
    ....
    if ( pass == "OK" ){
        printf( "Enter shell\n" );
        system( "/bin/bash" );
        printf( "Leave shell\n" );
    }
    ....
    ....
}

Supposons que vous avez réussi à remplir la condition :

pass == "OK"

Pour ce faire vous générez un payload avec python (ou tout autre langage) :

python3 -c 'print("ma_super_payload")' | ./programme.exe

En exécutant cette commande vous avez de grande chance de tomber sur les lignes suivantes :

$ python3 -c 'print("ma_super_payload")' | ./programme.exe
Enter shell
Leave shell

Une histoire de pipes

La première chose à comprendre ici, c’est le concept des pipes. Lorsque vous exécutez la commande

$ python3 -c 'print("ma_super_payload")' | ./programme.exe

Lorsque vous utilisez |, l’interpréteur de commandes crée un pipe (tube) et lance deux processus : python et programme.exe. La sortie standard de python est connectée (redirigée) à l’entrée du pipe, et la sortie du pipe est connectée à l’entrée standard de programme.exe.

Bien entendu, la sortie de programme.exe est connectée à la sortie terminal, tout comme l’entrée du terminal est connecté à l’entrée standard de python. Lorsque python se termine après avoir écrit les données sur la sortie standard (donc le pipe), le kernel ferme ses descripteurs de fichiers. L’entrée du pipe, dont l’entrée a été fermée, ajoutera un signal EOF dans son buffer.

Lorsque system( "/bin/bash" ) est lancé, celui-ci tente de lire les données sur le l’entrée standard de programme.exe mais ne rencontre que le caractère ASCII EOF. Le shell lit donc ce caractère et considère qu’il n’y a plus rien à lire sur l’entrée standard et se ferme directement.

Il faut savoir que le caractère EOF signifie "End Of Transmission". Il est donc interprété ainsi par le shell.

La solution

Pour résoudre le problème, il faudrait avoir un script qui mettra votre payload dans la sortie standard (ex: print() sur python) , et qui ensuite, récupérera vos commandes via son entrée standard ( ex: input() sur python) pour les écrire sur sa sortie standard connecté au pipe.

Écrire ce petit programme n’est pas complexe mais pourquoi faire compliqué quand on peut faire simple. En effet, il est possible de faire tout cela grâce à la commande cat.

Voici le sésame :

cat <( python -c 'print "ma_super_payload"' ) - | ./programme.exe

Explications

Expliquons un peu plus en détails cette commande magique.

La première partie permet de mettre sur l’entrée standard de cat le résultat de notre script python. Attention ici, <( ne doit pas contenir d’espace.

//programme1 <( programme2 ) 
cat <( python -c 'print "ma_super_payload"' )

La deuxième partie consiste à dire au programme cat de ne pas fermer sa sortie standard, même après y avoir écrit le payload. Pour cela on utilise l’argument -. Grâce à cette argument, cat va vous demander via son entrée standard ce que vous souhaitez écrire sur sa la sortie standard et cela jusqu’à ce que vous l’arrêtiez avec un [CTRL] + C par exemple.

cat <( python -c 'print "ma_super_payload"' ) - | ./programme.exe