n0p3x Crackme

By Christal

Infections:

- Programme compressé par UPX
- Un Nag shareware au lancement de l'application
- Un KeyFile
- Trois AntiSICE et autant d'antiNTICE
- Une boite d'enregistrement

Le crackme de n0p3x est compressé. Pour noyer le poisson, les sections ont été renommées, mais un outil comme Get Type n'est pas dupe:

TIT0  VS: 00007000  VA: 00001000  RS: 00000000  RA: 00000400  C: E0000080 
TIT1  VS: 00001000  VA: 00008000  RS: 00000200  RA: 00000400  C: C0000040 
TIT2  VS: 00001000  VA: 00009000  RS: 00000C1A  RA: 00000600  C: 60000020 

et il détecte de suite le packeur utilisé:

Portable executable (starting at 256 for 4378 bytes)
Packer: UPX 0.71 - 0.72 [PE]

UPX est particulièrement facile à reverser, poue peu que vous ayez changé le E00000080 en E00000020:

Soit vous utilisez UPX avec l'option "-d" pour que celui ci fasse le travail à l'envers (UnPackage)

Soit vous réalisez un dump à la main:

:00409000  60                  PUSHAD              > Entry Point
:00409001  E800000000          CALL      00409006
:00409006  83CDFF              OR        EBP,-01

Il n'y a plus qu'à trouver où est le POPAD:

:00409197  61                  POPAD
:00409198  EBFE                JMP       00401000

remplacer le jmp 00401000 (Entry Point classique de tout bon programme) par un JMP EIP (pour que l'application boucle sur elle-même), et utiliser l'excellent ProcInfo de TeeJi pour obtenir un dump runnable, désassemblable et avec Strings Data.

Cette formalité étant réglée, passons au vif du sujet.
Dans ce crackme, il ne semble pas qu'il y ait la possibilité d'avoir un statut "REGISTERED", et le nag de départ est prévu pour s'afficher toujours:

:0042B839    CALL      KERNEL32!GetModuleHandleA
:0042B83E    PUSH      EAX
:0042B83F    CALL      [ESI+18]          > apparition du Nag (via MessageBoxA)

Début du call [ESI+18]:

:0040136F    PUSH      EBP
:00401370    MOV       EBP,ESP
:00401372    PUSH      EBX
:00401373    MOV       EBX,00402074       > \\.\SICE
:00401378    PUSH      00001000           > Type du bouton de la Box
:0040137D    LEA       EAX,[EBX+00000229] > Titre "Warning"
:00401383    PUSH      EAX                > poussé sur la pile
:00401384    LEA       EDX,[EBX+000001EC] > message "This is a shareware..."
:0040138A    PUSH      EDX                > poussé sur la pile
:0040138B    PUSH      00                 > attribut d'affichage de la box
:0040138D    CALL      USER32!MessageBoxA > le nag apparaît

Un "D 402074" va nous donner pas mal d'indications:

:00402260 54 68 69 73 20 69 73 20-61 20 73 68 61 72 65 77  This is a sharew
:00402270 61 72 65 20 76 65 72 73-69 6F 6E 20 6F 66 20 74  are version of t
:00402280 68 69 73 20 70 72 6F 67-72 61 6D 2C 20 70 6C 65  his program, ple
:00402290 61 73 65 20 72 65 67 69-73 74 65 72 00 57 41 52  ase register.WAR
:004022A0 4E 49 4E 47 00 53 6F 66-74 49 43 45 20 69 73 20  NING.SoftICE is
:004022B0 6C 6F 61 64 65 64 2E 20-50 6C 65 61 73 65 20 64  loaded. Please d
:004022C0 6F 20 6E 6F 74 20 70 69-72 61 74 65 20 74 68 69  o not pirate thi
:004022D0 73 20 73 6F 66 74 77 61-72 65 2E 00 57 41 52 4E  s software..WARN
:004022E0 49 4E 47 00 53 6F 66 74-49 43 45 20 69 73 20 6C  ING.SoftICE is l
:004022F0 6F 61 64 65 64 2E 20 50-6C 65 61 73 65 20 64 6F  oaded. Please do
:00402300 20 6E 6F 74 20 70 69 72-61 74 65 20 74 68 69 73   not pirate this
:00402310 20 73 6F 66 74 77 61 72-65 2E 00 57 41 52 4E 49   software..WARNI
:00402320 4E 47 00 59 6F 75 27 72-65 20 6B 65 79 66 69 6C  NG.You're keyfil
:00402330 65 20 69 73 20 69 6E 76-61 6C 69 64 2E 20 50 6C  e is invalid. Pl
:00402340 65 61 73 65 20 6F 62 74-61 69 6E 20 61 20 63 6F  ease obtain a co
:00402350 72 72 65 63 74 20 6B 65-79 66 69 6C 65 20 66 72  rrect keyfile fr
:00402360 6F 6D 20 79 6F 75 72 20-73 6F 66 74 77 61 72 65  om your software
:00402370 20 76 65 6E 64 6F 72 00-57 41 52 4E 49 4E 47 00   vendor.WARNING.

Le programme est équipé d'au moins un AntiSice, et nécessite une KeyFile.
C'est un bon début, ça...
Voyons la suite:

:00401392    CALL      0040107C           > test de la présence de SICE
:00401397    TEST      EAX,EAX            > si EAX = de 0
:00401399    JZ        004013B7           > évite le MessageBox
:0040139B    PUSH      00001000           > type de bouton
:004013A0    LEA       ECX,[EBX+00000268] > charge string Titre
:004013A6    PUSH      ECX                > pousse tître
:004013A7    LEA       EAX,[EBX+00000231] > charge string Message
:004013AD    PUSH      EAX                > pousse message
:004013AE    PUSH      00                 > attribut de la box
:004013B0    CALL      USER32!MessageBoxA > "SoftIce Loaded"
:004013B5    JMP       00401414

Dans le call 0040107C, la présence de SoftIce est détectée par un MeltIce:

:0040107C    PUSH      EBP
:0040107D    MOV       EBP,ESP
:0040107F    PUSH      00                    > handle du fichier
:00401081    PUSH      00000080              > attributs du fichier
:00401086    PUSH      03                    > type de création
:00401088    PUSH      00                    > pointeur lpSecurityAttributes
:0040108A    PUSH      03                    > dwShareMode
:0040108C    PUSH      C0000000              > mode d'accès (read-Write)
:00401091    PUSH      00402074              > pousse SICE
:00401096    CALL      KERNEL32!CreateFileA  > recherche
:0040109B    CMP       EAX,-01               > -1 si non trouvé
:0040109E    JZ        004010AD            

un "D 00402074" va encore nous donner des tuyaux intéressants:

:00402074 5C 5C 2E 5C 53 49 43 45-00 5C 5C 2E 5C 4E 54 49  \\.\SICE.\\.\NTI
:00402084 43 45 00 52 65 67 69 73-74 65 72 2E 64 61 74 00  CE.Register.dat.

Pour se débarasser définitivement de l'antiSice, il suffit de remplacer les quatre caractères de SICE par n'importe quoi d'autre. Les trois tests de détection effectués par le crackme font tous référence à l'adresse 00402074, mais il ne faut pas oublier que le programme est packé. Un BPM 00402074 va nous permettre de savoir à quel moment cette adresse est décompréssée par UPX:

:00409028  8A06                MOV       AL,[ESI]  > ESI = 00409697
:0040902A  46                  INC       ESI       > byte suivant
:0040902B  8807                MOV       [EDI],AL  > al mis dans 00402074
:0040902D  47                  INC       EDI       > byte suivant
:0040902E  01DB                ADD       EBX,EBX   > vérifie état décompression
:00409030  7507                JNZ       00409039  > OK ? -> pas de décompression
:00409032  8B1E                MOV       EBX,[ESI] > chaîne à traiter dans EBX
:00409034  83EEFC              SUB       ESI,-04   > décrémente ESI d'un DWORD
:00409037  11DB                ADC       EBX,EBX   > décompression
:00409039  72ED                JB        00409028  > boucle

Il faut bien distinguer que UPX est un compresseur, pas un crypteur! Son rôle est de diminuer la taille de l'exécutable, pas de planquer du code...

.00409690:  FB 5F 61 D1-F9 58 5C 5C-2E 5C 53 49-43 45 08 FF  ¹_aШX\\.\SICE 
.004096A0:  FF FF 1D 4E-54 09 52 65-67 69 73 74-65 72 2E 64    NT	Register.d

En 00409690, le code est en clair et accéssible par un HexEditeur. C'est dans cette zone mémoire qu'UPX va récupérer les codes compressé et les déplacer vers la zone mémoire qui sera utilisée par l'exécutable. Tous les bytes qu'UPX aura lu lors de son application ne sont pas obligatoirement compressable, et il est normal que des portion du code se retrouve en clair. Morceau de chance, SICE font partie de ceux la.

Outre la présence de SoftIce pour Window 98, le programme va aussi chercher si le crackme ne tourne pas sur une version NT. Bien plus intéressant, vous remarquerez le "register.dat"...
Quand on se rappelle le "KeyFile" de tout à l'heure, il y a de quoi se faire une petite idée, non?
Mais chaque chose en son temps, continuons:

:004013B7    CALL      004010B1            > détection NTICE
:004013BC    TEST      EAX,EAX             > toujours pas un MeltIce
:004013BE    JZ        004013DC            > good boy -> Go on
:004013C0    PUSH      00001000            > type des boutons
:004013C5    LEA       EDX,[EBX+000002A7]  > tître
:004013CB    PUSH      EDX
:004013CC    LEA       ECX,[EBX+00000270]  > message
:004013D2    PUSH      ECX
:004013D3    PUSH      00                  > attribut de la boite
:004013D5    CALL      USER32!MessageBoxA  > "softIce detected"
:004013DA    JMP       00401414

Il faut reconnaître que les Crackme's ont du bon, comme ce sont de minuscules programmes, tout est sous la main...

:004013DC    CALL      004010FC  > traitement du fichier "register.dat"
:004013E1    TEST      EAX,EAX   > si absent, ou invalide
:004013E3    JZ        004013FA  > fermeture du programme

La recherche et le traitement de "Register.dat" est assez facile à suivre:

:004010FC    PUSH      EBP
:004010FD    MOV       EBP,ESP
:004010FF    ADD       ESP,-38
:00401102    PUSH      EBX       > pousse \\.\SICE 
:00401103    PUSH      ESI
:00401104    PUSH      00
:00401106    PUSH      00402087  > pousse "register.dat"
:0040110B    CALL      004017A6  > et en cherche la présence (dans cw3220.dll)
:00401110    ADD       ESP,08

:00402087 52 65 67 69 73 74 65 72-2E 64 61 74 00 57 68 79  Register.dat.Why
:00402097 20 64 69 64 6E 27 74 20-6E 30 70 33 78 20 75 73   didn't n0p3x us
:004020A7 65 20 61 20 6D 6F 72 65-20 64 69 66 66 69 63 75  e a more difficu
:004020B7 6C 74 20 6B 65 79 66 69-6C 65 20 6D 65 74 68 6F  lt keyfile metho
:004020C7 64 3F                                            d?

La phrase qui suit Register.dat est pour le moins étrange. Nous verrons ce qu'il en est dans un instant.

:00401113    MOV       EBX,EAX      > résultat dans EBX
:00401115    INC       EAX          > si était égale à -1 
:00401116    JZ        00401145     > fichier non trouvé
:00401118    PUSH      35
:0040111A    LEA       EDX,[EBP-38]
:0040111D    PUSH      EDX
:0040111E    PUSH      EBX
:0040111F    CALL      004017AC     > vers cw3220.dll
:00401124    ADD       ESP,0C
:00401127    MOV       ESI,EAX
:00401129    INC       EAX
:0040112A    JZ        00401145
:0040112C    PUSH      00402094     > pousse "Why didn't n0px3..."
:00401131    LEA       EDX,[EBP-38] > charge texte trouvé dans register.dat
:00401134    PUSH      EDX          > et le pousse sur la pile
:00401135    CALL      lstrcmp      > compare les deux chaînes 
:0040113A    TEST      EAX,EAX      > si OK, valeur de retour est un pointeur 
:0040113C    JNZ       00401145     > sinon EAX =00 -> Go Out
:0040113E    MOV       EAX,00000001 > Flag = 01 -> Good Boy
:00401143    JMP       0040114E     > et sortie
:00401145    PUSH      EBX
:00401146    CALL      00401782
:0040114B    POP       ECX
:0040114C    XOR       EAX,EAX      > Flag = 00 -> Bad Boy
:0040114E    POP       ESI
:0040114F    POP       EBX
:00401150    MOV       ESP,EBP
:00401152    POP       EBP
:00401153    RET

La suite est nettement plus amusante, si la comparaison entre la string "Why didn't..." et celle trouvée dans le fichier Register.dat (que vous aurez créé pour l'occasion) donne une valeur différente de NULL (valeur retournée en cas d'échec), le Crackme va ouvrir une boite d'enregistrement à trois champs: Name, Company et Serial:

:004013E5    PUSH      00                      > valeur d'initialisation
:004013E7    PUSH      00401301                > pointe vers dialogBox Proc
:004013EC    PUSH      00                      > handle de la fenêtre propiétaire
:004013EE    PUSH      01                      > identifie Template de la DialogBox
:004013F0    PUSH      DWORD PTR [EBP+08]      > handle Instance de l'application
:004013F3    CALL      USER32!DialogBoxParamA  > création d'une Modal DialogBox
:004013F8    JMP       00401414                > saute par dessus routine Pas Glop!

Une grande partie du traitement du serial va se faire via des allers/retours dans le fichier cw3220.dll.
Les contrôle sont assez nombreux, mais en voici les parties les plus intéressante, en utilisant un BPX GetDlgItemTextA comme point d'entrée (API qui sera utilisée pour saisir les caractères un à un):

:0040127C    LEA       EAX,[EBP-14]      > Name
:0040127F    PUSH      EAX               > poussé sur la pile
:00401280    CALL      KERNEL32!lstrlen  > calcul la longueur
:00401285    MOV       EBX,EAX           > met en mémoire
:00401287    LEA       EAX,[EBP-28]      > Company
:0040128A    PUSH      EAX               > poussé sur la pile            
:0040128B    CALL      KERNEL32!lstrlen  > calcul la longueur
:00401290    IMUL      EBX,EAX           > multiplie les 2 longueurs
:00401293    ADD       EBX,000F3EA9      > ajoute 0F3EA9 (clé)
:00401299    MOV       EAX,EBX           > met en mémoire
:0040129B    PUSH      0A                > pousse le diviseur
:0040129D    LEA       EDX,[EBP-70]      > @ où est stockée la clé
:004012A0    PUSH      EDX               > pousse @ de la clé
:004012A1    PUSH      EAX               > pousse la clé
:004012A2    CALL      0040179A          > Let's go...

Nous avons donc un calcul du nombre de caractères qui composent le Name et la Company. Ces deux valeurs sont multipliées entres elles, puis n0p3x y rajoute 0F3EA9. Cette valeur va être stockée de deux façons: principalement dans un des registres (ils se passent le tour...) et dans une adresse [mémoire]. 0A qui est poussé en 0040129B va jouer le rôle de diviseur...

Commençons par le commencement:

:0040124B    PUSH      EAX                > pousse serial
:0040124C    CALL      KERNEL32!lstrlen   > calcul longueur
:00401251    CMP       EAX,09             > compare avec 9
:00401254    JGE       00401275           > saute si > ou égal
:00401256    PUSH      00001000
:0040125B    LEA       EDX,[ESI+000000F9] > titre
:00401261    PUSH      EDX
:00401262    LEA       ECX,[ESI+000000B9] > message
:00401268    PUSH      ECX
:00401269    PUSH      00
:0040126B    CALL      USER32!MessageBoxA > "the serial number you have entered..."
:00401270    JMP       004012FB 

Le serial doit donc comporter 9 caractères minimum, sinon vous aurez une boite de message Pas Glop!
La partie la plus intéressante du traitement de cette clé se fait ici:

:0042573B    MOV       EAX,ESI      > clé dans EAX
:0042573D    XOR       EDX,EDX      > ré-initialise EDX (le reste)
:0042573F    DIV       EDI          > clé / 0A = clé 2 -> reste dans EDX

DIV EDI va diviser EDI par 0A. La partie entière (clé 2) va être stockée dans ESI, et le reste dans EDX. C'est ce reste qui va générer un caractère du serial.

:00425741    MOV       [ECX],DL       > stock le reste dans [mémoire]
:00425743    INC       ECX
:00425744    MOV       EAX,ESI        > EAX = partie entière
:00425746    XOR       EDX,EDX        > réinitialise EDX (reste)
:00425748    DIV       EDI            > clé 2 / 0A = clé 3 -> reste dans EDX
:0042574A    MOV       ESI,EAX        > ESI = partie entière
:0042574C    TEST      EAX,EAX        > si EAX = 0 -> plus de partie entière
:0042574E    JNZ       0042573B       > sinon -> boucle
:00425750    JMP       00425769       > EAX =0
:00425752    DEC       ECX  
:00425753    MOV       AL,[ECX]       > ECX pointe sur le bon serial

Ce serial est stocké sous la forme 07 03 01 09 09 09. Les valeurs calculées par le reste de la division sont stockées en "sens inverse" à l'adresse pointée par ECX (d'ou le dec ECX). Al va recevoir cette valeur, un chiffre hexédécimal inférieur à 10 (puisque c'est un reste!).

:00425755    CMP       AL,0A          > si plus de 9 caractères ?!
:00425757    JGE       00425761       > traitement particulier
:00425759    ADD       EAX,30         > ajoute 30h

Le fait d'ajouter 30h au reste va "transformer" un chiffre hexa en sa correspondance ASCII.

:0042575C    MOV       [EBX],AL       > stock valeur ASCII dans EBX
:0042575E    INC       EBX            > et pointe sur caractère suivant
:0042575F    JMP       00425769
:00425761    ADD       AL,[EBP+18]    > NA
:00425764    ADD       AL,F6          > NA
:00425766    MOV       [EBX],AL       > NA
:00425768    INC       EBX            > NA
:00425769    LEA       EDX,[EBP-24]
:0042576C    CMP       ECX,EDX        > tous les caractères ont été traités?
:0042576E    JNZ       00425752       > non -> boucle

Le nombre de caractères à traiter est de 6. Cette valeur est liée à l'addition du résultat de la multiplication de la taille du Name et du Serial, avec 0F3EA9. Comme le bon serial est basé sur une division par 10, il ne peut pas y en avoir plus, d'ou un traitement particulier destiné à gérer les erreurs.
Pour autant, si vous vous en rappelez, le serial est sensé être composé de 9 caractères. Il en manque donc trois. En affichant l'adresse de [EBX], où se trouve stocké le bon serial, dans la fenêtre des Data's de SoftIce et en continuant à tracer:

:004012AA    LEA       ECX,[ESI+00000102] > pointe sur string :-)
:004012B0    PUSH      ECX                > pousse adresse sur la pile
:004012B1    LEA       EAX,[EBP-70]       > pointe sur adresse serial
:004012B4    PUSH      EAX                > poussé sur la pile
:004012B5    CALL      KERNEL32!lstrcat   > concaténation des 2 strings

Voici ce que l'on trouve en [ESI+0102] = 00402176:

:00402126 52 4E 49 4E 47 21 00 54-68 65 20 73 65 72 69 61  RNING!.The seria
:00402136 6C 20 6E 75 6D 62 65 72-20 79 6F 75 20 68 61 76  l number you hav
:00402146 65 20 65 6E 74 65 72 65-64 20 69 73 20 6E 6F 74  e entered is not
:00402156 20 6F 66 20 74 68 65 20-63 6F 72 72 65 63 74 20   of the correct
:00402166 6C 65 6E 67 74 68 00 57-41 52 4E 49 4E 47 21 00  length.WARNING!.
:00402176 3A 2D 29 00 43 6F 6E 67-72 61 74 75 6C 61 74 69  :-).Congratulati
:00402186 6F 6E 73 2E 20 42 65 20-73 75 72 65 20 74 6F 20  ons. Be sure to
:00402196 65 6D 61 69 6C 20 6D 65-2E 00 57 65 6C 6C 20 64  email me..Well d

Cette fois ci le serial est complet, en y ayant ajouté :-)
Puis,

:004012BA    PUSH      EAX                > pousse le serial obtenu
:004012BB    LEA       EDX,[EBP-3C]       > pointe sur serial entré
:004012BE    PUSH      EDX                > poussé sur la pile
:004012BF    CALL      KERNEL32!lstrcmp   > comparaison
:004012C4    TEST      EAX,EAX            > si EAX = 0 
:004012C6    JNZ       004012E1           > -> Bad Boy 
:004012C8    PUSH      40
:004012CA    LEA       ECX,[ESI+0000012C] > titre
:004012D0    PUSH      ECX
:004012D1    LEA       EAX,[ESI+00000106] > message
:004012D7    PUSH      EAX
:004012D8    PUSH      00
:004012DA    CALL      USER32!MessageBoxA > Glop! Glop!
:004012DF    JMP       004012FB           > sortie
:004012E1    PUSH      00001000
:004012E6    LEA       EDX,[ESI+0000018D] > titre
:004012EC    PUSH      EDX
:004012ED    LEA       ECX,[ESI+00000136] > message
:004012F3    PUSH      ECX
:004012F4    PUSH      00
:004012F6    CALL      USER32!MessageBoxA > Pas Glop! Pas Glop!
:004012FB    POP       ESI               
:004012FC    POP       EBX                > restore les registres
:004012FD    MOV       ESP,EBP
:004012FF    POP       EBP
:00401300    RET

Voilà, nous sommes arrivés au bout de nos peines.
Il manquerait, pour se faire plaisir, un petit test qui au lancement suivant du programme permettrait à celui ci de ne pas afficher le Nag Screen Shareware...

Pour finir sur une touche sympathique, voici une proposition:

Pour un
crackGen:
En 004012BA le bon serial est dans EAX. Il suffirait juste après de glisser un jump 004012C8 pour que le programme se branche sur la MessageBox "Glop! Glop!", puis de supprimer le LEA EAX,[ESI+00000106] en 004012D1 pour que ce soit le bon serial qui s'affiche dans la Box à la place du texte (attention au test des 9 caractères attendus dans le champs SERIAL).

et pour un
KeyGen:

      lea    eax, [offset Name]     ; calcul de la longeur du Name
      push   eax
      call   lstrlen
      mov    ebx,eax                ; sauvegarde résultat
      lea    eax, [offset Company]  ; calcul de la longueur de Company
      push   eax
      call   lstrlen
      imul   ebx,eax                ; multiplication des 2 résultats obtenus
      add    ebx,000F3EA9h          ; ajoute nombre magique
      mov    eax, ebx               ; résultat dans eax
      mov    edi, 0Ah               ; edi est le diviseur
      mov    ecx,05                 ; initialise serial de 6 caractères
boucle1:
      xor    edx,edx                ; réinitialise le registre du reste
      div    edi                    ; division de eax/0A -> reste dans EDX
      add    edx,30h                ; transforme EDX (hexa) en ASCII 
      mov    [ecx+Serial],dl        ; stock dans mémoire (à l'envers)
      dec    ecx                    ; d'ou position -1 
      test   eax,eax                ; EAX n'est pas encore à 00 ?
      jne    boucle1                ; recommence 

      lea    ecx, [offset cle]      ; pointe sur string ":-)"
      push   ecx
      lea    eax, [offset Serial]   ; pointe sur serial
      push   eax
      call   lstrcat                ; concatène les deux strings

Bonne Journée

Christal