Crackme GOLD de CoOlViPeR

By Christal

CoOlViPeR Crackme Gold

A peu près en même temps, iorior et moi nous étions intéressés au CrakmeGold de CoOlViPeR.
A la lecture du texte que iorior en a écrit, je me suis rendu compte que nous avions eu une démarche identique, à quelques variantes prés.
C'est uniquement de ces petites différences dont il va être question ici, la base de la résolution du CrackmeGold étant particulièrement bien détaillée dans le texte de iorior.

Bidouilles sur le MZ header et le PE Header (schéma identique au CrackMe 1):

CoOlViPeR ayant inclu un anti ProcDump/anti Wdasm 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é...

Créer un dump:

Se reporter au texte de irorior.

Le Code polymorphé (ou CCA ou SMC):

Même approche que iorior: utilisation d'IDA, et suppressions des EB03... avec un HexEditeur.
Se reporter à son texte.

EB 01 50          -> 38 occurences
EB 02 8B EC       -> 43 occurences
EB 03 CD 20 C7    -> 41 occurences
EB 03 E9 21 47    -> 35 occurences
EB 04 EB CD 20 EB -> 26 occurences

Les anti SoftIce:

Dès le démarrage du Crackme, vous trouvez l'initialisation de l'Exception handler (SEH), pour qui les mémoires réservées 004038C, 00403388 et 00403392 vont servir à recevoir les adresses des "bonnes et mauvaises procédures".

:00401036  PUSH      EBP
:00401037  MOV       EBP,ESP
:00401039  ADD       ESP,-4C
:00401040  SIDT      FWORD PTR [0040338C]           ; stock IDT dans SEH3 
:0040104C  MOV       DWORD PTR [EBP-30],  00000030   
:00401058  MOV       DWORD PTR [00403388],004017C5  ; @ Sice Detected dans SEH2
:00401068  MOV       DWORD PTR [EBP-2C],  00000003
:00401073  MOV       DWORD PTR [EBP-28],  0040126F    
:0040107F  MOV       DWORD PTR [00403392],00401170  ; @ No problem dans SEH1
:0040108E  MOV       DWORD PTR [EBP-24],  00000000
:0040109B  MOV       BYTE PTR [00401214], 00        ; auto patch du programme !
:004010A7  MOV       DWORD PTR [EBP-20],  0000001E
:004010B3  PUSH      EAX                            ; adresse du MZ header sur la pile
:004010B8  INC       EDI
:004010B9  MOV       EAX,FS:[00000000]              ; récupère l'état de la Pile SEH original
:004010C5  MOV       [00403384],EAX                 ; et le sauve en mémoire SEH0
:004010CE  POP       EAX                            ; récupère l'adresse du MZ header
:004010D4  PUSH      DWORD PTR [EBP+08]
:004010DA  POP       DWORD PTR [EBP-1C]
:004010E2  MOV       DWORD PTR [EBP-10],  00000008
:004010EF  MOV       DWORD PTR FS:[0],    00403384  ; adresse de la mémoire SEH0 dans la pile SEH
:004010FD  MOV       DWORD PTR [EBP-08],  00403000
:00401107  PUSH      68                             ; identificateur de l'Icône
:00401109  PUSH      DWORD PTR [00403074]           ; handle de l'application
:0040110F  CALL      LoadIconA
:00401119  MOV       [EBP-18],EAX                   ; sauve l'handle de l'icône
:00401121  MOV       DWORD PTR [EBP-04],  00000000
:0040112D  PUSH      00007F00                       ; identificateur du curseur
:00401132  PUSH      00                             ; curseur standard
:00401134  CALL      LoadCursorA
:0040113F  MOV       [EBP-14],EAX
:00401146  LEA       EAX,[EBP-30]                   ; adresse de la structure DATA
:00401149  PUSH      EAX
:0040114A  CALL      RegisterClassExA
:00401154  PUSHAD
:00401158  MOV       [00403396],ESP
:00401163  SUB       EAX,EAX
:0040116A  DIV       EAX                            ; provoque une EXCEPTION

Le DIV EAX semble ne pas pouvoir être évité.
Si vous utilisez SoftIce ou TRW2000, vous aurez dans les deux cas une boite "SoftIce Detected", puis un ExitProcess (que Sice soit chargé ou non lors de l'utilisation de TRW 2000), alors qu'avec Wdasm en mode débuggage l'exception crée par le DIV EAX (division par 0) vous enverra vers la traçage de la boite de dialogue dans laquelle il vous sera demandé d'entre un Name et un serial.

La SEH peut être facilement contournée dans la routine de détection:

:004017C5  NOP
:004017C9  XOR       EAX,EAX
:004017D0  MOV       EDI,[0040338E]           ; EDI = 800A8000 (IDTbase)
:004017DB  MOV       AL,[EDI+0E]              ; AL = 4 -> présence d'un débuggeur (Magic Number)
:004017E3  OR        AL,AL                    ; AL reste à 4 -> à remplacer par xor AL,AL
:004017E9  JZ        00401851                 ; et on sautera vers la bonne procédure
:004017EE  MOV       EDI,USER32!MessageBoxA   ; charge l'adresse de l'API dans EDI
:004917F   MOV       EDI,[EDI+02]
:00401800  MOV       EDI,[EDI]
:00401806  MOV       EAX,[EDI]
:0040180D  AND       EAX,000000FF
:00401817  CMP       EAX,000000CC
:00401821  JNZ       00401831
:00401827  PUSH      00                   
:00401829  CALL      KERNEL32!ExitProcess
:00401831  PUSH      40
:00401833  PUSH      00403014
:00401838  PUSH      0040304C                 ; Numéga SoftIce Detected
:0040183D  PUSH      00
:0040183F  CALL      USER32!MessageBoxA
:0040184A  PUSH      00
:0040184C  CALL      KERNEL32!ExitProcess
:00401851  NOP                                ; Glop Glop !
:00401855  ADD       EAX,00403392             ; ajoute le contenu de AL à l'@ stockée en 00403392
:0040185F  PUSH      DWORD PTR [EAX]          ; pousse l'adresse de retour sur la pile
:00401864  RET

En l'occurence, l'adresse stocké dans 00403392 est 00401170 (adresse Glop!), une des adresses servant à initialiser la SEH, et celle stockée dans 00403392+4 est 0063FDB8, l'état de la Pile SEH original.
Pour shunter la détection anti XXX, il est possible d'empécher la mise à 04 de AL, ou plus simplement de modifier la SEH dès l'entrée:

:00401058  MOV       DWORD PTR [00403388],004017C5  ; -> en remplaçant 004017C5 par 00401170

en 0040338E se trouve stocké l'IDTbase (rappellez vous le SIDT FWORD PTR [0040338C] -> 0040338E = 0040338C+2).
Pulsar en avait déjà, depuis un moment, expliqué le mécanisme:

Int   Type     Sel:Offset     Attributes Symbol/Owner
IDTbase=800A8000  Limit=02FF
0000  IntG32   0028:C0001350  DPL=0  P   VMM(01)+0350
0001  IntG32   0028:C0001360  DPL=3  P   VMM(01)+0360
0002  IntG32   0028:C00046E0  DPL=0  P   Simulate_IO+02A0
0003  IntG32   0028:C0001370  DPL=3  P   VMM(01)+0370

:800A8000   00281350  C0008E00  00284B0F  C004EE00      P.(......K(.....
:800A8010   00284B1E  C0048E00  00284B2D  C004EE00      .K(.....-K(.....
:800A8020   00281380  C000EE00  00281390  C000EE00      ..(.......(.....

L'IDT réserve 8 bytes pour chaque interruption differente
Ce qui donne quelque chose dans ce gout là pour chaque ligne:

                                           ___________Lowword de la routine de l'int (n+1)
                                          |      ____________ high word de la routine (n+1)
                                         _|__ __|_	
                   XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX
                       ---- ----
Lowword de la routine___|    |_____high word
qui gere l'int n


En sachant que la premiere INT est l'INT0, on en deduit que les bytes concernants l'interrupt 03h sont les suivants:

00284B0F C004EE00

On trouve que le lowword de notre routine est 4B0F et le highWord C004, ce qui nous fait une adresse egale a C0044B0F.
Si en 800A800E vous avez un 4, c'est qu'il y a un débuggeur dans le coin (aussi bien Sice que TRW), principe que CoOlViPeR n'hésite pas à utiliser à son profit.

Un autre Anti XXX est très facile à trouver, W32dasm en donne l'adresse via Les String Data références:

:004016C7  PUSH      00
:004016C9  PUSH      00403065                    ; push \\.\SICE
:004016CE  CALL      KERNEL32!_lopen             ; _lopen  = version 16 bits de CreateFileA
:004016D8  INC       EAX                         ; si Sice est absent EAX = -1
:004016DE  JZ        0040174A                    ; et dans ce cas on revient en 00401673
:004016E3  MOV       EDI,USER32!MessageBoxA      ; petite technique à CoOlViPeR
:004016EE  MOV       EDI,[EDI+02]                ; pour afficher "discrétement" une MessageBox
:004016F4  MOV       EDI,[EDI]                   ; un BPM MessageBoxA permet d'obtenir un break
:004016FA  MOV       EAX,[EDI]                   ; alors qu'un BPX API échoue. 
:00401701  AND       EAX,000000FF                ; On coutourne le MeltIce en remplaçant SICE
:0040170B  CMP       EAX,000000CC                ; par MICE avec un HexEditeur.
:00401713  JNZ       00401727                    ; -> vers affichage de la box
:0040171B  PUSH      00
:0040171D  CALL      KERNEL32!ExitProcess
:00401727  PUSH      40
:00401729  PUSH      00403014
:0040172E  PUSH      0040304C                    ; Numega SoftIce detected
:00401733  PUSH      00
:00401735  CALL      USER32!MessageBoxA
:0040173E  PUSH      00
:00401740  CALL      KERNEL32!ExitProcess
:0040174A  RET

Et juste en dessous, dans la foulée, vous pouvez repérez de suite une INT68.
Merci CoOlViPeR, sympa de nous les mettre l'une au dessus de l'autre...

:0040174F  MOV       AH,43
:00401755  INT       68                         ; l'INT 68 à nopper
:0040175A  CMP       AX,F386
:00401763  JNZ       004017C4
 etc...
:004017C4  RET

Vala, vala...
C'en est fini avec les Anti XXX.

La recherche du serial:

Suite à une discution avec iorior, je me suis rendu compte qu'il était plus avancé que moi dans la recherche du serial, et pour tout dire, iorior m'a mis sur la bonne voie pour la dernière partie du contrôle de celui ci...
Dans son texte, il n'en développe pas jusqu'au bout le mécanisme, bien qu'il le connaissait:

:00401450  PUSH      00000100
:00401455  PUSH      0040317C                   ; buffer de stockage du Name
:0040145A  PUSH      69
:0040145C  PUSH      DWORD PTR [00403070]
:00401462  CALL      USER32!GetDlgItemTextA     ; saisie du 1er champ (Name)
:0040146D  PUSH      00000100
:00401472  PUSH      00403078                   ; buffer de stockage du Serial
:00401477  PUSH      6A
:00401479  PUSH      DWORD PTR [00403070]
:0040147F  CALL      USER32!GetDlgItemTextA     ; saisie du 2d champ (Serial)
:00401488  PUSH      00403078                   ; serial poussé sur la pile
:0040148D  CALL      KERNEL32!lstrlen           ; calcul de sa longueur
:00401497  CMP       EAX,06                     ; si inférieur à 6 digits
:0040149F  JL        00401682                   ; Go Out
:004014A8  PUSH      0040317C                   ; Name poussé sur la pile
:004014AD  CALL      KERNEL32!lstrlen           ; calcul de sa longueur
:004014B7  CMP       EAX,04                     ; si inférieur à 4 (important!)
:004014BF  JL        00401682                   ; Go Out

CoOlViPeR nous truande un peu (et c'est de bonne guerre!)...
Il fait un contrôle sur une longueur de serial de 6 digits, alors que dans la réalité le serial devra être beaucoup plus long que ca. Il se décomposera en cinq parties:

00403078 -> overflow          (A)
0040307C -> séparateur        (B)
0040307D -> autopatch 1       (C)
00403081 -> autopatch 2       (D)
00403084 -> checksum          (E)
:004014C8  XOR       EBX,EBX                    ; ebx = 0
:004014CD  XCHG      EAX,ECX                    
:004014D1  XOR       EAX,EAX                    ; eax = 0
:004014D6  MOV       ESI,0040317C               ; esi = Name entré
:004014DF  LODSB                                ; charge un caractère dans EAX
:004014E4  ADD       EBX,EAX                    ; l'ajoute à EBX
:004014EA  LOOP      004014DB                   ; et recommence jusqu'à épuisement

A la sortie de cette routine, EBX contient la somme ASCII des caractères du Name entré.

:004014F1  MOV       EAX,[0040307D]             ; EAX = point sur le Dword du 6ème caractère
:004014FB  ADD       EBX,EAX                    ; ajouté à la somme ASCII du Name
:00401502  MOV       EAX,[0040302E]             ; EAX = "clé secrète" -> 582105F5
:0040150C  MUL       EBX                        ; clé x somme ASCII
:00401513  MOV       [00401563],EAX             ; autopatch du programme

Ces quelques lignes sont riches...
Nous avons tout d'abord la somme ASCII des caractères du Name (type 0x035A) ajoutée à un Dword commencant au 6ème caractère du serial.
On obtient une nouvelle somme ASCII qui est multipliée par une clé de 4 bytes (toujours la même):

0030:0040302E F5 05 21 58 47 4F 4C 44-56 65 72 73 69 6F 6E 20  ..!XGOLDVersion
0030:0040303E 45 6E 72 65 67 69 73 74-72 E9 65 20 21 00 4E 75  Enregistr.e !.Nu
0030:0040304E 6D 65 67 61 20 53 6F 66-74 49 63 65 20 64 E9 74  mega SoftIce d.t
0030:0040305E 65 63 74 E9 20 21 00 5C-5C 2E 5C 53 49 43 45 00  ect. !.\\.\SICE.

Le résultat de la multiplication est stocké dans EAX, et le débordement (OverFlow) de celle ci dans EDX.
Vous verrez dans un instant l'importance que cela peut avoir...
Et pour finir, le programme s'autopatch, provoquant une erreur (et pour cause, puisque les codes du patch sont issus de la multiplication de votre Name) qui suivant les cas provoquera, ou non, une exception récupérée par la SEH original

Operation: MUL 
Fonction : Multiplie l'Accumulator par B 
Syntaxe  : MUL AB 

Instructions  OpCode  Bytes  Cycles  Flags 
MUL   AB       0xA4     1      4     C, OV 

Description: Multiplie la valeur non signée de l'Accumulator (A) par une valeur non signée stockée dans un des registres (B). Le byte le moins significatif (bit de poids faible) est replacé dans l'Accumulator et le plus significatif (bit de poids fort) dans le registre "B".

Le Carry Flag (C) est toujours vidé.

L'Overflow Flag (OV) est déclanché si le résultat est plus grand que 255 (si le bit de poids faible n'est pas zéro), sinon il est vidé.

Malgré ces explications, je ne voyais toujours pas bien ce que la fonction MUL faisait exactement, et notamment ce qu'il en était de son débordement (Overflow).
EDX contient donc le débordement de la multiplication non signée, et quelques lignes plus loin:

:0040156C  MOV       EAX,[00403078]    ; eax = 4 premiers caractères du serial entré (le 1er DWORD)
:00401577  CMP       EAX,EDX           ; comparé au débordement de la multiplication
:0040157D  JNZ       0040166E          ; et si la comparaison n'est pas favorable -> Go Out!

Nous avons donc d'une part le résultat direct de la multiplication (stocké dans EAX) qui va faire planter le programme, et d'autre par le débordement de cette même multiplication qui va être comparé aux 4 premiers caractères du serial.
Ces 4 premiers caractères formant un DWORD, il faut impérativement obtenir un DWORD identique dans EDX.
C'est là que j'ai souffert...

Je n'arrivais pas à obtenir mieux qu'un Word, et encore avec un Name de plusieurs kilomètres...
La vérité est ailleurs...

Revenons à notre multiplication non signée:

EAX     EBX     EAX   EBX   EDX
 2  MUL  3   =   6     3     0
-2  MUL -3   =   6    -3    -5
-4  MUL -5   =  20    -5    -9

La multiplication fonctionne comme une opération en valeur absolue pour le résulat dans EAX, EBX conserve sa valeur d'origine, EDX reçoit la somme de EAX + EBX (du moins pour les petites valeurs).

Ce qui est intéressant, avec les nombres négatifs, c'est qu'EDX contient du coup un DWORD (-9 = 0xFFFFFFF7)!
Et que ce DWORD deviendra le premier DWORD (4 premiers caractères) de notre serial...

Pour autant, il ne faut pas oublier l'autopatch...
Et pour éviter de mauvaises surprises, l'idéal serait que la MULtiplication génère un 90909090 comme résultat dans EAX.
Pour reprendre les explications de iorior:

La somme des caractères de iorior est de 294h

( somme du nom + car6789 ) * 582105F5h = 90909090h
( 294h + car6789 )         * 582105F5h = 90909090h

025f6450h                  * 582105F5h = 90909090h
025f6450h                  -      294h = 025F61BCh  <- caractere 6,7,8,9 du serial pour iorior

Dans ce cas, le serial sera du genre, pour le moment, XXXXXBC615F02XXXXXX
Un petit inconvenient, dans 02 5F 61 et BC, seul 5F (_) et 61 (a) sont des caractères affichables...

  EAX       MUL      EBX     =     EAX        EBX        EDX
582105F5          025f6450       90909090   025f6450   00D118D5

Le serial commence à prendre forme:

D518D100 2D BC615F02 XXXXXXXX XXXX
-------- -- -------- -------- ----
    A     B     C        D      E

A est égal à l'overflow de la multiplication (EDX)
B est un séparateur (le 5 caractère n'est pas testé, ni utilisé, mais il est indispensable)
C est egal à une valeur permettant d'obtenir 90909090 dans EAX -> AutoPatch Glop Glop !
D est ce qu'il reste à découvrir (AutoPatch 2)
E est le checksum

Petits problèmes (Et c'est CoOlViPeR qui me l'a fait remarquer), un serial dont l'un des digits est 00 ne peut pas coller: le 00 dans une chaîne de caractères est l'identificateur de la fin de celle ci!

Facheux!
Ainsi l'option prise par iorior (patcher le programme via le principe décrit ci dessus pour aller en routine "Code valide") fonctionne très bien, mais ne convient pas si l'on veut continuer à jouer avec le serial...
Il faut impérativement obtenir un résultat dans EDX sur 4 bytes, sans 00.
La conséquence est qu'EAX va à nouveau se retrouver avec une valeur qui ne doit en aucun cas perturber le programme au moment du patch. J'ai choisi un truc neutre à cet endroit: AND EAX,0F0F0F0F (250F0F0F) qui n'aura pas d'incidence sur le bon déroulement du crackme, truffé de NOP dans sa version débarrassée du codage CCA.

Pour trouver la valeur à placer dans EBX, j'ai écrit un rapide petit programme (Brute Forcing) qui s'est chargé de me calculer cette fameuse valeur, et qui m'a retourné:

  EAX       MUL      EBX     =     EAX        EBX        EDX
582105F5           7E7AF671      0F0F0F25   7E7AF671   2B8A9582

7E7AF671 - 035A (la somme du Name que j'ai entré) = 7E7AF317

Mon serial commencera ainsi:

82958A2B 2D 17F37A7E XXXXXXXX XXXX
-------- -- -------- -------- ----
    A     B     C        D      E

Second problème, mon AND 0F0F0F0F fonctionne très bien dans le programme nettoyé de son codage polymorphe, mais fiche la pagaille dans la version en CCA. Une autre "combinaison" finira par me donner une séquence admise par le code smc...

Le Second AutoPatch:

:00401590  XOR       EDX,EDX           ; EDX = 0
:00401597  MOV       EAX,[0040317C]    ; EAX = Name entré
:004015A1  AND       EAX,0F0F0F0F      ; And Logique
:004015A9  MOV       EBX,[00403081]    ; EBX = 3ème DWORD pointant sur D   
:004015B3  SUB       EBX,EAX           ; EBX - EAX 
:004015BA  MOV       [004015C4],EBX    ; AutoPatch2 -> le résultat "recode" le crackme
:004015C4  JMP       004015C8          ; pointe pour le moment vers un NOP
:004015CD  OR        DL,DL             ; l'état de EDX est testé (0 ou non ?)
:004015D2  JZ        0040166E          ; si Pas Glop va vers la sortie

Cette fois ci, pour éviter un autre recodage sauvage du Crackme, c'est en jouant avec le SUB EBX, EAX (EBX contenant la valeur du Dword D) que l'on va pouvoir obtenir un 90909090. C'est également ici que iorior m'a donné un coup de main, je m'obstinais à vouloir parvenir à un 90909090 en voulant jouer sur le AND 0F0F0F0F....
ARF!

EDX est égal à zéro au début de cette routine. Or si EDX reste à zéro, le JZ 0040166E va nous envoyer dans les choux...
Je me suis demandé pendant un moment si les opérations AND ou SUB ne jouaient pas sur le registre EBX, et c'est en regardant à nouveau le code smc (et après que iorior m'ait dit que lui n'avait pas de problème) que je me suis rendu compte que j'avais peut être un peu trop fait le ménage en virant à tour de bras ce qui m'avait semblé être du CCA. A l'adresse 004015CA, vous avez à l'origine EB024142, soit un saut et deux incrémentations de registres, toutes deux Jumpée par le EB02.
Abondance de bien de nuit pas (?), j'ai quand même décidé de patcher ma (mauvaise) copie, sachant que sur l'original, le patch fonctionnera sans problème.

J'ai alors repris le principe de iorior, et au lieu de patcher un 90909090 avec la partie D du serial, j'ai opté pour un 90909042 (inc EDX) pour pouvoir passer la routine, et continuer vers le checksum.

Le serial prend cette forme:

82958A2B 2D 17F37A7E 45989299 XXXX
-------- -- -------- -------- ----
    A     B     C        D      E

Le AND 0F0F0F0F sur les 4 premiers caractères (1er DWORD) du Name m'a donné 09020803h.
90909042 + 09020803 = 99929845h
Le SUB EDX, EAX (99929845 - 09020803) donnera le 90909042 souhaité.

Le CheckSum:

:004015DD  XOR       EAX,EAX           ; EAX = 0
:004015E5  XOR       EBX,EBX           ; EBX = 0
:004015EC  MOV       ESI,00403078      ; ESI = début du sérial
:004015F6  MOV       ECX,EDX           ; ECX récupère le résultat de EDX
:004015FC  ADD       ECX,05            ; ECX +5 = nombre de caractères à traiter
:00401602  LODSB                       ; charge la valeur ASCII pointée par EAX
:00401608  ADD       EBX,EAX           ; l'ajoute à EBX, jusqu'à épuisement
:0040160F  LOOP      00401602          ; des 5 premiers caractères du serial 
:00401614  AND       BX,F0F0           ; AND logique sur le résultat
:0040161F  ROR       BX,02             ; Rotation à droite de 2 caractères
:00401628  ADD       BX,3030           ; on y ajoute 3030
:00401631  CMP       [00403085],BX     ; et comparaison avec la partie E du serial 
:0040163B  JNZ       0040166E          ; si Pas Glop -> Go Out!

Je pense que les commentaires sont clairs.
Les 5 premiers caractères du serial + la valeur qui se trouvait dans EDX (1 en ce qui me concerne grace au INC EDX) vont être additionnées entre elles (du moins leur valeur ASCII), bidouillés un peu, et finalement comparés avec la partie E du serial.
Les deux derniers caractères du serial (partie E) vont donc être le résultat du checksum, du type 0x3034.

Le serial prend cette forme:

82958A2B 2D 17F37A7E 45989299 3430
-------- -- -------- -------- ----
    A     B     C        D      E

La partie est gagnée:

:00401643  PUSH      40                ; Si Glop Glop !
:00401645  PUSH      00403014          ; Message Box -> Version Enregistrée
:0040164A  PUSH      00403036
:0040164F  PUSH      00
:00401651  CALL      MessageBoxA
:0040165A  MOV       EAX,[00403384]
:00401664  MOV       FS:[00000000],EAX ; rétablie le SEH

Routine de sortie Go Out:

:0040166E  CALL      004016C3          ; vers le test MELTICE
:00401676  MOV       ESP,[00403396]    
:00401682  POPAD
:00401683  JMP       004016A9          ; vers XOR EAX,EAX / RET -> sortie sans message

Conclusion:

iorior avait raison, c'est un calcul de serial malin...

Le KeyGenerator:

Il est très facile à faire, dans la mesure ou plusieurs éléménts du serial sont des constantes:

      xor       ebx,ebx
      xor       eax,eax
      mov       esi, offset buffer
boucle1:
      lodsb
      add       ebx,eax
      loop      boucle1 
      mov       dword ptr [cle3],ebx        ; somme ASCII des lettres du Name entré

      mov       eax, dword ptr [cle1]       ; constante: AutoPatch1
      xchg      eax, ebx               
      sub       ebx, eax
      mov       eax, dword ptr [cle2]       ; 582105F5 -> constante: Clé secrète
      mul       ebx
      mov       dword ptr [cle4], ebx       ; variable: Partie C du serial
	
      mov       eax, dword ptr [buffer]     ; EAX = Name entré
      and       eax, 0F0F0F0Fh              ; AND logique
      mov       ebx, 90909042h              ; Soustrait le résultat à la valeur
      add       ebx, eax                    ; de l'AutoPatch2 qu'il faudra obtenir
      mov       dword ptr [cle5],ebx        ; variable: Partie D du serial

      mov       dword ptr [Key], 2B8A9582h  ; Constante: Partie A du serial
      mov       byte ptr [Key+4], 2Dh       ; Constante: séparateur (5ème caractère)

      invoke    lstrcat, offset Key, offset cle4  ; création du serial
      invoke    lstrcat, offset Key, offset cle5  ; placé dans Key

      xor       eax,eax                     ; calcul du CheckSum
      xor       ebx,ebx
      mov       esi,offset Key
      mov       ecx,6
boucle2:
      lodsb
      add       ebx,eax
      loop      boucle2
      and       bx,61680
      ror       bx,2
      add       bx,3030h

      mov       word ptr [Key+13], bx       ; Place le résultat du CheckSum dans Key

merci à CoOlViPeR et iorior

Bonne Journée

Christal