Solution de OliverSwift pour Iconic license

reverse windows x86/x64

3 février 2025

Préambule

Note: bien que l’accès à une machine Windows ne soit pas indispensable, son usage accélère considérablement l’investigation.

La solution proposée ici est basée sur Ghidra et gdb sous Windows via cygwin.

Le petit exécutable affiche un champ de saisie qui valide une licence en cliquant sur le bouton Vérifier :

fcsc_blank

Ghidra

Nous nous servirons de Ghidra pour “décompiler” l’exécutable. L’ensemble produit est assez compréhensible, on retrouve une structure classique de code WIN32 qui repose sur les ressources de l’exécutable. L’utilitaire tourne sur une séquence :

La fenêtre de Dialogue est construite sur des ressources et un callback de traitement de ses évènements (DLGPROC). Sans trop détailler, ce callback appelle une fonction de vérification de la chaîne saisie dans l’interface, pour le cas échéant la valider.

C’est cette fonction fait qui le travail principal et sur laquelle nous allons nous pencher.

ghidra_fcsc_licence

L’analyse statique de la fonction (FUN_00401130) montre les étapes suivantes:

  • Comparaison du début de la chaîne de caractères “FCSC{”. Ce qui est bon signe.
  • Extraction de 5 groupes de caractères (quintuplets), séparés par des "-" et produisant 5 entiers non signés de 25 bits.
  • Création de variables intermédiaires bâties sur des sous groupes de bits extraits des 5 entiers.
  • Combinaisons des sous-groupes de bits pour constituer deux entiers non signés de 64 bits.
  • Manipulation d’une première clé de 64 bits.
  • Extraction de deux clés de 64 bits dans les ressources (Icône N°3 de l’application, rappel: Iconic License est le nom de l’épreuve)
  • Combinaisons de XOR entre les valeurs et les clés.
  • Test final du résultat, qui si il est nul indique que la chaîne correspond à la licence attendue.

Les quintuplets

C’est la fonction FUN_00401000 qui se charge de transformer des groupes de 5 caractères en un entier de 25 bits.

L’analyse du code montre une correspondance de chaque caractère avec une valeur sur 5 bits (de 0 à 31). Cette correspondance est la suivante :

Caract. Valeur Caract. Valeur
0 0 1 1
2 2 3 3
4 4 5 5
6 6 7 7
8 8 9 9
A a 10 B b 11
C c 12 D d 13
E e 14 F f 15
G g 16 H h 17
I i 1 J j 18
K k 19 L l 1
M m 20 N n 21
O o 0 P p 22
Q q 23 R r 24
S s 25 T t 26
V v 27 W w 28
X x 29 Y y 30
Z z 31

On notera l’absence du ‘U’ et l’équivalence minuscule/majuscule (insensibilité à la casse).

Chaque caractère produit 5 bits “placés” dans un entier de 25 bits au final (MSB first).

La forme générique de la clé de licence ressemble donc à ceci :

FCSC{00000-00000-00000-00000-00000}

Nous allons nous servir de cette chaîne pour une analyse dynamique.

Iconic keys

Deux clés de 64 bits sont extraites des données de l’icône de l’application résidante dans les ressources. Au lieu d’aller chercher statiquement ces deux valeurs, nous allons nous aider de gdb pour les “attraper”.

L’application fait appel à :

Successivement pour récupérer les clés. Sous Windows/gdb on va se positionner juste après le calcul du pointeur après l’appel à LockResource pour le récupérer (position du curseur dans la capture Ghidra ci-dessus, adresse 0x40137a). Puis on dump les données au bon endroit.

L’interface va demander une licence et pour être certain d’atteindre notre point d’arrêt, la chaine doit être valide à défaut d’être la bonne licence. Nous utiliserons donc FCSC{00000-00000-00000-00000-00000} et cliquerons sur Vérifier.

Sous gdb:

$ gdb FCSCLIC.EXE
...
Reading symbols from FCSCLIC.EXE...
(No debugging symbols found in FCSCLIC.EXE)
(gdb) b *0x40137a
Breakpoint 1 at 0x40137a
(gdb) r
Starting program: /home/o.debon/Hackropole/FCSCLIC.EXE
[New Thread 7784.0x3e1c]
[New Thread 7784.0xc80]
[New Thread 7784.0x560c]

Thread 1 hit Breakpoint 1, 0x0040137a in ?? ()
(gdb) x/i $eip
=> 0x40137a:    lea    -0x10(%ebx,%eax,1),%eax
(gdb) nexti
0x0040137e in ?? ()
(gdb) x/2gx $eax
0x406c2e:       0x8db02aec536f789e      0x5e1d444098023476
(gdb)

Voilà nous avons nos deux clés :

  • 0x8db02aec536f789e
  • 0x5e1d444098023476

XORification

Comme évoqué, une valeur finale recherchée et testée à nulle à la fin est calculée avec des combinaisons de constantes et variables. Cette formule est (uVar11 nommée par Ghidra):

uVar11 = xor1 | xor2;

Donc xor1 et xor2 doivent être aussi nulles à cette étape. Or ces deux variables sont calculées ainsi :

          xor1 = xor ^ pKey[1] ^
                   (((ulonglong)a8 << 56) | ((ulonglong)a76 << 40) | ((ulonglong)a5 << 32) | ((ulonglong)a43 << 16) | ((ulonglong)a2 << 8) | ((ulonglong)a1));

          xor2 = xor ^ pKey[0] ^
                   (((ulonglong)b8 << 56) | ((ulonglong)b7 << 48) | ((ulonglong)b65 << 32) | (b4321));

Combinant une des deux clés récupérées et une constante fabriquée ainsi:

xor = psllw(0x8abc348ae21e167a,5) ^ 0x8abc348ae21e167a;
xor = 0x5780914043c0cf40 ^ 0x8abc348ae21e167a;
xor = 0xdd3ca5caa1ded93a

Ghidra ne nous donne pas d’équivalent de la fonction psllw, il se trouve que c’est l’instruction x86 PSLLW. Elle effectue un décalage vers la gauche de n bits pour chaque mots de 16 bits qui composent le registre de 64 bits utilisé. Nous obtientrons ainsi notre constante. Note: avec gdb nous l’aurions aussi déterminée.

Comme c’est l’opération XOR qui est utilisée, les combinaisons avec :

  • xor ^ pKey[1] = 0x8321e18a39dced4c
  • xor ^ pKey[0] = 0x508c8f26f2b1a1a4

respectivement pour aX et bX donneront 0 pour chaque.

Soient aX et bX égales à :

(((ulonglong)a8 << 56) | ((ulonglong)a76 << 40) | ((ulonglong)a5 << 32) | ((ulonglong)a43 << 16) | ((ulonglong)a2 << 8) | ((ulonglong)a1)) = 0x8321e18a39dced4c
et
(((ulonglong)b8 << 56) | ((ulonglong)b7 << 48) | ((ulonglong)b65 << 32) | (b4321)) = 0x508c8f26f2b1a1a4

ORification

Nous avons obtenu ce que doivent produire les combinaisons en OR des variables intermédiaires aX et bX. Ces dernières représentent des groupes d’octets dans deux valeurs de 64 bits que nous noterons a et b et décomposées ainsi :

a8 a76 a5 a43 a2 a1
8 bits 16 bits 8 bits 16 bits 8 bits 8 bits
b8 b7 b65 b43a2a1
8 bits 8 bits 16 bits 32 bits

Nous obtenons par correspondance les valeurs de ces variables :

a8 = 0x83 a76 = 0x21e1 a5 = 0x8a a43 = 0x39dc a2 = 0xed a1 = 0x4c
b8 = 0x50 b7 = 0x8c b65 =0x8f26 b4321 = 0xf2b1a1a4

Code C nettoyé

Le code produit par Ghida reste assez sibyllin, après une analyse et un rendu plus humain, le code de la fonction qui traite des quintuplets et des variables aX et bX ressemble à ceci:

        // Extract XXXXX- groups in string
        i = FUN_00401000(&licenceString,&last,0);
        j = FUN_00401000(&licenceString,&last,0);
        k = FUN_00401000(&licenceString,&last,0);
        l = FUN_00401000(&licenceString,&last,0);
        m = FUN_00401000(&licenceString,&last,1);

        b4321 = (i & 0xffffff) | (byte)j << 24; 
        b65 = (ushort)(j >> 8); 
        b7 = (byte)k;
        b8 = (byte)(k >> 8); 

        a1 = (byte)(k >> 16);
        a2 = (byte)l;
        a43 = (ushort)(l >> 8); 
        a5 = (byte)m;
        a76 = (ushort)(m >> 8); 

        a8 = (char)(l >> 24)  << 1 | (byte)(((int)k >> 24) << 2) |
                    (byte)(m >> 24) | (byte)(((int)j >> 24) << 3) |
                    (byte)(((int)i >> 24) << 4) | (byte)(i << 5); 

Nous pouvons remonter aux valeurs de i,j,k,l et m les 5 quintuplets.

i = b4321 | (a8>>5)                | (((a8 >> 4)&1) << 24)
j = (b65 << 8) | (b4321 >> 24)     | (((a8 >> 3)&1) << 24)
k = (b7) | (b8 << 8) | (a1 << 16)  | (((a8 >> 2)&1) << 24)
l = (a2) | (a43 << 8)              | (((a8 >> 1)&1) << 24)
m = (a5) | (a76 << 8)              | (((a8 >> 0)&1) << 24)

Valeurs des quintuplets

 i = 0xb1a1a4       11  3  8 13  4   ->   b38d4
 j = 0x8f26f2        8 30  9 23 18   ->   8y9qj
 k = 0x4c508c        4 24 20  4 12   ->   4rm4c
 l = 0x0139dced     19 19 23  7 13   ->   kkq7d
 m = 0x0121e18a     18  3 24 12 10   ->   j3rca

On rappelle que ces 5 valeurs sont codées sur 25 bits et chaque groupe de 5 bits correspondent à un caractère donné (voir le tableau).

Nous devrions donc avoir la bonne clé avec la chaîne :

FCSC{B38D4-8Y9QJ-4RM4C-KKQ7D-J3RCA}

Confirmation

Dans l’exécutable :

fcsc_licence

Tada.