Résumé partiel des DC18 CTF Quals

24 mai 2010 – 21:29

Contrairement à ce que certains pensent peut-être en voyant ce blog à l’abandon depuis plusieurs mois, non, je ne suis pas mort ! J’ai simplement été assez pris ces derniers temps, et j’essaye au passage de finaliser ma première release de XeeK pour la NDH le 19 juin…

Ce week-end, j’ai rapidement participé aux qualifications du CTF de la DefCon 18. Je n’avais pas refait de challenge depuis Insomni’hack 2010 donc je me suis senti obligé de me décrasser un peu le cerveau. Ma participation à ces qualifications est toutefois restée limitée ; n’ayant pas pu m’inscrire à temps j’ai du rejoindre une team à l’arrache (Big-Daddy).

Vue d’ensemble

Le challenge consistait en 6 séries de 5 épreuves, chacune intitulée de la sorte :

  • Poursuit trivial (épreuves générales)
  • Crypto Badness (cryptographie)
  • Packet Madness (réseau)
  • Binary L33tness (cracking, applicatif)
  • Pwtent Pwnables (exploitations de services)
  • Forensics (analyse de fichiers)

N’ayant pas énormément de temps à consacrer à ce challenge, je n’ai pas vraiment touché à tout mais me suis focalisé sur la crypto et le réseau.

Dans l’ensemble j’ai trouvé ça plutôt sympa, même si j’ai quand même deux remarques négatives à faire :

  • L’interface du panneau d’équipe codée en Java était vraiment plus que foireuse. Je ne sais pas si c’était du au fait qu’on soit plusieurs à partager le même compte, mais il fallait bouriner comme un dingue sur le bouton de validation afin d’avoir un espoir de valider les réponses (valides), voire même parfois se reconnecter.
  • Certaines épreuves étaient relativement capilotractées, dans le sens ou elles n’étaient pas réalistes du tout et qu’il fallait être à peu près aussi dingue que leur concepteur pour la valider. Enfin je pense que ça doit être à peu près pareil sur tous les challenges du genre…

Étant loin d’avoir réussi à valider toutes les épreuves, je vous propose de dévoiler la solution de deux d’entre elles que j’ai trouvées assez sympathiques.

Packet Madness 200

Dans cette épreuve, on fournissait une capture de paquets (à renommer en .pcap), le but étant comme toutes les autres épreuves de trouver une passphrase de validation.

Sous Wireshark, on constate qu’il s’agit d’un trafic TCP entre un client et un serveur. En utilisant l’option « Follow TCP Stream », on constate qu’il semble s’agir de trafic binaire…

Capture Wireshark

Cependant, l’indice laissé par l’épreuve suggère que les deux machines communiquent avec une « langue » inhabituelle. S’agirait-il d’un encodage connu mais peu courant ? En regardant un peu les encodages supportés par Wireshark, on s’aperçoit rapidement qu’ils ‘agit en fait de l’EBCDIC, encodage créé par IBM qui semble remonter à l’époque quasi-préhistorique des cartes perforées… En utilisant cet encodage, on y voit tout de suite plus clair :

Trafic décodé

Trafic décodé

Il s’agit donc d’une sorte de telnet encodé en EBCDIC. Visiblement, le serveur propose plusieurs options, comme la création d’un compte, l’authentification, un mode maintenance et un affichage de news. L’IP du serveur visible dans le dump, 192.42.96.121, existe bel et bien et héberge bien ce service sur le port 8686. Il semble donc que la réponse à l’épreuve doive être obtenue en s’y connectant. Malheureusement les outils classiques comme telnet et netcat ne supportent pas l’EBCDIC. Mais c’est sans compter Python, qui le supporte nativement, et qui va ainsi nous permettre de se coder un petit client maison :

#!/usr/bin/python

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.41.96.121", 8686))

while True:
 data = s.recv(3000)
 print data.decode('EBCDIC-CP-BE').encode('ascii')

 input = raw_input()
 input_to_send = input.decode('ascii').encode('EBCDIC-CP-BE')+"%"
 s.send(input_to_send)

Ce client permet d’envoyer des commandes au service tout en affichant ses réponses. Toutefois, j’ai constaté qu’il était toujours en retard d’un message par rapport au serveur. N’ayant pas le temps d’être perfectionniste, je m’en suis contenté et ai pu me créer un compte sur le serveur avec la commande « a », puis me loguer avec ce même compte (« l »). Le mode administrateur « m » ne fonctionnant pas, il ne restait que l’affichage des news (« n »), qui donnait cette sortie :

5/21/2010 - Defcon qualifiers are underway.

5/18/2010 - It's Bob Randolph's birthday today, wish him well
 if you see him

5/16/2010 - It's IBM old timer's night at the bowling alley.
 The key thing to remember at these things is that:
 once upon a time IBM ruled the world

4/29/2001 - First post! w00t!

La news du 16 mai 2010 m’interpelle du fait de la présence du mot « key »… Nous testons donc la réponse « once upon a time IBM ruled the world » à l’épreuve, et après un léger acharnement collectif sur le team board, nous parvenons à valider ! Finalement, ce n’était pas compliqué…

Crypto Badness 400

Dans cette épreuve épinglée de l’indice « crack me », on fournit une archive tgz (encore une fois à renommer). Celle-ci contient un fichier blob.dat visiblement chiffré et une clé publique pubkey.pem. Il s’agit donc visiblement de casser la clé publique afin de retrouver la clé privée et déchiffrer le fichier .dat. Voici la clé publique :

-----BEGIN RSA PUBLIC KEY-----
MGgCYQDK2YRVfJfgOUMaImrXJ/DG1D7z1BhGnxs3UEmyKYQ+6fg7H5dzisJ09fYf
QB8h8ZE+S2S7MbVaONOYwN/tALE5LwiJcRxEs1nnl2xhf8xzTwbj6VwmR2CRtS9G
LnlBPbUCAwEAAQ==
-----END RSA PUBLIC KEY-----

Il s’agit d’une clé RSA. Pour analyser ce genre de clé, l’outil tout indiqué est OpenSSL. Cependant, une mauvaise surprise nous attend…

$ openssl rsa -pubin -in pubkey.pem -text
unable to load Public Key
20934:error:0906D06C:PEM routines:PEM_read_bio:no start line:pem_lib.c:647:Expecting: PUBLIC KEY

Impossible de lire la clé avec cet outil, donc. Un peu de recherche Google, et je trouve quelqu’un qui a eu le même problème :

The problem is this an RSA public key PEM or DER generated by Ruby’s OpenSSL::PKey::RSA are unreadable by OpenSSL, Bouncy Castle and probably other crypto tools.

The actual issue is that Ruby’s OpenSSL::PKey::RSA#to_pem and #to_der generate a PKCS#1 public key format while the openssl rsa command only works PKCS#8 formatted public keys.

Ainsi, la clé semble avoir été générée avec le module OpenSSL de Ruby, et son format PKCS#1 empêche sa lecture avec OpenSSL. Qu’à cela ne tienne, nous allons utiliser ce même module pour la lire ! On ouvre le shell Ruby (IRB) et on tape :

$ irb
irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> a=OpenSSL::PKey::RSA.new(File.read("pubkey.pem"))
=> -----BEGIN RSA PUBLIC KEY-----
MGgCYQDK2YRVfJfgOUMaImrXJ/DG1D7z1BhGnxs3UEmyKYQ+6fg7H5dzisJ09fYf
QB8h8ZE+S2S7MbVaONOYwN/tALE5LwiJcRxEs1nnl2xhf8xzTwbj6VwmR2CRtS9G
LnlBPbUCAwEAAQ==
-----END RSA PUBLIC KEY-----

irb(main):003:0> a.params
=> {"dmq1"=>0, "dmp1"=>0, "iqmp"=>0, "n"=>12301866845301177551304949
58384962720772853569595334792197322452151726400507263657518745202199
78646938995647494277406384592519255732630345373154826850791702612214
29134616704292143116022212404792747377940806653514195974598569021434
13, "d"=>0, "p"=>0, "e"=>65537, "q"=>0}

On arrive ainsi à extraire le module (n) et l’exposant public (e). Ce dernier étant quasiment toujours le même, seul le module importe ici. Pour savoir si nous avons une chance de le casser, regardons sa taille en bits :

$ python
>>> import math
>>> n=12301866845301177551304949583849627207728535695953347921973224
52151726400507263657518745202199786469389956474942774063845925192557
32630345373154826850791702612214291346167042921431160222124047927473
7794080665351419597459856902143413
>>> math.log(n, 2)
767.66426718447133

Il semble codé sur 768 bits… Cela ne vous rappelle rien ? Le nombre RSA-768 a été cassé en décembre dernier. Et comme par hasard, c’est bien le même qui est utilisé ici… Nous allons donc pouvoir utiliser sa factorisation pour générer la clé privée correspondante. Par chance, StalkR de la team Nibbles a posté un article très similaire issu du CTF Codegate il y a quelques mois. Il y indique la procédure à suivre pour générer le certificat, qui pour simplifier se résume à télécharger un en-tete d’OpenSSL ainsi qu’un programme C servant à générer la clé. On lance tout ça :

$ gcc -lssl -o create_private create_private.c
$ ./create_private

La clé privée est générée dans le fichier private.pem. Il ne reste alors plus qu’à décrypter le fichier blob.dat en utilisant la commande :

$ openssl rsautl -decrypt -inkey private.pem -in blob.dat -out output.txt

Moment de vérité…

$ cat output.txt
how long until 1024 falls by the wayside?

Il s’agit bien de la réponse à l’épreuve (validée encore une fois grâce à un acharnement collectif).

Comme l’épreuve précédente, ce n’était techniquement pas compliqué, l’obstacle majeur étant la non-reconnaissance de la clé par OpenSSL… En tout cas merci à StalkR pour son article qui a été d’une grande aide.

Conclusion

Ces épreuves ont été l’occasion pour moi de me remettre un peu dans le bain des CTFs. Le niveau était globalement assez élevé (plus dur que l’année dernière à en croire certains), et nous n’avons réussi à valider que quelques épreuves; les résultats sont disponibles ici. Pour ceux que ça intéresse, vous trouverez le résultat de la dernière épreuve du Poursuit trivial ici.

Chapeau à Nibbles pour avoir terminé 10ème ; un grand bravo à eux. Et merci à tous ceux avec qui j’ai challengé, en particulier à Silkut, MatToufoutu, et Dad. En espérant que nous parviendrons à créer une équipe HZV l’année prochaine, du moins si nous avons plus de temps et de ressources…

  1. 12 réponses à “Résumé partiel des DC18 CTF Quals”

  2. Joli compte-rendu, et rapide dis-donc.

    Content que mon article ait aidé :) D’ailleurs as-tu regardé packet300 (la version mise à jour sans DHE) ? Ce même article répondait aussi à l’épreuve. En fait l’épreuve du codegate c’était crypto400 + packet300 de la defcon.

    Dire qu’à l’origine ils voulaient qu’on casse DHE, rien que ça… au moins on aura essayé ! D’après ce que j’ai vu ça devait être crypto500 :o

    Bravo à vous aussi !

    Par StalkR le 24 mai 2010

  3. Arf, je me suis cassé les dents sur cette épreuve, j’avais vu que le certif était dispo sur le web, mais Wireshark parvenait pas à déchiffrer. C’était donc à cause de l’échange Diffie-Hellman que ça foirait… Bien vu. Par contre je n’ai pas eu la version mise à jour, tu sais où on peut la trouver ?
    Sinon tu as réussi le packet 100 ? Parce que perso je suis pas allé loin… :)

    Par Emilien Girault le 24 mai 2010

  4. Hi,

    Beau write up ;) Perso on c’est pas mal cassé les dents sur fo300 et je crois que c’est le write up qui m’interesse le plus :s

    GG a toi dude ;)

    Par sanguinarius le 25 mai 2010

  5. Good post. I did not solve this particular challenge. The ruby trick was neat. Glad I learnt something :-)

    Par rajat le 25 mai 2010

  6. Voilà la version mise à jour de packet300 : http://stalkr.net/files/defcon/2010/quals/packet300/pkt300_235bc13c00343230782.pcap

    Avec Nibbles on a prévu de sortir quelques writeups, voilà déjà celui que j’ai fait pour packet100 si tu veux : http://stalkr.net/files/defcon/2010/quals/packet100/writeup.txt

    Et erratum c’était bin500 qui devait être crypto500 (quand on extrait on voit c500).

    Par StalkR le 25 mai 2010

  7. Super, merci. Effectivement, ça marche beaucoup mieux sans DHE… « twisted by design » ;) .
    Sinon, la pkt100 était vraiment capilotractée… gg à vous !

    Par Emilien Girault le 25 mai 2010

  8. hmmm interessant , quelqu’un a fait une sauvegarde de l’ensemble des epreuves et l’as mis on-line :)

    Par jerome le 3 juin 2010

  9. Bonjour,

    Vraiment très intéressant à lire. Merci pour ce retour d’expérience !

    Bon je retourne à mes docs car je suis loin de ce niveau ^^.

    Cordialement,

    Par Inazo le 4 juin 2010

  10. >>> math.log(n, 2)
    767.66426718447133

    Il semble codé sur 728 bits…

    —-

    Pourquoi 728 bits ?

    Par didier le 7 avril 2011

  11. Au temps pour moi, c’est corrigé ;)

    Par Emilien Girault le 11 avril 2011

  1. 2 Trackback(s)

  2. 25 mai 2010: Les tweets qui mentionnent Résumé partiel des DC18 CTF Quals | Segmentation fault -- Topsy.com
  3. 25 mai 2010: Les tweets qui mentionnent Résumé partiel des DC18 CTF Quals | Segmentation fault -- Topsy.com

Écrire un commentaire