Crackme V3.0 de Cruehead

By Christal

Histoire d'un crackme insoluble?


Dans le cadre de son défi médium, f@t@lity nous proposait le CrackMe v3.0 de Cruehead.
Le début paraissait prometteur:

"Cracked by:                 Now "
"CrackMe v3.0             "
"CRACKME3.KEY"
"DLG_ABOUT"
"Good work cracker!"
"MENU"
"No need to disasm the code!"

Le crackme semblait attendre une KeyFile (CRACKME3.KEY) dans laquelle devrait se trouver normalement un nom qui serait ensuite utilisé pour être affiché dans la boite de message "Cracked by: ".

//******************** Program Entry Point ********
:00401000 6A00                    push 00000000

* Reference To: KERNEL32.GetModuleHandleA, Ord:0000h
                                 
:00401002  Call 00401484                   > call GetModuleHandleA
:00401007  mov dword ptr [004020E9], eax   > récupère l'ImageBase
:0040100C  mov dword ptr [004020F9], 00    > initialise @ F9 = 0
:00401016  push 00000000                   > hTemplateFile
:00401018  push 00000080                   > dwFlagsAndAttributes
:0040101D  push 00000003                   > dwCreationDistribution
:0040101F  push 00000000                   > lpSecurityAttributes
:00401021  push 00000003                   > dwShareMode
:00401023  push C0000000                   > dwDesiredAccess

* Possible StringData Ref from Data Obj ->"CRACKME3.KEY"
                            
:00401028  push 004020D7                   > lpFileName

* Reference To: KERNEL32.CreateFileA, Ord:0000h
  
:0040102D  Call 004014A8                   > call CreateFileA
:00401032  cmp eax, FFFFFFFF               > fichier trouvé?
:00401035  jne 00401043                    > alors saute

La fonction CreateFile ouvre un objet (ici CRACKME3.KEY) et retourne le handle qui permettra d'accéder à la KeyFile (l'objet).

HANDLE CreateFile(
LPCTSTR lpFileName,              // pointe vers le nom du fichier (CRACKME3.KEY)
DWORD dwDesiredAccess,          // mode accès (read-write) 
DWORD dwShareMode,             // share mode 
LPSECURITY_ATTRIBUTES lpSecurityAttributes,// pointe vers les attributs de fichier 
DWORD dwCreationDistribution,// how to create 
DWORD dwFlagsAndAttributes, // attributs de fichier 
HANDLE hTemplateFile       // handle du fichier avec les attributs à copier  );

Si le fichier est trouvé, la valeur retournée est un handle qui permettra l'ouverture du fichier spécifié, et EAX contiendra le nombre de lettres correspondant à la taille du nom du fichier. Dans le cas contraire EAX vaudra -1.

:00401043  mov dword ptr [004020F5], eax  > stock nb de lettres
:00401048  mov eax, 00000012              > eax= 18d
:0040104D  mov ebx, 00402008              > initialise EBX= @ de stockage du texte
:00401052  push 00000000                  > pas de structure datas
:00401054  push 004021A0                  > @ de stockage du nb d'octets lus
:00401059  push eax                       > 18 caractères à lire
:0040105A  push ebx                       > adresse du buffer pointé par EBX
:0040105B  push dword ptr [004020F5]      > handle du fichier à lire

* Reference To: KERNEL32.ReadFile, Ord:0000h
                                 
:00401061  Call 00401496                  > call ReadFile
:00401066  cmp dword ptr [004021A0], 12   > comparaison si le nombre de
                                          > caractères contenu dans le 
                                          > fichier est égal à 18d 
:0040106D  jne 00401037                   > sinon Goto Bad Boy

La fonction ReadFile va lire le contenu du fichier concerné, à partir de la position indiquée par un pointeur. Après lecture, le pointeur contiendra le nombre de caractères qui restaient à lire dans le fichier. Ici, l'adresse d''EBX va pointer sur l'adresse dans laquelle se trouveront les caractères à lire dans le fichier CRACKME3.KEY.

BOOL ReadFile(

    HANDLE hFile,                  // handle du fichier à lire
    LPVOID lpBuffer,              // adresse du buffer qui recevra les datas  
    DWORD nNumberOfBytesToRead,  // nombre d'octets à lire
    LPDWORD lpNumberOfBytesRead,// adresse du nombre d'octets à lire 
    LPOVERLAPPED lpOverlapped  // adresse de la structure pour les datas   );
:0040106F  push 00402008                 > pousse l'adresse du buffer
:00401074  call 00401311                 > manipulation
:00401079  xor dword ptr [004020F9], 12345678 > opération logique
:00401083  add esp, 00000004             > rétablie la pile
:00401086  push 00402008                 > pousse l'adresse du buffer
:0040108B  call 0040133C                 > charge les 4 derniers caractères
:00401090  add esp, 00000004             > rétablie la pile
:00401093  cmp eax, dword ptr [004020F9] > comparaison
:00401099  sete al                       > si egalité alors AL = 1
:0040109C  push eax                      > sauvegarde la valeur de EAX sur la pile
:0040109D  test al, al                   > test si AL = 1
:0040109F  je 00401037                   > sinon Bad Boy

Voyons ce que nous avons dans le call manipulateur:

:00401311  xor ecx, ecx             > mise à zéro de ECX
:00401313  xor eax, eax             > et de EAX
:00401315  mov esi, dword ptr [esp+04] > Charge le texte trouvé dans le fichier
:00401319  mov bl, 41               > BL initialisé à 41 (lettre "A")
:0040131B  mov al, byte ptr [esi]   > AL = une lettre du texte trouvé
:0040131D  xor al, bl               > Xor AL avec BL
:0040131F  mov byte ptr [esi], al   > remplace la lettre lue par celle XORée
:00401321  inc esi                  > passe à la lettre suivante 
:00401322  inc bl                   > BL = "B", puis "C" etc...
:00401324  add dword ptr [004020F9], eax > Ajoute le résultat du XOR dans @ F9
:0040132A  cmp al, 00               > contrôle si différent de 00
:0040132C  je 00401335              > auquel cas saute en Bad Boy
:0040132E  inc cl                   > incrémente CL 
:00401330  cmp bl, 4F               > compare si BL = "N"
:00401333  jne 0040131B             > sinon boucle
:00401335  mov dword ptr [00402149], ecx > place le nb de caractères XORé dans @ 149
:0040133B  ret                      > fin

Si par hasard le texte que vous avez entré dans le fichier CRACVKME3.KEY est "ABCDEFGHIJKLMNOPQR", le A XOR A donnera 00, et vous serez éjecté en 0040132C. Par contre si aucune lettre XORée par BL ne se trouve être identique, les 14 première lettres seront XORées par A à N. Arrivé à la lettre N, le programme sortira de sa boucle (en 00401333). A chaque XOR, le résultat est ajouté au résultat précédemment trouvé (en 00401324 et dans l'adresse [004020F9]) et finalement le nombre de caractères traités sera stocké dans [00402149]. Le principe est donc le suivant:

(1er lettre XOR A)+ (2ème lettre XOR B)... + (14ème lettre XOR N) = Result1

Result 1 étant stocké dans [004020F9].

Vous aurez remarqué que les 4 dernières lettres (15 à 18) sont intouchées, et que les caractères manipulés ont pris la place des lettres du texte.

Result1 va ensuite être XORé (xor dword ptr [004020F9], 12345678) à nouveau, et va donner une clé du type
£P4. (ou encore 9C503412).

En 00401086, la chaine Xorée va être poussé sur la pile, et le programme va ensuite aller dans le call 0040133C:

:0040133C  mov esi, dword ptr [esp+04]  > ESI pointe sur la chaine XORée
:00401340  add esi, 0000000E            > ESI pointe sur le 14ème caractère
:00401343  mov eax, dword ptr [esi]     > EAX charge les 4 derniers caractères
:00401345  ret

Le rôle de cette routine va être de pointer sur les 4 derniers caractères, ceux qui n'avaient pas été touchés, et en charger la valeur ASCII dans EAX. Si par exemple ces caractères étaient EFGH, EAX vaudrait 48674645.

A la sortie de ce call, EAX est comparé avec la clé obtenu par le Xor 121345678, et en fonction du résultat de cette comparaison AL vaudra 0 ou 1. Si AL vaut 1, le crackme ouvrira une boite de message dans laquelle vous trouverez "Cracked by: " et les 14 premières caractères XORés du texte d'origine.

Pour casser la KeyFile, il faut donc avoir:

Result1 XOR 12345678 = 4 dernières lettres du texte placé dans CRACKME3.KEY

C'est là qu'il y a un problème…
Ou du moins que j'en vois un:

Les caractères ASCII affichables vont de 20h (espace) à 7Fh. Soit 127 comme plus grande valeur ASCII.
En cherchant à obtenir l'avant dernier symbole de la table ASCII, le"
~", pour les 14 premiers caractères (?<=:;896745230), et en les XORant avec ABC...MN, vous obtenez une addition ne dépassant pas 000006E4 soit 1764 en décimal. Une telle valeur se code sur un Word, or le XOR 12345678 va effectuer son OU logique sur un DWORD (et donner 9C503412 -> "£P4."), si bien que vous ne pouvez jamais avoir une clé dont la valeur puisse être 4 caractères ASCII affichables!

Par exemple le texte:

"*1-62&$n9;-*+ va donner, une fois XORée "christal'spage", mais la clé résultant de l'addition de tous les XOR va donner E9533412, soit seulement deux caractères ASCII affichables (53 = S et 34 = 4), les deux autres ne pouvant pas être frappé au clavier.
Or comme le fichier .KEY ne gère pas las caractères spéciaux et que la lecture du fichier se fait sur du texte, je ne vois pas comment faire...

Crackme insoluble?

En entrant des caractères au clavier, peut être...
Mais il existe souvent
d'autres solutions...

Bonne Journée

Christal