Crackme V1.0 de CoOlViPeR

By Christal

CoOlViPeR Crackme 1

Le Crackme de CoOlViPeR commence par une très désagréable INT68

:0040AEBD  MOV       AH,43
:0040AEBF  INT       68
:0040AEC1  CMP       AX,F386
:0040AEC5  JNZ       0040AECD
:0040AEC7  PUSH      00000000
:0040AECC  RET
:0040AECD  PUSH      0040AD70
:0040AED2  RET

Avec l’inconvénient d'un PUSH 00000000 si SoftIce est détecté, qui fait planter le programme.
L'avantage, c'est que l'INT 68 est au tout début du code, et immédiatement identifiable.
On nop l’interruption avec un HexEditeur, et on l'oublie...

Mais auparavant, il aura fallu changer les caractéristiques des sections pour que le programme accepte de breaker sur l'Entry Point, lors d'un lancement via le Symbol Loader de SoftIce, ou son alter ego TRW 2000.
CoOlViPeR ayant inclu un anti ProcDump dans son programme, celui ci plante immédiatement si vous voulez l'utiliser.

En analysant un minimun l'écran que SoftIce à ouvert lors du plantage de procdump (FAULT ON):

EAX=0000291B   EBX=0056FC40   ECX=D5198E30   EDX=BFFC9490   ESI=00580000        
EDI=0056FBF4   EBP=0056FB64   ESP=0056FB60   EIP=004056B6   o d I s z a P c     
CS=017F   DS=0187   SS=0187   ES=0187   FS=40B7   GS=0000   DS:0058291B=FFFF 
----------------------------------------------byte-----------------------PROT32
0187:0058C004 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ................
0187:0058C014 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ................
-------------------------------------------------------------------------PROT32
                                             
017F:004056A9  CMP       WORD PTR [ESI],5A4D       ; recherche de la signature MZ
017F:004056AE  JNZ       004056C0                  ; jump si ce n'est pas un .exe
017F:004056B0  PUSH      ESI                       ; sauve l'@ du MZ Header
017F:004056B1  ADD       ESI,3C                    ; pointe vers l'@ du PE Header
017F:004056B4  LODSD                               ; passe l'info dans EAX
017F:004056B5  POP       ESI                       ; récupère l'@ du MZ Header
017F:004056B6  CMP       WORD PTR [ESI+EAX],4550   ; recherche de la signature PE
017F:004056BC  JZ        004056C0                  ; jump si ce n'est pas un .exe

-----------------------------PROCDUMP!.text+46B5-------------------------------
               
Break due to Page Fault (0Eh).  Fault=0004 

ProcDump va planter parce que la valeur qu'il recoit en 004056B6 ne correspond à rien tant que l'application cible n'est pas en cours d'exécution. Un "d EDI" en 004056A9 va montrer que le programme pointe sur le début du MZ Header:

00000000 4D5A 9000 0300 0000 0400 0000 FFFF 0000 B800 0000 0000 0000 MZ......................
00000018 4000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 @.......................
00000030 0000 0000 0000 0000 0000 0000 1B29 0000 0E1F BA0E 00B4 09CD .............)..........
00000048 21B8 014C CD21 5468 6973 2070 726F 6772 616D 2063 616E 6E6F !..L.!This program canno

Et qu'à l'adresse ESI (0058C000) + 3C il y a un 291B (EAX). Ce champ est indispensable pour Windows, il s'agit du DWORD e_lfanew, le dernier du MZ Header, indiquant à quel offset le PE Header de l'exécutable est situé.

Nomalement, cette valeur devrait être C0, et pointer sur l'offset où se trouve PE (le début du PE Header), que l'on trouve quelques lignes plus bas:

000000C0 5045 0000 4C01 0000 9869 0342 0000 0000 0000 0000 E000 0F00 PE..L....i.B............
			

WORD   Machine Type                        ( 014C ) i386
WORD   Nombre de Sections                  ( 0000 ) 
DWORD  Time/Date                           ( 98690342 ) 
DWORD  Pointeur vers la table des Symboles ( 00000000 ) 
DWORD  Nombre de Symboles                  ( 00000000 ) 
WORD   Taille de l'Optional Header         ( E000 ) 
WORD   Caracteristiques                    ( 0F00 )
Address      Values        Meaning
00000080    00004550     Signature: PE
00000084        014C     Machine: 014C=I386
00000086        0000     Number of Sections
00000088    2FF3548D     Time/Date Stamp
0000008C    00000000     Pointer to Symbol Table
00000090    00000000     Number of Symbols
00000094        E000     Optional Header Size
00000096        0F00     Characteristics
Votre oeil vigileant a-t-il remarqué quelque chose de bizard? Continuons en regardant du coté des sections:

000001B0 0000 0000 0000 0000 2E74 6578 7400 0000 0080 0000 0010 0000 .........text...........
000001C8 0000 0000 0004 0000 0000 0000 0000 0000 0000 0000 8000 00E0 ........................
000001E0 2E64 6174 6100 0000 0020 0000 0090 0000 0020 0000 0004 0000 .data.... ....... ......
000001F8 0000 0000 0000 0000 0000 0000 4000 00E0 2E72 7372 6300 0000 ............@....rsrc...
00000210 0010 0000 00B0 0000 0006 0000 0024 0000 0000 0000 0000 0000 .............$..........
00000228 0000 0000 4000 00C0 0000 0000 0000 0000 0000 0000 0000 0000 ....@...................
Epluchons un peu:

Section Header (section .text)

BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]  ( 2E74 6578 7400 )
DWORD   PhysicalAddress 
DWORD   VirtualSize                    ( 0080 0000 )
DWORD   VirtualAddress                 ( 0010 0000 )
DWORD   SizeOfRawData                  ( 0000 0000 ) 
DWORD   PointerToRawData               ( 0004 0000 ) 
DWORD   PointerToRelocations           ( 0000 0000 )
DWORD   PointerToLinenumbers           ( 0000 0000 )
WORD    NumberOfRelocations            ( 0000 )
WORD    NumberOfLinenumbers            ( 0000 )
DWORD   Caracteristiques               ( 8000 00E0 ) -> E00000080 
Ici, rien d'anormal...

Alors que peut il bien y avoir à l'offset 291B, sachant que sans les informations du header, l'application plantera immédiatement.

Dans le cas de ce crackme, le PE Header a été détourné vers l'offset 291B, à fin du code:

Name:           VS: 00008000  VA: 00001000  RS: 00000000  RA: 00000400  C: C0000040 
Name:           VS: 00002000  VA: 00009000  RS: 00002000  RA: 00000400  C: C0000040 
Name:           VS: 00001000  VA: 0000B000  RS: 00000600  RA: 00002400  C: C0000040 

Pas de Nom de section, le minimum de chez minimum, mais on retrouve des points communs

0000291B                            50 4500 004C 0103 0098 6903 4200            PE..L....i.B.
etc...
00002A00 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ........................
00002A18 0000 0000 8000 0000 1000 0000 0000 0000 0400 0000 0000 0000 ........................
00002A30 0000 0000 0000 0040 0000 C000 0000 0000 0000 0000 2000 0000 ....... ............ ...
00002A48 9000 0000 2000 0000 0400 0000 0000 0000 0000 0000 0000 0040 .... ..................@
00002A60 0000 C000 0000 0000 0000 0000 1000 0000 B000 0000 0600 0000 ........................
00002A78 2400 0000 0000 0000 0000 0000 0000 0040 0000 C000 0000 0000 $..............@........
00002A90 0000 0000 0000                                              ......

Pour résumer, CoOlViPeR a détourné l'adresse de début du PE Header, et a créé un nouvel Header qu'il a placé à la fin de la section .rsrc, puis il a supprimé le nom des sections et modifié les caractéristiques de celles ci:

Généralement, pour empêcher le break et le désassemblage de la section, il suffit de mettre les caractéristiques suivantes :

   0x00000040 IMAGE_SCN_CNT_INITIALIZED_DATA 
   0x40000000 IMAGE_SCN_MEM_READ 
OR 0x80000000 IMAGE_SCN_MEM_WRITE 
-------------------------------------------- 
   0xC0000040 

En inversant le principe:

Chaque caractéristique à une valeur. Si vous voulez avoir deux caractéristiques, vous faites : caract1 OR caract2 ...
Donc, pour que Softice break à l'entrypoint, il faut que la section qui contient la première instruction ait les caractéristiques suivantes :

   0x00000020 IMAGE_SCN_CNT_CODE 
   0x20000000 IMAGE_SCN_MEM_EXECUTE 
   0x40000000 IMAGE_SCN_MEM_READ 
OR 0x80000000 IMAGE_SCN_MEM_WRITE 
----------------------------------- 
   0xE0000020 

Wdasm va rencontrer les mêmes difficultés que ProcDump face à de telles modifications. Seul le lancement du CrackMe permet de rendre "actif" le détournement du PE. Mais est ce vraiment satisfaisant?

Pas totalement.

Le mieux est encore de remettre de l'ordre dans le Chaos de CoOlViPer....

Et à commencer par rediriger le programme vers son VRAI PE Header, puis (et votre oeil exercé ne l'aura pas loupé), REINSCRIRE LE NOMBRE DE SECTIONS du CRACKME, à savoir 03...

00000030 0000 0000 0000 0000 0000 0000 C0000 0000 0E1F BA0E 00B4 09CD .............).........
et
000000C0 5045 0000 4C01 0300 9869 0342 0000 0000 0000 0000 E000 0F00 PE..L....i.B............

Mais il y a bien plus simple, il suffit d'utiliser un autre Dumper comme ProcInfo, et le tour est joué...

Regardons maintenant ce que le code exécutable nous réserve.

Première étape, l'adresse 0040AD70, dont le code commence avec l'inévitable PUSHAD des débuts de décompression:

:0040AD70  60                  PUSHAD
:0040AD71  BE00904000          MOV       ESI,00409000
:0040AD76  8DBE0080FFFF        LEA       EDI,[ESI+FFFF8000]
:0040AD7C  57                  PUSH      EDI
:0040AD7D  83CDFF              OR        EBP,-01
:0040AD80  EB10                JMP       0040AD92

Seconde étape, l'adresse 0040AD92, en plein dans le décompresseur:

:0040AD92  8B1E                MOV       EBX,[ESI]
:0040AD94  83EEFC              SUB       ESI,-04
:0040AD97  11DB                ADC       EBX,EBX
:0040AD99  72ED                JB        0040AD88
etc...

Plutôt qu'un tracing step by step qui peut se révéler fastidieux, une rapide dégringolade avec l'ascenseur dans la fenêtres du code, permet de trouver rapidement le POPAD de fin de décompression:

:0040AEB1  FF96A0A40000        CALL      [ESI+0000A4A0]
:0040AEB7  61                  POPAD
:0040AEB8  E95F66FFFF          JMP       0040151C

Chose étonnante, on trouve un saut direct vers...

:0040151C  60                  PUSHAD
:0040151D  BE00104000          MOV       ESI,00401000
:00401522  BF1B154000          MOV       EDI,0040151B
:00401527  BA52154000          MOV       EDX,00401552
:0040152C  8BCA                MOV       ECX,EDX
:0040152E  32FF                XOR       BH,BH
:00401530  FEC7                INC       BH

Ce qui ressemble à une nouvelle boucle de décompression, avec un beau 0040100, Entry point classique des programmes raisonnables...

Voyons si on trouve le POPAD:

:0040154B  61                  POPAD
:0040154C  68DD144000          PUSH      004014DD
:00401551  C3                  RET

Il est juste quelques lignes plus bas...
Cette fois ci, c'est le PUSH 004014DD/RET qui va être intéressant à suivre:

:004014DD  60                  PUSHAD
:004014DE  BE00104000          MOV       ESI,00401000
:004014E3  BFDC144000          MOV       EDI,004014DC
:004014E8  BA13154000          MOV       EDX,00401513
:004014ED  8BCA                MOV       ECX,EDX
:004014EF  32FF                XOR       BH,BH
:004014F1  FEC7                INC       BH
etc...
:0040150C  61                  POPAD
:0040150D  689E144000          PUSH      0040149E
:00401512  C3                  RET

Avec une reproduction du schéma précèdent.
Vive le coupé Collé...

C'est partie pour l'adresse 0040149E:

:0040149E  60                  PUSHAD
:0040149F  BE00104000          MOV       ESI,00401000
:004014A4  BF9D144000          MOV       EDI,0040149D
:004014A9  BAD4144000          MOV       EDX,004014D4
:004014AE  8BCA                MOV       ECX,EDX
:004014B0  32FF                XOR       BH,BH
:004014B2  FEC7                INC       BH
etc...
:004014CD  61                  POPAD
:004014CE  6800104000          PUSH      00401000
:004014D3  C3                  RET

On prend les mêmes, et on recommence...
Mais cette fois ci, nous y sommes, le programme va lancer l'original Crackme's.

Il suffit, en 004014CE, de remplacer le push 00401000 par un jmp EIP, et de faire un DUMP avec un outil comme le ProcInfo de TeeJi, ou avec TRW2000

:pedump crack_dump_trw.exe
PE dump
VirtualSize RVA PhysicalSize PhysicalOffset
----------
    8000     1000        0      400
    2000     9000     2000      400
    1000     b000      600     2400
Writing DOS head
Writing PE head, from 81741cac, len f8+78
Writing 4001f0 len be10

Dans les deux cas, après remplacement de la valeur de l'Entry Point d'origine par 1000 (l'ImageBase étant 00400000+1000 = 401000, l'Entry Point d'origine du Crackme), le résultat sera runnable et décompilable.

:00401000  6A00                PUSH      00                          > Original Entry Point
:00401002  E861040000          CALL      KERNEL32!GetModuleHandleA
:00401007  A360304000          MOV       [00403060],EAX
:0040100C  6A0A                PUSH      0A
:0040100E  6A00                PUSH      00
:00401010  6A00                PUSH      00
:00401012  FF3560304000        PUSH      DWORD PTR [00403060]
:00401018  E806000000          CALL      00401023
:0040101D  50                  PUSH      EAX
:0040101E  E83F040000          CALL      KERNEL32!ExitProcess

Passons maintenant au crack du programme.

Deux champs à remplir, Name et Serial, pas de message d'erreur en cas de Bad_Serial.
Mais comme il a bien fallu que le programme saisisse le contenu des deux champs, posons un breakpoint comme le classique GetDlgItemtextA:

:00401267  6800010000          PUSH      00000100             ; maxi 100h caractères à saisir
:0040126C  6868314000          PUSH      00403168             ; adresse de stockage du name
:00401271  6A69                PUSH      69                   ; IDentificateur du champ 1
:00401273  FF355C304000        PUSH      DWORD PTR [0040305C] ; handle de la fenêtre-objet
:00401279  E89C010000          CALL      GetDlgItemTextA
:0040127E  6800010000          PUSH      00000100
:00401283  6864304000          PUSH      00403064             ; adresse de stockage du serial
:00401288  6A6A                PUSH      6A                   ; ID du champ 2
:0040128A  FF355C304000        PUSH      DWORD PTR [0040305C]
:00401290  E885010000          CALL      USER32!GetDlgItemTextA

Ensuite vous trouvez les habituels contrôles:

:00401295  6868314000          PUSH      00403168              ; Name
:0040129A  E8E1010000          CALL      KERNEL32!lstrlen      ; calcul de sa longueur
:0040129F  EB03                JMP       004012A4              ; héhé!

Ben vi, le code a été "polymorphé", c'est un CCA (code changeant d'apparence), sensé nous pourrir la vie...
A partir de maintenant, un listing du code n'apportera rien d'immédiatement "visuel". Wdasm sera totalement dupe, seul IDA ne s'en laissera pas compter...

Pour résumer, le programme vérifie que votre Name comprend au moins 4 caractères (ce sera important pour la suite), et que le serial n'est pas absent.

Le générateur de code va ensuite fonctionner en cinq "étapes":

1- les trois premiers caractères du Name sont XORés un à un avec la string "!0585"
soit

c XOR  ! = B
h XOR 05 = m
r XOR 85 = F7

les trois caractères obtenus sont stocké dans l'ordre inverse: F7mB

2- les caractères suivant du name, jusqu'à épuisement, sont tous XORés avec 21, ce qui me donne pour istal = HRU@M

:004012F3  ADD       ESI,03    > pointe sur le quatrième caractère (i)
:004012F6  MOV       AL,[ESI]  > charge le caractère dans al
:004012F8  CMP       AL,00     > vérifie que ce n'est pas le dernier caractère de la string
:004012FA  JZ        00401309  > sinon Go_Out
:004012FC  XOR       AL,21     > XOR ce caractère avec 21
:004012FE  MOV       [EDI],AL  > passe au caractère suivant

3- le résultat du premier XOR (7FmB) est manipulé par SHL EAX, 1C (0000007F -> 70000000) et par SHR eax, 1C (70000000 -> 00000007), puis additionné avec 41 (lettre A) pour obtenir un caractère alphabétique. En fait le 00000007 obtenu sert à pointer dans l'alphabet. Le résultat final donne HNC.

MOV       AL,[ESI]  > la string 7FmB est stockée dans ESI. AL = 7F
SHL       EAX,1C    > 0000007F -> 70000000
SHR       EAX,1C    > 70000000 -> 00000007
ADD       EAX,41    > 07 + 41 = 48 (la lettre H) 
MOV       [EDI],AL  > stockage du résultat obtenu dans l'adresse pointée par EDI

4- même chose pour la seconde chaine XORée. Cette fois ci j'obtiens ICFAN

5- dernière étape, la fonction lstrcat va concaténéer les deux strings, dans laquelle se trouvera inclue un séparateur "-" entre les deux séries: HNC-ICFAN

:0040134E  PUSH      00403474            > 1ere chaîne créée précédemment        
:00401353  PUSH      0040367C            > adresse de stockage de la nouvelle chaine
:00401358  CALL      KERNEL32!lstrcat
:0040135D  PUSH      00403020            > "-"
:00401362  PUSH      0040367C            > adresse de stockage du résultat
:00401367  CALL      KERNEL32!lstrcat
:0040136C  PUSH      00403578            > 2eme chaîne créée précédemment
:00401371  PUSH      0040367C            > adresse de stockage du résultat
:00401376  CALL      KERNEL32!lstrcat

Juste après, caché dans le code polymorphe, vous trouverez en 00401380 un joli:

PUSH      00403064                       > le serial entré
PUSH      0040367C                       > la chaîne concaténée
CALL      lstrcmp                        > comparaison
CMP       EAX,00                         > si une différence a été trouvée
JNZ       004013B7                       > Beggar Off

mais si votre serial est Good, vous aurez droit à une boite de message "Good Serial":

:004013A0  PUSH      00403014            > titre de la messageBox
:004013A5  PUSH      0040367C            > Good message
:004013AA  PUSH      DWORD PTR [EBP+08]  > handle
:004013AD  CALL      USER32!MessageBoxA

L'option que j'ai retenue, est de pousser sur la pile l'adresse du bon code à la place du serial entré (en 00401380), et ce même good serial à la place du message en 004013A5, histoire d'utiliser le crackme pour qu'il retourne lui même le serial à entrer.

Par contre, vous vous en souvenez, le crackme est compressé/crypté...

J'ai patché le programme ainsi:

1- pose d'un BPM sur 004014CE PUSH 00401000 pour voir où et à quel moment le programme décompressait/décryptait l'application d’origine

:0040AE2C  MOV       EAX,[EDX]           > charge le contenu de EDX
:0040AE2E  ADD       EDX,04              > passe au DWORD suivant
:0040AE31  MOV       [EDI],EAX           > stock EAX à l'adresse pointée par EDI
:0040AE33  ADD       EDI,04              > passe au DWORD suivant

1ère remarque: le contenu de l'adresse EDX est déplacé de 12 octets, pour atterrir dans [EDI]
2ème remarque: EDX contient l'adresse en cours de traitement
3ème remarque: un JMP qui irait vers le Patch1 pourrait très facilement se glisser ici...

2- reload du crackme, via le symbol loader, pour savoir si cette adresse était elle aussi compressée: NOP!

3- recherche de place disponible. En l’occurrence, il y en a un peu plus loin, juste après l'INT68...

:0040AEBD  MOV       AH,43
:0040AEBF  INT       68
:0040AEC1  CMP       AX,F386
:0040AEC5  JNZ       0040AECD
:0040AEC7  PUSH      00000000
:0040AECC  RET
:0040AECD  PUSH      0040AD70
:0040AED2  RET
:0040AED3  ADD       [EAX],AL             > place disponible
:0040AED5  ADD       [EAX],AL
:0040AED7  ADD       [EAX],AL
:0040AED9  ADD       [EAX],AL

En fait de compresseur, il laisse pas mal de place derrière lui, les ADD [EAX],AL (00 00), alors que c'est justement son rôle que de les supprimer.
Un BPR 0040AD2 0040AFFF RW ne breakant pas, les adresses visées semblent inutilisées par le Crackme.

4- reroutage de programme vers la zone du patch

:0040AE2E  E9A0000000          JMP       0040AED3     > vers Patch1
:0040AE33  83C704              ADD       EDI,04       > adresse de retour
:0040AE36  83E904              SUB       ECX,04

5- écriture du Patch1. Son rôle sera de détourner le programme immédiatement avant le saut vers l'EIP pour qu'il se rendre au Patch2.

:0040AED3  81FACE144000        CMP       EDX,004014CE > l'adresse attendue en fin de décompression
:0040AED9  7C12                JL        0040AEED     > si EDX est inférieur, on saute
:0040AEDB  81FAD6144000        CMP       EDX,004014D6 > et idem si EDX est supérieur à l'adresse
:0040AEE1  7F0A                JG        0040AEED     > visée + 8 bytes
:0040AEE3  C705CF144000FEAE4000MOV       DWORD PTR [004014CF],0040AEFE  > on patch 
:0040AEED  83C204              ADD       EDX,04       > restoration des octets modifiés par le JMP
:0040AEF0  8907                MOV       [EDI],EAX    > en 0040AE2E
:0040AEF2  E93CFFFFFF          JMP       0040AE33     > et on quitte le Patch1 -> vers adresse de retour

Contrôle du résultat:

:004014CD  POPAD
:004014CE  PUSH      0040AEFE
:004014D3  RET
:004014D4  PUSH      EAX

C'est Good...

6- écriture du Patch2. Il modifie les deux adresses visées, en remplacant l'adresse du serial entré par l'adresse du bon serial:

:0040AEFE  C705811340007C364000MOV       DWORD PTR [00401381],0040367C 
:0040AF08  C705A61340007C364000MOV       DWORD PTR [004013A6],0040367C
:0040AF12  6800104000          PUSH      00401000   > retour à la normale
:0040AF17  C3                  RET                  > et GO OEP

Petit essai...
BINGO!

Le Crackme accepte n'importe quelle combinaison Name/Serial et retourne le bon serial dans la Boite de message.

Cool...

Thank You CoOlViPeR

Bonne Journée

Christal

PS: Le KeyGenerateur qui accompagne ce programme est équipé d'un neutraliseur pour l'Anti SoftIce, mais à la condition qu'ils se trouvent l'un est l'autre dans le même répertoire.