CRACKME2 DE CHRISTAL

 

L'objet de cert essai est pour le moins insolite, car nous allons voir comment cracker... une daube !! De l'avis même de son concepteur, Christal, qui déclare modestement qu'il n'est "ni codeur, ni crackeur, ni quoi que ce soit", il n'y a rien à attendre de ce crackme. Et pourtant ! Au programme on retrouve du code polymorphe, des anti-softice, un anti ForgIce, le tout solidement encrypté... Pour une daube, je la trouve bigrement bien ficelée. Enfin, trêve de considérations culinaires, passons au cracking.

 

I°) DUMP ET EPURATION DU LISTING

 

Commençons par faire un petit tour du crackme, histoire d'avoir un petit aperçu de la bête. On le lance et on a sucessivement deux MessageBox pour nous inviter à nous défaire de notre cher débugger. Une fois la fenêtre principale chargée, on a encore droit à une MessageBox quand on clique sur "About" (tiens c'est curieux le concepteur à protégé son nom, d'habitude c'est la dernière chose qu'il tient à cacher...:-) et une autre lorsque, découragé, on essaie de sortir du programme.

Par pure curiosité on peut aussi tenter que placer un BPX histoire de voir ce que ce crackme a dans le ventre et là HORREUR ! Le code est polymorphe ! A chaque F8 ou F10 tout le code affiché dans la console change sans prévenir ! La seule instruction qui est valable est celle à eip, toutes les autres sont à peu près incompréhensibles et de toutes façons elles changent tout le temps...

Pour finir ce petit tour on lance un analyseur d'exécutable et bien sûr, il est aussi crypté (par "PE-Encrypt" ou "VGCrypt" selon l'analyseur utilisé).

Bon ! Puisque le traçage semble quasi-impossible on va opter pous une approche de type "dead-listing", c'est à dire dumper le programme, et essayer de le desassembler pour travailler à partir du listing.

Le dump ne pose pas de problème, la technique est hyper-classique (si besoin, voir les tut sur les protections par compression/cryptage à christalpage.cjb.net). On commence par éditer lescaractéristiques des sections et à les remplacer par E0000020. Ensuite on charge le crackme dans le Symbol Loader de Softice et on le lance. L'une des premières instructions de PE-Encrypt étant "pushad", on va rechercher un "popad "pour localiser la fin de PE-Crypt et trouver le saut vers l'Entry Point (EP).

Malheureusement, le code de PE-Encrypt lui-même semble crypté car, après avoir atteint le pushad, on trouve bien un popad un peu plus bas, mais les instructions qui suivent n'ont pas l'air très sympathiques (pop eax et jmp 401CCC). Par contre si on continue à tracer, ces intructions se transforment en : popfd, mov ebx [xxx], mov [xxx] ecx, jmp ebx. PE-Encrypt décrypte au fur et à mesure ses propres instructions de manière à compliquer le patch. Le "jmp ebx" est bien le saut vers EP, et on relève la valeur de l'EP, 401019. Ensuite, il suffit de remplacer "jmp ebx" par "jmp eip" et de faire un "full dump" à l'aide de ProcDump. Et pour obtenir un dump exécutable, on finit par éditer l'EP du dump et remplacer la valeur par 401019.

C'est maintenant les desassembleurs qui entrent en scène. Personnellement, j'ai l'habitude d'utiliser WinDasm mais dans notre cas, pas de chance ! Le pauvre WinDasm est complètement déboussolé par le code polymorphe est nous sort un listing immonde. Qu'à cela ne tienne ! Voilà une bonne occasion de découvrir IDA. On lance le desassemblage et une bonne surprise nous attend. Je commence à comprendre pourquoi les +crackers ne jurent que par IDA, car le listing qu'il nous sort est impeccable et nous permet même de comprendre le polymorphisme. En voici un extrait :

loc_401457:				; CODE XREF: .text:00401452j
		cmp	ax, 0BBBh
		jnz	short loc_4014A1
		shr	eax, 10h
		or	ax, ax
		jnz	short loc_4014A1
		jmp	short loc_40146A
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
		db 0CDh	; Í
		db  20h	;  
		db 0EAh	; ê
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_40146A:				; CODE XREF: .text:00401465j
		call	sub_401982
		test	eax, eax
		jmp	short loc_401476
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
		db 0CDh	; Í
		db  20h	;  
		db 0C7h	; Ç
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_401476:				; CODE XREF: .text:00401471j
		jnz	short loc_40148E
		push	40319Ah

On voit que le code est truffé de "db 0CD, db 20, db XX", plus un saut pour les éviter ce qui décale tous les opcodes et les rendent illisibles. Simple mais terriblement efficace ! Cependant, le listing est quand même par endroit assez difficile à suivre car plus de la moitié des lignes sont parasitées par les "db", et mieux vaut s'en débarrasser. J'ai opté pour une méthode d'éradication aussi simple que bourrine : le CTRL-X à répétition. Il y a certainemlent une méthode plus fine de faire le boulot (du genre on parcourt l'exe en recherchent "0C20" puis on les élimine avec les deux octets qui précèdent (le jmp short) et celui qui suit (le 3e db); ensuite on incrémente un compteur qui servira à modifier tous les sauts et les calls qui doivent rester), mais bon....

Après une bonne demi-heure à pourchasser les "db" dans les moindres recoins, on va enfin pouvoir passer aux choses sérieuses.

 

II°) LOCALISATION DES PROTECTIONS ET PREMIER PATCH (voir aussi IV°)

 

L'étape suivante va consister à débusquer les protection anti-SI et à les patcher. Mais comment localiser dans ce listing de 12 pages les quelques lignes des procédures anti-SI ? En fait le problème est double, car à priori on ne sait pas OU sont situées ces routines, ni à QUOI elles ressemblent.

La solution la plus simple serait d'utliser FrogIce. Mais comme je l'ai annoncé en introduction, cet utilitaire reste sans effet car si on le charge puis on lance le crackme, rien ne se passe... pour une fois qu'on aimerait bien voir ce sympathique écran bleu sans lequel Windows ne serait pas vraiment le même, rien ! C'est pas grave, on va se débrouiller autrement.

On va faire l'hypothèse suivante : chaque détection de Softice entraine l'apparition d'une MessageBox. En lançant une recherche sur la chaîne "MessageBoxA" puis en remontant dans le listing, on devrait pouvoir localiser tous les anti-SI.

La recherche sur "MessageBox" donne 4 occurences. Il faut maintenant vérifier que ces MessageBox concernent bien des messages d'erreur : pour cela il faut aller voir la chaine de caractères qui est poussée en argument (avant dernier push)..

Pour trois des quatre appels trouvés on dispose directement des adresses des chaines qui constituent le corps du message : 0040319A (deux fois) et 0040314F.

Mais le quatrième n'a pas pour argument une adresse fixe, mais "ebp+08" (l'argument a été poussé dans la pile au préalable). Par ailleurs cet appels fait partie d'une "subroutine", donc il va falloir remonter au niveau de l'appel de cette routine pour retrouver l'adresse. Le "+08" de "ebp+08" indique que l'adresse que l'on cherche est le dernier argument poussé dans la pile juste avant le call. En effet, à l'appel d'un call le processeur commence par sauver dans la pile l'adresse de l'instruction appelante pour le retour (ret) et cette adresse est au format dword (4octets). Ebp+08 pointe donc sur la fin du dword poussé juste avant.

On lance donc une recherche sur "401913" pour localiser les call sub_401913, et on trouve trois appels, dont deux suivent un push 40319A et un un push 4031BF.

On dispose desormais des adresses des messages, alors pas de temps à perdre, on hex-édite vite fait, et là... pas de bol, on tombe sur des chaînes incompréhensibles : elles sont cryptées.

La solution de rechange consiste à relancer le programme, et à provoquer un break en espérant qu'à ce moment les chaînes soient déjà décryptées. C'est le cas si on pose un bpx MessageBoxA après le chargement de la fenêtre puis qu'on clique sur "Exit". Ensuite avec la commande "d" on consulte le contenu des trois adresses :

Seules les deux premières adresses nous concernent donc, la troisième contient sans doute le message du "About". Maintenant, on va donc reprendre les 5 appels qui nous interessent et en remontant dans le listing, essayer de trouver des ensembles "call+test+saut conditionnel" que l'on connait bien....

Cette nouvelle recherche nous conduit à trouver pas moins de 5 adresses de procédures anti-SI ! Ce sont 40192F, 401982, 4019A7, 401AE4 et 401B1C.

Sur les cinq on en a trois de la forme :

S u b r	o u t i	n e

sub_4019A7	proc near		; CODE XREF: start+81p
		xor	ebx, ebx
		push	4019E6h
		push	large dword ptr	fs:0
		mov	large fs:0, esp
		mov	esi, 4647h
		mov	edi, 4A4Dh
		int	3		; Trap to Debugger
		pop	large dword ptr	fs:0
		add	esp, 4
		mov	eax, ds:dword_4034C7
		retn	
sub_4019A7	endp

et deux autres qui ressemblent plutôt à çà :

;		S u b r	o u t i	n e

sub_401B1C	proc near		; CODE XREF: .text:0040188Fp
		push	0
		push	80h ; '€'
		push	3
		push	0
		push	3
		push	0C0000000h
		push	4031F1h
		call	j_CreateFileA
		cmp	eax, 0FFFFFFFFh        
		jz	short locret_401B3F              <-SI non détecté, eax reste à ff..ff  
		xor	eax, eax                            <- SI détecté, eax mis à 0
locret_401B3F:				; CODE XREF: sub_401B1C+1Fj
		retn	
sub_401B1C	endp

On va enfin pouvoir passer au patch. En regardant de plus près les tests qui suivent chacune de ces routines, on constate que le résultat "SI détecté" est toujours symbolisé par eax=0 et "SI non trouvé" par eax !=0. Il faut donc faire en sorte qu'à la fin de chacune de ces routines eax soit non nul.

Pour les trois premières, le résultat du test placé dans eax est copié depuis 4034C7, donc, si on remplace 4034C7 par une adresse qui pointe sur un octet dont on est sûr qu'il est non nul, le tour est joué ! On pourrait trouver des centaines d'octets qui conviennent et pour ma part j'ai choisi 403189 qui pointe sur le début d'une chaine de caractères.

Enfin, pour les deux autres routines on peut par exemple remplacer la séquence "jz+xor" par un "xor eax, eax" suivi de "inc eax", de telle sorte que quel que soit le resultat de CreatefileA, eax vaudra toujours 1.

Voici donc notre patch :

mov dword ptr [401B17], 9040C033 	<- remplace les "jz+xor" par des "xor+inc+nop"
mov dword ptr [401B3B], 9040C033	
mov dword ptr [40199C], 8931		<- remplace les 4034C7 par des 403189
mov dword ptr [401952], 8931
mov dword ptr [4019E1], 8931
jmp 00401019                      <- saut vers l'EP

 

 

III°) COMMENT PATCHER PE-ENCRYPT ?

 

Bien, maintenant que nous avons le patch, il reste deux choses à faire : trouver de la place pour le loger puis modifier le "jump vers Entry Point" (EP) en en "jump vers patch". Et c'est là que les choses se compliquent sérieusement...

Pour commmencer, on a vu précédemment que le jmp EP était lui-même décrypté, ce qui réduit à néant tout espoir de modification en dur. Une solution pourrait être de repérer la routine qui décrypte le saut et de la détourner dès qu'elle est finie vers un second patch. Mais cette technique est assez lourde, et de toutes façons, on aura toujours besoin de place pour caser nos patchs, donc passons au deuxième point, la recherche de place libre. Avant, remarquons tout de même que le contenu de ebx a été récupéré en 401D5B.

Pour rechercher la place libre on hex-édite l'exécutable original, pas le dump, bien sûr. On trouve deux plages pleines de 00 suffisamment grandes pour pouvoir faire l'affaire : entre 403200 et 4035F0 et entre 4060B0 et 40611F0. Mais voilà ! Cet espace n'est en réalité pas libre du tout ! Si on place un bpr 4060B0 40611F0, par exemple, Softice breake. On attérit ici :

:00401CE1 8DBDCD264000            lea edi, dword ptr [ebp+004026CD]

:00401CE7 8B37                    mov esi, dword ptr [edi]           <- esi = offset de la zone à décrypter         
:00401CE9 0BF6                    or esi, esi
:00401CEB 7431                    je 00401D1E                        <- esi nul ?
:00401CED 03B50D274000            add esi, dword ptr [ebp+0040270D]  <- si non, on ajoute l'image base (400000h)
:00401CF3 8B4F04                  mov ecx, dword ptr [edi+04]        <- charge la valeur initiale du compteur
:00401CF6 57                      push edi
:00401CF7 8BFE                    mov edi, esi
:00401CF9 8B9D11274000            mov ebx, dword ptr [ebp+00402711]  <- charge la clé de décryptage
:00401CFF F8                      clc
:00401D00 83E904                  sub ecx, 00000004

:00401D03 AD                      lodsd                              <- charge le double word à décrypter
:00401D04 03C3                    add eax, ebx                      \
:00401D06 33C3                    xor eax, ebx                       \  
:00401D08 D1CB                    ror ebx, 1                          <- décryptage   
:00401D0A 81EB66708090            sub ebx, 90807066                  /
:00401D10 03DB                    add ebx, ebx                      /
:00401D12 AB              		  stosd                              <- recopie le double word décrypté
:00401D13 83E904                  sub ecx, 00000004                  <- décrémente le compteur
:00401D16 73EB                    jnb 00401D03                       <- compteur à zéro ?
:00401D18 5F                      pop edi
:00401D19 83C708                  add edi, 00000008
:00401D1C EBC9                    jmp 00401CE7

Cette routine de décryptage n'est pas compliquée du tout. Elle repose en fait sur un simple XOR : le caractère à décrypter est chargé dans eax, additionné à ebx puis on réalise un XOR entre eax et ebx. Ensuite, ebx qui sert en fait de clé de cryptage subit diverses modifications (ror, soustraction puis multiplication par deux).

Cette routine est appelée 3 fois et sert à décrypter de vastes portions de PE-Crypt :

On constate que la place libre que nous avions trouver avec l'éditeur héxadécimal est bien de la place libre et que PE-Encrypt se contente de recopier des 00 sur les zéros de départ. En effet, si eax vaut 00000000, après le 'add eax, ebx' , eax contiendra ebx, et donc le 'xor eax, ebx' équivaut à un 'xor ebx, ebx' dont le resultat est 00000000...

Tout çà, c'est bien joli, mais puisqu'il n'y a pas de place libre, où va-t-on bien pouvoir placer notre patch ?? Réfléchissons un peu... On a vu que la routine de décryptage est commandée par un compteur (ecx), donc, si on pouvait modifier cette valeur, disons la diminuer, on écourterait la routine et on se ménagerait un peu de place vraiment libre.

Voyons donc où est stockée la valeur initiale de ecx : au troisième passage dans la routine (on s'intéresse à la zone pseudo libre entre 4060B0 et 4061F0) on a edi+04 = 00401D33. Tiens ! Comme c'est curieux ! Vous vous rappelez de l'adresse de la valeur placée dans ebx juste avant le jmp ebx ? Mais oui c'est tout proche (00401D5B)... C'est comme si PE-Encrypt disposait d'un espace où sont stockées toutes les données dont il a besoin...

Mais vu comme PE-Encrypt se présente, il y a fort à parier que ces données sont elles aussi cryptées. En effet, si on pose un bpr 00401D33 00401D5B et qu'on relance le programme, SI breake à nouveau. Voilà la seconde routine de décryptage, celle dédiée aux "données" de PE-Crypt :

:00401DA8 8DB547264000            lea esi, dword ptr [ebp+00402647]   <- début du code à... décoder !
:00401DAE 8BFE mov edi, esi
:00401DB0 B9CA000000 mov ecx, 000000CA <- initialisation du compteur
:00401DB5 8AA511274000 mov ah, byte ptr [ebp+00402711] <- valeur initiale de la clé de décryptage (F8)
:00401DBB AC lodsb <- chargement de la valeur à décoder
:00401DBC 32C4 xor al, ah \
:00401DBE FEC4 inc ah \
:00401DC0 C0C402 rol ah, 02 / <- décodage
:00401DC3 80C490 add ah, 90 /
:00401DC6 AA stosb <- recopie la valeur décodée
:00401DC7 E2F2 loop 00401DBB <- boucle tant que ecx !=0
:00401DC9 E9CBFEFFFF jmp 00401C99

Non ! c'est pas possible ! Mais si, regardez bien...:-) Un simple XOR ! La valeur à décrypter, en al, ne subit qu'un XOR par l'octet en ah. Ensuite, ah est incrémenté puis subit une rotation et une addition, mais çà n'a aucune importance puisqu'on connaît la valeur initiale de ah. Qui dit XOR dit INVERSIBILITE donc on est desormais en mesure de décrypter/recrypter ces données à volonté. Le plus drôle c'est que c'est EXACTEMENT la même routine qui permet de coder ET de décoder. Essayer de remplacer le jmp 0040C99 par un jmp 0041DA8 et le programme se lance dans une boucle folle dans laquelle il crypte et décrypte les données à l'infini !

On approche de la fin... En effet, maintenant que l'on sait comment crypter/décrypter les données, on va pouvoir les décrypter à partir du disque dur, les modifier, les ré-encrypter puis les réecrire sur le disque dur. PE-Crypt n'y verra que du feu, il décryptera les données modifiées par nos soin et les utilisera comme si de rien n'était. Et le plus beau, c'est qu'on va faire d'une pierre deux coups : créer une zone franche pour placer notre patch et modifier le jmp EP.

Ceci est la fameuse zone de donnée, une fois décryptée :


00401C99 E800 0000 008B 9D05 2740 0083 C328 582B ........'@...(X+
00401CA9 C389 850D 2740 00CC 8DBD 2426 4000 B93B ....'@....$&@..;
00401CB9 0000 00F3 AA64 678F 0600 005A 8B85 0D27 .....dg....Z...'
00401CC9 4000 0185 0927 4000 619D 8B9A 0927 4000 @....'@.a....'@.
00401CD9 898A 0927 4000 FFE3 8DBD CD26 4000 8B37 ...'@......&@..7
00401CE9 0BF6 7431 03B5 0D27 4000 8B4F 0457 8BFE ..t1...'@..O.W..
00401CF9 8B9D 1127 4000 F883 E904 AD03 C333 C3D1 ...'@........3..
00401D09 CB81 EB66 7080 9003 DBAB 83E9 0473 EB5F ...fp........s._
00401D19 83C7 08EB C9CF 0010 0000 760C 0000 0030 ..........v....0
00401D29 0000 0006 0000 0060 0000 0002 0000 0000 .......`........
00401D39 0000 0000 0000 0000 0000 0000 0000 0000 ................
00401D49 0000 0000 0000 0000 0000 0000 0000 761C ..............v.
00401D59 0000 1910 0000 0000 0000 F8             ...........

       
 

En 00401D33 on retrouve "0002" (en réalité, c'est un 0200, les octets sont inversés), le compteur qui sert lors du décryptage de la zone allant de 406000 à 406200. Et en 401D5B on a "1910" c'est à dire Virtual Offset de notre EP. On va donc remplacer "0002" par "F000" pour écourter le décryptage et libérer la zone commençant en 406100. Pour la modification du saut vers EP on va donc remplacer le "1910" par "0061".

Voici le code d'un petit programme qui va faire une grosse partie du travail à notre place :

; ////////////////////////////////////////////////////////////////////////

      .386
      .model flat, stdcall
      option casemap :none   ; case sensitive

;////////////////////////////////////////////////////////////////////////


      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc

      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib

;////////////////////////////////////////////////////////////////////////

.data
	
		longueur  		equ  	0CAh
     		txt_erreur    	db  	'ERREUR FATALE !! ;-(',0
     	 	txt_OK        	db  	'C EST DANS LA POCHE ! :-))',0
      	entete        	db  	'SALUT',0
      	crackme_name  	db  	'chris2.exe',0
;////////////////////////////////////////////////////////////////////////

.data?
        	Buffer             db      200h dup (?)
        	fhandle            dd      ?
        	bRead              dd      ?
        	bWritten           dd      ?
;////////////////////////////////////////////////////////////////////////

.code

debut:                                  ;<- on commence par lire le code crypté à modifier
        invoke      CreateFile, offset crackme_name, GENERIC_READ, \
        FILE_SHARE_READ, 0, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, 0
        mov         fhandle, eax
        invoke      SetFilePointer, fhandle, 1099h, 0, FILE_BEGIN      
        invoke      ReadFile, fhandle, addr Buffer, longueur, addr bRead, 0
        xor ebx, ebx

superboucle:                            ;\
        mov ecx, 0CAh                    ;\
        mov edi,  offset Buffer           ;> initialisation
        mov esi, edi                     ;/
        mov ah, 0F8h                    ;/
boucle:
        lodsb                           ;\
        xor al, ah                       ;\
        inc ah                            ;\
        rol ah, 2                          ;\    cryptage/décryptage
        add ah, 90h                        ;/
        stosb                             ;/
        loop boucle                      ;/
        inc ebx                         ;/
        cmp ebx, 2                      ;<- on vient de décrypter (ebx=1) ou de recrypter (ebx=2)?
        je ecrit_resultat

modifie:                                            ;<- si on vient de décrypter, on patche le code décrypté...
        mov edi, offset Buffer
        mov word ptr [edi+0c2h], 6100h              ;<- modif du saut : "vers ep" remplacé par "vers patch"
        mov word ptr [edi+9ah] , 00F0h              ;<- protection du patch
        jmp superboucle                             ;<- et on le recrypte !

ecrit_resultat:                                     ;<- le resultat est recopier dans l'exe
        invoke      CloseHandle, fhandle
        invoke      CreateFile, offset crackme_name, GENERIC_WRITE, \
        FILE_SHARE_WRITE, 0, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, 0
        mov         fhandle, eax
	  invoke      SetFilePointer, fhandle, 1099h, 0, FILE_BEGIN        
        invoke      WriteFile, fhandle, addr Buffer, longueur, addr bWritten,0
        cmp         eax, FALSE
        jz          erreur
	  
message_succes:
        invoke      MessageBoxA, 0, addr txt_OK, addr entete, 10h
        jmp         fin
        
erreur:
        invoke      MessageBoxA, 0, addr txt_erreur, addr entete, 10h      

fin:
        invoke      CloseHandle, fhandle
	  invoke ExitProcess, 0

end debut
;/////////////////////////////////////////////////////////////////////////

Et voilà ! il ne reste plus qu'à écrire, à la main, avec un éditeur héxadécimal notre patch en 406100. Le crackme se lance et se ferme desormais sans aucune de ces horripilantes MessageBox, et on a accès à la fenêtre "About".

 

IV°) ADDENTUM : LOCALISATION DES PROTECTIONS, ACTE II

Comme me l'a très justement fait remarqué Christal, arrivé à ce point, on a réussi à se débarrasser de toutes les MessageBox, mais lorsqu'on clique sur "Generate", rien ne s'affiche dans la boite de dialogue... Autrement dit, on a un générateur de clés qui ne génére rien du tout et c'est plutôt ennuyeux !-(

Cette fois on va partir de l'hypothèse suivante : il doit exister quelque part un test qui décide s'il faut afficher la clé dans la boite de dialogue ou pas. Pour repérer ce test on va lancer une recherche sur "SetWindowText" car c'est en général cette API qui est utilisée pour écrire dans une boite de dialogue. En dessous voici la spécification de cette API :

BOOL SetWindowText(

    HWND  hwnd,	// handle of window or control
    LPCTSTR  lpsz 	// address of string
   );

 

Pour faire le tri entre les appels qui nous intéressent et les autres il faudra donc regarder le contenu de l'avant dernier argument poussé dans la pile. On repère en tout quatre appels à SetWindowText. On peut immédiatement éliminer les trois premiers car ils ont respectivement pour argument les chaines "Entrez au moins quatre caractères" (deux fois) et "Pas de caractères numériques !". Par contre le quatrième et dernier semble beaucoup plus intéressant :

loc_4017CF:				; CODE XREF: .text:004017BBj
		push	eax
		push	403168h		
		push	3		
		push	405266h
		push	0		
		push	403180h		
		push	403182h
		call	j_GetPrivateProfileStringA	
		cmp	eax, 0
		jnz	short loc_401831
		jmp	short loc_401824

loc_401824:				; CODE XREF: .text:0040181Fj
		pop	eax
		push	eax
		push	ds:dword_4038E4
		call	j_SetWindowTextA

loc_401831:				; CODE XREF: .text:004014AAj

 

En effet on voit que selon le résultat de GetPrivateProfileStringA le programme évite ou exécute l'appel à SetWindowText : c'est bien la routine que l'on recherchait. Un petit coup d'oeil rapide dans le Win32.hlp nous apprend que GetPrivateProfileStringA sert à retirer des informations dans un .ini, qu'elle requiert 6 arguments et que son dernier argument est un pointeur vers le nom du fichier à consulter. Chez nous ce sixième argument est 403168 et que trouve-t-on à cette adresse ? Suspence.... on trouve "w32dasm8". C'est donc un anti-WinDasm cette fois ! Décidément il y en a pour tous les goûts dans ce crackme !

On va patcher çà simplement en nopant le jnz . Il faut donc rajouter la ligne suivante à notre patch :

mov word ptr [0040181D], 9090

Une fois le nouveau patch sauvegardé, on relance le programme, certain d'en avoir fini une bonne fois pour toute. Mais non ! Ce satané crackme est vraiment coriace !!! Cette fois, lorsqu'on clique sur "Generate" il apparait dans la boite de dialogue "Merci de désactiver votre debugger" !

Force est de constater qu'il doit y avoir encore d'autres anti-Softice planqués dans les recoins, on va donc se remettre en chasse. Si on regarde de plus près l'appel à SetWindowText, on remarque que le texte affiché est pointé par eax. Avant d'attérir dans eax, ce pointeur a été récupéré dans la pile (pop eax),au tout début de l'extrait on le voit même poussé dans la pile. Donc, en 4017CF eax contient soit le pointeur sur serial, soit le pointeur sur "Merci de désactiver..."

On va donc lancé une recherche sur "4017CF" pour repérer à quel endroit le choix du pointeur à mettre dans eax est fait et par la même occasion l'anti-Softice qui l'accompagne.

On obtient 4 nouveaux résultats qui ressemblent à :

loc_4014A6:				
cmp ax, 0BB9h
jnz loc_401831
shr eax, 10h
or ax, ax
jnz loc_401831
call sub_4019F8 <- anti-SI
test eax, eax
jnz short loc_4014E0 <- si SI non détecté, on continu normalement
mov eax, 40319Ah <- sinon eax recoit l'adresse de "merci de..."
mov esi, 4017CFh \
jmp esi \ equivaut à jmp 4017CF
 

Et ces quatres tests nous conduisent à quatre nouvelles routines anti-SI ! Elles sont situées en 4019F8, 401A18, 401B52 et 401B7B.

Les routines en 4019F8, 401A18 et 401B52 sont construites sur le même principe que celles vues précédemment : on a une séquence cmp+jnz+xor que l'on va patcher en cmp+xor+inc(+nop pour boucher les trous).

La routine en 401B7B est légèrement différente :

sub_401B7B	proc near		; CODE XREF: .text:00401746p
		sidt	qword ptr ds:406027h
		mov	edi, ds:406029h
		mov	bx, [edi]
		mov	ax, [edi+8]
		sub	ax, bx
		sub	ax, 10h
		retn	
sub_401B7B	endp

Le résultat "Softice détecté" est ici équivalent à eax non nul, donc on peut choisir de patcher en remplaçant le dernier "sub" par un "xor eax, eax".

Finalement le patch est de la forme :

mov dword ptr [401B17], 9040C033 	<- remplace les "jz+xor" par des "xor+inc+nop"
mov dword ptr [401B3B], 9040C033	
mov dword ptr [40199C], 8931		<- remplace les 4034C7 par des 403189
mov dword ptr [401952], 8931
mov dword ptr [4019E1], 8931
mov word ptr [40181D], 9090		<- anti-Windasm
mov dword ptr [401AD7], 9040C033
mov dword ptr [401A07], 9040C033
mov dword ptr [401B75], 9040C033
mov dword ptr [401B92], 9090C033    <- remplace le sub par un xor
jmp 00401019                      	<- saut vers l'EP 

 

Et voilà ! Cette fois le générateur de clés GENERE !! On l'aura mérité ce générateur de clé ! Il y avait là dedans pas moins de 10 protections différentes (9 anti-SI et 1 anti-WinDasm), et encore, on ne s'est pas intéressé à l'anti-Frogice... UNE SACREE DAUBE !

Pour finir, je tiens à remercier tous ces chefs qui prennent la peine de nous mijoter de sympathiques crackme, car ce sont de formidables moyens d'apprendre. Et plus généralement un grand merci à tous ceux qui partagent leurs connaissances.

KeYsER, le 7 Octobre 2000.

(pour toute réclamation, s'adrsesser au service après vente à k.e.y.s.e.r@caramail.com :-)