Keygen pour Easy CD-DA Extractor Version 3.6.0 (Build 1600)


MagTians, le 19 novembre 1999

Préliminaire

Easy CD-DA Extractor est le programme le plus décent que j'ai trouvé pour encoder les CD audio en mp3. Je me suis arrêté sur celui-là après avoir essayé MusicMatch Jukebox et Audiograbber que j'ai trouvés pitoyables. L'interface de MusicMatch Jukebox est une horreur et en essayant de cracker Audiograbber je me suis aperçu qu'il utilisait une DLL 16 bits.

Liminaire

Lorsqu'on lance Easy CD-DA Extractor, une boîte de dialogue apparaît et demande un code à l'utilisateur. La première chose que l'on remarque est que le bouton OK est grisé. J'ai essayé d'entrer n'importe quoi mais tous mes efforts n'ont pas réussi à dégriser ce bouton, d'où l'hypothèse que ceci n'arrive que lorsque que le code est correct.

Une possibilité est que le code est vérifié à chaque fois que l'utilisateur rentre un nouveau caratère du code. Sur cette idée, je me suis dit : "tain, ça change que dalle, le prog est bien obligé de récupérer le code saisi, je va faire un ptit bpx GetDlgItemTextA et on en parlera plus". Ben ca marche po, bpx GetWindowTextA ne marche pas non plus. bpx hmemcpy marche mais je me suis retrouvé complètement perdu dans les DLL de Windows. En fait j'ai trouvé la fonction de vérification du code tout à fait par hasard grâce à un bpx GetSystemTime. J'avais vu que cette fonction était utilisée grâce à wdasm mais je ne voyais pas du tout à quoi elle pouvait servir dans la fonction de vérification du code. Enfin, cette fonction se trouve à 4016FC, si elle retourne 1, le bouton se dégrise.

Comme le programme ne demande qu'un code (pas de nom d'utilisateur) je me suis dit que faire un générateur de clé serait hyper simple.

Tentative d'embrouille ?

Regardons ce qui se passe en 4016FC :

:004016FC 55               push ebp
:004016FD 8BEC             mov ebp, esp
:004016FF 83C4B0           add esp, -50                 ; 50 octets de variable locale
:00401702 8D45F0           lea eax, dword ptr [ebp-10]  ; appelons [ebp-10] d
:00401705 8D55F4           lea edx, dword ptr [ebp-0C]  ; appelons [ebp-0C] c
:00401708 8D4DF8           lea ecx, dword ptr [ebp-08]  ; appelons [ebp-08] b
:0040170B 50               push eax
:0040170C 52               push edx
:0040170D 51               push ecx
:0040170E 8D45FC           lea eax, dword ptr [ebp-04]  ; appelons [ebp-04] a
:00401711 50               push eax

* Possible StringData Ref from Data Obj ->"%08X%08X-%08X%08X"
                                  |
:00401712 6883E74C00       push 004CE783
:00401717 8B5508           mov edx, dword ptr [ebp+08]  ; d ebp+08 montre qu'il s'agit du code
:0040171A 52               push edx
:0040171B E808C40A00       call 004ADB28                ; manifestement sscanf
:00401720 83C418           add esp, 00000018

Le début est clair comme de l'eau de roche, le code doit être de la forme "%08X%08X-%08X%08X", c'est à dire quatre nombres hexadecimaux de huit caractères avec un tiret au milieu, par exemple : 0123456789ABCDEF-89ABCDEF01234567. Le but de sscanf est de transformer une telle chaine de caractère en quatre entier 32 bits.

En C ça donne la chose suivante :

int VerifieCode(char *code)
{
    int a, b, c, d;

    sscanf(code, "%08X%08X-%08X%08X", &a, &b, &c, &d);

La suite :

:00401723 8B45F8        mov eax, dword ptr [ebp-08]
:00401726 33D2          xor edx, edx
:00401728 8D4DE8        lea ecx, dword ptr [ebp-18]    ; appelons [ebp-18] u1
:0040172B 8BD0          mov edx, eax
:0040172D 33C0          xor eax, eax
:0040172F 52            push edx
:00401730 50            push eax
:00401731 8B45FC        mov eax, dword ptr [ebp-04]
:00401734 33D2          xor edx, edx
:00401736 0B542404      or edx, dword ptr [esp+04]
:0040173A 0B0424        or eax, dword ptr [esp]
:0040173D 8945E8        mov dword ptr [ebp-18], eax
:00401740 8955EC        mov dword ptr [ebp-14], edx    ; appelons [ebp-14] u2
:00401743 83C408        add esp, 00000008

Regardez bien ce code, il est totalement inutile ! Tout ce que ça fait est u1=a et u2=b. Les programmeurs de Easy CD-DA Extractor cherchent juste à nous embrouiller la tête, mais évidemment ça ne marche pas.

:00401746 8B45F0        mov eax, dword ptr [ebp-10]
:00401749 33D2          xor edx, edx
:0040174B 8BD0          mov edx, eax
:0040174D 33C0          xor eax, eax
:0040174F 52            push edx
:00401750 50            push eax
:00401751 8B45F4        mov eax, dword ptr [ebp-0C]
:00401754 33D2          xor edx, edx
:00401756 0B542404      or edx, dword ptr [esp+04]
:0040175A 0B0424        or eax, dword ptr [esp]
:0040175D 8945E0        mov dword ptr [ebp-20], eax    ; appelons [ebp-20] u3
:00401760 8955E4        mov dword ptr [ebp-1C], edx    ; appelons [ebp-1C] u4

Cette partie est exactement pareille que la précédente, ça fait u3=c et u4=d. Ne perdons pas plus de temps sur ce tas d'instructions horrible et regardons la suite :

:00401763 8B45E0        mov eax, dword ptr [ebp-20]   ; eax = u3
:00401766 8B55E4        mov edx, dword ptr [ebp-1C]   ; edx = u4
:00401769 81F0AAAAAAAA  xor eax, AAAAAAAA             ; eax = eax xor AAAAAAAA 
:0040176F 81F2AAAAAAAA  xor edx, AAAAAAAA             ; edx = edx xor AAAAAAAA
:00401775 3145E8        xor dword ptr [ebp-18], eax   ; u1 = u1 xor eax
:00401778 83C408        add esp, 00000008
:0040177B 3155EC        xor dword ptr [ebp-14], edx   ; u2 = u2 xor eax

Ceci semble plus intéressant, au final on a u1 = a xor c xor AAAAAAAA et u2 = b xor d xor AAAAAAAA. Regardons la suite :

:0040177E 8D45D8        lea eax, dword ptr [ebp-28]   ; appelons [ebp-28] u5 
:00401781 6A08          push 00000008
:00401783 51            push ecx                      ; regardez en 401728, ECX pointe vers u1
:00401784 50            push eax
:00401785 E8A2840A00    call 004A9C2C                 ; manifestement memcpy
:0040178A 83C40C        add esp, 0000000C

Regardez rapidemment 4A9C2C, vous avez reconnu au premier coup d'oeil memcpy. On fait donc memcpy(&u5, &u1, 8). Si on appelle [ebp-24] u6, ce memcpy est equivalent à u5=u1 et u6=u2. Pour l'instant, rien de véritablement intéressant, le résumé en C nous montre que rien ne s'est passé à part les XOR.

int VerifieCode(char *code)
{
    int a, b, c, d;
    int u1, u2, u3, u4;

    sscanf(code, "%08X%08X-%08X%08X", &a, &b, &c, &d);

    u1=a; u2=b; u3=c; u4=d;
    
    u1 ^= u3 ^ 0xAAAAAAAA;
    u2 ^= u4 ^ 0xAAAAAAAA;

    u5 = u1;
    u6 = u2;

La surprise du chef

La suite nous révèle quelques surprises :

:0040178D 8D55B0                  lea edx, dword ptr [ebp-50]
:00401790 52                      push edx

* Reference To: KERNEL32.GetSystemTime, Ord:0000h
                                  |
:00401791 E8DEB40C00              Call 004CCC74

Voilà l'appel à GetSystemTime qui m'a fait repérer cette fonction. Il rempli une structure SYSTEMTIME à l'adresse ebp-50, appelons la st.

:00401796 6A00                    push 00000000
:00401798 686D010000              push 0000016D
:0040179D 0FB745D8                movzx eax, word ptr [ebp-28]
:004017A1 33D2                    xor edx, edx
:004017A3 E8B0F50A00              call 004B0D58
:004017A8 52                      push edx
:004017A9 50                      push eax
:004017AA 6A00                    push 00000000
:004017AC 6A1E                    push 0000001E
:004017AE 0FB745DA                movzx eax, word ptr [ebp-26]
:004017B2 33D2                    xor edx, edx
:004017B4 E89FF50A00              call 004B0D58
:004017B9 030424                  add eax, dword ptr [esp]
:004017BC 13542404                adc edx, dword ptr [esp+04]
:004017C0 83C408                  add esp, 00000008
:004017C3 52                      push edx
:004017C4 50                      push eax
:004017C5 0FB745DC                movzx eax, word ptr [ebp-24]
:004017C9 33D2                    xor edx, edx
:004017CB 030424                  add eax, dword ptr [esp]
:004017CE 13542404                adc edx, dword ptr [esp+04]
:004017D2 83C408                  add esp, 00000008
:004017D5 8945D0                  mov dword ptr [ebp-30], eax
:004017D8 8955D4                  mov dword ptr [ebp-2C], edx

Regardez d'abord cette suite d'instruction en gros. Il s'agit d'un calcul fait à partir u5 (ebp-28 et ebp-26) et u6 (ebp-24). EAX et EDX sont utilisés tout au long du calcul et sont conservé à la fin dans [ebp-30] et [ebp-2C]. Par deux fois, ADD suivi de ADC sont utilisés. Tout ceci nous montre qu'on travaille sur des nombres de 64 bits. Regardez rapidement la fonction 4B0D58, il s'agit tout simplement de la multiplication de l'entier de 64 bits formé par EAX et EDX par l'entier de 64 bits formés par les deux paramètres que prend cette fonction.

Reregardez attentivement, et hop vous comprenez qu'il s'agit tout bêtement de l'instruction __int64 r = LOWWORD(u5)*0x16D + HIWORD(u5)*0x1E + LOWWORD(u6), où r est stocké en ebp-30 et ebp-2C

Je vous présente ça comme si j'avais compris tout de suite, mais en fait non. J'ai tracé ce bloc d'instructions une bonne dizaine de fois, j'ai pris des notes fait différents essais avec des valeurs différentes pour comprendre ce qui ce passe.

Nous remarquons aussi, mais cela ne nous étonnera pas vu les horreurs que nous avons déja vu, qu'il est totalement inutile de raisonner sur des entiers de 64 bits étant donné que le résultat de ce calcul tient forcement sur un DWORD.

La suite est du même acabi :

:004017DB 6A00                    push 00000000
:004017DD 686D010000              push 0000016D
:004017E2 0FB745B0                movzx eax, word ptr [ebp-50]
:004017E6 33D2                    xor edx, edx
:004017E8 E86BF50A00              call 004B0D58
:004017ED 52                      push edx
:004017EE 50                      push eax
:004017EF 6A00                    push 00000000
:004017F1 6A1E                    push 0000001E
:004017F3 0FB745B2                movzx eax, word ptr [ebp-4E]
:004017F7 33D2                    xor edx, edx
:004017F9 E85AF50A00              call 004B0D58
:004017FE 030424                  add eax, dword ptr [esp]
:00401801 13542404                adc edx, dword ptr [esp+04]
:00401805 83C408                  add esp, 00000008
:00401808 52                      push edx
:00401809 50                      push eax
:0040180A 0FB745B6                movzx eax, word ptr [ebp-4A]
:0040180E 33D2                    xor edx, edx
:00401810 030424                  add eax, dword ptr [esp]
:00401813 13542404                adc edx, dword ptr [esp+04]
:00401817 83C408                  add esp, 00000008
:0040181A 8945C8                  mov dword ptr [ebp-38], eax
:0040181D 8955CC                  mov dword ptr [ebp-34], edx

Exactement le même calcul que le précedent, mais cette fois avec des informations de la structure SYSTEMTIME.

[ebp-50] est l'année, [ebp-4E] est le mois et [ebp-4A] est le jour. Le calcul est __int64 d = année*0x16D + mois*0x1E + jour,d étant stocké sur [ebp-38] et [ebp-34]. Lorsque j'ai vu ça, ça a fait tilt dans ma tête. J'ai sorti ma TI-85 (vieux souvenir de terminale, ça doit être une antiquité maintenant) et, hmm très intéressant, 16D vaut 365 et 1E vaut 30.

Ceci calcule, grosso modo, un numéro de jour. Le code, ayant subi le même type de calcul, doit aussi représenter un numéro de jour.

Il est quasi certain à cette étape que ces deux numéros vont être comparés. Je me suis dit que le code doit certainement représenter une date de fin de validité de la license. Sur cette idée, je me suis forgé un code qui représente le 01/01/3000 mais ça na pas marché. Regardons la suite pour comprendre pourquoi :

 
:00401820 8B45C8                  mov eax, dword ptr [ebp-38]
:00401823 8B55CC                  mov edx, dword ptr [ebp-34]
:00401826 2B45D0                  sub eax, dword ptr [ebp-30]
:00401829 1B55D4                  sbb edx, dword ptr [ebp-2C]
:0040182C 8945C0                  mov dword ptr [ebp-40], eax
:0040182F 8955C4                  mov dword ptr [ebp-3C], edx
:00401832 837DC400                cmp dword ptr [ebp-3C], 00000000
:00401836 7504                    jne 0040183C
:00401838 837DC00A                cmp dword ptr [ebp-40], 0000000A

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401836(C)
|
:0040183C 7610                    jbe 0040184E
:0040183E 837DC4FF                cmp dword ptr [ebp-3C], FFFFFFFF
:00401842 7504                    jne 00401848
:00401844 837DC0FA                cmp dword ptr [ebp-40], FFFFFFFA

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401842(C)
|
:00401848 7304                    jnb 0040184E
:0040184A 33C0                    xor eax, eax
:0040184C EB0D                    jmp 0040185B

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040183C(C), :00401848(C)
|
:0040184E 66837DDE43              cmp word ptr [ebp-22], 0043
:00401853 7404                    je 00401859
:00401855 33C0                    xor eax, eax
:00401857 EB02                    jmp 0040185B

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401853(C)
|
:00401859 B001                    mov al, 01

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040184C(U), :00401857(U)
|
:0040185B 8BE5                    mov esp, ebp
:0040185D 5D                      pop ebp
:0040185E C3                      ret

Là, on voit que le code (après quelques manipulations) doit représenter une date comprise entre 10 jours avant la date actuelle et 6 jours après la date actuelle. On voit aussi que [ebp-22] (HIWORD(u6)) doit être égal à 43. Et on comprend le raisonnement vicieux des programmeurs de Easy CD-DA Extractor. Ils se sont dit :
- Eh didon, t'as vu, ya pleins de sites qui proposent des serial pour toutes sortes de sharewares.
- Tain, ça doit être des gars qui achètent les sharewares et refilent leurs serials dans l'idée d'obtenir un peu de gloire et de popularité, pfff bande de ptits cons va.
- Eh didon, je crois que j'ai une idée géniale !!!
- Tain, toi ! une idée géniale ! (ricanements contenus)
- Ben vi, on va faire un serial qui marche que pendant une courte période, comme ça les ptits cons vont mettre plein de serials sur leurs sites mais quelques jours après ils seront plus bons et ils l'auront dans l'os les ptits enculés de leur mère.
- Tain, génial génial, mais comment on va faire ça ?
- Ben facile ducon, pour le serial tu prend une date, tu fais quelques XOR, quelques trucs bidon pour que ca ressemble plus à une date. Dans le prog, on récupère la date contenue dans le serial et on compare avec la date actuelle pi le tour est joué
- Tain, génial excellent, on va les enculer comme des enculés les enfoirés

Génération de bons codes

Assez fantasmé sur la vie de programmeur de shareware, il est temps de passer à la génération de bons codes, pour commencer,

faisons un ptit exemple à la main, nous choisissons la date actuelle pour nous enregistrer, 19/11/1999 dans ce cas.

19/11/1999 en hexadécimal c'est 13/B/7CF
L'année c'est LOWWORD(u5), c'est 07CF
Le mois c'est HIGHWORD(u5), c'est 000B
Le jour c'est LOWWORD(u6), c'est 0013
HIGHWORD(u6) doit être égal à 43
On a donc :
u5 = 000B07CF
u6 = 00430013
Or,
u5 = a xor c xor AAAAAAAA et
u6 = b xor d xor AAAAAAAA
Donc,
a xor c = u5 xor AAAAAAAA = AAA1AD65
b xor d = u6 xor AAAAAAAA = AAE9AAB9
Là on peut choisir c et d au hasard, et ca nous donnera a et b. Si on choisi c=d=0, on a a=AAA1AD65 et b=AAE9AAB9
Donc notre serial est : AAA1AD65AAE9AAB9-0000000000000000
Vérifions, ça marche !!! Mais dans 10 jours il marchera plus, héhé pas si con comme principe.

Le C, ya que ça de vrai :

//================================================================================
// GenereCode génère un code valide pour Easy CD-DA Extractor pour le jour
// courant
// 
// Paramètres : c et d sont les nombres qui forme la partie du code après le tiret
//              on peut mettre ce que l'on veut
//             
//              code est le buffer dans lequel va être écrit le code. Le buffer
//              doit être de longueur 34 au moins
//================================================================================

void GenereCode(DWORD c, DWORD d, char *code)
{
    DWORD u5, u6, a, b;
    SYSTEMTIME st;

    GetSystemTime(&st);

    u5=MAKELONG(st.wYear, st.wMonth);
    u6=MAKELONG(st.wDay, 0x43);

    a = u5 ^ 0xAAAAAAAA ^ c;
    b = u6 ^ 0xAAAAAAAA ^ d;

    sprintf(code, "%08X%08X-%08X%08X", a, b, c, d);
}

Voilà, ben ma foi je trouve que le principe de la protection est pas mal du tout, même si, au final, le générateur de clé est ridiculement court.

Mes remerciments (il parait que c'est une tradition) vont à Christal (www.multimania.com/christalpage) et à TeeJi (www.crackfr.com).