CrackMe 02 de Chronos
par Kharneth
 
Outils utilisésPublicCible
 - PEId
 - SmartCheck
 - Exdec
 - OllyDbg
 - Calculatrice
 - Papier, Crayon, Cerveau 5.0
 Débutant en Cracking
ayant de bonnes connaissances en programmation
 CrackMe02_Chronos
 
1 - PEId 

      Une petite analyse de PeId pour vérifier que le programme n'est pas compressé ou crypté. Il ne l'est pas. Par contre on voit qu'il a été compilé avec Visual Basic 5.0.
Pour étudier les programmes VB, l'outil idéal est Smartcheck.

2 - SmartCheck 

      On lance SmartCheck et on charge le programme avec Ctrl+o.
      Mais là, petit problème! Smartcheck nous indique que le programme a été compilé en P-Code (Pseudo-code généré par Visual Basic et interprété à l'exécution)! Si on l'ouvre avec un debugger ou un désassembleur, on ne verra pas grand chose puisque le programme ne saura pas traduire les opcodes.
      On va quand même voir ce qu'arrive à capturer SmartCheck. On click sur "Continue Opening Programm" puis on lance le crackme avec F5. On entre un code bidon (123456), une MsgBox apparaît, on la ferme ainsi que le crackme.

      De retour dans SmartCheck, on double-click sur l'événement _Click, et on ne voit que la MsgBox... Pas bon! :o( On se démonte pas, on sélectionne la MsgBox puis on click sur le bouton "Show All Events".

      On parcours chaque ligne en surveillant la fenêtre de droite mais la seule ligne intéressante est varR8FromStr qui convertie notre chaîne en Réel. On remarque aussi que toutes les adresses données par SmartCheck sont dans la dll. Il va donc falloir regarder plus profondément en utilisant OllyDbg. Avant de se lancer, il faut comprendre comment fonctionne le P-Code.

3 - P-Code 

      Un programme compilé en P-code utilise une machine virtuelle (MSVBVM60.DLL ou 50 suivant la version de VB) qui charge les opcodes pour les interpréter à l'exécution. Chaque fonction utilisée dans un programme VB possède un numéro identifiant. Lorsque le programme est compilé, c'est ce numéro qui est écrit à la place des instructions ASM classiques. Puis quand on l'exécute, le moteur est chargé en mémoire et lit la liste d'opcode en exécutant les fonctions correspondantes, au fur et à mesure. Ces opcodes étant propres à VB, le debugger ne sait pas les traduire et les affiche tel quel:

      Les opcodes sont codés sur 8 bits et offrent donc 256 fonctions différentes. En réalité, il en existe pas loin de 1500! En fait, les 5 dernières valeurs, 0xFB à 0xFF sont réservés pour spécifier une nouvelle table offrant 256 nouvelles fonctions. Par exemple, dans le listing ci-dessus, pour accéder à la fonction CR8Str, le moteur lit l'opcode 0xFC à l'adresse 00402AD2, voit qu'il doit utiliser la table Lead1, puis lit l'opcode suivant, 0x33 à l'adresse 00402AD3, correspondant à la fonction dans la nouvelle table. Nous avons donc la table de base plus les 5 tables Lead0 à Lead4. Les opcodes servent à déterminer l'adresse de la fonction dans la dll.
      Voici la structure d'une fonction telle qu'on peut la voir dans la dll:

      Ceci est la fonction MulR8, son opcode est 0xB3 et elle ne prend aucun paramêtre. Toutes les instructions avant XOR EAX, EAX sont propres à la fonction. Ici on voit que cette fonction multiplie 2 réels entre eux stockés dans ST0 et ST1. Ensuite, toutes les fonctions respectent le schéma suivant:
- Le nombre d'octet utilisé par la fonction est ajouté à ESI
- EAX est mis à 0
- L'opcode suivant est chargé dans AL
- ESI est incrémenté de 1
- Un saut est effectué vers la fonction suivante après avoir calculé son adresse à partir de son opcode
      Ici, la fonction ne prend aucun paramêtre. Elle n'utilise donc qu'un seul octet, celui de son opcode. ESI contient en permanence l'adresse de l'opcode suivant.
      Mais pour cracker un programme compilé en P-code, il serait assez fastidieux de tracer dans la dll en essayant de compendre chaque fonction! Heureusement pour nous, certains Reversers ont eu la bonne idée de traduire tous les opcodes par leur nom de fonction plus ou moins compréhensible! On va donc utiliser Exdec pour "désassembler" le programme et traduire les opcodes.
      Il faut cependant savoir comment sont nommées les fonctions avant de pouvoir déchiffrer le listing.
      Les types de variables sont abrégés ainsi:
- Bool pour Boolean
- Str pour String
- I2 pour Byte
- UI2 pour Unsigned Byte
- I4 pour Long
- R4 pour Single
- R8 pour Double
- Var pour Variant (Variable qui peut être de n'importe quel type!)
      Dans les fonctions, les abréviations suivantes sont également employées:
C indique une conversion (ex: CR8Str convertie une chaîne en Réel).
Lit déclare une valeur (ex: LitI2 place un Byte sur la pile).
Ld pour Load.
Rf pour Référence.
      Les 2 dernières sont utilisées pour la gestion des variables. Le moteur doit accomplir quelques opérations avant d'utiliser les variables. La variable est d'abord copiée dans une cellule d'un tableau, puis une référence vers cette cellule est créée pour être finalement chargée dans une adresse mémoire. Lorsque le programme veut accéder à cette valeur, le moteur charge l'adresse mémoire dans la pile puis remonte jusqu'à la variable par la référence.
      Par exemple, la fonction FStVar crée une référence vers un variant, puis la fonction FLdRfVar charge l'adresse de la référence sur la pile.
      On note aussi que:
- Branch correspond à un saut.
- BranchF saute si faux (False).
- BranchT saute si vrai (True).
      On peut maintenant passer à l'analyse du listing grâce à Exdec.

4 - Exdec 
Proc: 402b90
402AB4: 00 LargeBos               
402AB6: 00 LargeBos               
402AB8: 4b OnErrorGoto            
402ABB: 00 LargeBos               
402ABD: 04 FLdRfVar                local_008C
402AC0: 21 FLdPrThis              
402AC1: 0f VCallAd                 text
402AC4: 19 FStAdFunc               local_0088
402AC7: 08 FLdPr                   local_0088
402ACA: 0d VCallHresult            get__ipropTEXTEDIT
402ACF: 6c ILdRf                   local_008C
402AD2: Lead1/33 CR8Str           
402AD4: f3 LitI2:                  0x29a   666  (..)
402AD7: eb CR8I2                  
402AD8: b3 MulR8                  
402AD9: Lead2/6b CVarR8           
402ADD: Lead1/f6 FStVar            local_009C
402AE1: 2f FFree1Str               local_008C
402AE4: 1a FFree1Ad                local_0088
402AE7: 00 LargeBos               
402AE9: Lead3/c1 LitVarI4:         ( local_61FF00AC )0x1c9e00 (1875456)
402AF1: Lead1/f6 FStVar            local_00BC
402AF5: 00 LargeBos               
402AF7: 04 FLdRfVar                local_009C
402AFA: 04 FLdRfVar                local_00BC
402AFD: 28 LitVarI2:               ( local_00AC ) 0x2  (2)
402B02: Lead0/b4 MulVar           
402B06: Lead0/33 EqVarBool        
402B08: 1c BranchF:                402B46
402B0B: 00 LargeBos               
402B0D: 27 LitVar_Missing         
402B10: 27 LitVar_Missing         
402B13: 27 LitVar_Missing         
402B16: f5 LitI4:                  0x0  0  (....)
402B1B: 3a LitVarStr:              ( local_00AC ) Bravo, tu peu aller 

      Voici le début du listing généré par Exdec, la fin n'étant pas intéressante. Les adresses indiquées sont les mêmes que dans le debugger. On peut donc mettre un point d'arrêt sous OllyDbg (Memory On Access) à l'adresse voulue puis tracer dans la dll pour étudier ou suivre l'exécution d'une fonction. Lorsque le moteur lira l'opcode, l'exécution du programme s'arrètera au début de la fonction.
Analyse du listing:
 402AB4: - L'opcode 00 correspond au NOP, la fonction ne fait rien.
 402AB8: - OnErrorGoto est assez explicite même si on ne voit pas où il doit aller...(Next ?)
 402ABD: - La référence à l'indice 8C est placée sur la pile. Elle correspond à la variable qui contiendra notre pass.
 402ACA: - 4 paramêtres sont passés à la fonction VCallHresult qui récupère les caractères du champ texte (Notre pass!).
 402ACF: - La référence à la variable qui contient notre pass est placé sur la pile.
 402AD2: - Pour être convertie en Réel.
 402AD4: - Le Byte 666 est déclaré sur la pile
 402AD7: - Pour être convertie en Réel
 402AD8: - Et multiplié par notre passe.
 402AD9: - Le résultat est convertie en Variant.
 402ADD: - Puis stocké avec la référence 9C.
 402AE1: - Les 2 références précédemment utilisées par la fonction VCallHresult sont détruites.
 402AE9: - Déclaration de la valeur 1875456.
 402AF1: - Référencé en BC.
 402AF7: - La référence du résultat de l'opération sur notre passe est placée sur la pile.
 402AFA: - Ainsi que la référence à 1875456.
 402AFD: - La valeur 2 est déclarée
 402B02: - Pour être multipliée par 1875456.
 402B06: - L'égalité entre les 2 précédentes références est testée.
 402B08: - Si le résultat est faux, on saute vers l'affichage de la MsgBox "Pas Bon..." sinon la MsgBox "Bravo..." est affichée.

      Pour simplifier tout ça, notre pass, convertie en réel, multiplié par 666 doit être égal à 1875456 multiplié par 2 soit:

Bon Pass = 1875456 * 2 / 666 = 5632

Kharneth 

Ride the snake, ride the snake
To the lake, the ancient lake, baby


Merci à toutes les personnes qui se battent pour que l'Information soit accessible à tous!