Description
Un groupe d’activiste vous a contacté, ils ont trouvé un serveur appartenant à une entreprise qui pollue énormément.
À priori, c’est sur cette même machine qu’ils stockent leurs documents confidentiels. Récupérez en le contenu afin de dévoiler tout leurs secrets !
Vous pouvez vous connecter à un service disponible sur ce serveur à l’aide de la commande
nc greenflag.valekoz.fr 8000
.Bonne chance 😉
Le programme tournant sur le serveur était également fourni.
Observations
Dans un premier temps, lançons le programme, et utilisons le normalement pour savoir de quoi il s’agit.
|
|
À priori, le programme nous demande un nom d’utilisateur, et nous indique dans notre cas que ce dernier n’est pas bon.
Du côté du code du challenge, avec un décompilateur (ghidra par exemple) on peut obtenir quelque chose qui se rapproche du code suivant:
|
|
Notre nom d’utilisateur n’est en fait même pas testé …
En revanche, on peut voir que le programme utilise la fonction gets
Extrait du man:
BUGS
Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to use.
La faille à exploiter est donc un buffer overflow. En effet, si on donne une entrée trop longue au programme, celui-ci crash:
|
|
Maintenant qu’on connaît la vulnérabilité, il faut savoir comment l’utiliser. En continuant notre analyse du binaire, on trouve une fonction qui a l’air intéressante:
|
|
On doit donc exploiter un buffer overflow, dans le but d’exécuter la fonction secret_function
.
Exploitation « à la main »
Pour exploiter un buffer overflow, l’objectif est de réécrire des variables stockées sur la stack après le buffer.
Une valeur importante pour le déroulement d’un programme est « l’adresse de retour », permettant au programme de reprendre son exécution où il en était après le retour d’une fonction.
Notre objectif va donc être de trouver combien de caractères on doit écrire avant d’arriver à l’adresse de retour pour pouvoir y mettre l’adresse de la secret_function
. Ainsi, lorsque le programme va quitter la fonction main
, il ne va pas retourner dans la fonction __libc_start_main
(comportement “normal”) mais va retourner à l’adresse qu’on lui a donné.
Pour chercher cet offset, on pourrait augmenter le nombre de caractères en entrée de 4 en 4 (car le programme est en 32 bits) jusqu’à ce que le programme crash (réécriture de l’adresse de retour).
Je vais vous présenter ici une méthode plus propre, utilisant la commande cyclic
(disponible avec pwntools
):
|
|
La commande cyclic 64
génère une chaîne de caractère « cyclique » (aaaabaaacaaadaaa
…). On donne cette entrée au programme (r < <(input command)
), et on voit que le programme crash avec une EIP ayant pour valeur 0x61616168
.
Cela signifie que la chaîne haaa
a remplacé l’adresse de retour. On peut donc trouver le nombre de caractères à écrire avant de pouvoir réécrire l’adresse de retour:
|
|
On pourrait donc générer une entrée remplaçant l’adresse de retour par l’adresse de notre fonction en faisant:
|
|
Attention: j’utilise python2
pour générer des payloads hexadécimaux avec un print
, car python3
n’affiche pas toujours les bonnes valeurs pour les caractères non ASCII.
La dernière chose à faire est de récupérer l’adresse de la fonction qu’on veut exécuter:
|
|
On le fait ici à l’aide de la commande readelf
, on pourrait également le faire directement depuis ghidra (ou autre logiciel de reverse-engineering).
On génère donc notre payload avec cette adresse, écrite en hexadécimal et en little-endian:
|
|
On peut également le faire de cette manière:
|
|
L’objectif est simplement de pouvoir passer l’entrée utilisateur au service nc
après l’exécution du script (ou l’affichage du payload)
Exploitation avec pwntools
Écrire des exploits avec pwntools
est beaucoup plus rapide, et permet de garder une trace des challenges après la fin du CTF. Voici deux scripts permettant de résoudre le challenge (le premier nécessitant de quand même trouver l’offset manuellement)
|
|
N’hésitez pas à aller voir la documentation pour plus de détails sur l’utilité des différentes fonctions utilisées.
Et pour l’automatisation complète:
|
|
Cet exploit reprend globalement les différentes étapes faites à la main dans la partie précédente.
|
|
Conclusion
Ce challenge me semble être une bonne introduction aux buffer-overflows.
Dans les challenges suivant, on aborde certaines sécurités permettant de contrer ce genre d’exploitation, ainsi que des techniques permettant de contourner ces sécurités.