#/ Lucifer48!64
#/ Lucifer!4666
#/ 64!Lucifer48


Programme  : CrAcK Me #1
PlateForme : Windows 95/98
Date       : Samedi 24 octobre 1998, 21h43
Protection : Serialzzz
Fichier    : crkme1.exe
Outils     : Soft-ice v3.2
Ou ça?     : http://cracking.home.ml.org
Temps passé: 50 minutes + la rédaction
Cours      : 19
Matos      : Bloc-notes en 640*480

Depuis que j'ai acquis ma nouvelle diabolique machine, j'ai pas franchement
eu le temps de penser au crack... Je l'avoue: je joue bêtement à motoracer,
hexen2 et autres Jedi Knigth... Il se trouve que j'ai eu l'occasion d'aller
chercher sur le net, le premier des trois crackme de phrozen crew et j'ai
donc décidé d'en faire mon affaire ce soir...

Dans 'about' on nous dit que le crackme est fait en Delphi 3: ça ce voit!
180ko pour une fenêtre! (contre quelques centaines octets en pur asm...)
Bref, le but ici, sera donc de démêler le vrai du faux. Plus c'est en haut
niveau, plus faut débrouissailler (i.e c'est chiant et TRES décourageant).


=============================
1. Y'A ARTHUR DANS LA TELOCHE
=============================

Le crackme est enregistré si on entre un bon serial. On voit dès le premier
coup d'oeil que le programme teste la validité du serial en boucle, avec un
bpx hmemcpy, on va pouvoir facilement rentrer dans le code et tracer.

XXXX:00425F99  E8AAF3FEFF    CALL 00415348       ;on sort d'ici
XXXX:00425F9E  8B45F8        MOV  EAX,[EBP-08]   ;adresse de notre serial
XXXX:00425FA1  E8CAD5FDFF    CALL 00403570       ;eax= taille du password
XXXX:00425FA6  8BD0          MOV  EDX,EAX
XXXX:00425FA8  8D45F4        LEA  EAX,[EBP-0C]
XXXX:00425FAB  E894D8FDFF    CALL 00403844

On trace (via F10), on remarque les ce sont les mêmes CALL qui reviennent,
y'a des boucles, mais véritablement rien de 'croustillant', en fait le but
c'est de trouver l'endroit intérêssant (je pense en particulier au jz (ou jx)
qui décide si OUI ou NON on est enregistré).

Tout d'un coup, on trouve THE passage:

XXXX:00426124  6685FF         TEST DI,DI
XXXX:00426127  741B           JZ   00426144     ;si saut: password incorrect
XXXX:00426129  668B45EC       MOV  AX,[EBP-14]
XXXX:0042612D  6633C1         XOR  AX,CX
XXXX:00426130  0FB7C0         MOVZX EAX,AX
XXXX:00426133  0FB7D7         MOVZX EDX,DI
XXXX:00426136  8BCA           MOV  ECX,EDX
XXXX:00426138  99             CDQ
XXXX:00426139  F7F9           IDIV ECX          ;division signée
XXXX:0042613B  83FA0D         CMP  EDX,0D       ;la clef du crackme
XXXX:0042613E  7504           JNZ  0426144      ;saut décisif...
XXXX:00426140  C645EB01       MOV  BYTE PTR [EBP-15],01
XXXX:00426144  807DEB00       CMP  BYTE PTR [EBP-15],00
XXXX:00426148  7415           JZ   0042615F
...
XXXX:0042615D  EB13           JMP  00426172

Tout est là. On voit donc qu'il faut que le reste de la division soit 13
Mais que divise-t-on ?

En [EBP-14] on nos 4 premiers chiffres convertis en héxa, le numérateur.
Pour DI ça dépend des boucles précédentes. (je rappelle que DI doit être non
nul en 00426124; puisque c'est le dénominateur).

On fait quelques essais:
Avec 2222995 (di=35 et eax=08AE sachant qu'il doit reste D, on  a eax=08AA)
soit 2126995, on ressaye, et di a changé de valeur...

Comment est calculé DI ? Eh bien, on remonte dans le code et on trouver la
boucle:

XXXX:0042606C  8845EA         MOV  [EBP-16],AL         ;nombre de boucles
XXXX:00426071  33C0           XOR  EAX,EAX
XXXX:00426073  8AC3           MOV  AL,BL
XXXX:00426075  8B55F0         MOV  EDX,[EBP-10]
XXXX:00426078  0FB64402FF     MOVZX EAX,BYTE PTR [EAX+EDX-01]
XXXX:0042607D  6633F8         XOR  DI,AX
XXXX:00426080  43             INC  EBX
XXXX:00426081  FE4DEA         DEC  BYTE PTR [EBP-14]   ;nombre de boucles
XXXX:00426084  75EB           JNZ  00426071

Une serie de XOR détermine la valeur de DI.
Comment est calculé le nombre de boucles (contenu dans AL)???
Là encore, on remonte:

XXXX:00425FFC  8A4402FF       MOV  AL,[EAX+EDX-01]  ;lit 1 à 1 les caractères
XXXX:00426000  25FF000000     AND  EAX,000000FF     ;inutile ici
XXXX:00426005  0FA30540784200 BT   [00427840],EAX   ;tout ce fait ici!
XXXX:0042600C  7321           JAE  0042602F

C'est ce BT qui fait toute la différence. J'avoue mon ignorance, je ne
connaissait pas cette instruction, mais heureusement que HelpPC est là:

BT - Bit Test        (386+ only)

Usage:  BT      dest,src
Modifies flags: CF

The destination bit indexed by the source value is copied into the Carry Flag.

Je vais donc pointer à l'adresse entre crochet (D 00427840):
-----CRKME1!DATA+0840-----------------------------byte--------------PROT---(3)-
XXXX:00427840 00 00 00 00 00 00 55 01-00 00 00 00 00 00 00 00  ......U.........
XXXX:00427850 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

Donc si le EAX ème bit est à 0, c'est ok le caractère compte mais s'il est
égal à 1 alors il ne compte pas. On voit un 55 01, en binaire c'est codé
01010101 00000001.

Si par exemple on prend le caractère "0" (30h) regardons la valeur du 48ème
bit: (en interne, le cpu va faire EAX=00000155, le premier bit est-il nul?)
ici, la réponse est non, donc le caractère "0" ne compte pas

Comme on le voit sur les valeurs binaires de 55 01, il n'y a donc que cinq
valeurs qui ne comptent pas, ce sont les caractères "0", "2", "4", "6", "8"
(soit 30h, 32h, 34h, 36h et 38h).
Donc comme y'a des 0 partout, on remarque que les lettres et les autres
chiffres (31h, 33h, ...) comptent bien.
On obtient donc DI en XORisant tous les caractères qui comptent, si par
exemple on a 124689 alors DI= 31 XOR 39 = 08 (soit 2 boucles)

Comme 0D est impair, on va pas pouvoir s'en sortir avec un password du genre
1246 (on aurait DI=31 (31 XOR 00) et AX=99C (=2460d) ), avec une unique
boucle c'est pas possible. 

Un autre problème apparaît: lorsque le password contient des lettres que vaut
AX ? On va donc faire un tour du côté de la routine de conversion, pour voir
comment il trouve AX en XXXX:00426129  (instruction: MOV  AX,[EBP-14])
Qu'est-ce qui est converti, et comment.

Ca se situe après la 'fabrication' de DI et avant la division de EAX par DI.

XXXX:004260EB  8B45F8         MOV EAX,[EBP-08]   ;adresse de la valeur à convertir
XXXX:004260EE  E8B5FFFDFF     CALL 004060A8      ;routine de conversion
XXXX:004260F3  668945EC       MOV [EBP-14],AX    ;sauvegarde le word en héxa

Le call 004060A8 convertit en hexa les quatres octets situés en [EBP-08], et
met le résultat dans AX, il faut quand même savoir comment on obtient ces
quatres caractères. Quelques lignes plus haut, une première boucle créée en
mémoire 8 octets en ne prenant que les caractèrent "qui ne comptent pas"
(voir définition plus haut) et en complétant avec des 30h (caractère "0").
Exemple: avec le serial 3615982 j'ai 68200000
Une seconde boucle prend les 4 premiers octets, et les stocke autrepart
(mais peut importe) et c'est là que la routine convertie (avec l'exemple
précédent 6820 sera convertie en 1AA4).


================
2. RESUMONS NOUS
================

Le cahier des charges pour le crackme est donc le suivant:

1/ les caractères "qui ne comptent pas" (soit "0", "2", "4", "6" et "8")
   forment le futur numérateur de la division
   (obtenu en convertissant en héxadécimal les 4 premiers de ces caractères)
2/ les caractères "qui comptent" (soit "1", "3", "5", "7", "9", "A",..,"Z")
   forment le futur dénominateur de la division
   (obtenu en pratiquant l'opération XOR sur chacun de ces caractères)
3/ Le reste de cette division doit être impérativement 0Dh


On a tous les éléments pour pouvoir trouver un bon serial.
Il me faut un numérateur suffissement grand pour que je puisse m'arranger
(c'est à dire au moins trois chiffres pairs dans mon serial)
Je me prend pas la tête, et je prend mon turbo pascal...


{-- Petite improvisation... by Lucifer48... ------------------------}

Function Calcul_DI(s: string): byte;
(* la chaine doit être exclusivement composée de caractères dits "qui
comptent" *)
Var a,i:byte;
Begin a:=0;
  For i:=1 to Length(s) do a:=a xor ord(s[i]);
Calcul_DI:=a;
End;

(****************************************)

Function byte2word (di_:byte):word; assembler;
Asm
 Mov al,di_
 Xor ah,ah
End;

(****************************************)

Function compose_de_chiffres_pairs(a:word) :boolean;
Var st:string[4];
Begin
Str(a,st);
If (((st[1]='0') or (st[1]='2') or (st[1]='4') or (st[1]='6') or (st[1]='8'))
and ((st[2]='0') or (st[2]='2') or (st[2]='4') or (st[2]='6') or (st[2]='8'))
and ((st[3]='0') or (st[3]='2') or (st[3]='4') or (st[3]='6') or (st[3]='8'))
and ((st[4]='0') or (st[4]='2') or (st[4]='4') or (st[4]='6') or (st[4]='8')))
    Then compose_de_chiffres_pairs:=true
    Else compose_de_chiffres_pairs:=false;
End;

(****************************************)

Procedure Affiche_correct_AX(di: word);
Var a,i: word;                            (* oblige de manipuler des words *)
Begin
  For i:=1 to 137 do begin a:=i*di+13;   (* 13= $000D = le reste à trouver *)
  If compose_de_chiffres_pairs(a) then writeln(a);
  End;
End;

(****************************************)

Var di1:byte;
    di2:word;
Begin
di1:=Calcul_DI('Lucifer!');        (* <----- mettez votre chaine ICI *)
di2:=byte2word(di1);               (* Attention! pas de chiffres pairs dans *)
Affiche_correct_AX(di2);           (* la chaine. *)
End.

{-------------------------------------------------------------------}

Avec comme mon nom "Lucifer!" il me sort quelques serials:
2488
2686
2884
4468
4666
4864
6448
6646
6844
8428
8626
8824

J'essaye donc 2488Lucifer! et zou: R-E-G-I-S-T-E-R-E-D!!

Remarque: vu que Lucifer! et obtenu via un xor, tout les anagrammes sont
possibles mais les chiffres pairs (caractères qui ne "comptent pas" doivent 
impérativement rester dans le bon ordre (à cause de la conversion héxa) sinon
ça marche plus).

Remarque encore, je sais que ma source n'est pas un chef d'oeuvre mais, ça
a le mérite de marcher! Je sais aussi qu'il ne tient pas compte de la
possibilité de la division signée (bidouillez les 'word' en 'integer'...).

Ultime remarque: pas mal ce crackme mais, le n°2 est vraiment le meilleur,
toute la difficulté ici résidait à y voir dans ce code (très mal) compilé
(par delphi). Au début, on est perdu, et peu à peu, on prend ses marques, et
le code paraît moins obscure et on comprend la signification des boucles et
des calls.


Voilà, ainsi s'achève mon 19 tutorial, j'espère avoir pas trop mal expliqué,
et à la prochaine...

LUCIFER48