The Cracking of Keeper

by

CASIMIR

Other Essays by Casimir
  • Cracking of Crypt-o-Text v1.21 & v1.24
  • Correspondence From Casimir On Reversing Turbo Encrypto
  • Cracking of Encrypt-It For Windows
  • Cracking of WinXFiles
  • The Cracking of File Locker
  • The Cracking of Gregory Braun's Crypto v3.5
  • The Cracking of SecurityPlus!
  • The Cracking of MasterKey v1.02/1.05
  • Salut Joe

    I found another proprietary crap on my way. This one was just too easy to break in, so i just give a simplified solution, with source code, as usual {;-) Maybe one day i should create my own page, and stop invading yours!!! But it's a good thing to have all the stuff gathered in one location.

    I received Randy's book last week, it's great!

    Note:

    The book Caz refers to is the ICSA Guide to Cryptography, by Randy Nichols. In chapter 21, pp. 617-624 Nichols discusses "Reverse Engineering to Snag the Password," which includes part of Casimir's Essay, "CRACKING OF ENCRYPT-IT FOR WINDOWS." --JP

    How are you doing with the paper for Bruce?

    Note:

    A while ago, Bruce Schneier asked me to summarize Caz's reversal and cracking of WinXFiles for his "Cryptogram." I summarized it, but I am not sure he will use it -- it is pretty technical...--JP

    Looks like Bruce had Mike Stay, a cryptographer at AccessData, write a cryptographic summary of WinXFiles to use in lieu of my somewhat esoteric reversal-assembly essay for Counterpane's February "CRYPTO-GRAM." You should subscribe to it at: Counterpane Systems. It's free and informative. The February issue is primarily on snake oil. --JP

    A la prochaine

    Caz


    YAC by

                             
          +++++                  +++          +       +
         +            +++       +   +   +++   ++     ++         ++++ 
        +            +   +       +       +    + +   + +   +++   +   +
        +          +++++++++      +      +    +  +++  +    +    +   +
         +         +       +       +     +    +   +   +    +    ++++   
          +++++   +         +   +   +   +++   +   +   +    +    +  +
                                 +++                      +++   +   +
    
    
    defenceless target: Keeper v3.0 by VictoryServices

    location: PlanetKeeper

    I decided cracking this program after reading their hilarious help file:

    Why should I use Keeper?

    There once was a man at the Zoo that had so many animals he did not know what to do. He would get them them, he would lose them and he would eventually find them. He called his friend the Keeper and ask him what to do.

    Keeper said to him, "Let me watch over them and I will tell you what to do". Well, Keeper watched and he thought and he watched and he thought, and after a while he learned what he should do;

    "Each type of animal that lives in the Zoo should have its own place - we will make them houses so we know where they are too." he explained to the owner. From that day on, the Zoo owner never had problems with lost or missing animals - and the Keeper was happy that he could be of service.

    Like the owner of the Zoo, we have a lot of animals to keep track of - except our animals come in the form of digital information. There are many different digital animals out there - the Password, the Serial Number and the Registration number just to name a few of them. Although they may be digital, they are like the zoo animals - they can not be found when you really need them.

    Wouldn't it be great if there was a digital Zoo Keeper to keep track of these digital animals? Well, now there is and it goes by the name of Keeper.

    Taken from the Keeper Help file - copyright (c) Geoffrey Steffens.

    Well, i would not give a penny for their crypto-software, but i'd really like to know what drug(s) they're using {:-)

    Anyway, this app doesn't deserve a fully-documented cracking procedure with tons of asm listing. If for some obscure reason you're interested in such details, e-mail Casimir me, i'll tell you the whole story.

    Launch Keeper, enter pwd: CASIMIR. Keeper creates a file called keeper.dat in same directory. Now exit Keeper without storing anything inside, and edit keeper.dat. The 9 first characters are:

    			8hMAn=LAe
    
    Then we have many 00 and 8hMAn=LAe again. Nothing else. We can assume that 8hMAn=LAe is a sort of fingerprint of correct password.

    Here is the important stuff:

    Keeper uses a table to perform encryption and decryption of both password and data.

    Table : 97 ASCII characters, all different

    
    		8x3p5Beabcdfghij	
    		klmnoqrstuvwyzAC
    		DEFGHIJKLMNOPQRS
    		TUVWXYZ 1246790-     //position 56: SPACE
    		.#/\!@$<>&*()[]{
    		}';:,?=+~`^|%_       //position 95: Carriage Return
                    "                    //position 96: Line Feed
    
    We start at position 1 with 8 and we end up at position 97 with ": Table[01]=8 Table[97]="

    To check if input=pwd, Keeper also uses a check string which is 9 characters long: 
    Check string:	ClearText 
    
    If input equal     good pwd, then Check string equal:     ClearText
    If input not equal good pwd, then Check string not equal: ClearText
    
    
         Check string  ->  C   l   e   a   r   T   e   x   t
    Position in Table  ->  32  18  07  08  23  49  07  02  25
    

    How check works

    Suppose you enter 123456 instead of CASIMIR. How can Keeper tell this is not the good pwd?

    1. Keeper looks up respective positions in Table for Fingerprint:

          Fingerprint  ->  8   h   M   A   n   =   L   A   e
    Position in Table  ->  01  14  42  31  20  87  41  31  07
    

    2. Keeper looks up respective positions in Table for Input:

                Input  ->  1   2   3   4   5   6
    Position in Table  ->  57  58  03  59  05  60
    
    But Keeper needs 9 characters to perform check. So if Input is < 9 characters, it starts reading it again from the beginning:
    
     Input (extended)  ->  1   2   3   4   5   6   1   2   3
    Position in Table  ->  57  58  03  59  05  60  57  58  03
    

    3. Keeper subtracts Fingerprint'positions from extended Input'positions::

    New position in    ->  57-01 58-14 03-42 59-31 05-20 60-87 57-41 58-31 03-07
        Table          ->  56    44    -39   28    -15   -27   16    27    -04       
    

    4. Two cases::

    	* position <  0 : position = position + 1 + 97
    	* position >= 0 : position = position + 1 
    
    So we obtain:
    Position in Table  ->  57  45  59  29  83  71  17  28  94         
    

    5. Keeper reads Table at specific positions to obtain Check string::

         Check string  ->  1   P   4   y   ;   $   k   w   _
    Position in Table  ->  57  45  59  29  83  71  17  28  94   
    

    6. Keeper compares Check string::

    1P4y;$kw_ to: ClearText. They differ, so Input is wrong. With Input: CASIMIR we would obtain Check string: ClearText, and Keeper would let us in.

    How we break it

    Given a Fingerprint and knowing that Check string is: ClearText, we can easily guess what good input is.

    Fingerprint is made of 9 known characters:   f1 f2 f3 f4 f5 f6 f7 f8 f9
        Input is made of 9 unknown characters:   i1 i2 i3 i4 i5 i6 i7 i8 i9
    
    We look for i1,...,i9.
    
          Fingerprint  ->  f1    f2    f3    f4    f5    f6    f7    f8    f9
    Position in Table  ->  pf1   pf2   pf3   pf4   pf5   pf6   pf7   pf8   pf9
    
                Input  ->  i1    i2    i3    i4    i5    i6    i7    i8    i9
    Position in Table  ->  pi1   pi2   pi3   pi4   pi5   pi6   pi7   pi8   pi9
    
    New position in Table 
    -> pi1-pf1 pi2-pf2 pi3-pf3 pi4-pf4 pi5-pf5 pi6-pf6 pi7-pf7 pi8-pf8 pi9-pf9
    
    
    To obtain ClearText, we must have:  positions = good positions (respectively 
    32, 18, ... , 25)
    
    	pi1 - pf1 = 32 -> pi1 = 32 + pf1 
    	pi2 - pf2 = 18 -> pi2 = 18 + pf2
    	pi3 - pf3 = 07 -> pi3 = 07 + pf3
    	pi4 - pf4 = 08 -> pi4 = 08 + pf4 
    	pi5 - pf5 = 23 -> pi5 = 23 + pf5
    	pi6 - pf6 = 49 -> pi6 = 49 + pf6
    	pi7 - pf7 = 07 -> pi7 = 07 + pf7 
    	pi8 - pf8 = 02 -> pi8 = 02 + pf8
    	pi9 - pf9 = 25 -> pi9 = 25 + pf9
    
    
    And we have 2 cases again:
    
    	* position <= 98 : position = position - 1
    	* position  > 98 : position = position - 1 - 97 
     
    For instance, let's recover pwd whose Fingerprint is: 8hMAn=LAe  
    
          Fingerprint  ->  8   h   M   A   n   =   L   A   e
    Position in Table  ->  01  14  42  31  20  87  41  31  07
    
       pi1 = 32+pf1 = 32+01 = 33<=98  -> i1 = Table[33-1]     = Table[32] = C 
       pi2 = 18+pf2 = 18+14 = 32<=98  -> i2 = Table[32-1]     = Table[31] = A
       pi3 = 07+pf3 = 07+42 = 49<=98  -> i3 = Table[49-1]     = Table[48] = S
       pi4 = 08+pf4 = 08+31 = 39<=98  -> i4 = Table[39-1]     = Table[38] = I
       pi5 = 23+pf5 = 23+20 = 43<=98  -> i5 = Table[43-1]     = Table[42] = M  
       pi6 = 49+pf6 = 49+87 = 136>98  -> i6 = Table[136-1-97] = Table[38] = I 
       pi7 = 07+pf7 = 07+41 = 48<=98  -> i7 = Table[48-1]     = Table[47] = R 
       pi8 = 02+pf8 = 02+31 = 33<=98  -> i8 = Table[33-1]     = Table[32] = C  
       pi9 = 25+pf9 = 25+07 = 32<=98  -> i9 = Table[32-1]     = Table[31] = A
    
    
    Because of wrap, we have 2 pwds possible: CASIMIR and CASIMIRCA. Keeper will stupidly accept both of them, but only one will correctly decrypt data. So if data looks weird, try the other pwd found! (there will never be more than TWO possible pwds)

    Conclusions

    Obviously, Keeper can't check pwd longer than 9 characters. Anyway, it doesn't matter 'cause Keeper keeps only the first 9 characters from your pwd !!! Yes, the encryption key is only 9 bytes long... quite short!

    You probably noticed there is no extended character in Table: in fact Keeper just can't handle extended characters set.

    Well, you may understand now why i recommend NOT using this big crap!

    And now, TARATATA!!! C source-code for CRKEEPER.EXE -----------------------------------------------------------------------------

    #include <stdio.h>
    #include <io.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <dir.h>
    #include <string.h>
    #include <dos.h>
    #include <alloc.h>
    #include <malloc.h>
    #include <stdlib.h>
    #include <time.h>
    #include <conio.h>
    #include <math.h>
    
    #define TRUE 1
    #define FALSE 0
    
    const int table_size=97;
    const int check_size=9;
    
    //table used by Keeper v3.0
    static int Table[table_size]={0x38,0x78,0x33,0x70,   0x35,0x42,0x65,0x61,
                                  0x62,0x63,0x64,0x66,   0x67,0x68,0x69,0x6A,
                                  0x6B,0x6C,0x6D,0x6E,   0x6F,0x71,0x72,0x73,
                                  0x74,0x75,0x76,0x77,   0x79,0x7A,0x41,0x43,
                                  0x44,0x45,0x46,0x47,   0x48,0x49,0x4A,0x4B,
                                  0x4C,0x4D,0x4E,0x4F,   0x50,0x51,0x52,0x53,
                                  0x54,0x55,0x56,0x57,   0x58,0x59,0x5A,0x20,
                                  0x31,0x32,0x34,0x36,   0x37,0x39,0x30,0x2D,
                                  0x2E,0x23,0x2F,0x5C,   0x21,0x40,0x24,0x3C,
                                  0x3E,0x26,0x2A,0x28,   0x29,0x5B,0x5D,0x7B,
                                  0x7D,0x27,0x3B,0x3A,   0x2C,0x3F,0x3D,0x2B,
                                  0x7E,0x60,0x5E,0x7C,   0x25,0x5F,0x0D,0x0A,
                                  0x22};
    
    //check_string used by Keeper v3.0: ClearText
    static int Good_pos[check_size]={32,18,7,8,23,49,7,2,25};
    
    /****************************** PROTOTYPES *********************************/
    void Fill_pattern(int , int ** , int *);
    void Print_pwd(int , int *);
    int Multiple(int , int);
    int Redundant(int , int , int * , int *);
    void Hi_folks(void);
    void Calc_finger_pos(int , unsigned char * ,int **);
    void Calc_pwd_pos(int , int * , int **);
    void Wait_key(void);
    void Read_fingerprint(unsigned char ** , int);
    
    /********************************* MAIN ************************************/
    main()
    {
    const int finger_size=9;
    int pat_size,redund,i;
    int *Pattern,*Password,*Finger_pos,*Pwd_pos;
    unsigned char *Fingerprint;
    
    Pattern=(int *)malloc(sizeof(int)*finger_size);
    Fingerprint=(unsigned char *)malloc(sizeof(char)*finger_size);
    
    Hi_folks();
    
    //read Fingerprint from file: keeper.dat
    Read_fingerprint(&Fingerprint,finger_size);
    
    //calculate positions in Table for Fingerprint
    Calc_finger_pos(finger_size,Fingerprint,&Finger_pos);
    
    //calculate positions in Table for Password
    Calc_pwd_pos(finger_size,Finger_pos,&Pwd_pos);
    
    //fill Password
    for(i=0;i<finger_size;i++)
       {
       Password[i]=Table[Pwd_pos[i]-1];
       }
    
    //check if we have a redundand pwd (ex: 123123123)
    redund=FALSE;
    for(pat_size=1;pat_size<finger_size;pat_size++)
       {
       Fill_pattern(pat_size,&Pattern,Password);
       if(Redundant(pat_size,finger_size,Pattern,Password))
          {
          redund=TRUE;
          Print_pwd(pat_size,Pattern);
          if(!Multiple(pat_size,finger_size)) {Print_pwd(finger_size,Password);}
          break;
          }
       }
    
    if(redund==FALSE) {Print_pwd(finger_size,Password);}
    
    Wait_key();
    exit(0);
    }
    
    
    /****************************** FUNCTIONS **********************************/
    
    /***************************************************************************/
    /*          we have: Pwd_pos[i] = Good_pos[i] +  Finger_pos[i]             */
    void Calc_pwd_pos(int finger_size,int *Finger_pos,int **Pwd_pos)
    {
    int i,j,pos;
    
    for(i=0;i<finger_size;i++)
       {
       pos=Good_pos[i]+Finger_pos[i];
       if(pos<=(table_size+1)) {(*Pwd_pos)[i]=pos-1;}
       else {(*Pwd_pos)[i]=pos-table_size-1;}
       }
    }
    
    /***************************************************************************/
    /*   find positions in Table for Fingerprint'characters                    */
    /*   ex: "a" = 0x61 -> position = 8                                        */
    void Calc_finger_pos(int finger_size,unsigned char *Fingerprint,
    		     int **Finger_pos)
    {
    int i,j;
    
    for(i=0;i<finger_size;i++)
       {
       for(j=0;j<table_size;j++)
          {
          if(Fingerprint[i]==Table[j])
    	 {
    	 (*Finger_pos)[i]=j+1;
    	 break;
    	 }
          }
       }
    }
    
    /***************************************************************************/
    /*   returns 1 if pattern is redundant (even if only partially), 0 if not  */
    /*     for instance with pattern=ABC:                                      */
    /*     ABCABCABC  -> redundant                                             */
    /*     ABCABCABCA -> redundant                                             */
    /*     ABCDABCABC -> not redundant                                         */
    int Redundant(int pat_size,int pwd_size,int *Pattern,int *Password)
    {
    int i;
    
    for(i=pat_size;i<pwd_size;i++)
       {
       if(Password[i]!=Pattern[fmod(i,pat_size)]) {return(0);}
       }
    return(1);
    }
    
    /***************************************************************************/
    /* Read the first pat_size characters from Block and store them in Pattern */
    void Fill_pattern(int pat_size,int **Pattern,int *Password)
    {
    int i;
    
    for(i=0;i<pat_size;i++) {(*Pattern)[i]=Password[i];}
    }
    
    /***************************************************************************/
    /*                        display password along with ASCII values         */
    void Print_pwd(int pwd_len,int *Pwd)
    {
    int i;
    
    printf("\n\n ASCII seq: ");
    for(i=0;i<pwd_len;i++)
       {
       printf("[%d]",Pwd[i]);
       if((i+1)%10==0) {printf("\n            ");}
       }
    
    printf("\n\n  PASSWORD: >>>");
    for(i=0;i<pwd_len;i++)
       {
       printf("%c",Pwd[i]);
       }
    printf("<<< (%d characters)\n\n",pwd_len);
    printf("(don't type >>> and <<<)\n");
    }
    
    /***************************************************************************/
    /*     return 1 if b = n*a   with n integer                                */
    /*     return 0 otherwise                                                  */
    int Multiple(int a,int b)
    {
    int remain;
    
    remain=fmod(b,a);
    if(remain==0) {return(1);}
    return(0);
    }
    
    /***************************************************************************/
    void Hi_folks(void)
    {
    printf("\n\nYet Another Password Cracker by CASIMIR {;-)");
    printf("\n-> Target: Keeper v3.0 by VictoryServices\n");
    }
    
    /***************************************************************************/
    /*   Called only once, reads first finger_size characters from keeper.dat  */
    /*   This string is the Fingerprint we rely on to recover password         */
    void Read_fingerprint(unsigned char **Fingerprint,int finger_size)
    {
    int fn;
    // try to open file keeper.dat
    fn=open("keeper.dat",O_BINARY|O_RDONLY);
    switch(fn)
       {
       case -1:printf("\nFILE keeper.dat NOT FOUND!");
       printf("\nkeeper.dat MUST be in SAME directory as Cracker!\n");
       Wait_key(); exit(0);
       }
    
    //(*Buffer)=(unsigned char *)malloc(sizeof(char)*buf_size);
    // read first check_size characters of file
    read(fn,*Fingerprint,finger_size);
    close(fn);
    }
    
    /***************************************************************************/
    /*                   wait for key pressed                                  */
    void Wait_key()
    {
    printf("\n\n");
    printf("              XXXXXXXXXXXXXXXXXXXXXXX\n");
    printf("              X hit any key to eXit X\n");
    printf("              XXXXXXXXXXXXXXXXXXXXXXX\n");
    getch(); {/* wait until key pressed */}      //kbhit
    }
    
    
    
    If you need more info, contact me at:  Casimir

    Here is Crkeeper, which includes a DOS executable a sample Keeper .DAT file.

    Converted to hypertext by Joe Peschel Feb. 14, 1999.