Grâce à la piste fournie par jB, turbodiff, godup et tuts4you j'ai fini (ou c'est tout comme) l'analyse de la vérification de clef par armadillo ainsi que leurs génération.

Je maintiens néanmoins que cette partie (et pas le reste de la protection Mr Papa d'Armadillo ;) ) est vraiment codée avec les pieds et mériterait des coups de genoux biens placés dans les roubignolles (ce qui se confirmera par la suite :p ).

Tout d'abord un petit récapitulatif des outils et "techniques" utilisées pour étudier ce code horrible.

J'ai tout d'abord dumpé la DLL utilisée par armadillo pour vérifier le sérial, elle n'est pas loadée à l'aide de LoadLibrary et donc non directement dumpable à l'aide de LordPE mais cela se fait bien quand même.

Après un passage sous IDA, les fonctions standards C sont automatiquement reconnues, ce qui commence déjà à faciliter le boulot. J'avais commencé mon analyse avec juste ces informations et commencé à renommer toutes les fonctions manipulant des bignums, il ne me manquait que les grosses fonctions utilisées pour manipuler les points d'une courbe elliptique.

Là j'ai utilisé les sources fournies par jB qui ne correspondent pas exactement au code d'armadillo mais qui ont l'avantage de présenter la même structure (la multiplication d'un point, sa génération, l'algorithme DSA, etc. sont composés de blocs chainés de façon particulière) j'ai donc utilisé turbodiff sur la dll ainsi que les .o des fichiers du dossier fast_onb et ai pu identifier les fonctions les plus complexes.

Enfin j'ai exporté le .map et l'ai modifié avec un petit script python afin de rajouter un delta de 0x1000 (godup utilisant le début de la page courant pour créer les labels et la dll étant loadé d'un bloc dans la mémoire, il fallait ajouter la taille du PE à l'adresse des labels) enfin je l'ai chargé à l'aide de godup dans ollydbg ce qui a permis une analyse en duo olly / IDA relativement confortable.

Passons maintenant à la vérification des clefs à proprement parler.

La vérification des clefs de armadillo s'articule autour de plusieurs étapes :

  1. décodage de la clef publique g, Q.x, Q.y; g étant un DWORD permettant de générer le point de base G sur la courbe elliptique.
  2. décodage du sérial, découpage en 3 tableaux de C(ExtraBytes||K), S1 et S2, ExtraBytes étant la date de génération du serial ainsi qu'un tableau de bytes qui sont paramétrables par le développeur pour panacher encore un peu les options fournies par armadillo et C étant une fonction de chiffrement que je détaillerai plus tard.
  3. vérification de la signature ECDSA, le couple (S1,S2) devant signer MD5(ExtraBytes||Name) (où || désigne l'opération de concaténation)
  4. vérification de la clef K à l'aide d'un algorithme lourd (et pas évident à bruteforcer) utilisant MD5, TEA et le prng de Robert Sedgewick.

Je vais maintenant détailler les différentes étapes :

Décodage des paramètres de ECDSA

Les paramètres publics de la signature ECDSA sont encodés sous la forme d'une string de 3 entiers décimaux séparés par des virgules "A,B,C" A correspond au DWORD g permettant de générer le point de base G sur la courbe elliptique et B et C sont respectivement l'abscisse et l'ordonnée du point public Q (pour plus de détails : wikipédia).

La génération du point est simple, un point preG est généré aléatoirement puis doublé pour donner G, un petit détail qui me turlupine est que le doublement assure juste que l'ordre de G n'est pas divisible par 2 (dans le cas contraire on aurait preG d'ordre 2*2*X divisant 2*n or n est premier (et égal à 5192296858534827627896703833467507)).

Ce qui m'amène à me demander ce qui se passerait si le point preG était d'ordre 2 (probabilité TREEEEEES faible, il n'y a qu'un point d'ordre 2 si je ne me goure pas, mais bon, pas nulle) et donc que l'on se retrouve avec le point à l'infini après doublement ... mais je divague peut être, il y a peut être une vérification à ce niveau que j'ai manqué.

Décodage du serial

Le décodage du sérial n'échappe pas à la règle et utilise des algorithmes lourds et complètement inutiles (division, multiplication, addition de bignums) pour découper le serial encodé en base 32 (0123456789ABCDEFGHJKMNPQRTUVWXYZ), les tirets sont ignorés, le serial doit commencer par le caractère 1 qui est ignoré (et qui sert, si on se réfère aux sources du keygen armadillo que j'ai fini par trouver à la toute fin de mon analyse ( :'( ) à différencier les différents niveaux de sécurité du serial).

Ainsi, le serial formé à l'aide du script suivant :

  1. >>> def encode(i) :
  2. ... s = ""
  3. ... base = "0123456789ABCDEFGHJKMNPQRTUVWXYZ"
  4. ... while i :
  5. ... s = base[i%32] + s
  6. ... i /= 32
  7. ... return "1"+s
  8. ...
  9. >>> encode(0xC0CAC01ADEADBEEF111213141516171819101A1B1C1D010203040506070809000A0B0C0D)
  10. '160TB01NQNDQVQH24GK2GAHC5RR3481M6RW3M0G40R40M30E2090050P30D'

Sera décodé comme suit :

  • C(bytes ExtraBytes||K) = C0CAC01ADEADBEEF
  • S1 = 010203040506070809000A0B0C0D
  • S2 = 111213141516171819101A1B1C1D

Rien de bien compliqué donc, sauf si l'on considère que pour faire ce découpage, notre cher ami armadillo utilise opérations de oufzor sur des bignums pour faire ça, il va vraiment falloir me dire qui a codé ça ....

Vérification de la signature ECDSA

La vérification de la signature est des plus classique, le nom est concaténé au tableau de byte C(bytes ExtraBytes||K) puis hashé à l'aide de MD5 ce qui nous donne un bigint qui est ensuite utilisé pour la vérification de la signature.

Et... C'est là que le bât blesse...

Les checks de base ne sont pas fait par armadillo sur S1 et S2, à savoir vérifier si ils sont non nuls ou pas, entrez donc 160TB01NQNDQVQG00000000000000000000000000000000000000000000 comme serial et vous allez avoir une surprise ;) armadillo crash lamentablement !

Après une rapide étude on se rend compte que la routine responsable de ce crash est celle chargée d'inverser le paramètre y de la signature (forcément essayer d'inverser 0 ca le fait pas), on pourrait se dire à ce moment là que la faille est certes amusante et révélatrice de la qualité du code mais qu'elle ne nous avance pas à grand chose... MAIS ! (suspens ...) 0 n'est pas le seul élément nul possible, en effet y = 5192296858534827627896703833467507 est aussi nul modulo ... ba 5192296858534827627896703833467507 ...

Seulement l'inversion de y ne fait pas crasher armadillo, la fonction d'inversion se contente de renvoyer un code d'erreur qui est bien évidemment ignoré par armadillo ... Nous avons alors y^-1 nul par conséquent l'équation (i,j) = [H(m)*y^-1)]G + [x*y^-1)]Q se simplifie en ... (i,j) = [0]G + [0]Q et donc (i,j) = 0 point à l'infini de la courbe et ce quelque soit x et m, ce qui est fort embêtant étant donné que la signature est considérée valide si x = i.

Pour produire une signature valide pour n'importe quel message, il suffit donc de fournir le couple (0.abs, n) à armadillo. Comme je suis gentil je vous fourni même la valeur de ce 0.abs : 0x192B24A1DC800400000000000000.

Le code permettant de générer un serial passant la première vérification de armadillo est disponible ci dessous :

  1. >>> infinite_point_abs = 0x192B24A1DC800400000000000000
  2. >>> n = 0x73EA6DAF91BFFDFFFFFFFFFFFFFF
  3. >>> misc = 0xDEADBEEFC0CAC01A
  4. >>> encode((misc << 28*8) | (n << 14*8) | (infinite_point_abs))
  5. '16YNPZEZG6AR0D77UKDNY8VZZFZZZZZZZZZZWCJP951VJ00800000000000'

C'est quand même con de tenter de coder des algorithmes cryptographiques puissants sans savoir coder et en ne sachant même pas lire un article sur wikipedia (ou tout simplement les documents très complets du NIST ou autre) ... Après ca fini en drame et la protection se fait trouer (assez tardivement, je l'avoue, ce qui est assez bizarre étant donné que la faille n'est pas récente (elle existe dans la version 6 d'armadillo et on en est à la version 7))

Petite précision avant de passer à la suite, il semblerait que la dernière version de armadillo, sortie avant les keygens d'armadillo, soit """résistante""" à cette faille, elle crash même en utilisant y = n, pour le vérifier, utilisez simplement le "serial" fourni plus haut.

Vérification de la clef K

Le déchiffrement de la clef et des EtraBytes est simplement fait à l'aide d'un xor effectué entre le chiffré et la sortie du générateur pseudo aléatoire de Robert Machin initialisé à l'aide du crc32 du nom (traité auparavant à l'aide d'une fonction de standardisation (suppression des caractères blancs et mise en majuscule)).

La vérification de la clef privée K n'est pas très intéressante, sachez juste qu'elle est xorée avec l'HWID si il existe et étendue à l'aide du prng de Roro passer d'une taille de 4 bytes à 0x400, le md5 de cette clef étendue est utilisé comme une deuxième clef et armadillo tente de l'utiliser pour déchiffrer une série de blocs (à l'aide de plusieurs itérations de 2 variantes de TEA, une en CBC et une en EBC), un des bloc une fois déchiffré devant avoir son dernier DWORD égal au not de l'avant dernier, ce bloc déchiffré servant après à faire plein de truc que je n'ai pas regardé...

Je pensais parler plus longuement de cette vérification mais je commence à fatiguer. J'ai codé un bruteforce pour tenter de retrouver cette clef privée, avec mon processeur il me faudrait 200 jours, j'ai donc recodé mon bruteforcer en CUDA, je gagne un petit facteur (de l'ordre de 3-4) ma carte graphique n'étant pas super puissante et en ayant besoin pour mater du pr0n (roooh non je rigole moman, l'auto-collant sur mon laptop n'est pas ce que tu crois) j'ai abandonné mes tentatives de bruteforce (et puis de toute façon c'est pas rigolo).

Voila ! J'espère que ca vous a plus ;)

Un grand bravo aux gars de NGEN qui ont trouvé cette faille qui trainait depuis un moment et un grand "shame on you" au zouave qui a codé cette partie de arma.