[ SpecialDelivery CrackMe by ged_ ]
by Amenesia
 

[ INPUT ]

I.1- Name :

 First i seek for  GetDlgItemTextA  :

_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
 

So the length of the  "Name" has to be > 0... ;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

 ... and length(Serial) = 32...


_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
 

 Then it checks if the serial is composed only of  [0-9A-F]...



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

 ...and it converts ascii to hexadecimal value  ( 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

  ...and checks if the serial is valid....



 
 

[ ANALYSE ]



II.1- Square

The procedure called (00402F49) uses 5 lookup-tables which  seems to be the ones used by : Square

So we can guess the "meaning" of these 2 call:
 

_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

So now we have :

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


II.2- Magic

Then a "transformation" it is performed on each Serial_hex dword...

_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

In other words :

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"

Using PEiD we can guess that it is  MD5 but it seems that it is modified ( initialisation, 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

Now:

[Buffer]      := modified_MD5[Buffer]; ( 4 dword )



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

It performs a "xor cpuid" on each dd of the  [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

At last it checks if  [Buffer] is equal to [Serial_hex]



 
 

[ KEYGEN ]


To summarize:
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]

So the algo of the keygen is:
[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);
 

The first part ( MD5 and "xor cpuid" ) isn't difficult...


III.1 - 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

We can take this part of the code as an endomorphism on GF(2^32)  ( so xor is regarded as + ) :

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

...and the table  [lpMat] = Mat as the matrix of the endomorphism, so to be able to compute DD thanks to DD ( ie  LinearMat-1(DD') = DD ) we have to find Mat-1 such as:

 Mat * Mat-1 = I32


How find Mat-1

Build the matrix T = ( I32 | Mat ) and then performs  Gauss-Jourdan algo ( very easy on Z/2Z ;p)
we will obtain T = ( Mat-1 |  I32 ).... 



 
 

III.2- SquareDecrypt

We can use squareGenerateRoundKeys to compute roundKeys_d but  where can we find "squareDecrypt" ?  Are we obliged to code "squareDecrypt" ? No... ;)

A part of the crackme seems to be useless ( never called )

_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

Moreover it seems that this procedure plays with 5 lookup table which are used in "squareDecrypt"... ;)  I don't know why "squareDecrypt" is here but can take advantage of this fact and uses the code in our keygen... ;)


[ END ]


 There is a bug in the CrackMe ( my keygen has the same ) so you have to restart the Crackme after each serial check...
 

Name   Amenesia
Serial D929B716E5C044B00D6714E25549D914

Despite my bad english feel free to contact me  : tchernozium@yahoo.fr