Pirates Corporation & Co.


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

#

NES R&D Challenge (PCAP) || Special Cypherpunk Reverser [part. I]


Je voulais justement faire un sujet sur les packers et il se trouve qu’étant d’un naturel un peu curieux j’ai mis la main sur un autre CTF (Capture The Flag) qui se cache sur le site de la société NES et dont l’une des épreuves consiste justement à unpacker un binaire PE sur Windows (Portable Executable) 32-bits classique, enfin … presque.

Mais avant de commencer, un petit intermède musicale (parce que … vives les Pirates!) 😛


Donc … pour votre challenge de sécurité habitants des strates, rendez-vous sur le site de la société NES.

Si vous avez lu mes posts précédents, c’est cette même société qui a conçu deux challenges de sécurités dans Misc. Magazine.

Le premier orienté cryptanalyst paru dans le magazine numero 89 et le second orienté password cracking laissent place à un tout autre défi, en software reversing.

Cette fois pas de QR Code, mais un lien, un fichier sans extention. J’utilise la commande file pour déterminer le type de fichier.

Il s’agit d’un fichier de capture réseau pcap (format de sortie par défaut de tcpdump avec le commutateur -w).

Je peux aussi le vérifier manuellement.

Les 4 premiers octets d4 c3 b2 a1 constituent la signature (magic number) utilisés pour identifier les fichiers pcap.

Les 4 prochains octets 02 00 04 00 sont la version majeure (2 octets) et la version mineure (2 octets), dans notre cas 2.4, le tout en little-indian.

J’ouvre le fichier avec Wireshark et … failed!. La capture réseau semble être corrompue, je note tout de même que j’apercois la lecture de deux packets dans le fond.

Deux solutions, comprendre le fonctionnement d’un fichier de capture de type pcap ou trouver/utiliser un outil qui se charge de ce travail à notre place.

Evidement, je choisi de commencer par utiliser l’utilitaire pcapfix.

Une fois réparé, je peux ouvrir le fichier de capture réseau.

J’observe, et puis … si je fais un focus sur les échanges HTTP (HyperText Transfer Protocol), un fichier Zip (File Format) nommé BreakMe.zip.

Depuis Wireshark, j’importe le fichier dans un repertoire de travail local.

Et je prends connaissance de son contenu.

Le fichier README.txt me donne quelques informations sur cette épreuve.

Et … bon sur le coup, je me suis dis que ce challenge risquait d’être un peu plus hard que les précédents (en me relisant, je confirme…).

Le dump de la ROM cryptée ressemble à ceci.

Dans une sandbox Windows, j’exécute le programme pour avoir un aperçu visuel de celui-ci.

J’utilise successivement PEiD et PE Studio pour obtenir quelques informations sur le binaire.

C’est un fichier PE (Portable Executable) pour les architectures 32-bits. En rouge, le programme m’indique que le fichier binaire ciblé est packé avec Yoda’s Protector version 1.3.

Je remarque que certains anti-virus détectent le binaire comme étant un Malware. Personnellement, je trouve ceci un peu discriminatoire car je ne vois pas en quoi ce code est malveillant. Mais c’est un autre débat.

Je penses (peut-être à tort pour certains) que protéger contre le RE (Reverse Engineering) un binaire est une bonne pratique. En fait, protéger en RE n’a pas vraiment de sens si l’on considère que les protections servent uniquement à le ralentir (ou le rendre plus compliqué).

Avec IDA, j’obtiens le message d’erreur ainsi que le graph suivant.

Au premier coup d’oeil (sans regarder le code) je pense tout de suite une routine de déchiffrement.

A savoir que j’ai également un chat qui s’appelle … Yoda (et des fois je me dis qu’un petit coup de désassembleur ne lui ferait pas de mal…).

Plus serieusement, voici une petite présentation du logiciel Yoda’s Crypter.

C’est un packer assez sympa au vue du nombre d’options (mais outdated depuis 2004/2005).

Désormais mon prochain objectif est d’unpacker le binaire original. En effet, si je veux faire une analyse dans les meilleurs conditions possibles je dois envisager de reconstruire le binaire original (avant qu’il soit packé).

Un packer en quelques mots sert a encapsuler un binaire dans un autre (un peu comme les poupées russes).

Pour être honnête, j’ai commencé par chercher un outils magique pour unpacker mais je n’en ai pas trouvé. Dans un second temps, j’ai cherché un tutorial traitant du sujet.

Mon avis personnel, éviter de coder, tout de suite, ou de trops réfléchir. Si le travail a déja été réalisé je dois commencer par m’appuyer sur ces informations.

Et après quelques recherches, je suis tombé sur un pseudo tutorial en … flash (que j’ai vraiment hésité à ouvrir). Et finalement, à la lecture du document (bin, dans une sandbox) j’ai trouvé ça cohérant et j’ai adapté quelques trucs en fonction du contexte (pas le même debugger, pas le même programme, …).


Unpack Yoda’s Protector 1.3

The hard way (inside memory)

Je commence par ouvrir l’application ProtectMe.exe avec un debugger, en l’occurence x64dbg (bien sûr c’est une application 32-bits, dans ce cas, il faut utiliser la version 32-bits du debugger).

A l’ouverture de l’application je suis stoppé à l’adresse mémoire 0x778F01C4.

Dans mon cas, j’appui deux fois sur la touche F9 pour arriver dans l’espace mémoire de l’application, adresse mémoire : 0x00407060.

Avec la touche F7 j’avance, pas à pas et je m’arrête sur le call à l’adresse mémoire 0x00407067, juste après l’instruction pushal.

Je mets un Hardware Breakpoint sur ESP (pour plus tard).

Et je recherche l’API Microsoft IsDebuggerPresent, qui, comme son nom l’indique … vérifie la présence d’un debugger.

Une fois la fonction localisée, dans mon cas à l’adresse mémoire 0x754538F0 (la localisation dépend de la version de la librairie kernel32.dll, en fonction du système d’exploitation).

Je positionne un autre Breakpoint sur l’instruction ret de cette fonction. Et je poursuis l’exécution du programme avec la touche F9 jusqu’à ce point.

La valeur 0x1 dans le registre EAX signifie qu’un débugger est détecté.

Pour passer cette protection, je remplace la valeur 1 par 0.

De nouveau, je poursuis l’exécution du programme avec la touche F9.

Je suis stoppé à l’adresse mémoire 0x00407976, c’est mon Hardware Breakpoint sur ESP.

J’exécute pas à pas les instructions, le jmp à l’adresse mémoire 0x00407982 me fait atterrir sur une zone mémoire vide. Ce qui déclenche une exception SEH (Specific Exception Handling).

Je pense que cette chose est une routine destinée à faire planter le programme lors d’une analyse dynamique, en utilisant SEH comme vecteur (à vérifier …).

Je continue pas à pas, tranquillement, jusqu’à l’adresse mémoire 0x778FFAE2.

Dans l’onglet SEH du debugger, j’obtiens comme information l’adresse de la prochaine instruction du déroulement normal du programme à l’adresse 0x0040790B, je positionne un BreakPoint ici.

Je poursuit ainsi l’execution avec F9 jusqu’au prochain BreakPoint, puis, avec la touche F7 je passe sur chaques instructions (pas à pas).

Juste après l’instruction ret, j’arrive dans l’espace mémoire d’un module (apparement dans la librairie ntdll.dll).

Je poursuis alors l’exécution avec Alt+F9 (run to user code) pour arriver dans l’espace mémoire de l’application unpacké.

Je me positionne sur l’instruction push ebp à l’adresse mémoire 0x00401220 et je définis cette position comme nouveau point d’origine (entry point).

Je garde le debugger ouvert et en parallèle, j’exécute l’application LordPE pour faire un dump mémoire de l’application.

Je commence par corriger la taille de l’image (qui parait bien … petite).

Puis, je réalise un dump complet de l’application.

Ensuite avec l’outil Import REConstructor, je selectionne l’application (toujours ouverte dans le debugger).

Dans IAT info needed je renseigne OEP (Original Entry Point). C’est simple, je connais l’adresse mémoire de mon nouveau point d’entrée 0x00401220 et je connais l’adresse mémoire de l’image de base du programme 0x00400000.

Je laisse les autres valeurs inchangées pour le moment et je clique sur IAT AutoSearch.

Si l’écran suivant apparait, c’est bon signe. J’ai trouvé une adresse dans l’IAT originale, mais avant de faire un Get Import je vais m’assurer d’avoir un RVA correct.

Je mets temporairement de coté Import REConstructor le temps de faire un tour dans le debugger. De trouver un pointeur memoire dans l’espace d’adressage (0x00400000) du programme.

Ici, j’ai trouvé l’adresse 0x0040613C, je me rends dans le dump memoire de cette adresse.

Dans le dump, je veux observer les adresses mémoires (je mets un filtre là-dessus). Et je redescends la mémoire pour trouver la première adresse 0x00406000.

Comme dans l’illustration ci-dessous.

De retour dans Import REConstructor, je fais une soustraction de l’adresse 0x00406000 moins l’image de base. Et cette valeur, je la renseigne dans le champs RVA.

A propos de la taille, ce n’est pas une valeur que l’on peut connaitre comme çà de façon générale (enfin, je crois). Donc, je choisis de garder la valeur 0x3B0 qui n’est ni trop grande, ni trop petite.

Et Get Imports.

Ensuite, j’obtiens comme résultat (en vert) la liste des fonctions trouvées. Je selectionne Show Invalide pour afficher la liste des fonctions invalides ou … non résolues.

Comme dans l’exemple ci-dessous.

Je selectionne tout ce qui n’a pas été résolu précédement, clique-droit et je selectionne Trace Level1 (Disasm).

Il y a d’autres méthodes, mais celle-ci fonctionne avec Yoda’s Protector.

Une nouvelle fois, je selectionne Show Invalid.

Je selectionne tout ce qui n’a pas été résolu, puis clique-droit et Cut thunk(s).

Ci-dessous, le résultat obtenu. Je peux désormais sélectionner Fix Dump pour appliquer un patch au fichier de dump de l’étape LordPE.

Dans le repertoire, j’obtiens un second fichier ProtectMe_dumped_.exe. Je l’exécute et je constate le fonctionnement.


Reverse unpacked!

Avant d’aller plus loin, je check le fichier avec PEiD, Pe Studio, …

J’utilise le plugin KANAL dans PEiD à la recherche de signatures cryptographiques.

Je n’ai rien observé de très flagrand, alors, j’ouvre IDA pour une approche un peu plus … statique. Mais je rencontre un problème lors de la décompilation du programme avec comme message d’erreur sp-analysis failed.

Je décide à ce moment de me passer de l’analyse statique pour avancer sur mon prochain objectif, à savoir, de faire péter la protection qui m’interdit d’utiliser le logiciel. Quitte à revenir sur IDA … plus tard.

Donc, j’exécute le programme avec x64dbg, je me rends jusqu’au point d’entrée. Ici, je n’oublie pas d’injecter les arguments au programme.

Je ne vais pas faire tout le cheminement, mais grosso merdo, jai regardé les intermodular calls et la présence de WSAStartup et gethostname m’a intrigué.

Je m’explique, WSAStarup est employée lorsque l’on veux programmer des sockets. Ici, la librairie Winsock est utilisée uniquement pour obtenir ne nom de l’ordinateur (avec la fonction gethostname).

Je suppose que la protection peut avoir un rapport avec le nom de la machine. Sinon, il reste comme possibilité de partir du point d’entrée mémoire du programme et de dérouler les instructions.

Je mets un Breakpoint sur WSAStartup et gethostname.

Je passe sur WSAStartup, puis, un peu plus loin je m’arrête sur l’appel à gethostname.

Je remarque à l’adresse memoire 0x0040194B un xor. Et plus bas à l’adresse 0x00401978 l’instruction cmp. Je commence alors par regarder ce qui se passe par ici.

C’est le nom de la machine qui est xoré avec comme clé 0xCC.

Le nom d’hôte crypté de ma sandbox est comparé avec une chaîne de caractères (également cryptée).

En analysant le code je comprends que le programme désire que le nom d’hôte sur lequel il est exécuté commence par une chaîne de caractères bien précise.

Je fais un script pour obtenir le decryptage de cette chaine de caractères.

from binascii import unhexlify

def unxor(input_str) :
  output_str = ""
  for c in unhexlify(input_str) :
    output_str += chr(ord(c) ^ 0xcc)
  return(output_str)

encrypted_str = "89ADB888AD9CA3A39CA3A3"
print(unxor(encrypted_str))

Mais voyons plutôt comment modifier ce comportement pour que tous les noms d’ordinateurs soient acceptés.

Pour cela, je dois supprimer l’instruction cmp et remplacer l’instruction jne par un jump inconditionnel jmp.

Je teste le comportement en mémoire et je patche manuellement à l’aide d’un éditeur héxadécimal.

Rien de compliqué, encore une fois, l’adresse 0x00401978 que je dois patcher je la soustrait à l’image de base 0x00400000 du programme.

Ainsi j’obtiens l’offset 0x1978 et j’écris la modification dans le fichier binaire directement.

J’exécute le programme nouvellement patché (sans oublier les arguments), je passe la protection, pour atterrir un peu plus loin. Je constate un appel à la fonction strlen qui sert à compter le nombre de caractères du mot de passe transmis en argument.

Et c’est … la catastrophe !

Je pense que je n’étais pas loin d’avoir la même expression quand le programme à planté…

Comme je connais les contraintes d’utilisation du logiciel (nom d’hôte et taille du mot de passe), j’exécute le programme original pour obtenir le même résultat.

J’ai fais ça, car, sur le coup j’ai quand même eu un doute sur la qualité de la reconstruction du programme original. Mais apparement pas.

Je réitère la même opération avec le programme patché et lors du plantage j’attache mon debugger.

Ca ressemble à un plantage intentionnel, l’instruction int3 est bien connue lorsque l’on veut faire un BreakPoint dans un programme. Après tout, c’est peut-être tout aussi bien le système qui genère cette interruption.

A priori, je ne suis plus très loin de la fin de ce challenge (je ne savais pas que quand j’ai écrit çà, je me trompais).

Je décide de regarder d’un peu plus près le désassemblage du programme ou plutôt ce qui l’en empêche.

J’ouvre IDA et je commence un nouveau projet avec le binaire patché.

Un peu plus haut dans IDA je me suis arrêté à l’erreur sp-analysis failed lors du désassemblage.

En réalisant quelques recherches sur Internet, j’ai trouvé un bout de script Python pour IDA qui me permets de localiser les adresses où se situe les erreurs dans les portions qu’IDA arrive à interprêter.

Je découvre ainsi trois adresses à problème 0x00401290, 0x0040130E et 0x004016B7.

C’est le script ci-dessous que j’ai utilisé dans IDA.

def get_sp_failed():
  failed_funcs = []
  ea           = 401000
  while ea != BADADDR :
    ea = idaapi.find_text(ea, 0, 0, "sp-analysis failed", idaapi.SEARCH_DOWN | idaapi.SEARCH_NEXT)
    if ea != BADADDR :
      func = idaapi.get_func(ea)
      failed_funcs.append(func.startEA)
      ea   = func.endEA
  return failed_funcs

print("-- Discovered SP Bad Address --")
for addr in get_sp_failed() :
  print("0x%08x" % (addr))
print("-------------------------------")

Je commence par la première adresse mémoire et j’observe ce qui ce passe à partir de l’instruction fnop à l’adresse mémoire 0x004012A0.

Je suits le call à l’adresse mémoire 0x004012AD qui m’enmène à l’espace ci-dessous. Et je fais de mon mieux (lol) pour suivre les instructions et voir jusqu’où ça va m’ammener.

Plus loin, en sortie de ce code j’atterri à l’adresse mémoire 0x004012F7. Ce qui n’est pas très loin de l’adresse mémoire 0x004012AD de départ.

Ainsi, à l’adresse mémoire 0x004012AD je supprime le call initial et je le remplace par un jmp 0x004012F7 précédé de quelques Nop’s.

Je reporte la modification dans le binaire avec l’aide d’un éditeur hexadécimal.

Et je fais la même chose pour les deux adresses suivantes, en appliquant le même patch. Et bla bla bla editeur hexadécimal.

Mais ce n’est pas fini, il faut répéter cette opérations plusieurs fois avant d’obtenir un désassemblage correct.

Voici la liste des adresses à patcher.

Dans IDA je peux désormais rechercher des fonctions avec des boucles et des maths…

Je me rends à l’adresse 0x004013D0 et j’analyse le code en mémoire, plus bas, je localise la chaine de caractère d’entrée corespondant au mot de passe. Je suis sur la bonne voie.

J’observe une boucle, des opérations de calcul, une sorte de table d’index…

Je fais quelques tours dans boucle et je passe en mode papier pour comprendre, et puis, je bricole un script Python qui reproduit le calcul.

Parce que je dois avouer que je n’ai pas tout de suite compris. Au départ j’ai tout simplement mal interprêté la chose (mais c’est de bonne guerre). Même si j’ai vu le jmp eax à l’adresse mémoire 0x0040014CF.

Ci-dessous, le résultat que produit le calcul avec un mot de passe. Bien sûr, le tableau varie en fonction du mot de passe entrée.

J’ai commencé par poser sur un ou deux forums pour glaner des informations, le calcul ne produit pas vraiment de condensat pouvant être comparé.

En regardant quelques thèses de crypto j’ai vite compris que ce n’était pas la bonne marche à suivre (en tous cas, ce n’est pas encore le moment) 🙁

Donc après quelques efforts, l’algorithme de calcul est le suivant. Je sais que par endroit ça pique les yeux, c’est une maquette.

#!/usr/bin/env python

DEBUG = 0

def create_hash_table(password, cryptogram) :
  k1         = 0
  c_loop     = 0
  while(c_loop != (256)) :
    if(DEBUG == 1) : print("Loop counter: %s" % (c_loop))
    if(c_loop == (len(password) - 1)) :
      a = ord(password[0]) + k1                       # 0x41  + 0x0   = 0x41  | 0x41  + 0x40  = 0x81  | ...
    else : 
      a = ord(password[c_loop]) + k1                  # 0x41  + 0x0   = 0x41  | 0x41  + 0x40  = 0x81  | ...
    if(DEBUG == 1) : print("%s\t= %s + %s\t || a  = ord(password[c_loop]) + k1" % (hex(a), hex(ord(password[c_loop])), hex(k1)))

    b       = a + int(cryptogram[c_loop], 16)         # 0x41  + 0xFF  = 0x140 | 0x41  + 0xFE  = 0x17F | ...
    if(b == 0) : b = 255                              # 0xFF
    if(DEBUG == 1) : print("%s\t= %s + %s\t || b  = a + int(cryptogram[c_loop], 16)" % (hex(b), hex(a), cryptogram[c_loop]))
  
    k1      = b - int(shl(int(sar(b, 8), 16), 8), 16) # 0x140 - 0x100 = 0x40  | 0x17F - 0x100 = 0x7F  | ...
    if(DEBUG == 1) : print("%s\t= %s - %s\t || k1 = b - int(shl(int(sar(b, 8), 16), 8), 16)" % (hex(k1), hex(b), hex(int(shl(int(sar(b, 8), 16), 8), 16))))
  
    param1  = c_loop
    param2  = k1 
    invert(cryptogram, param1, param2)
    if(DEBUG == 1) : print("%s" % ("*" * 50))
  
    c_loop += 1
  return(cryptogram)

def create_index_table() :
  list_itable = []
  for i in range(0, 256) :
    list_itable.append(hex(i))
  return(list_itable[::-1])	

def invert(cryptogram, x1, x2) :
  if(DEBUG == 1) : print("Invert: %s <> %s" % (cryptogram[x1], cryptogram[x2]))
  t_byte         = cryptogram[x1]
  cryptogram[x1] = cryptogram[x2]
  cryptogram[x2] = t_byte

def print_column(list_items, n) :
  list_copied = list(list_items)
  c_loop      = 0
  while(c_loop <= (len(list_copied) - 1)) :
    if(len(list_copied[c_loop]) == 3) :
      list_copied[c_loop] = "[" + list_copied[c_loop].replace("0x", "0x0") + "]"
    else :
      list_copied[c_loop] = "[" + list_copied[c_loop] + "]"
    c_loop += 1
  
  for i in range((len(list_copied) / n) + 1) :
    print("".join(list_copied[i * n : (i + 1) * n]))
  return()

def sar(dest, count) :
  return(shr(dest, count))
  
def shl(dest, count) :
  return(hex(dest << count))
  
def shr(dest, count) :
  return(hex(dest >> count))
  
def main() :
  password = "A" * 256
  print("Password: %s" % (password))
  print("Password len: %d\n" % (len(password)))
  print_column(create_hash_table(password, create_index_table()), 16)
  exit(0)
  
if(__name__ == "__main__") : 
  main()

L’application de l’algorithme me donne pour résultat le tableau suivant.

Désormais, il me faut comprendre l’instruction smsw eax car cette instruction est à l’origine d’un méchant plantage plus loin.

En analysant l’instruction smsw et en observant l’erreur produite à la fin de la fonction, je décide de modifier l’instruction jmp eax par jmp 0x004014F5.

En effet, l’erreur se produit à la fin de la fonction, EIP n’a pas la bonne adresse de retour et plante le programme. Par intuition (on va dire) j’ai supposé que le registre EAX n’était pas dépilé correctement.

Et un petit peu plus loin, je découvre que je suis dans ce qui semble être le processus d’execution normal du programme.

Intéressant, mais le problème n’est pas reglé pour autant. Je décide de poursuivre, il faut dire que je suis resté bloqué un bon moment sur cette étape (une grande pizza, une bouteille de coca et beaucoup de capsules de café…).

Donc, je me suis cassé les dents sur ce qui semble être une routine pour ralentir le RE.

Un peu plus loin, je peux observer à l’adresse mémoire 0x00401B9C le call que je suppose être à l’origine du chiffrement (entre les fonctions de lecture et d’écriture dans un fichier).

Je retourne dans IDA pour avoir un aperçu et je me retrouve confronté à la même technique contre le RE que précédement.

Mon père disait : le premier qui te traite de cheval met-lui ton poing dans la gueule, le second qui te traite de cheval traite-le de con, mais le troisième qui te traite de cheval, eh bien là il est peut-être temps d’aller te payer une selle. Le Rabbin

Je m’empresse de retourner du coté de la mémoire pour appliquer une modification.

Ainsi, en parallèle, je reporte les modifications suivantes dans un nouveau fichier ProtectME_cracked3.exe.

Dans IDA j’ai désormais une vue plus complète des routines de calcul du mot de passe aux adresses mémoire 0x00401408 et 0x00401879.

Pour confirmer, je poursuis l’exécution jusqu’à observer l’opération ci-dessous. Juste avant la fermeture du programme.

C’est le bon moment pour tester le patch. Par contre, je me suis contenté d’utiliser des simples sauts pour contourner les deux techniques anti RE (sans même ajuster d’autres registres). Alors sans doute le patch peut être amélioré.

Mais comme c’est fonctionnel.

Prochaine étape, cap sur l’algorithme de chiffrement/déchiffrement pour le modeliser, le comprendre et essayer de le casser.

Pour faciliter mes recherches (enfin, je crois…) je vais partir d’un fichier cleartext connu.

AZERTYUIOP

Je lance à nouveau le debugger, je me rends à l’adresse mémoire 0x00401879 (juste avant le call qui appel la fonction read). Et je pars de ce point.

A l’adresse 0x0040189C il y a un appel à une fonction qui a comme emplacement l’adresse mémoire 0x00401511, à l’interieur, il y a un algorithme de géneration de clés.

A l’adresse mémoire 0x00401BAB il y a une opération xor la clé est déterminée par l’algorithme précédent (pour chaque caractères à chiffrer).

Donc, le début de l’algorithme de génération de clé commence ici. Il est plutôt long en terme de ligne de code. Je me suis déja fait cette remarque de façon générale, il y a du Junk code (du code qui ne sert à rien) dans l’air.

Je note à l’adresse memoire 0x00401560 que je suis déja passé par ici, c’est mon patch.

Dans la boucle, je suis les instructions pour modeliser l’algorithme (aproximatif) de chiffrement ci-dessous.

#!/usr/bin/env python

DEBUG = 0

def chiffrement(cleartext, cryptokey) :
  return(cipher(cleartext, cryptokey))

def dechiffrement(ciphered, cryptokey) :
  return(cipher(ciphered, cryptokey))
  
def cipher(data_stream, c_square) :
  streamed = []

  c_loop   = 0
  c_pos    = 1
  k1       = 0
  while(c_loop != (len(data_stream))) :
    if(DEBUG == 1) : print("Loop counter: %s" % (c_loop))
  
    if(c_pos == 256) : c_pos = 0
  
    a = c_pos - int(shl(int(sar(c_pos, 8), 16), 8), 16)        # 0x1     = 0x1 + 0x0  | 0x2     = 0x2 - 0x0
    if(DEBUG == 1) : print("%s\t= %s - %s\t || a  = c_pos - int(shl(int(sar(c_pos, 8), 16), 8), 16)" % (hex(a), hex(c_pos), shl(int(sar(c_pos, 8), 16), 8)))
  
    b = int(c_square[a], 16) + k1                              # 0xa7    = 0xa7 + 0x0 | 0x12a   = 0x83 + 0xa7
    if(DEBUG == 1) : print("%s\t= %s + %s\t || b  = int(c_square[a], 16) + k1" % (hex(b), c_square[a], hex(k1)))
  
    k1 = b - int(shl(int(sar(b, 8), 16), 8), 16)
    if(DEBUG == 1) : print("%s\t= %s - %s\t || k1 = b - int(shl(int(sar(b, 8), 16), 8), 16)" % (hex(k1), hex(b), shl(int(sar(b, 8), 16), 8)))

    param1  = c_pos
    param2  = k1
    invert(c_square, param1, param2)                           # 0xa7 <> 0xa0         |
  
    c = int(c_square[c_pos], 16) + int(c_square[k1], 16)       # 0xa0 + 0xa7 = 0x147  |
    if(DEBUG == 1) : print("%s\t= %s + %s\t || c  = int(c_square[c_pos], 16) + int(c_square[k1], 16)" % (hex(c), c_square[c_pos], c_square[k1]))

    d = c - int(shl(int(sar(c, 8), 16), 8), 16)                # 0x147 - 0x100 = 0x47 |
    if(DEBUG == 1) : print("%s\t= %s - %s\t || d  = c - int(shl(int(sar(c, 8), 16), 8), 16)" % (hex(d), hex(c), shl(int(sar(c, 8), 16), 8)))

    if(DEBUG == 1) : print("xor key: %s" % (c_square[d]))      # 0xb7
    if(DEBUG == 1) : print("char: %s" % (data_stream[c_loop])) # A
    streamed.append(hex(ord(data_stream[c_loop]) ^ int(c_square[d], 16)))
    if(DEBUG == 1) : print("%s\t= %s ^ %s\t" % (streamed[c_loop], hex(ord(data_stream[c_loop])),  c_square[d]))

    c_pos  += 1
    c_loop += 1
  return((streamed, c_square))

def create_hash_table(password, cryptogram) :
  k1         = 0
  c_loop     = 0
  while(c_loop != 256) :
    if(DEBUG == 1) : print("Loop counter: %s" % (c_loop))
    if(c_loop == (len(password) - 1)) :
      a = ord(password[0]) + k1                       # 0x41  + 0x0   = 0x41  | 0x41  + 0x40  = 0x81  | ...
    else : 
      a = ord(password[c_loop]) + k1                  # 0x41  + 0x0   = 0x41  | 0x41  + 0x40  = 0x81  | ...
    if(DEBUG == 1) : print("%s\t= %s + %s\t || a  = ord(password[c_loop]) + k1" % (hex(a), hex(ord(password[c_loop])), hex(k1)))

    b       = a + int(cryptogram[c_loop], 16)         # 0x41  + 0xFF  = 0x140 | 0x41  + 0xFE  = 0x17F | ...
    if(b == 0) : b = 255                              # 0xFF
    if(DEBUG == 1) : print("%s\t= %s + %s\t || b  = a + int(cryptogram[c_loop], 16)" % (hex(b), hex(a), cryptogram[c_loop]))
  
    k1      = b - int(shl(int(sar(b, 8), 16), 8), 16) # 0x140 - 0x100 = 0x40  | 0x17F - 0x100 = 0x7F  | ...
    if(DEBUG == 1) : print("%s\t= %s - %s\t || k1 = b - int(shl(int(sar(b, 8), 16), 8), 16)" % (hex(k1), hex(b), hex(int(shl(int(sar(b, 8), 16), 8), 16))))
  
    param1  = c_loop
    param2  = k1 
    invert(cryptogram, param1, param2)
    if(DEBUG == 1) : print("%s" % ("*" * 50))
  
    c_loop += 1
  return(cryptogram)

def create_index_table() :
  list_itable = []
  for i in range(0, 256) :
    list_itable.append(hex(i))
  return(list_itable[::-1])	
  
def invert(cryptogram, x1, x2) :
  if(DEBUG == 1) : print("Invert: %s <> %s" % (cryptogram[x1], cryptogram[x2]))
  t_byte         = cryptogram[x1]
  cryptogram[x1] = cryptogram[x2]
  cryptogram[x2] = t_byte

def list_hex2ascii(l) :
  c_loop = 0
  while(c_loop != len(l)) :
    l[c_loop] = chr(int(l[c_loop], 16))
    c_loop += 1
  return(l)
  
def print_column(list_items, n) :
  list_copied = list(list_items)
  c_loop      = 0
  while(c_loop <= (len(list_copied) - 1)) :
    if(len(list_copied[c_loop]) == 3) :
      list_copied[c_loop] = "[" + list_copied[c_loop].replace("0x", "0x0") + "]"
    else :
      list_copied[c_loop] = "[" + list_copied[c_loop] + "]"
    c_loop += 1
  
  for i in range((len(list_copied) / n) + 1) :
    print("".join(list_copied[i * n : (i + 1) * n]))
  return()

def sar(dest, count) :
  return(shr(dest, count))
  
def shl(dest, count) :
  return(hex(dest << count))
  
def shr(dest, count) :
  return(hex(dest >> count))

def main() :
  password  = "A" * 256
  print("Password: %s" % (password))
  print("Password len: %d\n" % (len(password)))
  
  print("Init. cryptomap from passphrase:")
  cryptotable = create_hash_table(password, create_index_table())
  print_column(cryptotable, 16)
  
  print("Chiffrement...")
  
  text = "AZERTYUIOP"
  # text = text + "Z" * (512 - len(text))
  print("Text to encrypt: %s\n" % (text))
  
  print("%s\n" %("-" * 50))
  
  print("Last cryptomap state:")
  ciphered_text, cryptogram = chiffrement(text, cryptotable)
  print_column(cryptogram, 16)
  
  print("Ciphered text:")
  print_column(ciphered_text, 16)
  
  print("\nDehiffrement...")
  cryptotable = create_hash_table(password, create_index_table())
  print_column(cryptotable, 16)
  
  ciphered_text = list_hex2ascii(ciphered_text)
  clear_text, cryptogram = dechiffrement(ciphered_text, cryptotable)
  print_column(cryptogram, 16)
  print_column(list_hex2ascii(clear_text), len(clear_text))  
  
  exit(0)
  
if(__name__ == "__main__") : 
  main()

Le résultat est concluant. Mais décidément la cryptographie ce n’est pas trop mon truc. Je décide de poster un SOS parce que identifier l’algorithme est devenu une prioritée.

Egalement, je me suis dis qu’il fallait que je trouve une interpretation de plus haut niveau sur les instructions shl/sar. Qui s’avère être un modulo 256.

J’ai fini par obtenir une orientation dans mes recherches quand un membre de Zenk-Security à bien voulu répondre à une interrogation un peu … désespéré 🙁

A savoir si quelqu’un à déja vu cet algorithme quelque part. Orienté vers TEA, je suis parti sur Wikipedia voir cet algorithme. Mais ce n’est pas ce que je recherche, alors, j’ai regardé les dérivées XTEA, XXTEA et enfin RC4.

Et là ! ça peu correspondre à RC4. Par contre on observe quelques modifications par rapport à l’algorithme original.

J’ai cherché une implémentation de RC4 en Python et j’ai étudié les deux algorithmes. Aussi, j’ai grabbé d’autres informations sur Internet.

J’ai de nouveau modèlisé l’algorithme en me servant du squelette RC4 trouvé en exemple, pour obtenir le script ci-dessous.

#!/usr/bin/env python

DEBUG = 1

def ksa(key) :                                   # RC4_KSA_STANDARD
  S = []                                         # S = range(256)
  for i in range(0, 256) :
    S.append(i)

  S = S[::-1]
  i = 0
  j = 0
  while(i != 256) :
    if(i == (len(key) - 1)) :                    #   j = (j + S[i] + key[i % len(key)]) % 256
      j = (S[i] + j + key[i]) % 256
    else :
      j = (S[i] + j + key[i]) % 256
    
    S[i], S[j] = S[j], S[i]
  
    i += 1
  return(S)

def print_column(list_items, n) :
  list_copied = list(list_items)
  c_loop      = 0
  while(c_loop <= (len(list_copied) - 1)) :
    if(len(list_copied[c_loop]) == 3) :
      list_copied[c_loop] = "[" + list_copied[c_loop].replace("0x", "0x0") + "]"
    else :
      list_copied[c_loop] = "[" + list_copied[c_loop] + "]"
    c_loop += 1
  
  for i in range((len(list_copied) / n) + 1) :
    print("".join(list_copied[i * n : (i + 1) * n]))
  return()

def prga(S) :
  i = 0
  j = 0
  while(True) :
    i = (i + 1) % 256
    j =  (j + S[i]) % 256
  
    # 0xa7 <> 0xa0
    S[i], S[j] = S[j], S[i]
  
    K = S[(S[i] + S[j]) % 256] # 0xb7
    yield(K)

def rc4(stream, enc_key):
  cipher = []
  for c in stream :
    p_byte = ord(c)
    c_byte = p_byte ^ enc_key.next()
    cipher.append(chr(c_byte))
  return(cipher)

def main() :
  plaintext = "a" * 256  
  password  = "A" * 256
  
  print("Password: %s" % (password))
  print("Password len: %d\n" % (len(password)))
  
  print("Pseudo Key-scheduling algorithm (PKSA)")
  password = [ord(c) for c in password]
  S        = ksa(password)
  print_column([hex(i) for i in S], 16)
  
  # -----
  print("Text to encrypt: %s\n" % (plaintext))
  round = prga(S)
  ciphered = rc4(plaintext, round)
  print_column([hex(ord(i)) for i in ciphered], 16)
  
  # -----
  
if(__name__ == "__main__") : 
  main()

Donc, j’ai devant les yeux un algorithme ARCFOUR (RC4).

Sauf que, la fonction KSA() a été modifiée, ainsi, nous n’avons pas une implémentation standard de RC4 mais une variante si je peux ecrire çà !

Une autre faiblesse supposée de l’implémentation peut être observée dans la fonction PRGA(), car il est vivement recommandé de passer les premiers 512 bytes dans le vide (voir même, les premiers 1k dans ce que j’ai lu sur le sujet). Mais c’est un peu plus compliqué que ça.

En résumé, RC4, n’est pas un algorithme fiable, dans les faits, sous certaines conditions effectivement il ne l’est pas. Par exemple, les implémentations qui en sont faite dans SSL ou WEP.

Mais dans d’autres cas, on imagine que j’utilise l’agorithme de chiffrement de ce programme pour chiffrer un document avec une clé aléatoire de 256 caractères. Que cette clé n’est utilisée qu’une seule et unique fois pour cet échange. Je vous mets au défi de decrypter le message.

C’est ce que j’ai retenu de ces recherches. Il me semble également que ce post fait référence au même challenge et çà date d’octobre 2013 (je l’ai pourtant supposé quand j’ai regardé les dates dans le binaires).

Bref, j’ai essayé de combiner différentes attaques connues sur l’algorithme, sans résultats.

Je suis parti sur une attaque avec un dictionnaire de mots, j’ai testé rockyou et dark0de avec le script suivant.

#!/usr/bin/env python

def ksa(key) :                                   # RC4_KSA_STANDARD
  S = range(256)[::-1]                           # S = range(256)
  
  j = 0
  for i in range(256) :
    j = (j + S[i] + key[i]) % 256                # j = (j + S[i] + key[i % len(key)]) % 256
    S[i], S[j] = S[j], S[i]
  
  return(S)

def print_column(list_items, n) :
  list_copied = list(list_items)
  c_loop      = 0
  while(c_loop <= (len(list_copied) - 1)) :
    if(len(list_copied[c_loop]) == 3) :
      list_copied[c_loop] = "[" + list_copied[c_loop].replace("0x", "0x0") + "]"
    else :
      list_copied[c_loop] = "[" + list_copied[c_loop] + "]"
    c_loop += 1
  
  for i in range((len(list_copied) / n) + 1) :
    print("".join(list_copied[i * n : (i + 1) * n]))
  return()
  
def prga(S) :
  i = 0
  j = 0
  while(True) :
    i = (i + 1) % 256
    j =  (j + S[i]) % 256
  
    S[i], S[j] = S[j], S[i]
  
    K = S[(S[i] + S[j]) % 256]
    yield(K)

def rc4(stream, enc_key):
  cipher = []
  for c in stream :
    p_byte = ord(c)
    c_byte = p_byte ^ enc_key.next()
    cipher.append(chr(c_byte))
  return(cipher)

  
def repeat_to_length(string_to_expand, length):
  return(string_to_expand * (int(length/len(string_to_expand))+1))[:length]

def brute(known_bytes, unknown_bytes) :
  K = []
  i = 0
  for k in unknown_bytes :
    for n in range(256) :
    if((k ^ n) == known_bytes[i]) : K.append(n)
    i += 1
  return(K)

def verify_rc4_candidate(filename, password, first_bytes) :
  S  = ksa(password)
  KS = prga(S)
  C  = rc4(open(filename, "rb").read(9), KS)
  if("".join([chr(c) for c in first_bytes]) == "".join(C)) :
    return(True)
  return(False)

def main() :
  rom_filename = "ROM.dmp"
  known_bytes = [ord(c) for c in ":10000000"]
  
  # Open ROM.dmp and read the firsts known bytes.
  unknown_bytes = [ord(c) for c in open(rom_filename, "rb").read(9)] # 04 A0 8F 51 E8 B9 F8 8F 53 | :10000000 | len: 9
  C = brute(known_bytes, unknown_bytes)
  
  
  # Open dictionary
  # dictionary_path = "darkc0de.lst"
  dictionary_path = "rockyou.txt"
  
  try :
    with open(dictionary_path) as file : 
      for line in file :
        word     = line.strip()
    
        if(word != "") :
          password = [ord(c) for c in repeat_to_length(word, 256)]
          S        = ksa(password)
      
          K = prga(S)
          for _ in range(0, 9) : KS = K.next()
          
          # Candidates passwords
          if(C[8] == KS) :
            if(verify_rc4_candidate(rom_filename, password, known_bytes)) :
              print("Password: %s" % ("".join([chr(c) for c in password])))
              exit(0)	

  except Exception as Error :
    print("Error: %s" % (Error))
    exit() 
  
if(__name__ == "__main__") : 
  main()

Cela a pris un peu de temps, sans compter qu’avec un language comme Python le bruteforce n’est pas très efficient. Et puis, cette première attaque n’a rien donnée.

Ainsi, j’ai adapté un script trouvé sur Internet pour casser du RC4 avec du multithread en incrémental. Mais finalement ce n’est toujours pas très efficace.

Par contre, pour vérifier le mot de passe ce script utilise l’entropie de Shannon sur le texte après le décryptage.

Ci-dessous, le script (modifié) pour notre implémentation de RC4, mais la version originale est disponible ici.

__author__ = 'cdumitru'

import sys
import numpy
import string
import itertools
from multiprocessing import Pool
from time import time
import cProfile

#ALPHABET = string.digits
#ALPHABET = string.ascii_lowercase
#ALPHABET = string.ascii_uppercase
#ALPHABET = string.letters + string.digits
#ALPHABET = string.letters + string.digits + string.punctuation
#ALPHABET = string.printable
ALPHABET = string.ascii_lowercase + string.ascii_uppercase + string.digits

KEY_LENGTH = int(sys.argv[2])
FILE_NAME  = sys.argv[1]
CPU_COUNT  = 2

def ksa(key) :                                   # RC4_KSA_STANDARD
  S = range(256)[::-1]                           # S = range(256)
  
  j = 0
  for i in range(len(key)) :
    j = (j + S[i] + key[i]) % 256                # j = (j + S[i] + key[i % len(key)]) % 256
    S[i], S[j] = S[j], S[i]
  
  return(S)

def prga(S) :
  i = 0
  j = 0
  while(True) :
    i = (i + 1) % 256
    j =  (j + S[i]) % 256
  
    S[i], S[j] = S[j], S[i]
    
    K = S[(S[i] + S[j]) % 256]
    yield(K)

def rc4(stream, enc_key):
  cipher = []
  for c in stream :
    p_byte = ord(c)
    c_byte = p_byte ^ enc_key.next()
    cipher.append(chr(c_byte))
  return(cipher)

def gen():
    """
    Iterates through the alphabet one letter at a time
    """
    for i in ALPHABET:
        yield tuple([i])

def repeat_to_length(string_to_expand, length):
  return(string_to_expand * (int(length/len(string_to_expand))+1))[:length]

def check(key, data):
    # Decrypts the data with the given key and checks the entropy
    
    S = ksa([ord(c) for c in repeat_to_length(key, 256)])
    KS = prga(S)
    decr = "".join(rc4(data, KS))

    # compute for the decrypted data block

    # interpret decrypted data as an int array
    int_array = numpy.frombuffer(decr, dtype=numpy.uint8)
    count = numpy.bincount(int_array)
    # compute probability for each int value
    prob = count/float(numpy.sum(count))
    # thow away zero values
    prob = prob [numpy.nonzero(prob)]
    # Shannon entropy
    entropy = -sum(prob * numpy.log2(prob))

    # if this doesn't look like a random stream then jackpot
    if entropy < 7.9:
        print('Key: {0}, Entropy: {1}'.format(key, entropy))

def worker(base):
    # read 64KB from the file
    data = open(FILE_NAME, 'rb').read(2**16)

    # generate all the strings of KEY_LENGTH length and check them

    # We know prior that the key starts with a. Remove the next two lines for generic behavior
    if string.ascii_lowercase in ALPHABET:
        base = tuple(['a']) + base

    for i in itertools.product(ALPHABET, repeat=KEY_LENGTH-len(base)):
        check(''.join(base + i), data)

def parallel():
    # Starts a number of threads that search through the key space
    p = Pool(CPU_COUNT)
    p.map(worker, gen(), chunksize=2)
    p.close()
    p.join()

def serial():
    worker(tuple())

if __name__ == "__main__":
    serial()

Entre temps, on me souffle dans l’oreillette sur Zenk-Security que le bruteforce est la seule methode.

A savoir qu’avec le script précédent j’ai estimé à la louche :

Bon, je crois que c’est clair … je vais coder un programme en C.

Ci-dessous, le code de l’attaque tel qu’il a été intégré dans le programme. On va dire que c’est un PoC (Proof of Concept) qui nécessiterait quelques améliorations, en tout cas j’en ai vu quelques unes.

# include "Crypto.h"
# include "FileSystem.h"
# include "Math.h"

# include <Windows.h>
# include <stdio.h>

# define N 256				// 2^8
# define BYTES_READ 4096

static const char ALPHABET[] =
  "abcdefghijklmnopqrstuvwxyz"
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  "0123456789";

static const int ALPHABET_SIZE = sizeof(ALPHABET) - 1;

static void	bf_arcfour_loop(char *pass, int index, int actlen, char *encrypted) {
  char *data = encrypted;

  int	i      = 0;
  while (i < ALPHABET_SIZE) {
    pass[index] = ALPHABET[i];

    if (index == (actlen - 1)) {
      unsigned char *ciphertext = (unsigned char*)malloc(sizeof(int) * BYTES_READ + 1);
      Crypto::RC4::_cipher(pass, encrypted, ciphertext);

      double entropy            = Math::ShannonEntropy((unsigned char *)ciphertext, BYTES_READ);
      if(entropy < 7.9) {
        printf("[*] Password found: %s (with decoded text entropy %f).\n", pass, entropy);

        //printf("file:\n");
        //for (size_t i = 0, len = strlen((char *)ciphertext); i < len; i++) {
        //for (size_t i = 0, len = 15; i < len; i++) {
        //	printf("%02hhX", ciphertext[i]);
        //}
        exit(EXIT_SUCCESS);
      }
      free(ciphertext);
    } else { bf_arcfour_loop(pass, (index + 1), actlen, encrypted); }
    ++i;
  }
}

static void swap_byte(unsigned char *a, unsigned char *b) {
  int t = *a; *a = *b; *b = t;
}

//

void Crypto::RC4::_cipher(char *password, char *plaintext, unsigned char *ciphertext) {
  unsigned char S[N];
  char *key;

  if(strlen(password) < 255) {
    key = (char *)malloc((256 + 1) * sizeof(char));
    ZeroMemory(key, 256 + 1);
    int x = 0;
    for (int n = 0; n <= 255; n++) {
      if (x == strlen(password)) { x = 0; }
      key[n] = password[x];
      x++;
    }
  }

  _ksa(key, S);
  _prga(S, plaintext, ciphertext);
  free(key);
}

void Crypto::RC4::_ksa(char *key, unsigned char *S) {
  int i, j;
  for (i = 0; i < N; i++) {
    S[i] = 255 - i;
  }

  j = 0;
  for (i = 0; i < 256; i++) {
    j = (j + S[i] + key[i]) % N;
    swap_byte(&S[i], &S[j]);
  }
}

void Crypto::RC4::_prga(unsigned char *S, char *plaintext, unsigned char *ciphertext) {
  int i = 0;
  int j = 0;
  for (size_t n = 0, len = BYTES_READ; n < len; n++) {
    i             = (i + 1) % N;
    j             = (j + S[i]) % N;
    swap_byte(&S[i], &S[j]);
    int xor_key   = S[(S[i] + S[j]) % N];
    ciphertext[n] = xor_key ^ plaintext[n];
  }
}

void Crypto::RC4::_attack(char *filename, int len) {
  DWORD BytesRead = BYTES_READ;

  CHAR pData[BYTES_READ + 1];
  ZeroMemory(&pData, BytesRead + 1);

  printf("[*] Open encrypted file: %s\n", filename);
  HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  if (hFile != INVALID_HANDLE_VALUE) {
    if (FileSystem::_ReadFile(hFile, pData, BytesRead, &BytesRead, NULL)) {
      printf("[*] Read: %d bytes.\n", BYTES_READ);
    } else { 
      printf("[FileSystem::_ReadFile] Error: %s\n", filename);
      CloseHandle(hFile);

      printf("Exiting...");
      exit(EXIT_FAILURE);
    }
    CloseHandle(hFile);
  } else { 
    printf("[CreateFileA] Error: Crypto::RC4::_attack (%s)\n", filename);
    printf("Exiting...");
    exit(EXIT_FAILURE);
  }
  
  printf("[*] Attenpt brute-force attack: %d char(s).\n", len);
  int	i = 1;
  while(i <= len) {
    printf("  - Start with: %d char(s).\n", i);
    
    char *candidate = (char *)malloc(len + 1);
    ZeroMemory(candidate, len + 1);
    bf_arcfour_loop(candidate, 0, i, pData);
    free(candidate);

    ++i;
  }
}

J’ai un script Python qui à tourné pendant 24h, sans me donner le résultat attendu (j’ai eu le temps de coder quelque chose de plus efficace et aussi … de dormir un peu). Avec un programme en C le brute-force incrémental a prit quand même deux bonnes heures.

Maintenant, je vais essayer de décrypter la ROM avec le mot de passe K7fTw fraichement récolté.

Et voila, le fichier décrypté.

:10000000BDC10000E3C10000E1C10000DFC100008C
:10001000DDC10000DBC10000D9C10000D7C1000074
:10002000D5C10000D3C10000D1C4000074C50000D8
:10003000CDC10000CBC10000C9C10000C7C1000094
:10004000C5C10000C3C10000C1C10000BFC10000A4
:10005000BDC10000BBC10000B9C10000C5C30000A4
:10006000B5C10000B3C10000B1C10000AFC10000C4
:10007000ADC10000ABC10000A9C10000A7C10000D4
:10008000A5C10000A3C10000A1C100009FC10000E4
:100090009DC100009BC100000129022904290829F3
:1000A0001029202940298029012C022C0126022612
:1000B000042608261026202640268026402C802C48
:1000C0000123022304230823102320234023802319
:1000D0000120022004200820102020204020802021
:1000E000102C202C012F022F042F082F102F202F2F
:1000F000402F802F7EFE7EFD7EFB7EF77EEF7EDF33
:100100007EBF7E7F00010000680112000200007ABD
:10011000017400220000EE013F002100008C010963
:10012000002201002D023300210100A50109002257
:10013000020060022100210200BE0109002203002A
:1001400081025500210300DE010900030000D602F0
:100150000401030904DA023E01C13601C02601C1CF
:100160000601C10601C11600120100020000004094
:100170005E04F8FF0301000100010902740004019C
:1001800000C032090400000103010100092111012E
:100190000001223F00070583030800010904010054
:1001A00001030102000921110100012233000705AA
:1001B0008403080001090402000203000000092171
:1001C00011010001222100070581034000010705FC
:1001D00002032000020904030001030000000921BA
:1001E00011010001225500070585030C00020501DD
:1001F0000906A10175019508050719E029E7150011
:100200002501810295017508810395057501050891
:100210001901290591029501750391039506750849
:10022000150025680507190029688100C005010926
:1002300002A101050919012903150025019503757E
:100240000181029501750581030501093009311508
:1002500081257F750895028106093895018106C0C0
:1002600006C9FF0904A15C7508150026FF0095402A
:100270000975810295200976910295040976B102EB
:10028000C005010904A1011500250175019520058E
:1002900009190129208102150025073500463B0177
:1002A00075049501651405010939814205010901AB
:1002B000A100150026FF03750A95040930093109CC
:1002C0003209358102C0150026FF03750A9502091F
:1002D0003609368102C0040309043E035400650058
:1002E00065006E007300790020004B006500790006
:1002F00062006F006100720064002F004D006F000B
:100300007500730065002F004A006F0079007300CC
:100310007400690063006B0000002C5E7460616211
:1003200064346667656E362D3738271E1F202122FC
:10033000232425267333762E77785F444546474835
:10034000494A4B4C4D4E4F505152535455565758A5
:10035000595A5B5C5D2F3130636D35040506070823
:10036000090A0B0C0D0E0F10111213141516171885
:10037000191A1B1C1D6F7170752ADE0811241FBE0F
:10038000CFEFD0E2DEBFCDBF11E0A0E0B1E0E4E806
:10039000F2E100E00BBF02C007900D92A838B10750
:1003A000D9F71BBE11E0A8E8B1E001C01D92A33A45
:1003B000B107E1F713E0CCE7D3E003C02297FE01D9
:1003C0005BD7CA37D107D1F79DD55AC719CECF927F
:1003D000DF92EF92FF920F931F93CF93DF936C0105
:1003E000C0E0D0E07B010027F7FC0095102F13C080
:1003F00080916A01E82FF0E0BFD0C801B70134D284
:1004000080916A01E82FF0E0BDD064E670E080E002
:1004100090E02AD22196CC15DD0554F3DF91CF91DF
:100420001F910F91FF90EF90DF90CF900895BF92B2
:10043000CF92DF92EF92FF920F931F93DF93CF93B0
:10044000CDB7DEB7CA56D0400FB6F894DEBF0FBEA8
:10045000CDBFDE011196E0E0F1E089E001900D9260
:100460008150E1F780916A01E82FF0E085D0DE014C
:100470001A96E9E0F1E081E601900D928150E1F7F2
:1004800068EE73E080E090E0EFD180916A01E82FA0
:10049000F0E078D07E010894E11CF11C8E01065F2B
:1004A0001F4F9DE7B92E80E5C82ED12CCC0EDD1E46
:1004B00060ED77E080E090E0D7D180916A01E82F8D
:1004C000F0E05AD06FEF79EC8AE99BE3CDD1D70108
:1004D000F70103C080818B258193E017F107D1F7E5
:1004E000F801FE9604C080819D9189278193EC15C7
:1004F000FD05C9F7E0919C01F0919D010280F38117
:10050000E02D8CE991E0B80109958CE991E068E271
:10051000FDD48CE991E0FDD48CE991E060E0F6D463
:100520008CE991E0F6D480916A01E82FF0E02AD0BE
:100530006FEF79EC8AE99BE397D1BACF80916A019A
:100540000AD068E873E180E090E08ED18EE190E01F
:1005500069E170E03CCF8E3270F4E8E9F0E0880F9A
:10056000E80FF11D9591E491F0E02FB7F8948181A7
:10057000892B81832FBF0895EE3250F4EE0FE7539D
:10058000FD4F0994EE3220F4EE0FEB5DFC4F099421
:100590000895589AB6C0599AD0C05A9A08955B9A4D
:1005A00008955C9A08955D9A08955E9A08955F9AF9
:1005B0000895709A0895719A0895409A0895419AFD
:1005C0000895429A0895439A0895449AC8C0459A56
:1005D000C0C0469AB8C0479A0895769A0895779A07
:1005E0000895289A0895299A08952A9A08952B9A89
:1005F00008952C9A9CC02D9A88C02E9A8CC02F9A50
:1006000090C0109A0895119A0895129A0895139A15
:100610000895149A0895159A0895169A0895179AA8
:100620000895749A0895759A0895889A0895899AF4
:1006300008958A9A08958B9A08958C9A08958D9AB0
:1006400008958E9A08958F9A089558985AC0599887
:1006500074C05A9808955B9808955C9808955D98C1
:1006600008955E9808955F98089570980895719818
:1006700008954098089541980895429808954398A0
:10068000089544986CC0459864C046985CC04798EB
:100690000895769808957798089528980895299848
:1006A00008952A9808952B9808952C9840C02D9865
:1006B0002CC02E9830C02F9834C0109808951198EF
:1006C0000895129808951398089514980895159808
:1006D0000895169808951798089574980895759830
:1006E000089588980895899808958A9808958B9810
:1006F00008958C9808958D9808958E9808958F98F0
:100700000895E4B5EF7DE4BD0895E0918000EF77B2
:10071000E09380000895E0918000EF7DE0938000F9
:100720000895E0918000E77FE09380000895E091D4
:10073000B000EF77E093B0000895E091B000EF7D56
:10074000E093B0000895E0919000EF77E09390007F
:100750000895E0919000EF7DE09390000895E0917E
:100760009000E77FE093900008951DBA1092680012
:100770001CBC10BE1FBA10927A0010926E0010922C
:100780006F0010927000109271001092C9001092C8
:10079000BC0011B814B817B81AB81DB810BA12B8FE
:1007A00015B818B81BB81EB811BA0895F89480E2AD
:1007B0009EE40197F1F781E08093E00080E280936E
:1007C000D8001092C90080E69AEE0197F1F7CDDFCC
:1007D0000C9400FEFFCFF894C8DF80E69AEE0197F4
:1007E000F1F70C940000FFCF8F938FB78F93809118
:1007F00090018D5F8D37D0F48093900180918C01B2
:100800008F5F80938C01D8F080918D018F4F809302
:100810008D01A8F080918E018F4F80938E0178F02A
:1008200080918F018F4F80938F0109C08A578093E9
:10083000900180918C018E5F80938C0128F78091CC
:1008400088018C5F8093880158F0809189018F4FD7
:100850008093890128F080918A018F4F80938A01CB
:100860008F918FBF8F9118959B01AC0114D0FB0124
:100870000CC011D06E1B7F0B685E7340D0F321500B
:10088000304040405040E851FC4F211531054105B2
:10089000510579F708950FB6F89466B515B27091C1
:1008A00088018091890190918A010FBE10FE05C0D8
:1008B0006F3F19F07C5F8F4F9F4F11240024660F0C
:1008C000001C660F001C70290895F894E1E6F0E022
:1008D00020E82083108283E084BD85BDEEE6F0E051
:1008E00080818160808391E09093800082E080939A
:1008F00081009093B0008093B1009093900080931A
:10090000910086E880937A0020937B0010927E000D
:1009100002D0789408958091D80087FF02C085FFA7
:1009200054C081E88093D70080EA8093D80086E1A4
:1009300089BD09B400FEFDCF80E98093D8001092F4
:10094000E0001092A6011092A7011092A801109247
:10095000A9011092AA011092AB011092AC01109261
:10096000AD011092AE011092AF0191E09093A301FE
:100970008DE78093A4011092B0011092B101109202
:10098000B2019093A5011092B3011092B40110929C
:10099000B5011092B6018FE08093B70120E2209359
:1009A000B80190E89093B9011092BA0182E0809367
:1009B000BB0188E08093BC012093BD019093BE01F0
:1009C0001092E1008CE08093E20008951F920F9254
:1009D0000FB60F9211242F933F938F939F93809183
:1009E000E1001092E100382F83FF0FC01092E90060
:1009F00081E08093EB001092EC0082E38093ED00A5
:100A000088E08093F0001092A60132FF5AC08091D6
:100A1000A601882309F455C08091A801882391F08C
:100A200081508093A801882369F481E08093E900D4
:100A300002C01092F1008091E80085FDFACF8AE3B0
:100A40008093E8002091A4012223D9F180919101A3
:100A50008F5F8093910190E083709070892B89F56E
:100A600083E08093E9008091E80085FF2AC08091AF
:100A7000B0018F5F8093B001821719F51092B00119
:100A80008091A9018093F1001092F1008091AA0158
:100A90008093F1008091AB018093F1008091AC01D3
:100AA0008093F1008091AD018093F1008091AE01BF
:100AB0008093F1008091AF018093F1008AE38093ED
:100AC000E80030FF0CC080E18093E2001092A601A4
:100AD00081E08093A70180EA8093D80019BC8091BF
:100AE000A701882379F034FF0DC086E189BD09B4E0
:100AF00000FEFDCF80E98093D8008DE08093E20076
:100B00001092A7019F918F913F912F910F900FBE4F
:100B10000F901F9018951F920F920FB60F920BB661
:100B20000F9211242F933F934F935F936F937F9373
:100B30008F939F93AF93BF93EF93FF931092E9002E
:100B40008091E80083FFC8C17091F1009091F1009D
:100B50004091F1005091F1002091F1003091F100AD
:100B6000A091F100B091F10082EF8093E8009630FF
:100B700009F044C0E4E0F1E060E0859195918417CC
:100B8000950711F0359617C08591959182179307B7
:100B900011F0339610C0259135914491CD01AF3FAE
:100BA000B10519F010F08FEF90E0841708F4482F8A
:100BB000F9015EEF04C06F5F6C30F9F68DC1809172
:100BC000E800282F30E0C90185709070892BB9F3B7
:100BD00022FD85C1242F413408F020E4922F04C067
:100BE00085918093F10091509923D1F7421B509346
:100BF000E800442321F7203411F371C1953061F4EA
:100C00008EEF8093E8008091E80080FFFCCF842F76
:100C100080688093E30063C19930F9F4772309F089
:100C200068C04093A6011092A8018EEF8093E8005F
:100C3000E8E5F1E091E09093E90085918093EB0085
:100C4000882331F085918093EC0085918093ED00AD
:100C50009F5F973081F78EE147C0983051F470382C
:100C600009F047C08091E80080FFFCCF8091A60189
:100C7000B9C09923C1F48091E80080FFFCCF72389D
:100C800011F080E00DC02093E9008091EB0090E02E
:100C900025E0969587952A95E1F781701092E900F5
:100CA0008093F10093C0913011F0933011F57230C0
:100CB00001F541155105E9F4622F6F77862F8150B8
:100CC0008730B8F48EEF8093E8006093E9009330AA
:100CD00009F402C189E18093EB0081E090E002C059
:100CE000880F991F6A95E2F78093EA001092EA0054
:100CF000F6C02115310509F056C0713AB1F59130B1
:100D000009F58091E80080FFFCCF8091A9018093D4
:100D1000F1001092F1008091AA018093F10080917E
:100D2000AB018093F1008091AC018093F100809140
:100D3000AD018093F1008091AE018093F10080912C
:100D4000AF0150C0923039F48091E80080FFFCCFB1
:100D50008091A40147C0933009F0BEC08091E800A3
:100D600080FFFCCF8091A3013DC0713209F05EC0CD
:100D7000993061F48091E80082FFFCCF8091F1000E
:100D80008093B1018BEF8093E8004CC09A3029F436
:100D90005093A4011092B00145C09B3009F046C0A9
:100DA0004093A3013FC02130310531F5713AE9F498
:100DB000913079F48091E80080FFFCCF8091B201FE
:100DC0008093F1001092F1001092F1001092F10066
:100DD00029C0933009F080C08091E80080FFFCCFEB
:100DE0008091A5018093F1001DC07132F9F49B3010
:100DF000E9F44093A50116C023303105B9F4713AE6
:100E000009F06AC0913009F067C08091E80080FF66
:100E1000FCCFE3EBF1E081918093F10081E0EF3BC7
:100E2000F807C9F78EEF8093E80059C022303105EA
:100E300009F052C0913019F5713A09F04DC04A2FAE
:100E40005EEF8091E800282F30E0C9018570907036
:100E5000892BB9F322FD43C0942F413408F090E46C
:100E6000892F03C01092F10081508823D9F7491BC4
:100E70005093E800442329F7903419F330C0993097
:100E800059F5713249F54050534031F5149721F529
:100E90008091E80082FFFCCF9091F1002091F10059
:100EA0003091F1004091F1008BEF8093E8008EEFDC
:100EB0008093E800993A39F4253471F4323C61F4B6
:100EC0004B3651F473DC9B3839F4253C29F43D3121
:100ED00019F4403709F47FDC81E28093EB00FF9145
:100EE000EF91BF91AF919F918F917F916F915F91A2
:100EF0004F913F912F910F900BBE0F900FBE0F900F
:100F00001F901895E2DC1ADB92DAFECF6093AA01FB
:100F100008958091A601882309F43FC02FB7F89463
:100F200083E08093E9009091E4009E5C33E080913F
:100F3000E80085FD0EC02FBF8091A601882369F1CE
:100F40008091E400891749F12FB7F8943093E900B4
:100F5000EECF8091A9018093F1001092F100809171
:100F6000AA018093F1008091AB018093F100809100
:100F7000AC018093F1008091AD018093F1008091EC
:100F8000AE018093F1008091AF018093F1008AE37C
:100F90008093E8001092B0012FBF0895CF93DF93A4
:100FA000EC0166FD02C080E001C082E08093A901EF
:100FB0006F736093AA011092AB011092AC01109272
:100FC000AD011092AE011092AF01CE01A2DF1092DE
:100FD000A9011092AA01CE019CDFDF91CF91089563
:100FE000662309F0DBCF0895FB016032710528F418
:100FF0006A30710549F468E2D1CF6038710520F498
:10100000E650FD4F6491ECCF0895FC0167FD03C0ED
:10101000128270E00DC0603CE0F46F7322812130D9
:1010200041F4128270E083819481682B792BCF0187
:10103000DBCF223041F5862F90E036E0880F991FF4
:101040003A95E1F723813481282B392B348323838C
:101050000DC0603E68F4862F90E08F71907026E09E
:10106000880F991F2A95E1F79483838381E007C055
:10107000603F38F46295607F1382648382E08283EC
:1010800008958FEF828308952FB7F8948091A60179
:10109000882341F082E08093E9003BE68091E800FC
:1010A00085FD03C02FBF90E00FC09091F100992300
:1010B00019F43093E800F2CF8091E80085FD03C079
:1010C0008BE68093E8002FBF892F08952091A2011D
:1010D000222349F4D9DF882319F42FEF3FEF04C00E
:1010E0008093A201282F30E0C90108952091A20128
:1010F000222319F01092A20107C0C6DF882319F439
:101100002FEF3FEF02C0282F30E0C90108959C0166
:101110008091A201882349F4C901B6DF882319F41C
:1011200020E030E004C08093A20121E030E0C9015A
:1011300008958091A601882371F09FB7F89482E00A
:101140008093E9002BE602C02093E8008091E8003C
:1011500085FDFACF9FBF1092A20108958091A6014C
:10116000882359F12FB7F89481E08093E90090919A
:10117000E400975E31E08091E80085FD0EC02FBF4E
:101180008091E4008917C9F08091A6018823A9F015
:101190002FB7F8943093E900EECF6093F10080917F
:1011A000E80085FD06C08AE38093E8001092A8015C
:1011B00003C084E08093A8012FBF08951092980186
:1011C0001092990110929A0110929B0188EE93E07F
:1011D000A0E0B0E08093940190939501A0939601D4
:1011E000B093970180E791E09093930180939201EF
:1011F00010929E011092A00110929F0182E891E04E
:1012000090939D0180939C011092A1010895EF920B
:10121000FF920F931F93CF93DF938C017B01EA0121
:101220000CC0D7016D917D01D801ED91FC91019029
:10123000F081E02DC80109952197209791F7DF9162
:10124000CF911F910F91FF90EF900895CF93DF936F
:10125000DB010D900020E9F71197A61BB70BEC01FD
:10126000E881F9810480F581E02DAD010995DF91D8
:10127000CF910895EE0FFF1F0590F491E02D099492
:04128000F894FFCF10
:10128400C7295CCF0F9F39BD004E696365206A6F23
:1012940062202120596F752068617665207265731C
:1012A4006F6C76656420746865206368616C6C6536
:1012B4006E6765203A2D292053656E642074686535
:1012C400207061737320228E206CFE429477B222C8
:1012D40020746F20667239376868597A326B3769BF
:1012E4006865406E65732E6672000600000000009B
:1012F400AE082609070987087608660899080000D9
:08130400000005082609070995
:00000001FF

Pour résumer une attaque bruteforce incrémental ça peut être très long, plus le mot de passe est fort, moins l’attaque a de chance d’aboutir. Mais ce n’est pas le seul facteur il faut prendre en compte les vulnérabilités dans l’algorithme ou son implémentation (par ex: les collisions), temps de calculs, …

Ou des fois, le mot de passe qui traine dans un coin du bureau comme une boîte de cédocaze sur la table (en référence à la vidéo ci-dessous).


Ce qui conclut cette première partie du challenge.

Quand j’aurai un peu plus de temps, je poursuivrais avec l’analyse de la ROM Teensy++ dans une seconde partie.