• STATISTIQUES
  • Il y a eu un total de 1 membres et 6691 visiteurs sur le site dans les dernières 24h pour un total de 6 692 personnes!


    1 membre s'est inscrit dans les dernières 24h!


    Membres: 2 606
    Discussions: 3 580
    Messages: 32 820
    Tutoriels: 78
    Téléchargements: 38
    Sites dans l'annuaire: 58


  • ANNUAIRE
  • [FR] Newbie Contest
    Crackme: 35, Cryptographie: 49, Hacking: 27, Javascript/Java: 17, Logique: 31, Programmation: 23, Stéganographie: 53
    Challenges
    [FR] Forum-Webmaster
    Une communauté webmaster pour apporter / recevoir de l'aide en création de site internet. Webmaster...
    Webmaster
    [FR] Root-me
    Script: 5, Système: 20, Cracking: 16, Cryptanalyse: 17, Programmation: 8, Réaliste: 11, Réseau: 10, Stéganog...
    Challenges
    [FR] dcode
    dcode.fr est le site indispensable pour décoder des messages, tricher aux jeux de lettres, résoudre des énigmes...
    Outils / Add-on
    [FR] Kalkulators
    Ce projet a plusieurs buts, le premier étant l’étude de toutes formes cryptographiques, le cot&ea...
    Cryptographie
    [FR] Microcontest
    Cryptographie: 7, Mathématiques: 8, Image Son Vidéo: 5, Intelligence artificielle: 3, Réseau: 2, Divers: 7, Phy...
    Challenges
    [EN] CS Tutoring Center
    Site de challenge spécialisé dans les challenges de programmation C++ et java cependant, d'autres langages pe...
    Challenges

  • DONATION
  • Si vous avez trouvé ce site internet utile, nous vous invitons à nous faire un don du montant de votre choix via Paypal. Ce don servira à financer notre hébergement.

    MERCI!




Cracking "The XOR Algorithm II"

Bonjour,

Ce tutoriel a pour but de montrer en quelque sorte la "démarche" de résolution d'un keygenme assez simple mais loin d'être trivial (trouvé sur http://www.crackmes.de/, très bon site où vous pouvez trouver des crackmes/keygenme en tous genres et de tous les niveaux).

Ce tuto se découpe en 3 parties: tout d'abord le premier contact avec le keygenme, puis l'analyse de l'algorithme en lui-même, pour terminer sur l'élaboration d'un keygen.
Comme vous pouvez le constater, ce tuto repose plus sur des morceaux de code commentés que sur de réelles explications. Il est du coup recommandé d'avoir quelques bases en assembleur (ou de profiter de ce tuto pour vous perfectionner en assembleur Tongue) pour la compréhension de ce tuto (prendre des notes à côté n'est pas une mauvaise idée non plus Wink ).

Bref vous pouvez trouver le crackme ici.

I - Premier contact avec le keygenme

Il est désormais temps d'avoir notre premier contact avec notre keygenme, pour cela on va l'ouvrir avec notre désassembleur préféré (pour ma part ce sera IDA Pro), qui nous donne un point d'entrée ressemblant à peu près à ceci:

Code ASM :
public start
start proc near
push    0               ; lpModuleName
call    GetModuleHandleA
mov     hInstance, eax
push    0               ; dwInitParam
push    offset DialogFunc ; lpDialogFunc
push    0               ; hWndParent
push    65h             ; lpTemplateName
push    hInstance       ; hInstance
call    DialogBoxParamA
push    eax             ; uExitCode
call    ExitProcess
start endp

On constate alors que notre programme crée un dialog à partir d'une ressource (que je vous invite à regarder à l'aide de "Resource Hacker"), dont la WndProc (la fonction qui traite les évènements, pour ceux qui n'ont jamais fait de dev sous Windows) a été nommée très logiquement "DialogFunc" par IDA. Allons donc jeter un oeil à cette fonction (que je ne posterai pas ici en entier, vu qu'il est assez "conséquent"). Notre crackme ayant besoin de récupérer la valeur notre mot de passe, on doit s'attendre à trouver un "GetDlgItemText" quelque part, et c'est le cas:

Code ASM :
.text:004010F2                 push    80h             ; cchMax
.text:004010F7                 push    offset String   ; String est une var globale contenant le serial
.text:004010FC                 push    3E9h            ; nIDDlgItem
.text:00401101                 push    [ebp+hDlg]      ; hDlg
.text:00401104                 call    GetDlgItemTextA
.text:00401109                 call    sub_40106E ; fonction mystérieuse
.text:0040110E                 xor     eax, eax
.text:00401110                 xor     ebx, ebx
.text:00401112                 xor     ecx, ecx
.text:00401114                 xor     edx, edx
.text:00401116                 lea     esi, aWeGoAboutOurDa ; "We go about our daily lives understandi"...
.text:0040111C                 lea     edi, unk_4030F1
loc_401122: ; boucle de comparaison
.text:00401122                 mov     al, [esi] ; esi est un pointeur vers la str aWeGoAboutOurDa
.text:00401124                 mov     cl, [edi] ; edi est un pointeur vers unk_4030F1
.text:00401126                 cmp     al, cl ; comparaison des caractères des 2 chaînes
.text:00401128                 jnz     short loc_40114F ; on saute vers un call à ExitProcess en cas de fail
.text:0040112A                 inc     esi ; on passe au char suivant de la str
.text:0040112B                 inc     edi ; on passe au char suivant du "unk"
.text:0040112C                 inc     edx
.text:0040112D                 cmp     edx, 0F0h
.text:00401133                 jz      short loc_401137 ; on jmp vers le "good boy" si on arrive à la fin
.text:00401135                 jmp     short loc_401122

On aperçoit ici un appel à une fonction mystérieuse juste après l'appel à GetDlgItemTextA, suivi d'une routine de comparaison entre la chaîne "aWeGoAbout" et le unk_4030F1. Cette fonction mystérieuse doit donc faire des rites sataniques sur aWeGoAbout ou sur le "unk" pour éviter que la comparaison échoue tout le temps.
Il est donc grand temps pour nous d'aller plonger dans cette fonction mystérieuse.

II - Voyage en terre inconnue

Une fois arrivé en 0040106E, on peut observer quelque chose similaire à ceci:
Code ASM :
 sub_40106E      proc near               ; CODE XREF: DialogFunc+2Cp
.text:0040106E                 push    ebp
.text:0040106F                 mov     ebp, esp
.text:00401071                 sub     esp, 10h
.text:00401074                 xor     ebx, ebx
.text:00401076                 xor     ecx, ecx
.text:00401078                 xor     edx, edx
.text:0040107A                 cmp     eax, 20h ; comparaison de la longueur du serial à 32
.text:0040107D                 jnz     short loc_4010B0 ; exit si le serial fait pas 32 caractères
.text:0040107F                 lea     edi, aWeGoAboutOurDa ; " on fout aWeGo dans edi
.text:00401085                 call    sub_401014 ; fonction mystérieuse 1
.text:0040108A                 mov     ebx, edx
.text:0040108C                 call    sub_401000 ; fonction mystérieuse 2
loc_401091:
.text:00401091                 lea     esi, String ; on fout le serial entré dans esi
.text:00401097                 cmp     byte ptr [edi], 0
.text:0040109A                 jz      short loc_4010B0 ; si on est à la fin de la str, GTFO
.text:0040109C                 add     esi, edx ; début de l'algo "mystérieux"
.text:0040109E                 mov     al, [esi]
.text:004010A0                 xor     [edi], al
.text:004010A2                 add     [edi], dl
.text:004010A4                 add     bl, [edi]
.text:004010A6                 add     bl, dl
.text:004010A8                 call    sub_401000 ; fonction mystérieuse 2
.text:004010AD                 inc     edi
.text:004010AE                 jmp     short loc_401091 ; on revient au début de la boucle
loc_4010B0:
.text:004010B0                 add     esp, 10h
.text:004010B3                 pop     ebp
.text:004010B4                 retn
.text:004010B4 sub_40106E      endp

Il nous reste maintenant à savoir quel rôle jouent la "fonction mystérieuse 1" et la "fonction mystérieuse 2" pour comprendre l'algo mis en oeuvre.

Examinons donc la "fonction mystérieuse 1":
Code ASM :
 sub_401014      proc near               ; CODE XREF: sub_40102D+12p
.text:00401014                                         ; sub_40106E+17p
.text:00401014                 xor     edx, edx
.text:00401016                 lea     esi, String ; charge le mdp rentré par l'utilisateur
.text:0040101C                 mov     ecx, 20h ; on place 32 dans le compteur
.text:00401021
.text:00401021 loc_401021:                             ; CODE XREF: sub_401014+14j
.text:00401021                 add     dl, [esi] ; on ajoute à dl le code ASCII du mdp
.text:00401023                 inc     esi ; on décale d'un caractère
.text:00401024                 dec     ecx ; on décrémente le compteur
.text:00401025                 cmp     ecx, 0
.text:00401028                 jg      short loc_401021
.text:0040102A                 xor     esi, esi
.text:0040102C                 retn
.text:0040102C sub_401014      endp

On observe ici une routine qui fait la somme des codes ASCII des caractères du mot de passe entré par l'utilisateur, le tout tronqué à 1 octet (la taille du registre dl).
Avant de pouvoir examiner la routine principale, il faut d'abord savoir ce que fait la "fonction mystérieuse 2", dont le code est posté ci-dessous:
Code ASM :
sub_401000 proc near
push    ecx
mov     eax, ebx
mov     ebx, 20h
mov     ecx, edx
xor     edx, edx
div     ebx
xor     ebx, ebx
xor     ecx, ecx
pop     ecx
retn
sub_401000 endp

On remarque que cette fonction place la valeur de ebx dans eax, qui sera divisé par ebx, qui vaut 32. Le quotient de la division se trouve dans eax, tandis que le reste se situe dans edx (cf la doc Intel).
Grâce à ces précieuses informations, on peut déduire de la fonction principale (sub_40106E) le processus de génération, dont le début est:
- Somme des codes ASCII du serial entré par l'utilisateur (on invoque "fonction mystérieuse 1")
- Modulo 32 de cette somme (on invoque "fonction mystérieuse 2" et on récupère edx)
Il faut maintenant comprendre la boucle principale de l'algo:
Code ASM :
loc_401091:
.text:00401091                 lea     esi, String ; on fout le serial entré dans esi
.text:00401097                 cmp     byte ptr [edi], 0
.text:0040109A                 jz      short loc_4010B0 ; si on est à la fin de la str, GTFO
.text:0040109C                 add     esi, edx ; on décale le mdp de la somme modulo 32
.text:0040109E                 mov     al, [esi] ; on récupère le code du caractère entré par l'user
.text:004010A0                 xor     [edi], al ;on xor aWeGo avec ce code
.text:004010A2                 add     [edi], dl ;on ajoute la somme modulo 32 à aWeGo
.text:004010A4                 add     bl, [edi]
.text:004010A6                 add     bl, dl ; on ajoute le caractère calculé à la somme modulo 32
.text:004010A8                 call    sub_401000 ; on refait un algo modulo 32
.text:004010AD                 inc     edi ; on décale aWeGo d'un caractère
.text:004010AE                 jmp     short loc_401091 ; on revient au début de la boucle

En une sorte de pseudo-C, notre algo donnerait quelque chose du genre:

Code C :

x = sumUserPassword() % 32; // fonction mystérieuse 1
int i;
for(i=0; str[i] !=0; i++) {
   aWeGoAboutOurDa[i] = (aWeGoAboutOurDa[i] ^ userPassword[x]) + x;
   x = (userPassword[x] + x) % 32
}

Cela transforme donc notre chaîne aWeGoAboutOurDa, qui va ensuite être comparée au unk qu'on a vu précédemment, et nous renvoyer le "good boy" si ces deux zones mémoires ont le même contenu.

III - Let's keygen it !

On remarque assez vite que l'algorithme qui "chiffre" aWeGoAboutOurDa dépend d'un paramètre lié à l'entrée de l'utilisateur, que nous ne connaissons évidemment pas. Cependant (et heureusement pour nous), ce paramètre ne peut avoir que 32 valeurs possibles (merci le modulo !), ce qui nous autorise à bruteforcer légèrement pour trouver la (ou les) bonnes clés. De plus, ce paramètre étant égal à la somme des codes des caractères du serial, il faut vérifier que le serial trouvé vérifie ce critère.

Ainsi, l'algo sera (toujours en "pseudo-C"):
Code C :

char serial[32];
for(x=0;x<32;i++) {
  int offset = x;
  while(aWeGoAboutOurDa[i] != 0) {
    serial[x] = (aWeGoAboutOurDa[i] ^ (unk_4030F1[i] -x);
    x = (unk_4030F1[x] + x) % 32
  }
  if (sumUserPassword(serial) == offset)
     printf("%s", serial);
}

Finalement, l'implémentation de notre keygen (en C) donne quelque chose comme:
Code C :
#include <stdio.h>

char *to_encode = "We go about our daily lives understanding almost nothing of the world."
                  " Few of us spend much time wondering why nature is the way it"
                  " is; where the cosmos came from;... why we remember the past and"
  " not the future; and why there is a universe.";


char encoded[] = {0x87, 0x2B, 0x73, 0x5A, 0x30, 0x20, 0x5F, 0x5F, 0x27, 0x39, 0x1E, 0x73, 0x2E, 0x64, 0x5D, 0x72, 0x29, 0x68, 0x76, 0x67, 0x19, 0x23, 0x42, 0x3C, 0x1E, 0x5C, 0x3D, 0x6D, 0x48, 0x78, 0x37, 0x37, 0x5B, 0x37, 0x47, 0x32, 0x52, 0x3A, 0x76, 0x69, 0x65, 0x64, 0x20, 0x2D, 0x54, 0x48, 0x3D, 0x39, 0x60, 0x2E, 0x52, 0x34, 0x3B, 0x6A, 0x71, 0x29, 0x8D, 0x2D, 0x5A, 0x6D, 0x47, 0x2B, 0x41, 0x6D, 0x4A, 0x38, 0x24, 0x6D, 0x29, 0x66, 0x72, 0x07, 0x5E, 0x6B, 0x20, 0x5D, 0x57, 0x24, 0x1B, 0x45, 0x68, 0x71, 0x60, 0x55, 0x37, 0x55, 0x70, 0x6E, 0x3A, 0x4C, 0x4F, 0x64, 0x35, 0x29, 0x33, 0x49, 0x73, 0x6A, 0x5D, 0x5F, 0x37, 0x37, 0x5B, 0x1D, 0x6F, 0x57, 0x60, 0x35, 0x61, 0x19, 0x23, 0x44, 0x31, 0x39, 0x33, 0x3B, 0x5C, 0x70, 0x6A, 0x5C, 0x60, 0x34, 0x3B, 0x5E, 0x14, 0x36, 0x60, 0x68, 0x76, 0x60, 0x65, 0x31, 0x2B, 0x23, 0x24, 0x64, 0x36, 0x59, 0x2B, 0x25, 0x23, 0x77, 0x65, 0x2E, 0x59, 0x7E, 0x4C, 0x25, 0x6A, 0x33, 0x43, 0x6A, 0x76, 0x5A, 0x62, 0x64, 0x54, 0x10, 0x69, 0x5B, 0x23, 0x2B, 0x7D, 0x6F, 0x1B, 0x84, 0x72, 0x38, 0x28, 0x46, 0x17, 0x28, 0x59, 0x7E, 0x5B, 0x3B, 0x6E, 0x2A, 0x41, 0x4B, 0x2E, 0x56, 0x09, 0x46, 0x61, 0x49, 0x73, 0x67, 0x58, 0x67, 0x47, 0x73, 0x58, 0x3C, 0x4D, 0x23, 0x44, 0x27, 0x38, 0x19, 0x6B, 0x77, 0x2B, 0x73, 0x59, 0x38, 0x2A, 0x65, 0x65, 0x50, 0x70, 0x8D, 0x1F, 0x34, 0x51, 0x0D, 0x5E, 0x6B, 0x5A, 0x73, 0x39, 0x28, 0x2A, 0x40, 0x49, 0x73, 0x50, 0x24, 0x09, 0x33, 0x71, 0x4E, 0x22, 0x2F, 0x69, 0x64, 0x25, 0x31, 0x6C, 0x62};

char password[50];

int main()
{
  int x=0, i=0, j=0;
  char s;
 
  // We try to bruteforce the key
  for(i=0; i<32; i++)
    {
      printf("[+] Bruteforcing with initial x = %d\n", i);
      x = i;
      memset(password, 0, 50); // clear the password
      while(to_encode[j] != 0)
        {
          s = encoded[j] - x;
          s = s ^ to_encode[j];
          password[x] = s;
          x = (x + encoded[j]) % 32;
          j++;
        }
      if(checkPassword(i))
        printf("Potential password for x=%d: %s\n",i, password);
      j = 0;
    }
  return 0;
}

int checkPassword(int x)
{
  int i;
  int acc = 0;
  for(i=0; password[i] != 0; i++)
    {
      acc = (acc + password[i]) & 0xff;
    }
  return ((acc % 32) == x);
}
 
, et on obtient avec ce code le serial "A(U*SYGF(GH9(*5y9][@%}+)78y3htxa" (en enlevant les guillemets bien sûr).
Bien sûr, si quelqu'un a une solution plus propre pour keygen, je suis preneur Wink
03-10-2012, 06h57
Message : #1
Horgh Hors ligne
Membre actif
*



Messages : 197
Sujets : 4
Points: 91
Inscription : Mar 2012
RE: Cracking "The XOR Algorithm II"
Nice post, c'est toujours sympa à lire Smile
03-10-2012, 08h26
Message : #2
supersnail Hors ligne
Éleveur d'ornithorynques
*******



Messages : 1,610
Sujets : 72
Points: 466
Inscription : Jan 2012
RE: Cracking "The XOR Algorithm II"
Merci beaucoup Tongue
Mon blog

Code :
push esp ; dec eax ; inc ebp ; and [edi+0x41],al ; dec ebp ; inc ebp

"VIM est merveilleux" © supersnail
03-10-2012, 08h58
Message : #3
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Sujets : 48
Points: 317
Inscription : Sep 2011
RE: Cracking "The XOR Algorithm II"
Pas mal, tes explications sont clair, c'est cool Smile

New Project News White Hat Hacker V2.3
Accueil | Challenges | Tutoriels | Téléchargements | Forum