The PicaView32 KeyGenerator
(creating KeyGenerators instead of lame patches)
student 
Not Assigned 
05 June 1998
by +mISu 
Courtesy of Fravia's page of reverse engineering
slightly edited
by fravia+
fra_00xx 
980605 
+mISu 
0100 
NA 
PC
Well, +mISu writes that writing a small C/C++ program is better than giving lamer patches that don't find the program's dir, and patch in other part that it should, etc, etc. and this is surely true. Yet I don't agree about his statement "keymaker is the future of cracking, patches are dead by now", because in fact, there are TWO sort of patches: patches that are made by lamers, for lamers, in order to automatically register a shareware program -why? Either you know how to crack it, or you learn how to crack it, why should people that don't care to understand nothing at all -with few exceptions (third world universities, Micro$oft targets, etc.)- enjoy software for free?- anyway: about this kind of patches I agree with +mISu! Yet there is a SECOND type of patches: patches that YOU make for YOURSELF in order to check a protection scheme, or ameliorate a target, or experiment a bit with alien code you don't happen to have the source code of... and THESE patches, far from being lame, are often EXTREMELY interesting, from our point of view... in fact the first kind of really lame patches can be coupled with the "lame keymaker little programs": programs that have been made, purposely, with the sole intent of providing absolute zombies with free warez (why? Either they know how to crack them, or they should learn how to crack them... why should people that don't understand nothing at all -with few exceptions (third world universities, Micro$oft targets, etc.)- enjoy shareware programs for free?). So it's the purpose of your patch or of your keygen, not the technique per se, IMHO, that decides if it's a lame product or a correct approach.
This said, here you have an important essay whose purpose is "simply" to teach KNOWLEDGE, written by +mISu (a good reverser who has been contacted by +ORC himself) for all intermediate C++ buffs... Enjoy!
There is a crack, a crack in everything That's how the light gets in
Rating
( )Beginner (x)Intermediate ( )Advanced ( )Expert 
Creating keymakers is the future of cracking, patches are dead by now. 
The PicaView32 KeyGenerator
(creating KeyGenerators instead of lame patches)
Written by +mISu
 
 
Introduction 
PicaView is a fine picture viewer, plugin for Explorer, with a serial number and 30-days protection. Here I will crack the serial number protection, but I won't show you how to make a patch, coz if you can read here this essay you surely could crack it yourself very well.
Also I will show you how to make a keymaker – that's the hard cracking part. Reversing the whole encoding algorithm and building a keymaker leaving unmodified the target program.
 
Tools required 
For this cracking session you'll need the following:
PicaView32 1.21:       Download it from ACDSystems site: http://www.acdsystems.com/
Soft-Ice for Window$95:    You'll find it almost everywhere on the web (but on this site)
W32Dasm 8.9 regged:    You'll find it almost everywhere on the web (but on this site)
or IDA Pro 3.75:    You'll find it almost everywhere on the web (but on this site)
C/C++ compiler:    You must have one around there, The complete Borland 4,5 suite
(with BRW!) has been published by a lot of mags lately
Brains:    Don't know where you can get this
Pinpointing the code 
 Detecting the location where the whole reading/encoding/comparing is the most important thing in cracking, if you pinpointed the code, you can consider your cracking is done. In our case, the pinpointing is made by following our registration window's messages.
Press the right mouse button on a bitmap in explorer and select from the menu Register… Fire Winice and do a 'hwnd explorer'. You'll see something like this:

:hwnd explorer
Window Handle   hQueue  SZ  QOwner    Class  Name           Window Procedure
 0538(1)        0F77    32  EXPLORER  #32770 (Dialog)       1777:00004757  ;this is our window
  053C(2)       0F77    32  EXPLORER  Button                1777:0000102E
  0540(2)       0F77    32  EXPLORER  Static                1777:000052FA
  0544(2)       0F77    32  EXPLORER  Edit                  1777:00000BF4  ;this is an edit field

Okay, let's trap messages referring to text reading from this window: 'bmsg 544 wm_gettext'. Enter a name and a registration code and press OK. Boom! we land in some kernel/gdi functions, but after a few F12's (p ret) we find the following piece of code:

:1000445A 8B3594F70710            mov esi, dword ptr GetDlgItemTextA
:10004460 8D4C241C                lea ecx, dword ptr [esp+1C]   ;push the address for username
:10004464 6A1F                    push 0000001F
:10004466 51                      push ecx
:10004467 68C8000000              push 000000C8
:1000446C 57                      push edi
:1000446D FFD6                    call esi   ;call GetDlgItemTextA
:1000446F 8D542460                lea edx, dword ptr [esp+60]   ;push the address for code
:10004473 68C9000000              push 000000C9
:10004478 52                      push edx
:10004479 68C9000000              push 000000C9
:1000447E 57                      push edi
:1000447F FFD6                    call esi   ;call GetDlgItemTextA

This is a classic routine for reading username and code. The username is at [esp+1c] and the code at [esp+60]. Now we'll se how are manipulated these two string and get compared.
 
The username string filtering
Usually the username string is filtered a little, i.e. the characters that are not letters or spaces are removed. PicaView is filtering the username string as follows:

:10004491 0FBE06                  movsx eax, byte ptr [esi] ;at [esi] is the username string
:10004494 50                      push eax
:10004495 E8F6AA0400              call 1004EF90   ;convert the character in eax to uppercase
:1000449A 83C404                  add esp, 00000004
:1000449D 3C41                    cmp al, 41  ;'A'
:1000449F 7C04                    jl 100044A5
:100044A1 3C5A                    cmp al, 5A  ;'Z'
:100044A3 7E04                    jle 100044A9
:100044A5 3C20                    cmp al, 20  ;' '
:100044A7 7505                    jne 100044AE
:100044A9 8A0E                    mov cl, byte ptr [esi]   ;esi points to unfiltered string char
:100044AB 880B                    mov byte ptr [ebx], cl   ;ebx points to filtered string char
:100044AD 43                      inc ebx
:100044AE 8A4601                  mov al, byte ptr [esi+01]
:100044B1 46                      inc esi
:100044B2 84C0                    test al, al
:100044B4 75DB                    jne 10004491

For example, if the string is '+mISu '98 +mISu', the filtered string will be 'mISu  mISu'.
 
Good buyer/bad cracker
The verification routine is very simple and easy to patch: the program pushes the adresses of the two strings, calls a verification routine which returns in eax: 1 if  correct username/regcode, 0 if bad username/regcode. Than verifies the value in eax and separates the good buyer/bad cracker:

:10003B70 56                      push esi
:10003B71 8B742408                mov esi, dword ptr [esp+08]   ;username string address
:10003B75 56                      push esi
:10003B76 FF158CF50710            Call lstrlenA
:10003B7C 83F805                  cmp eax, 00000005   ;verifies if the username string
:10003B7F 7D04                    jge 10003B85        ;has at least 5 characters
:10003B81 33C0                    xor eax, eax
:10003B83 5E                      pop esi
:10003B84 C3                      ret
:10003B85 682D224900              push 0049222D     ;an encoding key
:10003B8A 56                      push esi        ;username string address
:10003B8B E820500000              call 10008BB0     ;call encryption routine
:10003B90 83C408                  add esp, 00000008 ;in eax is the key
:10003B93 8BF0                    mov esi, eax
:10003B95 8B44240C                mov eax, dword ptr [esp+0C]   ;regcode string address
:10003B99 50                      push eax
:10003B9A E801B30400              call 1004EEA0     ;code transforming routine
:10003B9F 83C404                  add esp, 00000004
:10003BA2 33C9                    xor ecx, ecx
:10003BA4 3BF0                    cmp esi, eax      ;compare the two keys
:10003BA6 0F94C1                  sete cl
:10003BA9 8BC1                    mov eax, ecx      ;set in eax the result
:10003BAB 5E                      pop esi           ;1 if good buyer
:10003BAC C3                      ret               ;0 if bad cracker
 
Strings encryption
Usually the programs generate a key from the username, a key from the code, and compare the two keys. In order to make our keygenerator, we'll have to reverse the username encoding algorithm, obtaining the key from the username string, and then reverse the code encoding routine, obtaining the registration code from the username string key.
PicaView's code encoding routine transforms the code string that represents a decimal number in the 32-bit key which is actually the 32-bit hex value of the registration code. That means that the result of encrypting the username string is the registration code in decimal.
 
Username string encryption algorithm. The keygenerator
Now you really need brains, coz it's the hardest part of the cracking session. Put a CD with some rock music (I suggest Metallica or Guns'n'Roses) and sit with the dead listing of the routine in front of your eyes and think. Take the code piece by piece and build in the same time the C program.
The C program will have three functions:
int validate(char*)  filters the username string and returns the number of characters (without spaces)
long createkey(char*) creates the key using the filtered username string (with spaces)
 int main(void)  main function
We can build the validation function now:

int  validate(char* name)
{
    int i,j=0,v=0;

    for(i=0;i<strlen(name);i++)
    {
        name[i]=toupper(name[i]);
        name[j]=name[i];
        if(name[i]>='A'&&name[i]<='Z'||name[i]==' ')
        {
            if(name[i]!=' ') v++;
            j++;
        }
    }
    name[j]=0;

    return v;
}

The main function will read the name until it's valid and then display the key:

int main(int argc, char* argv[])
{
    long key;
    int  i;
    char name[100];

    cout << endl;
    cout << "============================" << endl;
    cout << "KeyMaker for PicaView32 1.21" << endl;
    cout << "----------------------------" << endl;
    cout << "Coded by +mISu, 05-29-1998  " << endl;
    cout << "============================" << endl;
    do
    {
        cout << "Name: ";
        gets(name);
        if(strlen(name)>30) name[30]=0;
    }
    while(validate(name)<5);
    key=createkey(name);
    cout << "Key:  " << key << endl;
    cout << "============================" << endl;

    return 0;
}

Okay, now let's get the first block of code from the encryption routine:

:10008BB0 81ECA8000000            sub esp, 000000A8
:10008BB6 53                      push ebx
:10008BB7 55                      push ebp
:10008BB8 8BAC24B4000000          mov ebp, dword ptr [esp+000000B4] ;username string address
:10008BBF 56                      push esi
:10008BC0 57                      push edi
:10008BC1 8BFD                    mov edi, ebp
:10008BC3 83C9FF                  or ecx, FFFFFFFF
:10008BC6 33C0                    xor eax, eax
:10008BC8 33F6                    xor esi, esi
:10008BCA F2                      repnz
:10008BCB AE                      scasb
:10008BCC F7D1                    not ecx
:10008BCE 49                      dec ecx      ;in ecx is now the username string length
:10008BCF 89742410                mov dword ptr [esp+10], esi
:10008BD3 6683F901                cmp cx, 0001
:10008BD7 0F82A0010000            jb 10008D7D
:10008BDD 6683F950                cmp cx, 0050
:10008BE1 0F8796010000            ja 10008D7D  ;verifies if it is between 1 and 80, if it isn't, exit
:10008BE7 39B424C0000000          cmp dword ptr [esp+000000C0], esi ;in esi is 0
:10008BEE 0F85C6000000            jne 10008CBA  ;in [esp+c0] is the encryption key

Comparing 0 with 0049222d it will always jump to 10008cba. Let's continue our reverse engineering to
1008cba:

:10008CBA 8BD9                    mov ebx, ecx ;in ebx is strlen(username)
:10008CBC 33FF                    xor edi, edi ;edi is the counter
:10008CBE 81E3FFFF0000            and ebx, 0000FFFF
:10008CC4 7E2D                    jle 10008CF3
:10008CC6 0FBE4C3500              movsx ecx, byte ptr [ebp+esi] ;gets char
:10008CCB 51                      push ecx
:10008CCC E8BF620400              call 1004EF90 ;uppercase eax
:10008CD1 0FAF8424C4000000        imul eax, dword ptr [esp+000000C4] ;multiplies with encryption key
:10008CD9 03C7                    add eax, edi ;adds counter to the result
:10008CDB 83C404                  add esp, 00000004
:10008CDE 25FFFF0000              and eax, 0000FFFF ;keeps only 16-bit value
:10008CE3 99                      cdq
:10008CE4 F7FB                    idiv ebx ;gets in edx the rest of the division of eax by strlen
:10008CE6 47                      inc edi ;next char
:10008CE7 6689547418              mov word ptr [esp+2*esi+18], dx ;saves the value
:10008CEC 0FBFF7                  movsx esi, di
:10008CEF 3BF3                    cmp esi, ebx
:10008CF1 7CD3                    jl 10008CC6

So, the program generates a table with some values between 0 and strlen-1 at [esp+18]. The table has strlen values. Let's do the same thing in C/C++:

    length=strlen(name);
    key=0x0049222d;
    for(i=0;i<length;i++)
    {
        var=(name[i]*key+i)&0xffff;
        table[i]=(int)(var%length);
    }

The table is stored in int table[100]. Let's get the next block of code:

:10008CF3 33FF                    xor edi, edi
:10008CF5 3BDF                    cmp ebx, edi
:10008CF7 897C2414                mov dword ptr [esp+14], edi
:10008CFB 7E43                    jle 10008D40
:10008CFD 33F6                    xor esi, esi
:10008CFF 668B747C18              mov si, word ptr [esp+2*edi+18] ;gets number from the table
:10008D04 0FBE543500              movsx edx, byte ptr [ebp+esi]   ;gets the sith character from string
:10008D09 52                      push edx
:10008D0A E881620400              call 1004EF90 ;uppercase eax
:10008D0F 8BD0                    mov edx, eax
:10008D11 8BCE                    mov ecx, esi
:10008D13 D3E2                    shl edx, cl  ;shifts left edx by table value
:10008D15 83C404                  add esp, 00000004
:10008D18 47                      inc edi
:10008D19 8B742410                mov esi, dword ptr [esp+10]
:10008D1D 0FAFD7                  imul edx, edi ;multiplies edx with (counter+1)
:10008D20 0FAF9424C0000000        imul edx, dword ptr [esp+000000C0] ;multiplies edx with enc. key
:10008D28 0BD0                    or edx, eax ;ors edx with char
:10008D2A 8B442414                mov eax, dword ptr [esp+14] ;updates the counter
:10008D2E 03F2                    add esi, edx ;ads to the key edx
:10008D30 40                      inc eax
:10008D31 0FBFF8                  movsx edi, ax
:10008D34 3BFB                    cmp edi, ebx
:10008D36 89742410                mov dword ptr [esp+10], esi ;saves the key until now
:10008D3A 89442414                mov dword ptr [esp+14], eax ;saves the counter
:10008D3E 7CBD                    jl 10008CFD

Well, I think this is the part that does everything. Let's do the same operations in C/C++:

keynow=0;
    for(i=0;i<length;i++)
    {
        t=table[i];
        var=name[t];
        var<<=t;
        var*=(i+1)*key;
        var|=name[t];
        keynow+=var;
    }

Let's see the last part:

:10008D40 8B442410                mov eax, dword ptr [esp+10]
:10008D44 85C0                    test eax, eax
:10008D46 7D06                    jge 10008D4E
:10008D48 F7D8                    neg eax ;eax=modulo eax
:10008D4A 89442410                mov dword ptr [esp+10], eax
:10008D4E 8B442410                mov eax, dword ptr [esp+10]
:10008D52 85C0                    test eax, eax
:10008D54 7508                    jne 10008D5E
:10008D56 C7442410DC6F2400        mov [esp+10], 00246FDC ;if the key is zero, gets this value
:10008D5E 8B442410                mov eax, dword ptr [esp+10]
:10008D62 B900CA9A3B              mov ecx, 3B9ACA00
:10008D67 99                      cdq
:10008D68 F7F9                    idiv ecx ;gets only the last ten digits
:10008D6A 89542410                mov dword ptr [esp+10], edx
:10008D6E 8B442410                mov eax, dword ptr [esp+10]
:10008D72 5F                      pop edi
:10008D73 5E                      pop esi
:10008D74 5D                      pop ebp
:10008D75 5B                      pop ebx
:10008D76 81C4A8000000            add esp, 000000A8
:10008D7C C3                      ret

Let's finish this:

if(keynow<0) keynow=-keynow;
    if(keynow==0) keynow=0x00246fdc;
    keynow%=1000000000;
 
KeyGenerator C/C++ source

 I'm tired, I'm working here for five hours long. Here is the whole C/C++ program:

#include <iostream.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

long createkey(char*);
int  validate(char*);

int main(int argc, char* argv[])
{
    long key;
    int  i;
    char name[100];

    cout << endl;
    cout << "============================" << endl;
    cout << "KeyMaker for PicaView32 1.21" << endl;
    cout << "----------------------------" << endl;
    cout << "Coded by +mISu, 05-30-1998  " << endl;
    cout << "============================" << endl;
    do
    {
        cout << "Name: ";
        gets(name);
        if(strlen(name)>30) name[30]=0;
    }
    while(validate(name)<5);
    key=createkey(name);
    cout << "Key:  " << key << endl;
    cout << "============================" << endl;

    return 0;
}

int  validate(char* name)
{
    int i,j=0,v=0;

    for(i=0;i<strlen(name);i++)
    {
        name[i]=toupper(name[i]);
        name[j]=name[i];
        if(name[i]>='A'&&name[i]<='Z'||name[i]==' ')
        {
            if(name[i]!=' ') v++;
            j++;
        }
    }
    name[j]=0;

    return v;
}

long createkey(char* name)
{
    int  table[100], length, i, t;
    long var, key, keynow;

    length=strlen(name);
    key=0x0049222d;
    for(i=0;i<length;i++)
    {
        var=(name[i]*key+i)&0xffff;
        table[i]=(int)(var%length);
    }
    keynow=0;
    for(i=0;i<length;i++)
    {
        t=table[i];
        var=name[t];
        var<<=t;
        var*=(i+1)*key;
        var|=name[t];
        keynow+=var;
    }
    if(keynow<0) keynow=-keynow;
    if(keynow==0) keynow=0x00246fdc;
    keynow%=1000000000;

    return keynow;
}
 
Final Notes 

Hope you are now convinced that the key generators are the future of cracking, writing a small C/C++ program is better than giving lamer patches that don't find the program's dir, and patch in other part that it should, etc, etc.
For feedback questions, you can contact me at  plusmisu@hotmail.com
 
Ob Duh 

I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell.

You are deep inside fravia's page of reverse engineering, choose your way out:


redhomepage redlinks red anonymity red+ORC redstudents' essays redacademy database
redtools redcounter measures redcocktails redantismut redbots wars redsearch_forms redmail_fravia
redIs reverse engineering legal?