Crackme de Zone 14
Seconde Release

By Christal

Octobre 2000

Crackme de Zone 14 Seconde Release

Depuis la sortie du Patch VB pour Wdasm, casser du Visual Basic devient nettement plus évident.
En voici un exemple assez facile, et pour commencer, un morceau choisi des String Data References :

"\\.\SICE"
"Bravo, vous "
"Erreur"
"Le nom doit faire au moins 4 caract"
"Tous les champs sont obligatoires "
"Utilisateur Enregistr"

Il me semble qu'il y a là, tout ce qui va nous être nécessaire.
A tout Seigneur, tout Honneur :

* Possible StringData Ref from Code 0bj ->"\\.\SICE"

:00408F69 68A46D4000              push 00406DA4
:00408F6E 51                      push ecx

La détection de SoftIce, sollicité lors du clic sur GO et à la sortie du CrackMe, peut être très facilement shuntée en modifiant la string contenue en 00406DA4 :

00406DA0: 10 00 00 00-5C 00 5C 00-2E 00 5C 00-53 00 49 00      \ \ . \ S I
00406DB0: 43 00 45 00-00 00 00 00-23 3D FB FC-FA A0 68 10  C E   

En remplaçant par exemple SICE par... FUCK comme le fait si joliment FrogSice !
Au passage vous remarquerez que les chaînes VB sont Unicodes, en Wide Char Format (les lettres séparées les unes des autres par des 00, chose à ne pas oublier quant on fait des recherches de strings).

L'anti SoftIce (de type MeltIce, appellé par un
CreateFileA) est très facilement visible sous SmartCheck dont je vous encourage à lire le très bon essai écrit récemment par Falcon :

lblmenu(3)_Click
    CreateFileA(LPSTR:004174D0,FLAGS:00,
    FLAGS:00, PTR:0063F84C,DWORD:00, 
    FLAGS:00, HAND OLE1.DoVerb
    End()
.... signed char * lpFileName = 004174D0
....    = "\\.\SICE"
.... unsigned long dwDesiredAccess = 0 
.... unsigned long dwShareMode = 0

En fait l'appel à CreateFileA se fait ainsi :

:00408FA6       push eax                           > \\.\SICE
:00408FA7       cal1 00406C8C                      > call CreateFileA
:00408FAC       mov dword ptr [ebp+FFFFFF40], eax  > Résultat dans [ebp-BF]

* Reference To: MSVBVM50.  vbaSetSystemError, 0rd:0000h
:00408FB2       Call dword ptr [0040D168]
:00408FB8       mov edi, dword ptr [ebp+FFFFFF40]  > puis transvasé dans esi
:00408FBE       lea ecx, dword ptr [ebp-2C]

* Reference To: MSVBVM50.  vbaFreeStr, 0rd:0000h
:00408FC1       Call dword ptr [0040D238]
:00408FC7       cmp edi, FFFFFFFF                   > et finalement comparé à -1
:00408FCA       je 00408FD2                         > si égale, on continue 

Ben, ca doit pas être bien rapide le VB, comparativement aux mêmes routines écrites en C ou Delphi...

Le hors d'œuvre étant consommé, passons au plat de résistance.

* Possible StringData Ref from Code 0bj ->"Erreur"

" Erreur " génère un très grand nombre d'occurrences différentes en cliquant sur la String Data.
Soyons plus positif !

Je connais très mal les API Visual Basic, mais j'ai au moins appris que ce langage ne disposait pas d'un grand panel de fonctions de comparaisons entre deux chaînes. Essayons une recherche sur
__vbaStrCmp. Dans la mesure ou il s'agit d'un micro-programme il y a peu de chance d'en trouver des dizaines, et en effet, il n'y en a que deux :

* Referenced by a (U)nconditional or (C)onditiona1 Jump at Address:
|:00409E8F(C)

:00409EA3       mov  ecx, dword ptr [ebp-34]  > pousse le serial entré
:00409EA6       push ecx
:00409EA7       mov  edx, dword ptr [ebp-38]  > pousse le serial généré
:00409EAA       push edx

* Reference To: MSVBVM50.  vbaStrCmp, Ord:0000h
:00409EAB       Call dword ptr [0040D1A8]     > les compare
:00409EB1       mov  esi, eax
:00409EB3       neg  esi
:00409EB5       sbb  esi, esi
:00409EB7       neg  esi
:00409EB9       neg  esi
:00409EBB       lea  ecx, dword ptr [ebp-38]

* Reference To: MSVBVM50.  vbaFreeStr, 0rd:0000h
:00409EBE       Call dword ptr [0040D238]
:00409EC4       lea  ecx, dword ptr [ebp-3C]

* Reference To: MSVBVM50.  vbaFree0bj, 0rd:0000h
:00409EC7       Call dword ptr [0040D234]
:00409ECD       test si, si                  > et si SI est <> de 0
:00409EDO       jne  00409FDl                > Goto Pas Glop!
:00409ED6       mov  ecx, 80020004           > début de la routine Glop! Glop!
:00409EDB       mov  dword ptr [ebp-74], ecx
:00409EDE       mov  eax, 0000000A
:00409EE3       mov  dword ptr [ebp-7C], eax
:00409EE6       mov  dword ptr [ebp-64], ecx
:00409EE9       mov  dword ptr [ebp-6C], eax

* Possible StringData Ref from Code 0bj ->"Gr3tzzzz"

L'autre réponse obtenue pour vbaStrCmp ne sert qu'à vérifier si un Nom et un Serial ont bien été entré dans la boite d'enregistrement, et en fonction du résultat branchera vers l'affichage de la MsgBox " Tous les champs sont obligatoires " (au passage : l'ensemble des messages qui apparaissent dans ce crackme sont passés par MsgBox, et les paramètres sont toujours poussés très longtemps avant l'affichage en lui-même).

La routine " Pas Glop ! " va nous donner une autre piste :

* Referenced by a (U)nconditional or (C)onditiona1 Jump at Addresses:
|:00409E2D(C), :00409ED0(C)

:00409FDl       mov ecx, 80020004
:00409FD6       mov dword ptr [ebp-74], ecx
:00409FD9       mov eax, 0000000A
:00409FDE       mov dword ptr [ebp-7C], eax
:00409FE1       mov dword ptr [ebp-64], ecx
:00409FE4       mov dword ptr [ebp-6C], eax

* Possibleo Code 0bj ->"Erreur"

Il y a deux sauts conditionnels qui branchent sur cette routine. Allons voir l'allure du second :

:00409E0F       mov  word ptr [ebp+FFFFFF40], ax > ax dans ebp+FFFFFF40
:00409E16       lea  edx, dword ptr [ebp-5C]
:00409E19       push edx
:00409E1A       lea  eax, dword ptr [ebp-4C]
:00409E1D       push eax                         > puis poussé sur la pile
:00409E1E       push 00000002
:00409E20       call edi
:00409E22       add  esp, 0000000C               
:00409E25       cmp  word ptr [ebp+FFFFFF40], 00 > si ebp+FFFFFF40 = 0 
:00409E2D       jne  00409FD1                    > Goto Pas Glop !

La seconde routine étant placée au-dessus de la première, il est facile d'émettre l'hypothèse suivante :

1- Génération du serial (en une seule fois ou par bribes)
2- Contrôle du serial (caractère par caractère par exemple)
3- Si pas good -> Goto Pas Glop!
4- contrôle du serial généré avec le serial entré
5- Si pas good -> Goto pas Glop!
6- Routine Glop! Glop!

Donc au-dessus de l'item 2, on doit trouver quelque chose qui permette de générer le serial...
CQFD !

OK, mais dans le foutoir Visual Basic, comment cibler la zone qui m'intéresse ?
Pourquoi pas en utilisant SmarctCheck une nouvelle fois ?

Voici des extraits de ce que j'ai obtenu :

Mid(VARIANT: "christal", long:1, VARIANT:Integer:1)  > récupère la 1ère lettre du Name
Asc(String:"h") returns Integer:99                   > conversion ASCII
Str(VARIANT:Long:122)                                > Tiens ! d'où il sort celui là...

Puis mon serial entré va apparaître :

.. string (variant)
_ .. unsigned short * * .pbstrVal = 0063F744
_ .. String   = 00417788
....    = "489999"                                   > mon serial
.... Long  length = 1 0x00000001
_ .. start (variant)
.... Long  .lVal = 3 0x00000003

Et trois caractères de celui ci vont être "prélevés " :

_  vbaVarTstNe(VARIANT:Const String:"", VARIANT:String:"489") returns DWORD:FFFFFFFF
.. lhs (variant)
.... unsigned short .vt = 32776 0x8008
_ .. rhs (variant)
_ .. unsigned short * .bstrVal = 004177D0
....    = "489"

Ensuite le programme récupère le " 122 " qui m'avais surpris, et le message d'erreur " désolé... " apparaît.
Je dois donc être au niveau du second saut conditionnel Good Boy/Bad Boy que j'avais trouvé :

:00409E25       cmp  word ptr [ebp+FFFFFF40], 00 > si ebp+FFFFFF40 = 0 
:00409E2D       jne  00409FD1                    > Goto Pas Glop !

En Noppant purement et simplement ce saut, je vais me rendre compte que ce petit Mic-Mac va être appliqué à chacune des lettres du Name entré.

En continuant à parcourir le rapport d'observation de SmarCheck, je trouve ceci :

_  vbaStrMove(String:"12211310...", LPBSTR:0063F740) returns DWORD:4178A8

En cliquant sur cette ligne, la fenêtre de droite du débuggeur VB va me donner :

.. unsigned short * pSource = 004178A8
....    = "122113107112106109120117"

A la ligne d'en dessous, il récupère mon serial entré, et aussitôt après je vois les strings de la MsgBox "Désolé...".
Facile d'en conclure qu'il est dans la routine vbaStrCmp que j'avais repéré en premier, et que je viens de trouver le bon serial.

Pour " mieux voir " ce qu'il se passe " réellement ", j'ai posé un bpx sur le saut conditionnel en 00409E25, et une fois de plus j'ai shunté celui ci. En traçant un peu, je me suis rendu compte que j'étais au milieu d'une boucle, et que la routine qui m'intéressait commençait en 00409C61.
Partant de cette adresse, j'ai recommencé à tracer en surveillant ce qu'il se passait :

:00409C87       mov dword ptr [ebp+FFFFFF7C], ecx    > D * ECX = Name entré

puis :

* Reference To: MSVBVM50.__vbaStrVarVal, Ord:0000h
:00409CB8       Call dword ptr [0040D1E0] 
:00409CBE       push eax                    > pousse un caractère ASCII du Name
                                            > par exemple 63 pour " c "
* Reference To: MSVBVM50.rtcAnsiValueBstr, Ord:0204h
:00409CBF       Call dword ptr [0040D15C]
:00409CC5       push eax                    > le transforme en Unicode " 6.3 "

Plus loin (ca ne donne vraiment pas une idée de langage optimisé tout ca, à moins que ca ne soit une manière de noyer le poisson) :

:00409CEE       push ecx                    > rappel " 6.3 "

* Reference To: MSVBVM50.__vbaI4Str, Ord:0000h  
:00409CEF       Call dword ptr [0040D1FC]
:00409CF5       xor  eax, 00000019          > 63 Xor 19 = 7A !
:00409CF8       mov  dword ptr [ebp-44], eax

Et la valeur décimale de 7Ah est 122 !
Voilà d'où vient le 122 qui m'avait surpris...

Beaucoup plus loin, 122 va être comparé avec " 489 " les trois premiers caractères de mon serial.

Pas besoin d'être un génie pour comprendre le truc :

1- le crackme prélève, un à un et dans l'ordre, les caractères du Name
2- le caractère sous sa correspondance ASCII (63 par exemple) est xoré avec 19
3- le résultat hexadécimal (7A) est transformé en décimal (122)
4- puis comparé avec trois caractères du serial, pris dans l'ordre.

Rien de plus facile, maintenant que de bidouiller un petit KeyGenerator.
Par contre, comme je ne suis pas plus doué en assembleur qu'en Visual Basic, celui ci est un puzzle de routines récupérées à droite et à gauche :

- la partie graphique (GDI) et la saisie du serial sont issues d'un KeyGen de Papaow
- la fonction " copy " vient d'un KeyGen de SyntaxError
- la conversion Hexadécimale -> Décimale de TeeJi

Vous vous doutez bien que dans ces conditions,
le source qui est joint est loin d'être optimisé...

En voici la partie la plus importante : le générateur

;========================================================================= 
;                            KeyGenerator 
;        la bonne clé se trouve dans Serial à la fin de la routine   
;========================================================================= 
      invoke  GetWindowTextA,hwndEdit1,ADDR buffer,12 
      > saisie le Name entré (placé dans buffer)    
      mov     dword ptr [Serial+00],0000    > pas très élégant, mais j'ai eu 
      mov     dword ptr [Serial+04],0000    > la flemme de trouver mieux pour 
      mov     dword ptr [Serial+08],0000    > effacer un précédent serial
      cmp     eax, 04                       > le Name est < à 4 caractères ?
      jl      Trop_court                    > c'est pas good -> affiche Erreur
      cmp     eax, 10                       > il est supérieur à 10 caractères ?
      jng     Long_OK                       > pas good non plus...

A ce sujet, le crackme ne limite absolument pas le nombre de caractères du Name. C'est simplement pour que le résultat obtenu puisse s'afficher dans le second champ de mon KeyGen que j'ai restreint sa longueur à 10 caractères. Pour des noms plus long, utilisez le patch joint au Zip.

      invoke MessageBoxA, NULL,addr TextAbout, addr Caption, 040h 
      jmp    Fin

Long_OK:
      Xor    ebx,ebx                    ; compteur des caractères
      
boucle:
      Xor   eax,eax                     ; ré-initialise EAX
      mov   al, byte ptr [ebx+buffer]   ; prend 1 caractère dans Buffer
      test  al, al                      ; il y en a encore?  
      je    Affiche                     ; c'est good, on affiche
      xor   al, 19h                     ; création clé n°1
      mov   esi,eax                     ; met la clé 1 calculé dans ESI
      call  decimal                     ; conversion hexa -> décimal
      mov   eax, Key                    ; Key contient l'élément du serial
      xor   esi, esi                    ; ré-initialise ESI

place_libre:
      cmp   byte ptr [Serial+esi],00    ; cherche de la place libre
      je    ecrit_serial                ; si trouvé va placer la Key
      inc   esi                         ; sinon passe à l'octet suivant
      jmp   place_libre                 ; et cherche à nouveau

ecrit_serial:
      mov   dword ptr [Serial+esi],eax  ; écrit la Key à la suite des autres
      inc   ebx                         ; passe au caractère suivant du Name
      jmp   boucle                      ; et recommence le Process

La Key obtenu peut être de 2 ou 3 caractères de long, en fonction des Majuscules, Minuscules, Chiffres et autres Symboles que vous aurez entré comme Name. La petite routine ci dessus se contente (très mal sûrement) de chercher de la place (des 00) à partir du début de l'adresse allouée au Serial généré, et ira placer la Key à la suite des autres pour composer ce serial, avant de l'afficher dans le second champ du KeyGenerator.

Pas bien glorieux tout ca, mais c'est la faute à Falcon, et à son excellent tutoriel sur "
How to configure and use SmartCheck "...

Merci à l'équipe de Zone 14

Bonne Journée

Christal