| Niveau | Outils | Auteur |
|---|---|---|
| Newbie avancé | Ollydbg 1.9d - StudPE - HexWorkShop | elooo |
Tout d'abord, si on ouvre ce crackme dans StudPE, on constate qu'il est packé avec Aspack 2.12.
L'unpacking manuel d'Aspack 2.12 n'est pas compliqué, cependant je n'expliquerai pas la démarche dans ce tutorial.
Telechargez une version récente du Stripper ou éventuellement Aspackdie (je n'ai pas testée) qui unpackeront le crackme à votre place.
Une fois le crackme décompressé, interessons-nous de plus près à ce crackme.
Si on l'execute, on tombe sur une messagebox similaire à celle-ci :
Bon apparemment on est en face d'un cd-check. Tant qu'on ne l'aura pas contourné on ne saura rien faire d'autres.
On lance le crackme dans le debuggeur et là essayez de poser un bpx quelconque, de lancer le processus et de tracer un peu. BOUM, ollydbg se ferme ! Intuitivement, je dirais que ça sent l'anti-debugger. Voyons voir dans l'éditeur hexa si on y voit quelque chose déjà.
On voit clairement des chaînes de caractères qui correspondent à des noms de debuggers, qu'est-ce que ça peut bien faire ici ? C'est quand même étrange.
Il s'agit en fait d'une sorte de Meltice évolué que Kharneth a incorporé dans son crackme.
Le crackme va chercher en mémoire les chaînes à trouver : les drivers de Softice (SICE pour win 9x et NTICE pour win NT) ainsi qu'ollydbg et windbg. Si elles sont repérées c'est qu'un debugger est actif.
Mais si dans l'éditeur hexa on change les chaînes à trouver, que se passera-t-il ? Le crackme cherchera tout autre chose en mémoire et si ces chaînes correspondent à aucune application active voire existante,
il ne se passera rien et le debugging se déroulera normalement. C'est d'ailleurs comme ça que nous allons nous y prendre :
Revenons à nos moutons, un cd-check nous attend. Maintenant que nous sommes en mesure de debugger, nous allons pouvoir étudier le code correctement.
Donc comme on l'a dit plus haut, la première chose qu'il nous faudra virer c'est le cd-check. Les api caractéristiques sont par exemple :
- GetDriveType (si eax=5 alors il s'agit d'un cdrom check)
- GetDriveTypeA
- GetLogicalDrives
- GetLogicalDrivesA
- GetLogicalDriveStrings
- GetLogicalDriveStringsA
Donc sans plus tarder, posons un bpx GetDriveType. Ollydbg nous renvoit cette fenêtre :
Et si l'on fait défiler la liste des api, on voit bien qu'aucun bpx n'a été posé et qu'en plus GetDriveType n'est pas présent dans la liste. Par contre GetDriveTypeA existe.
Donc on recommence mais cette fois avec bpx GetDriveTypeA et là on voit bien que le bpx est posé :
on lance le processus et on arrive ici :
NDLR SeVeN : Sans passer par un bpx en voulant faire vite, on aurait pu aussi faire une recherche dans ollydbg de l'instruction
cmp eax,5
On remarque tout de suite la comparaison de eax à 5, il s'agit bien de notre vérification du cd-rom. Un peu plus bas on voit toute une série de jnz / je :
La solution bourrine serait d'inverser judicieusement ces jump, mais on va
essayer de faire qelque chose de plus propre qui va nous permettre de comprendre
le fonctionnement de GetDriveType.
Si le 5 du cmp eax,5 correspond au lecteur de CDrom, il pourrait suffir
de changer cette valeur par la valeur spécifique au disque dur par
exemple. Ces valeurs correspondent en fait au code de retour de la fonction GetDriveType.
Le code du disque dur est 3.
NDLR SeVeN:
GetDriveType Return Function codes:
Value Meaning
0 Drive Cannot Be determined
1 Root Dir Does not exist
2 DriveRemoveable
3 A Fixed Disk (HardDrive)
4 Remote Drive(Network)
5 Cd-Rom Drive
6 RamDisk
Cependant un autre test juste en dessous (GetVolumeInformationA) rend cette correction insuffisante, le nom du volume, entre autres, étant attendu, il faudrait alors quand même inverser des sauts.
La solution la plus rapide et la moins contraignante consiste à poser un ret tout au début du call (en 004042F4, sur le PUSH EBP),
afin qu'il en sorte aussi vite qu'il y est rentré, et la vilaine message box ne nous embêtera plus. Elle laissera même apparaître une nouvelle
fenêtre comme celle-ci :
C'est donc le moment de s'interesser à la routine de génération du serial. Seulement rentrez un pass bidon plus de 3 fois de suite et regardez la fenêtre qui s'affiche :
On va donc se charger de supprimer cette limitation pour pouvoir continuer. Le plus simple à mon humble avis est de poser un bpx MessageBoxA.
Ce qui nous fait breaker ici :
Si on remonte un peu on voit une comparaison à la valeur 2, avec juste en-dessous un JBE qui se charge de jumper en-dessous
de la message box. En fait ça signifie que si on trouve une valeur inférieure ou égale à 2 en 00403CD1, la message box ne s'affichera pas.
Ne nous cassons pas la tête, un JMP à la place du JBE et l'affaire est réglée :)
NDLR SeVeN :
On voit clairement entre 00403C30 et 00403CCC des appels à des API spécifiques à la manipulation de la base de registre.En effet, le nombre de tentatives est enregistré ici :
HKEY_CURRENT_USER\MPCrackMe 4 InfosHackers By Kharneth
dans une valeur DWORD nommée MPCrackMe 4 InfosHackers By Kharneth, qu'on pourrait réinitialiser.
Et cette fois, pour de bon, on va rellement pouvoir s'occuper du serial !
En faisant défiler les api tout à l'heure, j'ai repéré un GetWindowTextLengthA, qui est
surement loin d'être ininteressant. Il pourrait par exemple récupérer la longueur du pass rentré
pour la comparer à la longueur attendue. Donc essayons : bpx GetWindowTextLengthA. Pof, on arrive ici :
Et c'est effectivement ce que je pensais. Le serial attendu fait donc 14 caractères.
En tracant un peu, plus bas, on peut voir :
Il y a donc trois call importants avant les checksums qui serviront à la vérification du serial.
Le premier du chargement de la string KHAO. On ne sait pas encore à quoi elle va servir, mais nulle doûte qu'elle
aura une fonction :).
Le deuxième call se présente ainsi pour une partie :
Il va vérifier que le 5eme caractère correspond bien à un -, l'autre partie du call qui ne figure pas sur le screenshot s'assure que le serial commence par la fameuse string KHAO de tout à l'heure.
On s'attend donc à avoir un serial qui fait 14 caractères et qui commence par
KHAO-
Puis comme je l'ai commenté plus haut, on voit voit deux appels successifs à un même call suivis de checksum,
qui contiendront la routine de calcul de la valeur qui sera comparée à chacun des checksum.
On y voit :
On sait dès lors que le serial est composé de trois parties séparées par des -, que la 1ere partie est formée de KHAO, que les deux parties suivantes sont également composées de 4 caractères , tous des alphabétiques et en majuscules.
Si on regarde les calculs effectués, sachant que les valeurs obtenues seront comparées aux checksum, si on reverse l'algo on pourra récupérer les bonnes lettres.
Les équations à résoudre sont d'ailleurs :
26325072=((((((car1+car2+car3+car4)^(car1*car2*car3*car4))+car1)*car2)/car3)-car4)pour le 1er checksum (191B050h étant égal à 26325072) et
31304602=((((((car1+car2+car3+car4)^(car1*car2*car3*car4))+car1)*car2)/car3)-car4)pour le 2eme checksum(1DDAB9Ah étant égal à 31304602).
Les équations étant à 4 inconnues, il semble difficile d'en venir à bout autrement qu'en bruteforcant.
Vous trouverez ci-dessous 3 codes-sources dans 3 langages différents qui feront le travail pour vous.
/*
Name: Bruteforce du MPCrackMe 4 InfosHackers by Kharneth
Author: SeVeN
Date: 29/12/03 01:48
Description: Algo en C
*/
#include <stdio.h>
#include <stdlib.h>
void brute(int checksum)
{
char a, b, c, d;
for (a = 'A'; a <= 'Z'; a++)
for (b = 'A'; b <= 'Z'; b++)
for (c = 'A'; c <= 'Z'; c++)
for (d = 'A'; d <= 'Z'; d++)
if ((((unsigned int)((((a + b + c + d)^(a * b * c * d))+ a)* b)/ c)- d) == checksum)
{
printf("-%c%c%c%c", a, b, c, d);
return ;
}
}
int main()
{
printf("\nBruteforce du \"MPCrackMe 4 InfosHackers by Kharneth\"\npar SeVeN\n\n");
printf("Le serial est KHAO");
brute(0x191B050);
brute(0x1DDAB9A);
printf(".\n\n\n\n");
system("PAUSE");
return (0);
}
/*
* AUTEUR: elooo
* DATE: 29/12/03
* TITRE: Bruteforce MPCrackme_cd de Kharneth
* FONCTION: Permet de choper le serial attendu en fonction des checksums
*/
class bruteforce
{
public static void main (String[] args)
{
System.out.println("\n\n**********************************************");
System.out.println("* Bruteforce en JAVA *");
System.out.println("* MPCrackme_cd de Kharneth *");
System.out.println("* Par elooo *");
System.out.println("**********************************************");
System.out.println("**** ****");
System.out.print("**** Le serial attendu est KHAO");
brutozor(26325072);
brutozor(31304602);
System.out.println(" ****");
System.out.println("**** ****");
System.out.println("**********************************************\n");
}
public static void brutozor (int checksum)
{
char car1;
char car2;
char car3;
char car4;
int resultat = 0;
for(car1 = 'A' ; car1 <= 'Z' ; car1++)
for(car2 = 'A' ; car2 <= 'Z' ; car2++)
for(car3 = 'A' ; car3 <= 'Z' ; car3++)
for(car4 = 'A' ; car4 <= 'Z' ; car4++)
{
resultat = (((car1+car2+car3+car4)^(car1*car2*car3*car4))+car1);
long resultatlong = resultat & 0xffffffffL;
if (checksum ==(((resultatlong*car2)/car3)-car4))
System.out.print("-"+car1+car2+car3+car4);
}
}
}
(Désolée d'avance pour les grands codeurs en asm, je ne suis qu'une débutante ^^)
; **************************************************************************
; Catégorie : Bruteforce MPCrackme_cd de Kharneth.
; Type : Bruteforce
; Asm : MASM 8
; Auteur : elooo
; **************************************************************************
[...]
FILTRE1 db "KHAO-%c%c%c%c-%c%c%c%c",0
[...]
Brute PROC
pushad
xor edx, edx
xor eax, eax
xor ecx, ecx
xor esi, esi
debut:
mov CAR1, 65
mov CAR2, 65
mov CAR3, 65
mov CAR4, 65
call routine
jmp checksum
checksum:
cmp edi, 1
je suite2
cmp esi, 26325072
je suite
jmp incrementation
incrementation:
cmp CAR4, 91
je incrementation2
add CAR4, 1
call routine
jmp checksum
incrementation2:
mov CAR4, 65
cmp CAR3, 91
je incrementation3
add CAR3, 1
call routine
jmp checksum
incrementation3:
mov CAR3, 65
cmp CAR2, 91
je incrementation4
add CAR2, 1
call routine
jmp checksum
incrementation4:
mov CAR2, 65
cmp CAR1, 91
je fin
add CAR1, 1
call routine
jmp checksum
suite:
mov edi, 1
movzx edx, byte ptr CAR1
mov byte ptr TEMP1, dl
movzx edx, byte ptr CAR2
mov byte ptr TEMP2, dl
movzx edx, byte ptr CAR3
mov byte ptr TEMP3, dl
movzx edx, byte ptr CAR4
mov byte ptr TEMP4, dl
xor edx, edx
mov CAR1, 65
mov CAR2, 65
mov CAR3, 65
mov CAR4, 65
call routine
suite2:
cmp esi, 31304602
je fin
jmp incrementation
fin:
push dword ptr CAR4
push dword ptr CAR3
push dword ptr CAR2
push dword ptr CAR1
push dword ptr TEMP4
push dword ptr TEMP3
push dword ptr TEMP2
push dword ptr TEMP1
invoke wsprintf, offset SERIAL, offset FILTRE1
add esp, 32
popad
ret
Brute ENDP
routine PROC
movzx edx, byte ptr CAR1
mov eax, edx
movzx ecx, byte ptr CAR2
add eax, ecx
movzx ebx, byte ptr CAR3
add eax, ebx
movzx esi, byte ptr CAR4
add eax, esi
imul edx, ecx
imul edx, ebx
movzx ecx, byte ptr CAR4
imul edx, ecx
mov ecx, eax
xor ecx, edx
movzx eax, byte ptr CAR1
add eax, ecx
movzx edx, byte ptr CAR2
imul edx
movzx edx, byte ptr CAR3
mov ecx, edx
xor edx, edx
div ecx
mov esi, eax
movzx eax, byte ptr CAR4
sub esi, eax
ret
routine ENDP
Après utilisation d'une des bruteforces, on obtient alors comme serial :