[ SpecialDelivery CrackMe by ged_ ]
par Amenesia

[ ENTREES ]

I.1- Name :

 La recherche de l'api  GetDlgItemTextA nous mene ici :

_text:00402FD7 GetName: 
_text:00402FD7                 push    63h             ; nMaxCount
_text:00402FD9                 push    offset Name     ; lpString
_text:00402FDE                 push    0C8h            ; nIDDlgItem
_text:00402FE3                 push    [ebp+hDlg]      ; hDlg
_text:00402FE6                 call    GetDlgItemTextA
_text:00402FEB                 mov     ds:SerialSize, eax
_text:00402FF0                 cmp     ds:SerialSize, 0
_text:00402FF7                 jnz     short GetSerial
_text:00402FF9                 push    offset aEnterName ; lpString
_text:00402FFE                 push    0C9h              ; nIDDlgItem
_text:00403003                 push    [ebp+hDlg]        ; hDlg
_text:00403006                 call    SetDlgItemTextA
_text:0040300B                 jmp     EndCheckSerial
 

 Donc le "Name" entrée par l'utilisateur doit etre composé d'au moins un caractére... ;p 


I.2- Serial :

_text:00403038 GetSerial: 
_text:00403038                 push    23h             ; nMaxCount
_text:0040303A                 push    offset Serial   ; lpString
_text:0040303F                 push    0C9h            ; nIDDlgItem
_text:00403044                 push    [ebp+hDlg]      ; hDlg
_text:00403047                 call    GetDlgItemTextA
_text:0040304C                 mov     ds:SerialSize, eax
_text:00403051                 cmp     ds:SerialSize, 20h
_text:00403058                 jnb     short Next
_text:0040305A                 push    offset aMaybeAskJoe? ; lpString
_text:0040305F                 push    0C9h            ; nIDDlgItem
_text:00403064                 push    [ebp+hDlg]      ; hDlg
_text:00403067                 call    SetDlgItemTextA
_text:0040306C                 jmp     EndCheckSerial

 Puis on recupere le Serial ( qui doit etre composé de 32 caracteres )...


_text:00403071 Next: 
_text:00403071                 mov     ds:FlagValidCar, 0
_text:0040307B                 mov     ds:CurrentCar, 0
_text:00403085                 jmp     short O9AF
_text:00403087 ; ---------------------------------------------------------------------------
_text:00403087 
_text:00403087 LoopO9AF: 
_text:00403087                 mov     edx, ds:CurrentCar
_text:0040308D                 movzx   edi, Serial[edx]
_text:00403095                 cmp     edi, 30h
_text:00403098                 jge     short loc_0_4030A4
_text:0040309A                 mov     ds:FlagValidCar, 1
_text:004030A4 
_text:004030A4 loc_0_4030A4: 
_text:004030A4                 mov     edx, ds:CurrentCar
_text:004030AA                 movzx   edi, Serial[edx]
_text:004030B2                 cmp     edi, 39h
_text:004030B5                 jle     short loc_0_4030F1
_text:004030B7                 mov     edx, ds:CurrentCar
_text:004030BD                 movzx   edi, Serial[edx]
_text:004030C5                 cmp     edi, 46h
_text:004030C8                 jle     short loc_0_4030D4
_text:004030CA                 mov     ds:FlagValidCar, 1
_text:004030D4 
_text:004030D4 loc_0_4030D4: 
_text:004030D4                 mov     edx, ds:CurrentCar
_text:004030DA                 movzx   edi, Serial[edx]
_text:004030E2                 cmp     edi, 41h
_text:004030E5                 jge     short loc_0_4030F1
_text:004030E7                 mov     ds:FlagValidCar, 1
_text:004030F1 
_text:004030F1 loc_0_4030F1: 
_text:004030F1                 inc     ds:CurrentCar
_text:004030F7 
_text:004030F7 O9AF:
_text:004030F7                 mov     edi, ds:SerialSize
_text:004030FD                 cmp     ds:CurrentCar, edi
_text:00403103                 jb      short LoopO9AF
_text:00403105                 cmp     ds:FlagValidCar, 0
_text:0040310C                 jnz     short ErrorSerialCar
 

 On verifie ensuite que le serial ne comporte que des caracteres appartenant à [0-9A-F]...



_text:0040310E                 push    offset Serial
_text:00403113                 call    ascii2hex
_text:00403118                 add     esp, 4

 Puis on le convertit en une valeur hexadecimal ( ie '0' => 00h, ..., '9' => 09h, 'A' => 0Ah, ... ,'F' => 0Fh )


_text:00403120                 push    ds:SerialSize
_text:00403126                 push    offset Serial
_text:0040312B                 push    offset Name
_text:00403130                 call    CheckSerial
_text:00403135                 add     esp, 0Ch
_text:00403138                 cmp     eax, 0
_text:0040313B                 jz      short BadSerial

  Puis on verifie la validité du serial....



 
 

[ ANALYSE ]



II.1- Square

La routine SquareCipher utilise 5 tables dont les valeurs correspondent à celles utilisées par : Square
Quelques recherches sur Square nous permettent de deduire le role de certains procedure:
-squareGenerateRoundKeys (key,  roundKeys_e, roundKeys_d);
-squareEncrypt (word32 text[4],  roundKeys_e);

_text:00402F0A                 push    ebp
_text:00402F0B                 mov     ebp, esp
_text:00402F0D                 sub     esp, 168h
_text:00402F13                 push    esi
_text:00402F14                 push    edi
_text:00402F15                 lea     edi, [ebp+lpPhrase]
_text:00402F18                 lea     esi, [ keyword ]
_text:00402F1E                 mov     ecx, 47h
_text:00402F23                 repe movsb

_text:00402F25                 lea     edi, [ebp+RoundKey_d]
_text:00402F2B                 push    edi
_text:00402F2C                 lea     edi, [ebp+RoundKey_e]
_text:00402F32                 push    edi
_text:00402F33                 lea     edi, [ebp+lpPhrase]
_text:00402F36                 push    edi
_text:00402F37                 call    GenerateRoundKeys_e
_text:00402F3C                 add     esp, 0Ch

_text:00402F3F                 lea     edi, [ebp+RoundKey_e]
_text:00402F45                 push    edi
_text:00402F46                 push    [ebp+Serial_hex]
_text:00402F49                 call     squareEncrypt
_text:00402F4E                 add     esp, 8

On se retrouve donc avec 4 dword correspondant au chiffrement de Serial_hex par Square ( avec keyword pour mot clef ;))

squareGenerateRoundKeys (keyword,  roundKeys_e, roundKeys_d);
Serial_hex    := squareEncrypt (Serial_hex,  roundKeys_e); 


II.2- Magic

Puis on effectue une transformation sur chacun des 4 dword obtenus:

_text:00403250                 mov     ecx, 4
_text:00403255                 mov     ebx, 4078BCh
_text:0040325A                 mov     esi, [ebp+Serial_hex]
_text:0040325D                 mov     edi, esi
_text:0040325F 
_text:0040325F LoopC: 
_text:0040325F                 lodsd
_text:00403260                 push    eax
_text:00403261                 add     ebx, 80h
_text:00403267                 push    ebx
_text:00403268                 call    LinearMat
_text:0040326D                 add     esp, 8
_text:00403270                 stosd
_text:00403271                 loop    LoopC

C'est a dire :

Serial_hex[0] := LinearMat(Serial_hex[0],Mat[0])
Serial_hex[1] := LinearMat(Serial_hex[1],Mat[1])
Serial_hex[2] := LinearMat(Serial_hex[2],Mat[2])
Serial_hex[3] := LinearMat(Serial_hex[3],Mat[3])


II.3- "MD5"

D'apres PEiD cette routine est un hash MD5 cependant plusieurs modifications ont ete apportés ( initialisation, utilisation de tables...)

_text:00403233                 push    offset Buffer
_text:00403238                 call    Ini
_text:0040323D                 add     esp, 4
_text:00403240                 push    offset Buffer
_text:00403245                 push    [ebp+Name]
_text:00403248                 call    Hash

Quoiqu'il en soit on se retrouve avec un hash ( de 4 dword ) du Name dans [Buffer]...

[Buffer]      := modified_MD5[Buffer];



II.4- CPUiD

_text:00403273                 mov     esi, offset Buffer
_text:00403278                 xor     eax, eax
_text:0040327A                 cpuid
_text:0040327C                 xor     [esi], ebx
_text:0040327E                 rol     ebx, 0Bh
_text:00403281                 xor     [esi+4], ebx
_text:00403284                 rol     ebx, 0Bh
_text:00403287                 xor     [esi+8], ebx
_text:0040328A                 rol     ebx, 11h
_text:0040328D                 xor     [esi+0Ch], ebx

Finalement on applique un xor de la valeur renvoyée par cpuid sur la valeur contenue dans [Buffer]

[Buffer + 0]  := [Buffer + 0] xor cpuid;
[Buffer + 1]  := [Buffer + 1] xor cpuid;
[Buffer + 2]  := [Buffer + 2] xor cpuid;
[Buffer + 3]  := [Buffer + 3] xor cpuid;



II.5- Final Cmp
 

_text:00403273                 mov     esi, offset Buffer
[ .................................................. ]
text:00403290                  xor     eax, eax
_text:00403292                 mov     edi, [ebp+Serial_hex]
_text:00403295                 mov     ecx, 5
_text:0040329A                 repe cmpsd

Et pour terminer on compare la valeur contenue dans [Buffer] à celle contenue dans [Serial_hex]



 
 

[ KEYGEN ]


Pour resumer:
squareGenerateRoundKeys (keyword,  roundKeys_e, roundKeys_d);
Serial_hex    := squareEncrypt (Serial_hex,  roundKeys_e);
Serial_hex[0] := LinearMat(Serial_hex[0],Mat[0]);
Serial_hex[1] := LinearMat(Serial_hex[1],Mat[1]);
Serial_hex[2] := LinearMat(Serial_hex[2],Mat[2]);
Serial_hex[3] := LinearMat(Serial_hex[3],Mat[3]);
[Buffer]      := modified_MD5[Buffer];
Buffer[0]     := Buffer[0] xor cpuid;
Buffer[1]     := Buffer[1] xor cpuid;
Buffer[2]     := Buffer[2] xor cpuid;
Buffer[3]     := Buffer[3] xor cpuid;
[Buffer] ?= [Serial_hex]

L'algo du keygen correspondant est donc:
[Buffer]      := modified_MD5[Buffer];
[Buffer + 0]  := [Buffer + 0] xor cpuid;
[Buffer + 1]  := [Buffer + 1] xor cpuid;
[Buffer + 2]  := [Buffer + 2] xor cpuid;
[Buffer + 3]  := [Buffer + 3] xor cpuid;
Buffer[0]     := LinearMat(Buffer[0],Matt[0]);
Buffer[1]     := LinearMat(Buffer[1],Matt[1]);
Buffer[2]     := LinearMat(Buffer[2],Matt[2]);
Buffer[3]     := LinearMat(Buffer[3],Matt[3]);
squareGenerateRoundKeys (keyword,  roundKeys_e, roundKeys_d);
Serial_hex    := squareDecrypt (Buffer,  roundKeys_d);
 

La partie MD5 et cpuid ne pose pas vraiment de probleme...


III.1 - LinearMat

Un petit detour par LinearMat:

_text:004032A7 LinearMat       proc near
_text:004032A7 
_text:004032A7 lpMat           = dword ptr  10h
_text:004032A7 lpDD            = dword ptr  14h
_text:004032A7 
_text:004032A7                 push    ecx
_text:004032A8                 push    esi
_text:004032A9                 push    ebp
_text:004032AA                 mov     esi, [esp+lpMat]
_text:004032AE                 mov     eax, [esp+lpDD]
_text:004032B2                 or      ebp, 0FFFFFFFFh
_text:004032B5                 mov     ecx, 20h
_text:004032BA                 xor     edx, edx
_text:004032BC                 test    eax, eax
_text:004032BE                 jnz     short ForEachBit
_text:004032C0                 xor     eax, eax
_text:004032C2                 jmp     short end_LinearMat
_text:004032C4 ; ---------------------------------------------------------------------------
_text:004032C4 
_text:004032C4 ForEachBit: 
_text:004032C4                 inc     ebp
_text:004032C5                 test    eax, 1
_text:004032CA                 jz      short DontAdd
_text:004032CC                 xor     edx, [esi+ebp*4]
_text:004032CF 
_text:004032CF DontAdd: 
_text:004032CF                 shr     eax, 1
_text:004032D1                 loop    ForEachBit
_text:004032D3                 xchg    eax, edx
_text:004032D4 
_text:004032D4 end_LinearMat: 
_text:004032D4                 pop     ebp
_text:004032D5                 pop     esi
_text:004032D6                 pop     ecx
_text:004032D7                 retn
_text:004032D7 LinearMat       endp
 

Il y a plusieurs facon d'interpreter cette partie du code... Je pense que la plus simple est de la considerer comme une application lineaire de GF(2^32) dans GF(2^32) ( le xor est alors consideré comme un + ) :

LinearMat(DD) = LinearMat(DD and 2^0) + ... + LinearMat(DD and 2^31) = DD'

...et le tableau  [lpMat] = Mat comme la matrice de l'endomorphisme... donc pour pouvoir passer de DD' à DD il suffit de trouver la matrice Mat-1 telle que:

 Mat * Mat-1 = I32

On obtiendra alors:  LinearMat-1(DD') = DD

Comment trouver Mat-1

En construisant la matrice T = ( I32 | Mat ) et en lui appliquant l'agorithme de Gauss-Jourdan ( tres simple dans Z/2Z ;p)
on obtient T = ( Mat-1 |  I32 ).... on a plus qu'a recuperer Mat-1 et le tour est joué...



 
 

III.2- SquareDecrypt

Pour cette partie il suffit de  recuperer roundKeys_d et d'utiliser "squareDecrypt"... en l'implementant ? :)

En observant un peu le code on s'apercoit que certaines parties ne sont jamais executées (aucun call/jmp permettant d'y acceder )

_text:004022BA                 push    ebp
_text:004022BB                 mov     ebp, esp
_text:004022BD                 sub     esp, 10h
_text:004022C0                 push    ebx
_text:004022C1                 push    esi
_text:004022C2                 push    edi
_text:004022C3                 mov     edi, [ebp+8]
_text:004022C6                 mov     esi, [ebp+0Ch]
_text:004022C9                 mov     esi, ds:0[esi]
_text:004022D0                 xor     ds:0[edi], esi
_text:004022D7                 mov     edi, [ebp+8]
_text:004022DA                 add     edi, 4
[...................................................]
_text:00402E7E                 movzx   ebx, Sd[ebx]
_text:00402E86                 shl     ebx, 10h
_text:00402E89                 xor     esi, ebx
_text:00402E8B                 mov     ebx, [ebp-4]
_text:00402E8E                 shr     ebx, 18h
_text:00402E91                 and     ebx, 0FFh
_text:00402E97                 movzx   ebx, Sd[ebx]
_text:00402E9F                 shl     ebx, 18h
_text:00402EA2                 xor     esi, ebx
_text:00402EA4                 mov     ebx, [ebp+0Ch]
_text:00402EA7                 xor     esi, [ebx+8Ch]
_text:00402EAD                 mov     [edi+0Ch], esi
_text:00402EB0                 pop     edi
_text:00402EB1                 pop     esi
_text:00402EB2                 pop     ebx
_text:00402EB3                 leave
_text:00402EB4                 retn
 

 Plus etrange encore, cette partie du code fait reference a 5 tableaux dont les valeurs correspondent a celles normalement utilisées par la procedure squareDecrypt... et effectivement cette portion du code est la procedure squareDecrypt :p


[ END ]


 Il suffit de tout rassembler et on obtient un joli keygen... au detail pres que le CrackMe contient un bug et qu'il faut donc le relancer entre chaque test de serial... ;)
 

Name   Amenesia
Serial D929B716E5C044B00D6714E25549D914

Si vous trouvez une erreur ou avez une question : tchernozium@yahoo.fr