Why not a sandbox? (pwn, 500)

Description du challenge

Votre but est d'appeler la fonction print_flag pour afficher le flag.

Service : nc challenges1.france-cybersecurity-challenge.fr 4005

Solution

On se connecte au service et on est accueilli avec ce qui a tout l'air d'être un shell Python :

$ nc challenges1.france-cybersecurity-challenge.fr 4005
Arriverez-vous à appeler la fonction print_flag ?
Python 3.8.2 (default, Apr  1 2020, 15:52:55) 
[GCC 9.3.0] on linux
>>> print_flag
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'print_flag' is not defined

A partir de ce moment, je me dis que c'est une Python jail classique et j'essaie un peu tous les payloads usuels. L'importation semble être autorisée, mais sur un nombre restreint de modules :

>>> import binascii
Exception ignored in audit hook:
Exception: Action interdite
Exception: Module non autorisé
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: Action interdite

En farfouillant un peu toutefois à l'aide de dir(), on arrive à importer les modules builtins que l'on veut :

La fonction open existe, mais on dirait que d'un hook empêche de l'utiliser :

Cependant, toujours en tatonnant, on trouve une fonction open dans le module codecs qui fonctionne. Génial.

On peut maintenant lire des fichiers arbitraires sur le serveur... on essaie quelques noms du style server.py ou chall.py, mais rien de probant. Essayons de lire /proc/self/maps :

Fantastique : il semblerait que le serveur soit en fait lancé par un binaire nommé /app/spython, et on remarque aussi l'existence d'un fichier très intéressant nommé /app/lib_flag.so. Probablement la fonction print_flag tant recherchée se trouve à l'intérieur !

On dump le binaire spython, par exemple en l'encodant en hexadécimal et en le rapatriant sur sa machine à l'aide d'un habile copier-coller :

On l'analyse avec Ghidra. Le binaire utilise l'API CPython et semble utiliser une mécanique de hooks pour bloquer certaines opérations, mais je ne connais pas le fonctionnement plus en détail et je n'ai pas réussi à comprendre exactement tout le fonctionnement du binaire. Heureusement ce n'est pas très important pour réussir l'épreuve.

On remarque la fonction welcome qui affiche le message du début : ce symbole n'existe pas dans le binaire, il provient certainement de la fameuse lib_flag.so.

Essayons d'ailleurs de lire ce fichier :

Mince... A ce moment-là je me suis dit que j'allais continuer à traiter l'épreuve comme une jail classique, et j'ai trouvé le moyen d'importer os et d'obtenir un shell. Spoiler alert, ce shell ne sert à rien pour la résolution.

Voici donc la source de tous nos problèmes : seul ctf-init peut lire lib_flag.so.

Cette deuxième partie de l'épreuve fut la plus difficile. Il faudrait soit trouver un moyen d'appeler print_flag depuis le shell Python, soit trouver un moyen de lire directement le contenu de lib_flag.so.

Après beaucoup d'essais infructueux, la solution m'est finalement apparue en m'inspirant de la toute fin de ce writeup : https://germano.dev/fuckpyjails/

Avec le module ctypes, on peut aller fouiller la mémoire du processus. En plus, on a le mapping mémoire grâce à /proc/self/maps, et en particulier les adresses des pages de là où est chargée libc_flag.so : c'est gagné.

Voici un exemple de lecture en mémoire :

Bon, le "salut" apparaît en réalité quelques dizaines d'octets plus tard, parce que la structure des objets string est plus complexe que ça (voir https://rushter.com/blog/python-strings-and-memory/).

Écrivons maintenant une fonction très utile qui nous permettra de dump la mémoire sur un nombre d'octets donné :

Je passe les détails du dump des pages associées à lib_flag.so, toutes les adresses sont données, j'encode en hexa le total et je rapatrie sur ma machine.

On obtient un ELF mais il semble corrompu. En l'examinant, j'ai l'impression qu'une page (4096 octets) a été dupliquée pour une raison que je ne connais pas. En l'enlevant, ça fonctionne, et on fait chauffer Ghidra :

Un petit coup de CyberChef et c'est plié.

Une épreuve très fun qui m'aura appris un tas de choses, fait lire beaucoup de doc, et qui avec du recul n'est pas si tirée par les cheveux. J'adore !

Last updated

Was this helpful?