Je commence un nouveau post, mais à peine entamé, celui-ci a un goût de réchauffé :-(
Alors, je ne pense pas passer trop de temps sur ce sujet. C’est le second challenge de sécurité de la société NES pour Misc Magazine. Si le premier était orienté cryptographie, celui-ci, commence tout pareil mais avec des épreuves bien différentes.
En introduction, je vous invite à suivre et à écouter Le Comptoir Sécu, notamment l’épisode 36 sur les mots de passe.
Autrement, voici la publicité en question.
Et le QR Code
de l’épreuve.
J’ai utilisé le script du précédent challenge pour extraire les données de l’image.
Un premier coup d’œil permet d’identifier une substitution mono-alphabétique, qui s’avère être un ROT-13
. J’ai donc décodé cette chaine de caractères en utilisant le logiciel Cryptool.
La seconde URL
m’a emmené vers un contenu sur pastebin.fr et c’est ici que tout commence. Dans un premier temps, il faut télécharger un fichier.
Une fois le fichier en ma possession, j’utilise la commande file
pour savoir à quoi ce fichier correspond.
C’est une archive zip
que je décompresse pour obtenir … une autre archive. J’ai donc réitéré l’opération de décompression, cette fois-ci, avec du XZ
, et ainsi de suite…
Jusqu’à obtenir un fichier de données (ci-dessus, data
en vert). Je lis ce fichier avec un éditeur hexadécimal. j’observe que c’est un fichier UDF
(Universal Disk Format). Autrement dit, une image CD-ROM.
Je découvre quatre dossiers que je copie localement.
Je regarde dans le dossier 01
, c’est la première épreuve.
Dans ce dossier, avec le fichier readme.txt, il y a :
Je lis le fichier de capture avec Wireshark
, à l’intérieur il y a une image au format PNG
(Portable Network Graphics) à récupérer.
Un aperçu de l’image, agrandie, avec le logiciel Gimp
.
Généralement, je commence toujours par regarder dans les métadonnées. Mais à la vue du dégradé de gris, j’entreprends de noter soigneusement les valeurs pour chaque pixels.
NOTE : En rouge, les valeurs RGB. Et en bleue, la représentation hexadécimal des valeur
RGB
.
A l’aide d’un script Python
, pour le fun, je décode le message ci-dessous.
#!/usr/bin/env python
rgb_pixels = [
(69, 69, 69),
(88, 88, 88),
(73, 73, 73),
(70, 70, 70),
(32, 32, 32),
(69, 69, 69),
(83, 83, 83),
(84, 84, 84),
(32, 32, 32),
(84, 84, 84),
(79, 79, 79),
(78, 78, 78),
(32, 32, 32),
(65, 65, 65),
(77, 77, 77),
(73, 73, 73)
]
def rgb_to_hex(rgb_tuple) :
from struct import pack
return(pack("BBB", *rgb_tuple).encode('hex'))
def main() :
from binascii import unhexlify
secret = ""
for color in rgb_pixels :
secret += unhexlify(rgb_to_hex(color)[:2].upper())
print("Message: %s" % (secret))
if(__name__ == "__main__") :
main()
Le message obtenu suggère de regarder les métadonnées (justement ^^), ainsi, en utilisant la commande exiftool
.
Dans les métadonnées j’observe le Token
et je remarque au passage qu’ils ont de l’humour chez NES.
J’avoue aussi être un peu intrigué par les coordonnées GPS retrouvées plus bas.
Je décide de creuser un peu avec Googlemap
pour obtenir avec facilité la localisation sur carte (pas très loin de la NSA).
Le Token
récupéré ressemble à un Hash
. J’utilise l’outils hash-identifier
pour déterminer le type de Hash
(md5
, sha-1
, …).
Plusieurs possibilités ? Oui et … non, si j’interprète correctement le résultat obtenu les deux Possible Hash
sont basés sur SHA-1
(Secure Hash Algorithm 1). Ainsi, je suppose que c’est un condensat SHA-1
.
Pour l’anecdote, dans le cas du Hash
de MySQL5
, on comprend bien dans l’image ci-dessus que la fonction SHA-1
est utilisée deux fois :
Les autres algorithmes (Tiger-160, Haval-160, …), moins communs, peuvent être testés en derniers recours.
Aussi, je dispose d’un fichier de règles pour JtR
(John-the-Ripper) et ce qui semble être un dictionnaire de mots de passe.
J’ajoute la règle dans le fichier de configuration /etc/john/john.conf
et je fais un test qui plante lamentablement.
[List.Rules:NesSpecialRules_french]
# Uppercase first letter and l33t5p34k transform
# I love Cyber-ish things !
>12 <13 lcse3sE3sé3sè3
Alors avec un peu de documentation je vais surement m’en sortir… Bin oui ! Mais que fais cette règle exactement ?
<N
rejeter le mot à moins qu’il ne soit inférieur à N caractères,>N
rejette le mot à moins qu’il ne soit plus long que N caractères,l
permet de convertir en minuscules,c
permet de mettre en majuscule la première lettre du mot,sXY
remplace tous les caractères X du mot par Y.Ainsi, le premier problème se situe au niveau du contrôle de la taille. En effet, il y a une incohérence sur cette action, d’autre part, cette fonction semble prendre qu’un seul digit pour son contrôle.
Le second problème se situe dans les commandes de classe de caractères (sXY
) qui ne fonctionnent pas avec les caractères éè
dans notre cas. J’ai cherché un bon moment sans pour autant avoir trouvé une solution native à JtR
.
En mode bourrin (pas tout le temps, mais … presque), je réécrit cette règle dans le fichier /etc/john/john.conf
.
[List.Rules:NesSpecialRules_french]
# Uppercase first letter and l33t5p34k transform
# I love Cyber-ish things !
>9 lcse3sE3
Et j’exécute la commande suivante.
Je profite de ce moment pour proposer une solution alternative qui respecterais plus efficacement la règle décrite initialement (en modifiant quand même le contrôle de la taille). Une représentation théorique ci-dessous.
[List.Rules:NesSpecialRules_french]
# Uppercase first letter and l33t5p34k transform
# I love Cyber-ish things !
>12 lcse3sE3sé3sè3
L’idée est de créer un script Python
qui va faire ce contrôle à la place de JtR
.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def capitalize(str):
from string import capwords
return(capwords(str))
def transform(str) :
str = str.decode("utf-8")
if(len(str) > 12) : # >12
str = str.lower() # l
str = capitalize(str) # c
str = str.replace(u"e", u"3") # se3
str = str.replace(u"E", u"3") # sE3
str = str.replace(u"é", u"3") # sé3
str = str.replace(u"è", u"3") # sè3
else :
str = ""
return(str.encode("utf-8"))
def main() :
import sys
from argparse import ArgumentParser
from fileinput import input
parser = ArgumentParser(prog=sys.argv[0])
parser.add_argument('-f', '--file', type=unicode, required=True, help='specify input file.')
args = parser.parse_args()
candidate = ""
for line in input([args.file]) :
candidate = transform(line.strip())
if(candidate) : print("%s" % (candidate))
if(__name__ == "__main__") :
main()
Cette première étape est enfin terminée avec l’obtention du mot de passe : Cyb3rs3curit3
.
Dans le dossier de la seconde étape, j’observe des fichiers texte contenant les informations des deux épreuves suivantes.
Avec Wireshark
je lis le premier fichier de capture réseau qui s’appelle sniff02.pcapng.gz
. On peut observer un AP (Access Point) diffusant de nombreux SSID
.
En opérant une concaténation des SSIDs
, je peux lire un message. J’ai réalisé un script pour le démontrer car Wireshark
rencontre des difficultés avec les statistiques WLAN (il n’affiche pas tous les SSIDs
).
#!/usr/bin/env python
from scapy.all import *
def main() :
from argparse import ArgumentParser
parser = ArgumentParser(prog=sys.argv[0])
parser.add_argument('-f', '--file', type=unicode, required=True, help='specify input file.')
args = parser.parse_args()
ap_list = []
packets = rdpcap(args.file)
for packet in packets :
if(packet.haslayer(Dot11)) :
if((packet.type == 0) and (packet.subtype == 8)) :
if(packet.info not in ap_list) :
ap_list.append(packet.info)
print("AP MAC: %s with SSID: %s " % (packet.addr2, packet.info))
if(__name__ == "__main__") :
main()
Effectivement le message : WEP
(Wired Equivalent Privacy) est sécurisé si vous n’avez pas assez d’IVs
(Initialisation Vectors).
Le SSID
qui m’intéresse s’appel WEP
, dans Wireshark
, les statistiques WLAN
m’informent que le niveau de sécurité est basé sur … WEP
.
Alors, en effet si j’essais de casser la clé WEP
de façon traditionnelle en comptant sur le nombre de IVs
cela ne fonctionnera pas.
Je commence par convertir le fichier de capture fourni dans un format interprétable par Aircrack-NG.
Et je lance une attaque, juste pour confirmer…
Le logiciel Aircrack-NG ne permet pas de faire une attaque bruteforce (caractères par caractères de façon incrémentielle), cependant, il est possible d’utiliser un dictionnaire de mots.
Ainsi, je vais utiliser JtR
pour lui donner un petit coup de main, en générant un dictionnaire on the fly
avec l’option –incremental
.
Ce qui équivaut à … une attaque bruteforce.
Ainsi le mot de passe du colocataire est obtenu : dani1
.
Mais, il y a un autre fichier de capture sniff03.pcapng.gz
que je m’empresse d’ouvrir. C’est la seconde partie de cette deuxième étape.
Je lis le nom du SSID
, CRACK_ME_IF_YOU_CAN. En parcourant les packets je remarque le protocole EAPOL
(Extensible Authentication Protocol).
La version IEEE_802.11i-2004 observée ci-dessous indique qu’il s’agit d’un échange 4-Way Handshake pour WPA2 (Wi-Fi Protected Access).
Je dois avouer que j’ai passé un peu de temps sur cette épreuve. D’une part, j’ai commencé par tester avec le dictionnaire fourni dans l’étape précédente, et puis, en ajoutant des règles. Mais sans succès.
J’ai alors décidé de changer mon approche en commençant par le logiciel utilisé que j’ai remplacé par oclHashcat. Ainsi sur un poste disposant d’une bonne carte graphique (mais tout est relatif).
En utilisant l’accélération GPU
(de la carte graphique) je dispose de plus de puissance de calcul. L’idée est simple :
Pour effectuer le calcul avec avec oclHashcat, comme précédemment, je commence par convertir le fichier de capture pour qu’il convienne à Aircrack-NG.
La commande Aircrack-NG suivante me permet d’obtenir un fichier contenant le Hash
interprétable par oclHashcat (à l’aide du commutateur -J
).
Et le mot de passe apparait : janeimeelyzza
.
C’est la fin de la seconde étape.
Il est temps de découvrir ce que cache la troisième partie de ce challenge.
Youhou ! C’est un binaire qu’il faut désormais étudier, pour ma part, c’est le moment que je préfère…
Quelques vérifications d’usages, juste pour confirmer qu’il s’agit réellement d’un PE
(Portable Executable).
J’utilise PEiD
(comme d’habitude) pour graber d’autres informations…
J’utilise aussi PE Studio pour contrôler les données optional-header
et regarder les chaines de caractères hardcodé.
C’est un binaire 32-bits
qui n’est pas packé, j’exécute alors le fichier binaire sur une sandbox. Et … même s’il est tard, il n’est pas l’heure.
Je commence par une analyse statique avec IDA. J’ai renommé les call
(en rouge) pour plus de compréhension (j’ai croisé les informations avec une analyse en mémoire).
Il y a une fonction de crypto très basique (xor
).
En mémoire avec x64dbg, le point d’entré du programme est à l’adresse 0x00409BA4
. L’appel à l’adresse 0x00409BF1
exécute une série d’instructions, de calls, … , et fini par stocker l’heure courante en mémoire. D’où le nom que j’ai donné à la fonction dans IDA (GetCurrentTime
).
Ainsi, la chaine de caractère obtenu (heure:minutes:seconde) est comparée par un appel à l’API CompareStringA
. Cet appel se situe dans le call
à l’adresse mémoire 0x004009C51
. Dans IDA je l’ai nommée TimeStringComparisonA
.
L’idée est d’exécuter le programme jusqu’à l’appel call crack_me.4067AE0
. Pour ceci, j’ai positionné un point d’arrêt (Breakpoint
) à l’adresse 0x00409BF1
et une fois sur cette instruction j’appuie sur la touche F8
pour exécuter le call
. L’heure courante est ainsi chargée en mémoire.
Je remplace la valeur 03:46:31
par 05:22:43
.
Plus loin, la chaine de caractères 05:22:43
est xoré avec la clé 0F1F53466303495C
.
Et voici … le Token
.
J’ai fais un script Python
pour illustrer tout ce bazar.
#!/usr/bin/env python
def xor(data, key) :
n = 0; enc = ""
for k in key :
enc += chr(ord(data[n]) ^ int(k, 16))
n += 1
return(enc)
def main() :
from binascii import hexlify
xor_key = ['0x0F', '0x1F', '0x53', '0x46', '0x63', '0x03', '0x49', '0x5C']
token = xor("05:22:43", xor_key)
print("{NesToken}:%s hex:%s" % (token, hexlify(token).upper()))
if(__name__ == "__main__") :
main()
Et voilà, …, le sésame de cette troisième étape : ?\*tQ9}o
.
Enfin, la quatrième et dernière étape.
Bien organisé, je commence par me constituer une wordlist
contenant toutes les combinaisons possible du mot de passe à rechercher.
Cyb3rs3curit3Cyb3rs3curit3
Cyb3rs3curit3dani1
Cyb3rs3curit3janeimeelyzza
Cyb3rs3curit3?*itQ9}o
dani1dani1
dani1Cyb3rs3curit3
dani1janeimeelyzza
dani1?*itQ9}o
janeimeelyzzajaneimeelyzza
janeimeelyzzaCyb3rs3curit3
janeimeelyzzadani1
janeimeelyzza?*itQ9}o
?*itQ9}o?*itQ9}o
?*itQ9}oCyb3rs3curit3
?*itQ9}ojaneimeelyzza
?*itQ9}odani1
Je propose deux méthodes pour retrouver le mot de passe de l’archive.
Première méthode, en utilisant JtR
combiné avec un script Bash
.
#!/bin/bash
# 7z-JtR Decrypt Script
# -DEBUG
# set -e
# set -x
#
if [ $# -ne 2 ]; then
echo "Usage $0 <7z file> <wordlist>";
exit;
fi
john --wordlist="$2" --stdout | while read i
do
echo -ne "\rTrying \"$i\" ";
7z x -p$i $1 -aoa > /dev/null;
STATUS=$?;
if [ $STATUS -eq 0 ]; then
echo -e "\rArchive password is: \"$i\"";
break;
fi
done
La seconde méthode, en utilisant oclHashcat.
Et le mot de passe est : dani1janeimeelyzza
.
Le mot de passe découvert je peux lire le contenu de l’archive arch04.7z
qui s’avère être la dernière épreuve de ce challenge de sécurité.
Alors que va raconter le fichier readme.txt
?
Il me faut désormais obtenir le mot de passe d’une clé privée GPG
.
Je commence par faire une copie du fichier secret-key-B16B984C.asc
(qui contient une copie de sauvegarde du couple clé privée, clé publique).
Avec l’éditeur de texte ViM
j’édite le fichier que j’ai nommé private-key.asc
pour opérer un peu de nettoyage…
Notez : La commande
%s/^M//g
va nettoyer le fichier en supprimant lesCtrl-M
en fin de ligne. Aussi, je garde uniquement la clé privée et je supprime tout le reste.
Le résultat ainsi obtenu.
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v2.0.17 (MingW32)
Comment: Gnu Privacy Tools
Comment: Download at http://www.gnupt.de
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
1HcLOY4bHI+u6M7d3rRUXoZvHw8=
=AZeb
-----END PGP PRIVATE KEY BLOCK-----
J’utilise gpg2john
pour convertir la clé privée ci-dessus dans un format interprétable par JtR
. Et c’est parti, avec le même dictionnaire que l’étape précédente…
Le dernier mot de passe s’affiche enfin : Cyb3rs3curit3?\*itQ9}o
.
Le mot de passe obtenu sert à lire le contenu du fichier bravo.txt.asc
.
Un petit mail pour dire bonjour et …
Fin du challenge.