Pirates Corporation & Co.


> Hop là ho ! une bouteille de rhum...

#

Misc. N°91 (NES Challenge-2) || Passw0rd Cr4ck1ng


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.


Décoder le QR-Code

J’ai utilisé le script du précédent challenge pour extraire les données de l’image.


Substitution mono-alphabétique

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.


Extraction des données

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.


John the Ripper (JtR)

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()


Les métadonnées

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, …).


Le condensat (hash)

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 :

  1. Une première fois, un condensat est obtenu à partir d’un texte clair.
  2. Une seconde fois, un autre condensat est obtenu à partir du condensat précédant.

Les autres algorithmes (Tiger-160, Haval-160, …), moins communs, peuvent être testés en derniers recours.


Attaque par dictionnaire avec JtR

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 ?

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.


Wireless (WEP/WPA)

WEP (Wired Equivalent Privacy | Weak Encryption Protocol)

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.


WPA (Wi-Fi Protected Access)

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 :

  1. En commençant avec une attaque par bruteforce sur 1-9 caractères (en fait, dans ce cas 8-9 caractères) si l’on considère un mot de passe de 8 caractères minimum pour un mot de passe WPA2. Bien sûre en commençant doucement, en testant uniquement en minuscule par exemple. Et puis en enrichissant avec des digit, des majuscules, …
  2. En utilisant des listes de mots de passe (Crackstation, rockyou, … et toutes les autres).
  3. En ajoutant des règles de transformation.
  4. En lançant des attaques hybrides.

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.


Rétro-ingénierie sur un binaire

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.


Gnu Privacy Guard (GnuPG)

Le fichier 7z

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.


La clé privé GPG

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 les Ctrl-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

lQO+BFjP9BABCAC86m5BhFGg4Yy1Jbt/56CPI8yJ7FYxx1uAVXtXiOYzuoJM81O9
exfc/Ep+5QYggy2i7dAN5GlgHA8M2Cgz1PiPpXZLoi1GfyOVJQGbs1coT5rSmXhQ
j/xvBb8GpaegsDAl0EPa6hRpcrdFXJwwgJIeRS6nLGnoyK7aEsutf/Wfi0A5rHll
H+ZtiI1NFs0x8iIaO9/Evrlb2RtxXj6e4x3LIQF1fbR5sXaZ6wF+LFO1l5nmfXZV
JKflvDLR53/VrHYe7wwx5HqZZRyT4ttihcgcbp+NnN+c2ziDBZdZ451NWBE6HBJa
jLDDk2Wk1QIV/LWXGvA45GxJv9UJa15bDomrABEBAAH+AwMCK0MZpnr0hqS1Kd7a
ZLgenHQX7zlHJleQc1NYSSJGNQMNS+xvKOoaT+b/7jgeS56ZvQG6dE2vYaOY6qv4
zFD2jRQzZBuqf3TA4gDu67QkzR7BEeHZd0LntRohoggEMC2DlAw60dKFqH8uJ6ZT
DQ50wmxF9QnhrDFpAxfRyHhwVrrcoXzlUSFheLnTUUjrTT3ZUueZZz/IHUErW+Sp
7GT+Hz3segyZxZSbX9QMko2GCSwIeU3nk8NqB+gDfuSWb4ZRbAyn7JBhNUJ+eQ2C
PtRqr8hjZIpuPry6xBpAGUMIpvhreucSLL4CaGJS81gjeL7UHnW8Y8mmM49VuB7P
BhwXe2p9KxzOxifZcxFigFlx7RP5Y2csnu1Dcn2EiYy3AVjT5RU75nMB8EbUqYml
/c91WCmL3WGnocNtzFqXRJBKTLGaUjxuf5NrMRvT3L7hU9QDLvVVaL+8MLzB9OdC
jxoWjFGskqK9at3BGCmJBpzxO7MFPbKh+OB5QF9nqnZRyZ7mtrx3biovXn2406zd
HhbIx1Ofl3dIN/enfvdYKPOkI2CUgm9ytkCcWyUfBVRxthsNf/lVS7kU4v1OLpQR
eyCsOApzxoNpQQTqsE1GUrM7blTl4aVLAd/ldFmZYlRxhpUMVzu4GagldjHt7rde
ZbM0RBh1mom2/2DyqICkYRWOHbpEPOiP7s1NOABlF2LPiK5pE0MIVSRtKmY8mQqX
NEHH5fxXzWlqi/dRcOjET2rXslnT2GuN1n9SnIMQW8b6Kc4pbOCSLoIMPtoeOVCz
/duucxnPE9HrucwUA1WyW5F3VbGeZPTzFQXlJuWsvdRPOG30lieq37nDQfONIXLG
dSVj926DE1QbYi0d9Dd313yv8OCL+xreDObEIF5pA1cHaJqH+jIaGlvEBTzOrxF9
kLQjUVJjaGFsbGVuZ2VfbmVzMiA8Y2hhbGxlbmdlQG5lcy5mcj6JATgEEwECACIF
AljP9BACGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEC8gM8yxa5hM65wH
/2F7V+Y+2I4pCB/AFZ81iLLeWRH9FRSlYdzxEizhIIfMU0TJlm9bfZDh19uc0RHb
KjU7k5ECaAh6QEnt2a4LvUcDn7cOkmonLt2mIQwegRCJgB3XPeuPEKd+80UMhmkQ
zJ9VYaJHX7Yl3iETH2oXVzrD5eGvlfNuQW9QrXA23LcRq6rUaASr9ERzm0u8tyQ/
4Gm2O7uTaaiPHyL8rXrakqjEdEiv2D9I90rPRI//PGGFsiv1g5HmGqag2sKQpIWM
b5zAMo7Kq5Vzq0GvQl+EbUJ2oIo7iq1LAtvTEVCYbZOGLjzdxAFZ9aRAE/P3V3i2
Z8lhWU3SMy7YwV0xNdShI/adA74EWM/0EAEIALu3veh9Te5jjHnKABdW/KVu9NEl
nEPxLNKOA6R5yzn9nSBpK628e7nwT16frcsbtd5hVMmNPeh2bfoLffo0+N7EeZ/6
pEICl+O4UQttYye8PIZ8KvZQvDIm6BaOU0GaQXKIhopQWFExy0hsNtUBNU+rtX60
jfLTLKG5z7LzKb0Wgk04L+nGyZPxZ3m7bCGtOCo3eB6xeMSC3x7LcuqvO3gj4o97
vqk2UCtv6YEKwEhrR/C31rQ7HGASWPs453gJFkq+mMK0AOlr6DxpnPas70jswI5Y
qFsd+IgZZ4Po/nVObNWoReFDMfVOUWei1lYzaNQyBzGpjYaOMs/Hfmjb1n8AEQEA
Af4DAwIrQxmmevSGpLUKBDjgnH/G66J2pixNlLjOdgmqtUV+9wQG7zdYNufPIbAO
rdzGhMczbi09YVthW7c5eLKcBUwsJ7XG+ZG2Ls+53uTuOO+tesky/yKz64tKtGoB
GEevL6kb+mCaJd+F03SwO2EmgvvjzBLpJ1OA7kMIKC2sx8uwsEkPOZ86CSSwOlxO
R/sZWQs3zP9+Yy/MMso/j45oGv+3WWiaKTlbdsTn0derFtyGpQ0Qm0R0xoF4lApZ
mJrxpJf5l4Majv6YX5W2dIGlGGVEWHMnSwuTe5uTh7TRb/tUxzicR0xYkM3C9a3o
2IuARDX8OydY6dvtgRA+yv6KrIZGtTV8SInsKeYkb0bSFGa4hPbH8zIRbpKVIcMX
I3EFrsfqJK/j9YbRP2SS7VEVZ5I5xn7CphMtkd5s7soh8WG55ahLTCL1T81tU0mt
EkV3H9laagozcnCjhL0xrd7T+kLJ7byKhnA9T3zYrkmUzvQZ+iqVkii4kexuiGia
YpcWB/ZiqejAnrybX3V/o7J5DS358tJrcrC6RhFBEKXENA8fpP8gngGESwaonmV1
8fZxv5w2X0B9dPWdLDUaXSm/Ix88XxaLfIQgZBToM/a+dVA0vGUCK+txs8wLz+LL
lsvDdqarYsWFHHAWN7A2ewFP8YrQHK4nIJdCVRz0Zh+3VPLz4OgqTvicp3GBXAg3
fCJT3el1qDNWYBO7+UfSJH2zTyumVWpCrxSsTn9yz432tWhKrnEvUBgQNKd/nF8d
BAsTI7M7KUwBhh0OUrJ5M0fAV4pl8K7hhGGyRqOsKR8i4PxygwcPYlSnpzk0kdTs
WplSeFBsIHnzNlixHhySE0RO+5VYGYUrDLvONWBw2WWYqqA/wHUdocbgSoqArfni
uXxrWc3jXBLX1RGwqLG104iiiQEfBBgBAgAJBQJYz/QQAhsMAAoJEC8gM8yxa5hM
6JgH/iIekmsFZIG+WPlpmrr3AoPedLSqaq2kYjFa5q9H5hPoc8WJi2PI6GI9ZP+1
JrcVZdlHmoh/M7X6e0a0MlCnLGZg6c6eI+popAND0DlSpISAHtkMYRrrKi1ah94d
h1DvSVxuwb2BCjrq+zxiZcAyHtU7gEJVEXYBSwUB7h8SJXxGutRj+CZhCYh+hynu
RN858NrNscYRCyUoCVDLGr1Cu7utQG/mMd5QEYvVGMN91sEYcz8jUsxv8fo+rqWh
uVtZ1mqLGAhpW5VmfThpvLdRRrE456QA9mv98RyfMby+lPkLdMJOx5pxfmpmB4Ev
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.