#/ 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