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 qui se cache sur le site de la société NES et dont l’une des épreuves consiste justement à « unpacker » un binaire 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 codé, 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 ».

OEP = { adresse memoire de OEP } – {adresse de l’image de base}.

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 (un 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 » la « 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 :

  • 2 caractères ~ 1 seconde
  • 3 caractères ~ 1 minute
  • 4 caractères ~ 62 minutes
  • 5 caractères ~ 2 jours

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.

Ce contenu a été publié dans Write-Ups. Vous pouvez le mettre en favoris avec ce permalien.