Hello,

Ce 1er Avril 2016, à 23H59 (Europe/Paris), a commencé le Capture The Flag Qualifications de la Nuit Du Hack. Il dure 24 heures, et permet de tester ses compétences en hacking dans 6 catégories : Web App, Exploit Me, Crack Me, Cryptography, Inforensic, Steganalysis.

La particularité de ce CTF, et la raison pour laquelle j’y participe : il est français ! Et oui, ça existe.

Les règles :

  • On doit se créer un compte pour participer, un seul compte par équipe de 5 personnes.
  • On peut gagner des tickets si on est bon (pas mon cas :D), et surtout on gagne sa place pour le CTF final de la Nuit Du Hack.
  • Le classement dépend de la rapidité.
  • Les drapeaux peuvent ressembler à [NDH]{flag} s’ils sont impossibles à distinguer du reste du … “truc”.
  • 4 et 2 heures avant la fin, un indice sera donné pour tous les challenges non résolus.

La liste exhaustive des challenges est en dessous.


Plan d’action

  1. Introduction (Présentation) au dessus
  2. Matriochka - Step 1
  3. Matriochka - Step 2
  4. Matriochka - Step 3
  5. Invest
  6. Find Me I’m Famous
  7. Trololo
  8. Who am I?
  9. Mickey
  10. Conclusion

Matriochka - Step 1

Matriochka signifie littéralement Poupée russe … on s’attend donc à trouver des fichiers cachés les uns dans les autres.

Sur la page du challenge, on a un lien. On le télécharge sur une machine virtuelle Linux, et on peut commencer à jouer.

Premiers pas, histoire de voir à quoi s’attendre.

$ file stage1.bin
stage1.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=d023239e2bf37734ba5a67401a092ba6273c37b6, not stripped

Il s’agit donc d’un executable.

$ chmod +x stage1.bin
$ ./stage1.bin
Usage: ./stage1.bin <pass>
$ ./stage1.bin trololol
Try again...

L’objectif est donc de trouver le pass.

En considérant que le challenge est simple, on essaie un strings ./stage1.bin. Perdu dans la masse, on voit …

Usage: %s <pass> 
Much_secure__So_safe__Wow
Good good!
Try again...

On tente ./stage1.bin Much_secure__So_safe__Wow … et ça fonctionne. On obtient un contenu qui ressemble à du base64. gist

Et c’est là que les ennuis commencent. Je n’ai pas réagi tout de suite en me disant que Much_secure__So_safe__Wow était le flag, et je ne l’ai validé que bien après. Alors que le challenge pouvait être résolu en quelques minutes Bref, on valide sur la page du challenge, et on obtient nos premiers points. Mais ce n’est que le début.


Matriochka - Step 2

La page du challenge Matriochka 2 nous indique que nous devons réussir la partie 1 pour avancer (ce qui semble logique s’il s’agit de coffres imbriqués). On prend notre contenu en base64, et on le décode : base64 -d stage2.base64.bin > stage2.bin.

Cette fois-ci, il s’agit d’un POSIX tar archive (GNU). binwalk est un peu plus bavard :

$ binwalk stage2.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             POSIX tar archive (GNU), owner user name: "jherve", owner group name: "jherve"

Étant donné que c’est une archive tar, un simple tar xvf stage2.bin suffit à l’extraire.

Attention: le fichier dans l’archive s’appelle stage2.bin et remplace donc le fichier de départ. Il est normal qu’aucun nouveau fichier ne soit créé.

Un binwalk nous en apprend plus sur la structure du binaire :

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV)
33888         0x8460          LZMA compressed data, properties: 0x7E, dictionary size: 16777216 bytes, uncompressed size: 100663296 bytes
34080         0x8520          LZMA compressed data, properties: 0x8A, dictionary size: 16777216 bytes, uncompressed size: 100663296 bytes
34144         0x8560          LZMA compressed data, properties: 0x90, dictionary size: 16777216 bytes, uncompressed size: 33554432 bytes
34464         0x86A0          LZMA compressed data, properties: 0xC8, dictionary size: 16777216 bytes, uncompressed size: 50331648 bytes

Il s’agit ici encore d’un exécutable, qui réagit de la même manière que le premier, cependant strings ne nous apprend rien de plus.

N’étant pas fan du Reverse Engineering, j’ai essayé pendant quelques heures à comprendre comment faire, mais je n’ai pas de résultats satisfaisant. Le seul détail que j’ai su trouver, en supposant qu’il soit correct, est que le mot de passe à trouver est de 11 caractères et commence par un P. C’est un peu trop vague pour continuer.

Après un peu (beaucoup) d’aide sur l’IRC officiel, et des heures de recherche, je peux vous affirmer que c’est faisable, et que le mot de passe commence bien par P et fait bien 11 caractères. Ma méthode d’ouvrir le fichier avec Hopper Disassembler fonctionnait, mais générait du bruit (des appels à des fonctions qui n’existent pas). On m’a alors conseillé d’utiliser radare2 : re2 ./stage2.bin, puis en tapant aaaa, V @main puis en appuyant sur la touche espace on tombe sur une jolie interface qui permet de lire le code ASM.

Je ne vais pas tout détailler, mais on a une interface qui ressemble à ça :

Je n’ai pas la prétention de vous apprendre l’assembleur, mais sub X, Y soustrait Y à X (pareil pour add), mov copie une variable sous un autre nom, cmp compare deux valeurs, je saute à l’instruction indiquée selon si la condition est respectée. Ce qui est bien avec radare2, c’est que le flux de l’application est conservée.

Sur le long terme c’est très pratique : on sait où on en est.

On retrouve le pass en reversant le code, ce qui revient souvent à partir de la fin pour remonter à la variable originale. C’est le cas ici. Par manque de motivation, (et parce que j’avais faim), j’ai mis de côté la résolution complète. On m’a transmis le binaire 3, et je peux vous en parler … mais pas en bien !


Matriochka - Step 3

Le binaire obtenu cette fois-ci est … très différent. Il est séparé en plusieurs fonctions (ce qui n’était pas le cas avant, où on avait une seule méthode main et une secondaire, à la limite) … beaucoup de fonctions. La fonction main ne répond plus qu’à 3 tâches : afficher l’usage si l’argument n’est pas donné, mettre en place les signaux et quitter l’application si le password est mauvais.

Les signaux ? Oui, les processus Linux peuvent communiquer entre eux par le biais de signaux … et un processus peut même communiquer tout seul. Il y a donc un handler, défini avec la fonction signal … et un appel de signal sur son propre PID (getPID).

L’objectif est donc de comprendre comment l’application fonctionne, quels sont les signaux (et donc, fonctions) appelées, et voir ce qu’elles font de l’entrée … Bref, effectuer le travail de radare2 à la main. BONNE CHANCE. Mais ce sera sans moi ! :D


Invest

fichier

Dans ce challenge de la catégorie Inforensic, on nous donne un dump pcapng (c’est à dire, du traffic réseau), que l’on doit analyser à la recherche … de quelque chose.

Pour le plaisir de tester un nouvel outil, j’installe foremost et je l’invoque.

$ foremost -v -i invest.pcapng
Foremost version 1.5.7 by Jesse Kornblum, Kris Kendall, and Nick Mikus
Audit File

Foremost started at Sat Apr  2 15:10:18 2016
Invocation: foremost -v -i invest.pcapng
Output directory: /root/output
Configuration file: /etc/foremost.conf
Processing: invest.pcapng
|------------------------------------------------------------------
File: invest.pcapng
Start: Sat Apr  2 15:10:18 2016
Length: 602 KB (616512 bytes)

Num	 Name (bs=512)	       Size	 File Offset	 Comment

0:	00000039.jpg 	      56 KB 	      20278
1:	00000330.jpg 	      24 KB 	     169159
2:	00000024.gif 	      216 B 	      12413 	  (20 x 22)
3:	00000025.gif 	      245 B 	      13114 	  (20 x 22)
4:	00000032.gif 	      309 B 	      16746 	  (20 x 22)
5:	00000034.gif 	      229 B 	      17537 	  (20 x 22)
6:	00000416.gif 	      245 B 	     213429 	  (20 x 22)
*|
Finish: Sat Apr  2 15:10:18 2016

7 FILES EXTRACTED

jpg:= 2
gif:= 5
------------------------------------------------------------------

Foremost finished at Sat Apr  2 15:10:18 2016

00000039.jpg Des portes logiques (ET, OU), cinq entrées et une sortie … il faut sans doute écrire un programme qui applique un filtre sur une image.

00000330.jpg Une image qui semble incomplète. Clic droit, rechercher l’image sur Google … \o/ The Godfather, et on a même une image plus complète.

Les fichiers .gif sont anodins.

Autre détail, 00000330.jpg pèse 56KB. Beaucoup, pour une image jpg.

$ binwalk 00000330.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard  1.01
17091         0x42C3          JPEG image data, JFIF standard  1.01

On extrait la seconde image : dd if=00000330.jpg skip=17091 bs=1 of=new.jpg, et …

Bonjour toi.

Petit troll innocent.

Petit troll réponse, un peu moins innocent … C’est vraiment trop inzuste.

J’abandonne ici pour ce challenge, avec l’espoir de revenir plus tard dessus. (C’était sans compter qu’il y avait beaucoup de défis et que j’étais seul.)


Find Me I’m Famous

Cool, un défi dans la catégorie WebApps, ça je sais faire.

On arrive sur une jolie page qui demande notre prénom et notre âge, puis qui nous demande notre prénom et répond avec notre âge. Deux étapes, il y a donc un stockage, au moins temporaire. La présence du cookie nommé cook le confirme : Tzo0OiJVc2VyIjoyOntzOjM6ImFnZSI7czoyMzoidmFpbmNyYSBsYSBudWl0IGR1IGhhY2siO3M6NDoibmFtZSI7czo5OiJVbkdlZWsuZnIiO30%3D.

Il est encodé en base64, et contient ceci : O:4:"User":2:{s:3:"age";s:2:"19";s:4:"name";s:9:"UnGeek.fr";}7 ..; ce qui correspond à un objet PHP sérializé. Et je sais qu’il existe des exploits, vu que j’ai lu des articles à propos de ça ici (securitycafe.ro). … … Mais, c’est même pire ! Le code donné en example sur securitycafe.ro est exactement celui utilisé sur NuitDuHack : une classe utilisateur qui stocke le nom et l’âge, et surtout, une classe FileClass qui nous permet d’accéder en lecture à tous les fichiers du serveur … ainsi qu’à toutes les URL auquel le serveur a accès. En pratique, ça correspond à beaucoup de choses. Tous les fichiers de configuration (Apache2), les fichiers de logs potentiels, les fichiers qui stockent les packages installés … les fichiers sources du site … le fichier config.php qui nous dit gentiment NOT here... et même l’URL http://localhost/server-status si l’envie nous prend d’espionner les autres. (Les espionner tenter une méthode de bruteforcing, surtout).

Bref, trop d’endroits où donner de la tête, et je ne suis pas le seul à m’en plaindre sur leur IRC. Mais des gens ont réussi, donc nous devons persévérer … ou changer de challenge ! :D

Finalement, il fallait aussi regarder dans /git, qui est lisible, et qui grâce au fichier index permet de trouver ufhkistgfj.php… Qui contient la clé, lue avec l’exploit … Pfiou. Tout ça pour ça.


Trololo

fichier (trololo.pcap) Ahh le nom m’inspire. Dans la catégorie Inforensic, ce challenge est jugé comme relativement simple par ceux qui l’ont réussi. Raison de plus pour le réussir nous aussi !

La consigne nous indique qu’un virus impacte un ordinateur, et qu’ils ont pu enregistrer le traffic réseau. Bien entendu, l’utilisateur écoutait une musique … et le dump réseau fait 12Mo. Vilain. Surtout quand on sait ce qu’il écoutait … Bref, concentrons-nous sur ce qui nous intéresse : le traffic pas musical.

On ouvre notre fichier pcap avec Wireshark, et on filtre sur not rtp. On remarque qu’un accès HTTP est effectué. Cool. On filtre sur http, et on obtient la seule requête HTTP effectuée : GET /config.enc, l’extension .enc nous prévient que le fichier sera chiffré … et vu que le challenge vaut 100 points, ce qui n’est pas tant que ça, on tente XOR.

La particularité de XOR, c’est que si la clé de chiffrage est plus petite que le texte, elle est répétée et peut-être devinée par analyses statistiques. Notre fichier fait environ 1000bits, si la clé était si longue ce serait impossible de retrouver le contenu original … Sortons donc l’artillerie ! On installe xortool :

 ./xortool ../packet.bin
The most probable key lengths:
   1:   82.9%
  58:   9.2%
  64:   7.9%
Key-length can be 4*n
Key-length can be 8*n
Key-length can be 16*n
Most possible char is needed to guess the key!

La documentation nous conseille de tester en précisant le caractère le plus fréquent. En considérant que c’est le bit null, avec ./xortool ../packet.bin -c 00, on obtient un fichier presque lisible, mais pas assez pour dire qu’on a réussi. Mais cela confirme que notre méthode est bonne ! Et cela nous apprend que le fichier est au format XML. Donc rempli de <, >, (espace ;D).

En considérant que l’espace est le caractère le plus fréquent, on a :

$ ./xortool ../packet.bin -c 20 -l 4
1 possible key(s) of length 4:
\xff\xff\xff\xff
Found 1 plaintexts with 95.0%+ printable characters

Et on obtient un fichier parfaitement lisible. La clé est donc \xff\xff\xff\xff, c’est à dire … \xff … Vraiment, une clé d’un caractère ?!

On a la clé, on peut valider sur le site. À nous les 100 points !


Who am I?

Celui que j’ai préféré. On nous donne un dump de disques Android, et on nous demande de retrouver le fichier vérolé, c’est à dire celui qu’un vilain pirate a placé ici.

C’est aussi celui que j’ai commencé à 23Hxx, donc en temps très limité. #Adrénaline !

On commence, le fichier est corrompu. Super.

$ zip -FF whoami.zip --out who.zip
Fix archive (-FF) - salvage what can
	zip warning: Missing end (EOCDR) signature - either this archive
                     is not readable or the end is damaged
Is this a single-disk archive?  (y/n): y
  Assuming single-disk archive
Scanning for entries...
 copying: boot.img  (4900506 bytes)
 copying: cache.ext4.tar  (0 bytes)
 copying: cache.ext4.tar.a  (182 bytes)
 copying: data.ext4.tar  (0 bytes)
 copying: data.ext4.tar.a  (70849805 bytes)
 copying: nandroid.md5  (224 bytes)
 copying: recovery.img  (7276639 bytes)
 copying: system.ext4.tar  (0 bytes)
 copying: system.ext4.tar.a  (391363518 bytes)

Ensuite, on extrait les systèmes de fichiers : (Aucune idée de pourquoi le .a…)

$ tar xvf data.ext4.tar.a
$ tar xvf system.ext4.tar.a

Le reste pourrait nous intéresser, mais on va déjà se concentrer sur ça.

Je commence par regarder dans system/app et system/priv-app/, et je vérifie manuellement que les applications sont légitimes. jarsigner -verify -verbose -certs *.apk

Soit ma vérification était bancale, soit j’ai manqué d’attention, mais je n’ai pas su trouver l’intrus.

Par hasard, je grep -Ri NDH . … rien. (Tant mieux, sinon c’est vraiment nul)

Petit tour dans data/, je grep -R SMS . à la recherche des applications qui ont besoin des SMS. Pareil, rien ne me saute aux yeux, à part Google Authenticator (qui n’est même pas installé) qui a besoin de lire les SMS .. étrange. Je me dirige dans data/data/com.google.android.GoogleAuth, en utilisant Tab pour auto-compléter … quand tout à coup ! il y a un blocage pour compléter entre com.google.android ou com.google.andorid. Surprenant ? Coup de chance ! En effet, l’application Alarm était l’application vérolée. On récupère l’APK dans system/app, on s’arrange pour le décompiler et on récupère le seul fichier source : com.google.andorid.alarm.wake_up.

Pour le décompiler, j’ai utilisé javadecompilers.com/apk qui utilisé lui même jqdx-gui.

On récupère les classes statiques qui renvoient des String (et donc, potentiellement le flag) et on lance tout ça dans Ideone.com, afin de gagner du temps. Vu qu’Ideone.com ne permet pas de lancer de code Android, on supprime la ligne d’import de Base64 (il est dans java.utils) et on adapte le code en Base64.getEncoder().encode(...getBytes()), en supprimant le second argument. On aurait même pu retirer totalement le passage en Base64 …

En sortie, on obtient le mot FLAG pour m3112e(), et en passant la chaîne de caractères qui apparait partout en argument à la méthode compliquée (je nomme les méthodes comme je veux, ok ?), on obtient base64("FLAG" + "quelquechose"). Ce quelque chose est ceci : THEFLAGISNDHTHENLEFTSQUAREBRACKETxxxTHENRIGHTSQUAREBRACKET, le flag est donc xxx !

Et c’est gagné ! :D + 150 Points !


Mickey

Challenge gratuit, si jamais les participants s’ennuient … Pourquoi pas.

Juste non. Je n’ai ni la méthode, ni même une piste. (ah si, Big Numbers et Modulo à gogo)

Du coup j’ai perdu mon temps sur le logo.png, qui contient (comme tous les ans / à tous les CTF) du contenu caché. En l’occurence, il s’agit d’un ZLIB :

$ binwalk logo.png
binwalk logo.png

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 347 x 50, 8-bit colormap, non-interlaced
361           0x169           Zlib compressed data, best compression, uncompressed size >= 17400

On l’extrait et on le décompresse :

$ dd if=logo.png of=logo skip=361 bs=1
$ printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" |cat - test|gzip -dc > test.txt

On l’ouvre dans un éditeur qui ne remet pas à la ligne et on aligne les symboles. Par soucis de lisibilité, j’ai remplacé les T par des . et les S par des #.

Et … on obtient ceci : (qui est parfait pour clore cet article, même si ça n’a rien à voir avec le challenge) (Ce qui se situe à gauche et à droite n’est rien de plus que le logo NDH, que je n’ai pas réussi à aligner)


Conclusion

J’ai fait ce que j’ai pu, j’ai récolté 500 points et ça me suffit largement, je suis classé environ 150ème sur 700. L’important, c’était de participer, et de voir à quoi ça ressemblait. Et de progresser dans ce domaine. Je n’avais utilisé quasiment aucun outil que je présente dans cet article. J’ai appris en lisant la documentation, et en lisant d’autres CTF.

Et surtout, j’ai pu tester mes compétences … et c’est à ça que servent les CTF !

PS: Je parle de mon expérience en temps réel, lors de la Nuit du Hack, en temps imparti. En temps illimité, d’autres choses auraient pu se produire … qui sait ?


VOUS EN VOULEZ ENCORE ?! Si oui, consultez un médecin. Rendez-vous sur CTFTime pour en avoir plus !