Reverse de Crypto v3.5 de Grégoire Braun
Par
CASIMIR

Partie A
Sommaire:
  Introduction
  De quoi ai-je besoin ?
  Vue de l'extérieure

  PART A. REVERSING OF BRAUN'S CRYPTO V3.5
       A1. Input handling 
       A2. Hash tables and the making of KEY_0
       A3. The making of KEY_1
       A4. The making of KEY_2
       A5. The check routine
       A6. The decryption process
  PART B. ANALYSIS AND BREAKING OF BRAUN'S CRYPTO V3.5
       B1. The recovery of KEY_1
            B11. Method 1
            B12. Method 2
            B13. Method 2 vs Method 1
       B2. The recovery of KEY_0 and PASSWORD
            B21. Feasibility
            B22. Unicity
            B23. KEY_0's range
            B24. Reduced ASCII Character Set
            B25. Choosing suitable Password's length(s)
            B26. Building a Password
       B3. Benchmark
      
  PART C. 'C' SOURCE FOR CRACKER

Braun prétend employer l'algorithme BlowFish pour produire un cryptage sûr de Crypto v3.5. En réalité l'algorithme employé est assez faible.
Pour décrypter un fichier, Crypto v3.5 utilise un Mot de passe et, en fonction d'un hash fait maison, produit une clef 32 bits : KEY_1. KEY_1 qui est manipulée (des manipulations ANDs) pour produire une deuxième clef 32 bits : KEY_2. KEY_2 est comparée contre une troisième clef 32 bits : KEY_CHK, qui est stockée dans le header du fichier chiffré. Si KEY_2 = KEY_CHK, alors Crypto v3.5 décrypte le fichier, employant KEY_1 comme clef de décryptage.
La faiblesse de Crypto v3.5 vient du fait que :
- Donnant KEY_CHK, KEY_1 peut être facilement et rapidement deviné (vous n'avez pas besoin d'essayer 2^32 KEY_1 possibilités, approximativement 500000 essais sont suffisant dans chaque cas);
- Donnant KEY_1, un Mot de passe peut être facilement et rapidement contrefait (ce n'est même pas obligatoire, puisque crypto v3.5 emploie KEY_1 comme clef de décryptage).

De quoi ai-je besoin ?
Cible : Crypto v3.5 crypto.exe : 188416 octets Crypto 3.5

Outils nécessaires :
Winice par Nu-Mega, un éditeur hexadécimal, une table d'ASCII avec les valeurs hexadécimales des caractères et une bonne calculatrice : Excalibur 32 bits par David Bernazzani (Cette calculatrice a une quantité étonnante de fonctions incorporées, comme ROL et ROR.

Vue de l'extérieur
BIEN, avant de commencer le reverse de ce programme, commençons par jouer un peu avec lui. Chiffrez des fichiers divers avec des tailles et de contenus différents, des longueurs de noms et de mots de passe variables. Examinez-les maintenant avec Hex Workshop : vous remarquerz que Crypto ajoute 276 octets à chaque fichier chiffré, quel que soit le mot de passe utilisé. Crypto ne semble pas exécuter de compression avant le cryptage, il n'ajoute probablement pas d'éléments aléatoires au fichier (la taille des fichiers varierait beaucoup plus). Donc nous pouvons assumer que :

                             encryption
 1 caractère du plaintext ----------------> 1 caractère du ciphertext  

Ces 276 bytes constituent le header, qui a la structure suivante:

        CryptoHdrBlk????pathfilename.............encryptedfile
        <------------- 276 bytes ---------------><  original >
                                                      size

Huuummmm... Ces 4 caractères mystérieux entre l'emplacement original et le nom de fichier ont titillé mon intérêt. Leurs valeurs semblent être liées à la fonction du mot de passe, mais ils ne dépendent pas du contenu du plaintext. Ce serait une place agréable pour un mot de passe chiffré (en fait ce ne sera pas le cas…).

Puisque Braun a employé un algorithme fait maison (et a oublié de nous en donner la recette {:), nous devrons changer complètement son programme. Préparez un fichier d'essai : braun.txt avec un contenu : ABCDEF (6 octets, MAJUSCULE). Chiffrez-le avec le pwd : CASIMIR (MAJUSCULE), vous obtiendrez un fichier chiffré braun. (= txt =)

Dump de braun.txt :

00000000 43 72 79 70 74 6F 48 64 72 42 6C 6B 92 F4 F6 6B CryptoHdrBlk... K 
00000010 43 3A 5C 43 52 59 50 54 4F 32 5C 64 65 66 69 5C C:\CRYPTO2\defi\ 
00000020 62 72 61 75 6E 2E 74 78 74 00 00 00 00 00 00 00 braun.txt.. ...... 
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
000000B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 
00000110 00 00 00 00 B1 3E 3E 18 12 A7..... > >...    
Excellent!
Ce fichier de 282 octets sera notre base de référence pendant le reverse  du processus, donc sauvegardez-le.

PARTIE A. REVERSE DE BRAUN'S CRYPTO V3.5

======================
A1. Traitement des entrées
======================

Comme d'habitude, je prendrai une approche Live : j'emploierai Winice pour découvrir ce qui arrive à mon entrée. Choisissez le fichier braun. (= txt =) pour le décryptage, entrez un faux mot de passe : 13579 (n'employez pas 012345... cet ordre est souvent présent en mémoire). Avant la vérification du pwd, mettez des BPXS sous Softice aux fonctions de Windows qui sont d'habitude employées pour la saisie d'info. Cette fois la fonction GetDlgItemTextA fonctione et voici ce que nous voyons :

...
CS:405EB2    mov ebx,[USER32!GetDlgItemTextA]
CS:405EB8    push 100
CS:405EBD    push eax    <-- Watch out! Input is copied to DS:EAX
CS:405EBE    push 65
CS:405EC0    push esi
CS:405EC1    call ebx    <-- get input and copy it to DS:EAX  ...

====================================
A2. Tables de hash et la fabrication de KEY_0
====================================

Mettez un BPR DS:EAX EAX+5 RW pour savoir où et quand votre entrée est employée. L'entrée est lue dans CS:405EE0 (ne me demandez pas pourquoi), et nous arrivons à la partie intéressante :

* Référence : KERNEL32.lstrlenA, Ord:029Ch   
:0040CD68 xor edi,edi <-- edi=0 
:0040CD6A push esi 
:0040CD6B Call dword ptr [0042D48C] <-- return input's length in eax 
:0040CD71 test esi, esi 
:0040CD73 je 0040CDA7 
:0040CD75 test eax, eax 
:0040CD77 je 0040CDA7
:0040CD79 mov ecx, 00000000
:0040CD7E jle 0040CDA7 

Avant d'aller plus loin, remarquez que Crypto emploi 2 tables de hash :

- HashTable_1 starting at DS:41DDF8
23 73 65 72 42 26 6E 7A
7C 6D 66 4D 31 2F 35 28	
21 73 64 24 4D 71 2E 7B
73 5D 2B 73 46 6A 74 4B			
70 7A 53 64 74 7A 6F 58
71 6D 62 5E 41 6C 40 64	
76 3A 73 3F 78 2F 00 00
7C 62 21 70 7A 2A 6C 73	
3B 72 6E 7C 6C 66 24 76
69 5E 41 78 70 65 29 72
78 35 61 69 63 26 39 2F
32 6D 35 6C 73 69 34 40
30 64 6D 5A 77 39 34 63
6D 71 70 66 68 77 00 00
   - HashTable_2 starting at DS:41DE30	
            7C 62 21 70 7A 2A 6C 73
            3B 72 6E 7C 6C 66 24 76
            69 5E 41 78 70 65 29 72
            78 35 61 69 63 26 39 2F
            32 6D 35 6C 73 69 34 40
            30 64 6D 5A 77 39 34 63
            6D 71 70 66 68 77 00 00

Ces tables sont toujours les mêmes, et ne dépendent pas de l'entrée ou plaintext.

***>:0040CD80  movsx ebx, byte ptr [eax+ecx+0041DDF8] <-- read 1 character
*                                                         from HashTable_1
*   :0040CD88  movsx ebp, byte ptr [esi+ecx]          <-- read 1 character from input
*   :0040CD8C  lea edx, dword ptr [ecx+01]            <-- edx=ecx+1
* L :0040CD8F  imul ebx, ebp
* O :0040CD92  movsx ecx, byte ptr [ecx+0041DE30]     <-- read 1 character from 
* O                                                       HashTable_2
* P :0040CD99  imul ebx, ecx
*   :0040CD9C  imul ebx, edx
*   :0040CD9F  add edi, ebx                           <-- edi=accumulator 
*   :0040CDA1  mov ecx, edx
*   :0040CDA3  cmp eax, edx
***<:0040CDA5  jg 0040CD80                            <-- loop eax (5) times
    :0040CDA7  mov eax, edi
    :0040CDA9  pop ebp
    :0040CDAA  pop edi
    :0040CDAB  pop esi
    :0040CDAC  pop ebx
    :0040CDAD  ret

En résumé, nous avons length=len de l'entrée :

edi = HashTable_1[len+1]   * HashTable_2[1]   * 1   * Input[1]      +
      HashTable_1[len+2]   * HashTable_2[2]   * 2   * Input[2]      +
      ...                                                           +     
      HashTable_1[len+len] * HashTable_2[len] * len * Input[len]

let Coef[i] = HashTable_1[len+i] * HashTable_2[i] * i ,

nous avons:

edi = Coef[1]*Input[1] + Coef[2]*Input[2] + ... + Coef[len]*Input[len]

par exemple, avec l'entrée: 13579, on obtient: edi=0x00868500

Il y a quelque chose d'intéressant du point de vue du cracker : les coefficients dépendent seulement de la longueur de l'entrée. Cela signifie que, pour la longueur d'une entrée donnée, nous pouvons pré calculer chaque coefficient. Il nous épargnera beaucoup de temps de calcul pendant le reverse du processus.
Nous appellerons la valeur d'edi : KEY_0 dorénavant.

======================
A3. La fabrication de KEY_1
======================

Ensuite KEY_0 est copié dans eax (CS:40CDA7) et eax est copié dans DS:42A0F0. Comme d'habitude, nous examinons cet emplacement mémoire et découvrons que la valeur est copiée dans CS:405D4C et CS:402282. Mais le traitement le plus important arrive dans CS:402AC4 :

CS:402AC4 or [esi],06A30DE8 // DS:[esi] contains KEY_0
                                        06A30DE8 is a constant

Nous obtenons une nouvelle clef de 4 octets, KEY_1 :

KEY_1 = KEY_0 ou 06A30DE8     
            = 00868500 ou 06A30DE8        
            = 06A78DE8

======================
A4. La fabrication de KEY_2
======================

KEY_1 est utilisé dans le call suivant :

:0040B2F0    push esi:0040B2F1    push edi
:0040B2F2    mov esi, dword ptr [esp+0C]  // copy KEY_1 to esi
:0040B2F6    push esi
:0040B2F7    call 00411450                // copy KEY_1 to DS:4205F4
:0040B2FC    add esp, 00000004
:0040B2FF    call 00411460      // first_call
:0040B304    mov edi, eax       // eax=00001799 (see first_call walk-thru)
:0040B306    call 00411460      // second_call
:0040B30B    shl eax, 00000010  // eax=00004411 (see second_call walk-thru)  
:0040B30E    add eax, edi       // eax = 44110000+00001799 = 44111799
:0040B310    pop edi
:0040B311    add eax, esi       // add KEY_1 to eax, giving KEY_2:
                                // KEY_2 = 06A78DE8 + 44111799 = 4AB8A581
:0040B313    pop esi:0040B314    ret

Voyons ce qui se passe dans ce premier call en CS:411460

First_call
----------
:00411460    mov eax, [004205F4]              // copy KEY_1 to eax
:00411465    lea ecx, dword ptr [eax+4*eax]   // ecx = 5*eax
:00411468    lea ecx, dword ptr [ecx+4*ecx]   // ecx = 5*ecx = 19*eax 
:0041146B    add ecx, eax                     // ecx = 1A*eax
:0041146D    lea ecx, dword ptr [eax+8*ecx]   // ecx = eax+8*1A*eax = D1*eax
:00411470    shl ecx, 00000008                // ecx = 100*ecx = D100*eax
:00411473    sub ecx, eax                     // ecx = ecx-eax = D0FF*eax
:00411475    lea eax, dword ptr [eax+4*ecx+269EC3] // eax = 343FD*eax+269EC3 
:0041147C    mov [004205F4], eax                   // copy KEY_TMP_1 = eax to DS:4205F4 
:00411481    and eax, 7FFF0000                     // mask eax
:00411486    shr eax, 00000010                     // right shift : 12345678 becomes 00001234 
:00411489    ret

Donc avec KEY_1 = 06A78DE8 nous obtenons : KEY_TMP_1 = 343FD*KEY_1+269EC3 = 1799950B et eax (la valeur de retour) = (KEY_TMP_1 et 7FFF0000) / 10000 = 00001799
Cette partie de code est appelée une deuxième fois et la valeur de KEY_TMP_1 est employée :

Second_call
-----------
:00411460    mov eax, [004205F4]              // copy KEY_TMP_1 to eax
:00411465    lea ecx, dword ptr [eax+4*eax]   // ecx = 5*eax
:00411468    lea ecx, dword ptr [ecx+4*ecx]   // ecx = 5*ecx = 19*eax 
:0041146B    add ecx, eax                     // ecx = 1A*eax
:0041146D    lea ecx, dword ptr [eax+8*ecx]   // ecx = eax+8*1A*eax = D1*eax
:00411470    shl ecx, 00000008                // ecx = 100*ecx = D100*eax
:00411473    sub ecx, eax                     // ecx = ecx-eax = D0FF*eax
:00411475    lea eax, dword ptr [eax+4*ecx+269EC3] // eax =343FD*eax+269EC3
:0041147C    mov [004205F4], eax  // copy KEY_TMP_2 = eax to DS:4205F4 
:00411481    and eax, 7FFF0000    // mask eax
:00411486    shr eax, 00000010    // right shift : 12345678 becomes 00001234
:00411489    ret

Ainsi avec KEY_TMP_1 = 1799950B nous obtenons :

	 KEY_TMP_2 = 343FD*KEY_TMP_1+269EC3 = 4411CBA2Et	 
     Eax (valeur retournée) = (KEY_TMP_2 et 7FFF0000) / 10000 à 00004411

=====================
A5. La routine de contrôle
=====================

Comme nous le verrons bientôt, KEY_2 est employé immédiatement après sa génération. Le retour se fait en CS:40B314, et voici ce que vous voyez :

:00402D73    call 0040B2F0                <--- the place we return from
:00402D78    add esp, 00000004
:00402D7B    cmp eax, dword ptr [esp+258] // compare KEY_2 against KEY_CHK
:00402D82    je 00402DC3                  // jump only if KEY_2 = KEY_CHK
:00402D84    mov eax, dword ptr [esp+14]  // bad guy, let's kick his ass
:00402D88    push eax
* Reference To: KERNEL32.CloseHandle, Ord:0017h
:00402D89    Call dword ptr [0042D4E0]
:00402D8F    mov eax, dword ptr [esp + 10]
:00402D93    push eax
* Possible Reference to String Resource ID=02007: "File: %sThis file was not encrypted with the current key"
:00402D94    push 000007D7
:00402D99    jmp 004033BF      

Nous avons: KEY_2 = 4AB8A581 et KEY_CHK = 6BF6F492

Les clefs ne sont pas les identiques, donc Braun refuse de décrypter le fichier.
Question : d'où vient KEY_CHK ?
Vous vous rappelez les 276-bytes du header, n'est-ce pas ?
Il y avait 4 octets mystérieux dans ce header :

  00000000 43 72 79 70 74 6F 48 64 72 42 6C 6B 92 F4 F6 6B CryptoHdrBlk... K 
                                               <-KEY_CHK->

Cette très petite clef est employée par Braun pour savoir si le mot de passe est bon ou non.

=========================
A6. Le processus de décryptage
=========================

Si le mot de passe est correct, Braun décrypte le fichier. Le processus de décryptage ne présente aucun intérêt, donc je ne le décrirai pas ici. La seule chose importante est que Braun n'emploie pas de mot de passe pour décrypter le fichier, mais KEY_1.

Copyright septembre 1999 par Casimir.
Mail:
Casimir