Cracking of Crypt-o-Text v1.21 & v1.24
(good cryptology cracking)

by CASIMIR


This essays comes courtesy of Fravia's page of reverse engineering and republished here with only slight modifications by Joe Peschel.
~
Well, this essay is VERY interesting... programs devised to hyde encrypted messages must have EXSPECIALLY STRONG encryption schemes, else they would not have any sense. Encryption schemes and protection schemes, as you will see, have MANY points in common. CASIMIR is a good cracker, and his cracking program (in "C") at the end of this essay is very interesting, because it can be -very well- used for other, related and unrelated, crackprobes that you, our dear reader, may want to develop in the future

Cracking of Crypt-o-Text v1.21 & v1.24

by

CASIMIR

Other Essays by Casimir
  • Correspondence From Casimir On Reversing Turbo Encrypto
  • Cracking of Encrypt-It For Windows
  • Cracking of WinXFiles
  • The Cracking of File Locker
  • The Cracking of Keeper
  • The Cracking of Gregory Braun's Crypto v3.5
  • The Cracking of SecurityPlus!
  • The Cracking of MasterKey v1.02/1.05
  • Extract of Savard Software propaganda : "Using Crypt-o-Text, you can "scramble" your message so that it is unreadable... at least until it is "unscrambled" by the person receiving your message. Additionally, you can apply password-protection so that the scrambled message can only be unscrambled by someone who knows the password you used to scramble it."

    ARE YOU SURE ???

    Note:


    Have fun ... and USE PGP !!!

    GETTING STARTED

    Start Winice from DOS prompt.
    Launch COT, enter a message and encrypt it with, let's say: "CASIMIR" :-)

    1. Where is my INPUT?

    Usually, to find out what a prg is doing with my input (fake pwd, fake registration number...whatever you want), i set a breakpoint (BPX) on functions such as: GetDlgItemText, GetDlgItemInt,... and i land smack into the routine busy checking my input.

    Try it: you'll discover IT DOESN'T WORK in this case. I still haven't figured out exactly how COT gets the input. Nevermind, since prg stupidly beeps when input is wrong, we perform following actions:

    Note:

    Our current position is now (*):
     CODE:OFFSET
     
     0137:00434E22   JZ 00434E55               // 0 => good guy    
            434E24   PUSH 10                   // bad guy, let's beep him
            434E26   CALL USER32!MessageBeep   // code that triggered Winice 
            434E2B * PUSH 00 
     

    2. Checking pwd lenght

    Having a look up in code (smaller offset values) and setting some BPX we find interesting stuff:

     0137:00434CAA * CMP EAX,[EBP-20]  // EAX=bogus pwd lenght, [EBP-20]=good pwd
            434CAD   JNE 434E24                                        lenght (7)
     
    We now know good_pwd_lenght (if you don't believe me, try with bogus pwd of various lenght)! We'll find out later in which location it is "hidden" in crypted file.

    3. Damned, where is the ECHO?

    Wouldn't it be nice if COT would perform the following actions:

    It that case, when comparaison being performed, there would have an echo of good pwd somewhere in memory... So let's find where this comparaison occurs.

    We can assume that comparaison takes place between CS:434CAD and CS:434E22.

    Wandering for a while, the CMP sequence shows up in all her Glory :
    (enter "1212121" as a bogus pwd)

     0137:00434E17   MOV EAX,[EBP-34] -> 31.32.31.32.31.32.31 ("1212121")
            434E1A   MOV EDX,[EBP-38] -> D2.54.D1.A5.C2.7A.26
            434E1D   CALL 00403700   // trace it 
            434E22   JZ 00434E55     // 0 => good guy
     
        
     0137:00403700   PUSH BPX 
                   .
                   .
            403729   MOV ECX,[ESI] -> 31.32.31.32.31.32.31 ("1212121")
            40372B   MOV EBX,[EDI] -> D2.54.D1.A5.C2.7A.26 // funny string
            40372D   CMP ECX,EBX
     
     
    Oh my God! There is NO echo of good pwd in memory. Instead of "CASIMIR", we get that funny string: D2.54.D1.A5.C2.7A.26. But if we enter good pwd, we obtain 43.41.53.49.4D.49.52 ("CASIMIR") instead of meaningless funny_string. It seems to me that author of COT didn't want good pwd to be uncrypted every time a bogus pwd is entered, it would have been a huge security'breach (and i would not been writting this file at 3AM).

    4. Where does funny_string come from?

    That is THE question... Setting dozens of BPRs, i finally find out that funny_string pops up "by parts" on memory location DS:[68FB41 68FB42]. It seems to be written down here by the following code sequence:

     0137:00434D78   MOV EAX,100
                     CALL 00402984
                     XOR EDX,EDX      // clean EDX
                     MOV DL,[EBP-3F]
                     XOR EAX,EDX      // EAX=6E  EDX=BC
                     MOV [EBP-3F],AL  // AL=D2 : 1° byte of funny_string 
                     MOV EAX,100
                     CALL 00402984
                     XOR EDX,EDX      // clean EDX
                     MOV DL,[EBP-3E]
                     XOR EAX,EDX      // EAX=4A  EDX=1E
                     MOV [EBP-3E],AL  // AL=54 : 2° byte of funny_string
                     MOV EAX,100
                     CALL 00402984
                     MOV EBX,EAX
                     XOR EAX,EAX      // clean EAX
                     MOV AL,[EBP-3D]
                     XOR EBX,EAX      // EBX=8A  EAX=5B
     0137:00434DB3   MOV [EBP-3D],BL  // BL=D1 : 3° byte of funny_string
     
    This routine is called several times, until funny_string is constitued. We get some extra bytes if pwd lenght is not a multiple of 3 (e.g: if pwd lenght=7, we obtain 7 valid bytes + 2 useless bytes). Now let's try with correct pwd ("CASIMIR") :
     
         LEFT                 RIGHT               CHECK
         VECTOR               VECTOR              VECTOR
     
         EAX=FF      xor      EDX=BC      =>      AL=43 ("C")
     
         EAX=5F      xor      EDX=1E      =>      AL=41 ("A")
     
         EBX=08      xor      EAX=5B      =>      BL=53 ("S")  
            .                    .                  .
            .                    .                  .
            .                    .                  .
     
    ...and so on. Only LEFT-hand vector (our funny_string) changes, RIGHT-hand vector remains the same. Situation sounds pretty obvious to me : left-hand vector is calculated as a function of input, whereas right-hand vector is calculated as a function of good pwd (stored in crypted file, where else???). Then check_vector is built by "xorization" of left and right vectors, and compared against input.
        
                    => GOOD input : input = check_vector
                    => BAD input : input != check_vector
     
     

    5. The Making of left vector

    Don't be so impatient and stop complaining! We are nearly done, COT is already, humm, 25% dead...
    OK, what is the duty of input in making of left vector? Let's investigate the mysterious calls to 00402984. Here is what we get :

     
     1° call
     0137:00402984   IMUL EDX,[0043902C],08088405  // [0043902C]=00000857
                     INC EDX                       // EDX=FF0505B3
                     MOV [0043902C],EDX            // EDX=FF0505B4
                     MUL EDX                       // EDX=100*EDX  
                     MOV EAX,EDX                   // EDX=FF (higher-weight byte)
     0137:00402999   RET            
     
     2° call
     0137:00402984   IMUL EDX,[0043902C],08088405  // [0043902C]=FF0505B4
                     INC EDX                       // EDX=5FA9EC84
                     MOV [0043902C],EDX            // EDX=5FA9EC85
                     MUL EDX                       // EDX=100*EDX    
                     MOV EAX,EDX                   // EDX=5F
     0137:00402999   RET            
     
    We trace a third call too, just to have fun (and make sure there are no tricks):
     
     3° call
     0137:00402984   IMUL EDX,[0043902C],08088405  // [0043902C]=5FA9EC85
                     INC EDX                       // EDX=086E3299
                     MOV [0043902C],EDX            // EDX=086E329A
                     MUL EDX                       // EDX=100*EDX    
                     MOV EAX,EDX                   // EDX=08
     0137:00402999   RET            
     
    Pseudo-code:
     for(i=0;i<pwd_lenght;i++)
        {
        Seed = (08088405*Seed + 1); 
        left_vector[i] = 100*Seed;
        } 
     
    Conclusion : left_vector depends entirely on FIRST value -the "Seed"- present at location [0043902C] (in our case : 0x857).

    Subsidiary question : WHO built the Seed ?

    6. The Seed

    Pay attention to memory location DS:0043902C and so on. Is this address already referenced in code? Yes Sir! Just after pwd lenght check [part 2 of this file], we have:

     
     0137:00434CAA   CMP EAX,[EBP-20]  
                     JNE 434E24                 
                     MOV EAX,[EBP-34] -> "CASIMIR"
                     CALL 00433978
     0137:00434CBB   MOV [0043902C],EAX  // EAX=857
     
    Seed seems to come from call to 433978. As usual, we take our machine-gun and go for it:
     
     0137:00433978   PUSH EBP
                   .
                   .
                     XOR EBX,EBX  // EBX
                   .
                   .
            4339A2   MOV EDX,EAX  // pwd_lenght (7)
                     TEST EDX,EDX
                     JLE 004339CC 
                     MOV EAX,00000001
     *****> 4339AD   MOV ECX,[EBP-04]
     *               MOVZX ECX,BYTE PTR[ECX+EAX-01] // ECX=43,41,53,49,4D,49,52
     *               IMUL ECX,EAX                                   ("CASIMIR")
     *               JNO 004339BF
     *               CALL 00402B10
     *      4339BF   ADD EBX,ECX  // EBX=1*43+2*41+3*53+4*49+5*4D+6*49+7*52=857
     *               JNO 004339C8
     *               CALL 00402B10
     *      4339C8   INC EAX
     *               DEC EDX
     **************< JNZ 004339AD  // check if pwd'end reached 
     
    Loop is executed pwd_lenght times, summing each pwd'ascii value weighted by its position in pwd : So DIFFERENT inputs can produce the SAME Seed, BUT right_vector will always tell COT which is the correct one!

    7. SUM-UP and Meditation

    OK, everybody here? Did someone get lost in the code? Now we stop tearing off COT'guts (just for a while), and we lay down, a good cup of Tea at the hand (don't fall asleep, the nice part of the story is coming up!!!).

    Here are actions performed by COT:

    HUUUMMMMM.....(intensive thought, IT HURTS!!!) we can discover pwd_lenght and right_vector using our old friend Winice... Given pwd_lenght, what number of first seeds possible do we get? Investigating a little, i.e entering as input ALT 0, ALT 1,...,ALT 255 and watching how COT translate it, we learn that COT only deals with 136 ascii values when summing input characters (see "car set" in CRACKCOT.CPP). code_MIN=0x20, code_MAX=0xF7, so:
                   Seed_MIN = (1+2+...+pwd_lenght)*code_MIN
                            = 0.5*pwd_lenght*(pwd_lenght+1)*code_MIN
     
                   Seed_MAX = 0.5*pwd_lenght*(pwd_lenght+1)*code_MAX 
    
     
    COT v1.24 accepts up to 45 characters as input, so we obtain:
               Seed_MIN=0.5*45*46*32           Seed_MAX=0.5*45*46*247
                       =33120                          =255645
     
    So, with pwd_lenght=45 (worst situation from our cracker point-of-view), we ONLY have (Seed_MAX-Seed-MIN)= 222525 first Seeds possible!!! What a SMALL number!!! Weakness of COT comes -mostly- from this ridiculous 222525...

    Let's quickly write an ugly C prg performing the following steps:

    1. calculate Seed_MIN and Seed_MAX 2. for(seed=Seed_MIN;seed&lt;=Seed_MAX;seed++) { for(i=1;i&lt;=pwd_lenght;i++) { 1. build left_vector[i] 2. build check_vector[i]=left_vector[i] XOR right_vector[i] 3. TEST1 is check_vector[i] a valid character, i.e member of car set? valid=FALSE =&gt; break; let's try next seed valid=TRUE =&gt; OK, goto TEST2 4. TEST2 is check_vector built to this point valid? (we must have: 1*check_vector[1]+...+i*check_vector[i] &lt; seed IF i&lt;pwd_lenght " " " " == seed IF i==pwd_lenght) valid=FALSE =&gt; break; let's try next seed, valid=TRUE =&gt; if i&lt;pwd_lenght : go on, we are on the good way, but pwd_lenght not reached if i==pwd_lenght : PASSWORD FOUND !!! } }

    CONGRATULATIONS!!! WE MADE IT!!!

    8. Hunt for pwd_lenght and right_vector

    Well, we can now find out what pwd was used to encrypt file, but for that purpose we must first find out pwd_lenght and right_vector using Winice... Quite boring and time-costing, so let's finish the work.

    Finding pwd_lenght doesn't require any reversing, tracing, or stack-fishing... Go into COT and encrypt various files using different pwd_lenght. WATCH IT!!! And if you can't see it LOOK CLOSER!!! YES, those guys just wrote it down, between 3° and 4° slash, without any encryption of any kind. Never seen such self-confidence...For instance :

    We just open crypted file and read it.

    Finding of right_vector is -a little- more complicated. First of all, go into COT, and encrypt the message "LONG LIFE TO CASIMIR" using "CASIMIR" as pwd. Here is what we get:

     ********************** START  Crypt-o-Text **********************
     CoT/0121/01/7/20
     CLy68KoYUEBAFiqBzkfCTGjGiH-+k-LHaQnpio+Z1
     CoT/5225
     **********************  END   Crypt-o-Text **********************
     
    Do you remember portion of code studied in Part 4 (i'm sure you do)? It was something like this:
     
     0137:00434D78   MOV EAX,100
                         ...
                     MOV DL,[EBP-3F]->SS:0068FB41
                         ...
                     MOV DL,[EBP-3E]->SS:0068FB42
                         ...
                     MOV AL,[EBP-3D]->SS:0068FB43
                         ...
     0137:00434DB3   MOV [EBP-3D],BL  
     
    Humm...It's certainly worth spying those memory locations. Doing so, we discover that something is going on in following code sequence:
     0137:00433D71   MOV DL,[EBP-04]     -> 4C ("L")
                     CALL 0040358C
                     MOV EAX,[EBP-14]    -> 4C ("L")
                     MOV EDX,[004388F8]  -> AaBbCc... 
                     CALL 0040387C
                     MOV EBX,EAX         // EAX=17
                     SUB EBX,1           // EBX=16
                        ...
          00433D97   IMUL EBX,[004388DC] // EBX=40000*16=580000
                        ...
          00433DA4   MOV DL,[EBP-03]     -> 79 ("y")
                     CALL 0040358C
                     MOV EAX,[EBP-14]    -> 79 ("y")
                     MOV EDX,[004388F8]  -> AaBbCc... 
                     CALL 0040387C
                     SUB EAX,1           // EAX=31
                        ...
          00433DC4   IMUL DWORD PTR [004388E4] // EAX=1000*31=31000
                        ...
          00433DD1   ADD EBX,EAX // EBX=EBX+EAX=580000+31000=5B1000
                        ...
          00433DDD   MOV DL,[EBP-02]     -> 36 ("6")
                     CALL 0040358C
                     MOV EAX,[EBP-14]    -> 36 ("6")
                     MOV EDX,[004388F8]  -> AaBbCc... 
                     CALL 0040387C
                     SUB EAX,1           // EAX=3A
                        ...
          00433DFD   IMUL DWORD PTR [004388EC] // EAX=40*3A=E80
                        ...
          00433E0A   ADD EBX,EAX // EBX=EBX+EAX=5B1000+E80=5B1E80
                        ...
          00433E16   MOV DL,[EBP-01]     -> 38 ("8")
                     CALL 0040358C
                     MOV EAX,[EBP-14]    -> 38 ("8")
                     MOV EDX,[004388F8]  -> AaBbCc... 
                     CALL 0040387C
                     SUB EAX,1           // EAX=3C
                        ...
          00433E36   IMUL DWORD PTR [004388F0] // EAX=1*3C=3C
                        ...
     0137:00433E43   ADD EBX,EAX // EBX=EBX+EAX=5B1E80+3C=5B1EBC
     
    5B1EBC??? BC,1E,5B!!! Guess who's popping up? Right_vector himself! It is being extracted from crypted file, starting on second line just after "C": Ly68...

    Ascii values from crypted file are read 4 by 4, until right_vector is built.

    But a question remains: how do we know that, for instance, an "8" (38) read in crypted file will produce an "<" (3C)? To learn more about it, we investigate suspicious calls to 0040387C. Before tracing call, notice that EDX points on the following DATA structure:

     
     DS : 00432E3C   Aa Bb Cc Dd Ee Ff Gg Hh
     DS : 00432E4C   Ii Jj Kk Ll Mm Nn Oo Pp
     DS : 00432E5C   Qq Rr Ss Tt Uu Vv Ww Xx
     DS : 00432E6C   Yy Zz 01 23 45 67 89 +-
     
    OK, now we can follow call:
     
     0137:0040387C   TEST EAX,EAX
                        ...
          0040388B   MOV ECX,[EDI-04] // ECX=40 (size of look-up table shown above)
                     PUSH EDI
                     MOV EDX,[ESI-04] // EDX=1 (we are looking for 1 character)
                     DEC EDX
                     JS 004038B0
                     MOV AL,[ESI]     ->38 ("8"): we are looking for "8" in table 
                     INC ESI
                     SUB ECX,EDX
                     JLE 004038B0
                     REPNZ SCASB     ->search 38
                     JNZ 004038B0    ->NOT FOUND (file corrupted?)
                         ...
          004038B8   MOV EAX,EDI     // EDI=432E79=our actual position in table ("9")    
                     SUB EAX,EDX     // EDX=432E3C=first position in table ("A")
      
    So : EAX=3D (offset of "8" + 1). After CALL 0040387C, we perform a SUB EAX,1 to obtain correct offset.

    Final formula is therefore:

           right_vector=40000*offset("L")+1000*offset("y")+40*offset("6")+1*offset("8")
    This system offers COT the possibility of transforming whatever right_vector into an ASCII string that any word-processor can handle...

    Now we add those improvement to our ugly prg and we obtain nice CRACKCOT.EXE that will run provided ONLY with the name of the encrypted file!!! (lazy guys will appreciate).

    9. Benchmark and Source code

    Compiled with Borland C++ v5.0 and running on a 150MHz Pentium, pwd are found in a matter of seconds by CRACKCOT.EXE.
    For a given pwd_lenght, the WORST pwd you can think of is the one with the HIGHEST first Seed value, because it will be tested at the very end of iteration. Corresponding ASCII character to code_MAX is : "÷" (ALT 246)

     
        PWD                                             PROCESSING TIME
     
      1 ÷                                                      <1s
     
     10 ÷÷÷÷÷÷÷÷÷÷                                              3s
     
     20 ÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷                                   10s      
     
     30 ÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷                         21s
     
     40 ÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷               36s
     
     45 ÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷          46s
     
     input  : name of crypted file
     output : password and equivalent ALT sequence
     
    BONUS : a valid Registration Number for COT v1.21, found during my "training period"
     !!!!!!!!!!!!!!!!!!!!!! CRACKCOT.CPP !!!!!!!!!!!!!!!!!!!!!
     
     #include <stdio.h>
     #include <io.h>
     #include <fcntl.h>
     #include <errno.h>
     #include <dir.h>
     #include <string.h>
     #include <dos.h>
     #include <malloc.h>
     #include <stdlib.h>
     #include <time.h>
     #include <conio.h>
     
     
     const int car_nb=136;  // number of characters in car set
     
     // car[0->135] set of characters used
     static int car[car_nb]={0x20,0x21,0x22,0x23,  0x24,0x25,0x26,0x27,
                             0x28,0x29,0x2A,0x2B,  0x2C,0x2D,0x2E,0x2F,
                             0x30,0x31,0x32,0x33,  0x34,0x35,0x36,0x37,
                             0x38,0x39,0x3A,0x3B,  0x3C,0x3D,0x3E,0x3F,
                             0x40,0x41,0x42,0x43,  0x44,0x45,0x46,0x47,
                             0x48,0x49,0x4A,0x4B,  0x4C,0x4D,0x4E,0x4F,
                             0x50,0x51,0x52,0x53,  0x54,0x55,0x56,0x57,
                             0x58,0x59,0x5A,0x5B,  0x5C,0x5D,0x5E,0x5F,
                             0x60,
                                            0x7B,  0x7C,0x7D,0x7E,0x7F,
                                            0x83,
                                                                  0x9F,
                                  0xA1,0xA2,0xA3,  0xA4,0xA5,0xA6,0xA7,
                             0xA8,0xA9,0xAA,0xAB,  0xAC,0xAD,0xAE,0xAF,
                             0xB0,0xB1,0xB2,0xB3,  0xB4,0xB5,0xB6,0xB7,
                             0xB8,0xB9,0xBA,0xBB,  0xBC,0xBD,0xBE,0xBF,
                             0xC0,0xC1,0xC2,0xC3,  0xC4,0xC5,0xC6,0xC7,
                             0xC8,0xC9,0xCA,0xCB,  0xCC,0xCD,0xCE,0xCF,
                             0xD0,0xD1,0xD2,0xD3,  0xD4,0xD5,0xD6,0xD7,
                             0xD8,0xD9,0xDA,0xDB,  0xDC,0xDD,0xDE,0xDF,
                                                                  0xF7};
     
     // alt[0->135] look-up table for ASCII code
     static int alt[car_nb]={0x20,0x21,0x22,0x23,  0x24,0x25,0x26,0x27,
                             0x28,0x29,0x2A,0x2B,  0x2C,0x2D,0x2E,0x2F,
                             0x30,0x31,0x32,0x33,  0x34,0x35,0x36,0x37,
                             0x38,0x39,0x3A,0x3B,  0x3C,0x3D,0x3E,0x3F,
                             0x40,0x41,0x42,0x43,  0x44,0x45,0x46,0x47,
                             0x48,0x49,0x4A,0x4B,  0x4C,0x4D,0x4E,0x4F,
                             0x50,0x51,0x52,0x53,  0x54,0x55,0x56,0x57,
                             0x58,0x59,0x5A,0x5B,  0x5C,0x5D,0x5E,0x5F,
                             0x60,
                                            0x7B,  0x7C,0x7D,0x7E,0x7F,
                                            0x9F,
                                                                  0x98,
                                  0xAD,0xBD,0x9C,  0x0F,0xBE,0xB3,0x15,
                             0xF9,0xB8,0xA6,0xAE,  0xAA,0xF0,0xA9,0xEE,
                             0xF8,0xF1,0xFD,0xFC,  0xEF,0xE6,0x14,0xFA,
                             0xF7,0xFB,0xA7,0xAF,  0xAC,0xAB,0xF3,0xA8,
                             0x85,0xA0,0x83,0xC6,  0x84,0x86,0x91,0x80,
                             0x8A,0x82,0x88,0x89,  0x8D,0xA1,0x8C,0x8B,
                             0xD0,0xA4,0x95,0xA2,  0x93,0xE4,0x94,0x9E,
                             0x9B,0x97,0xA3,0x96,  0x81,0xEC,0xE7,0xE1,
                                                                  0xF6};
     
     const int xor_nb=0x40;   // number of elements in xor_table
     
     // xor_table[0->3F] alphabeta table COT uses when creating right_vec[] from
     // crypted text
     static int xor_table[xor_nb]={0x41,0x61,0x42,0x62,  0x43,0x63,0x44,0x64,
                                   0x45,0x65,0x46,0x66,  0x47,0x67,0x48,0x68,
                                   0x49,0x69,0x4A,0x6A,  0x4B,0x6B,0x4C,0x6C,
                                   0x4D,0x6D,0x4E,0x6E,  0x4F,0x6F,0x50,0x70,
                                   0x51,0x71,0x52,0x72,  0x53,0x73,0x54,0x74,
                                   0x55,0x75,0x56,0x76,  0x57,0x77,0x58,0x78,
                                   0x59,0x79,0x5A,0x7A,  0x30,0x31,0x32,0x33,
                                   0x34,0x35,0x36,0x37,  0x38,0x39,0x2B,0x2D};
     
     // PROTOTYPES
     
     void Hello(void);
     int Get_target(void);
     int Read_pwd_lenght(int,int,char **,int *);
     void Calc_right_vec(int,char *,int,int,int **);
     int Position(char);
     void Read_xor_ascii(int,char *,int,int,char **);
     void Calc_min_max(int,int *,int *);
     void Calc_left_vec(unsigned int *,unsigned int *);
     int In_list(int,int *);
     void Print_pwd(int,int *,time_t);
     void Chrono(void);
     void Wait_key();
     
     
     main()
     {
     int fn;            // crypted file'handle
     int pwd_lenght;    // password lenght (1 to 45 characters)
     const int buf_size=1000;  // buffer size
     char *buf;         // buffer to store first 1000 characters of file
     int pos_buf;       // current position in buffer
     int code_min;      // smaller code possible
     int code_max;      // larger code possible
     int code;          // code_min <= code <= code_max
     int code_chk;      // code check, let us know if we are on the good way
     unsigned int seed; // number used to crypt character (first seed = code)
     int i;             // General Purpose variable
     time_t time_start; // chrono starting
     
     unsigned int *left_vec;   // left_vector built using seed
     int *right_vec;            // values used by COT to uncrypt pwd'characters
     int *pwd;                 // password uncrypted
     int *car_pos;             // character'position in set
     
     
     Hello();
     
     fn=Get_target();
     
     // init buf
     buf=(char *)malloc(sizeof(char)*buf_size);
     
     pwd_lenght=Read_pwd_lenght(fn,buf_size,&buf,&pos_buf);
     
     if(pwd_lenght==0)
        {
        printf("\nNO PWD required to uncrypt file...\n");
        Wait_key();
        exit(0);
        }
     
     
     right_vec=(int *)malloc(sizeof(int)*pwd_lenght);
     
     Calc_right_vec(pwd_lenght,buf,buf_size,pos_buf,&right_vec);
     
     // Init_pwd
     pwd=(int *)malloc(sizeof(int)*pwd_lenght);
     
     car_pos=(int *)malloc(sizeof(int)*pwd_lenght);
     
     left_vec=(int *)malloc(sizeof(int)*pwd_lenght);
     
     Calc_min_max(pwd_lenght,&code_min,&code_max);
     
     time_start=time(NULL);
     printf("\nProcessing");
     
     // main loop
     
     for(code=code_min;code<=code_max;code++)
        {
        // ini
        //printf("\ncode : %x",code);
        Chrono();
        seed=code;
        code_chk=0x00;
        for(i=0x00;i<pwd_lenght;i++)
           {
           Calc_left_vec(&seed,&left_vec[i]);
           pwd[i]=(left_vec[i]^right_vec[i]);   // XORization
           //printf("\npwd : %x",pwd[i]);
           if(In_list(pwd[i],&car_pos[i])!=1) // is it a member of car set ?
              {
              break;   // not a valide character, let's try next code
              }
           else        // OK, character valide
              {
              code_chk+=((i+1)*pwd[i]);
              if(code_chk>code)
                 {
                 break;  // characters too big, let's try next code
                 }
              else
                 {
                 if(i==(pwd_lenght-1))
                    {
                    // OK, pwd lenght reached, so let's check
                    // if the pwd built is the good one
                    if(code_chk!=code)
                       {
                       break;  // no good, let's try next code
                       }
                    else
                       {
                       // PASSWORD FOUND !!!
                       //printf("found");
                       Print_pwd(pwd_lenght,car_pos,time_start);
                       exit(0);
                       }
                    }
                 }
              }
           }
        }
     }
     
     /*************************************************************/
     /**********************       FUNCTIONS       ****************/
     /*************************************************************/
     
     void Hello(void)
     {
     printf("\n Cracker for CRYPT-o-TEXT v1.21 & v1.24\n");
     }
     
     /**********************************************************************/
     /*    try to open crypted file  (must be in the SAME directory)       */
     /*    - success : return file'handle                                  */
     /*    - fail : exit prg                                               */
     
     int Get_target(void)
     {
     unsigned char buf[100];
     int fn;
     
     printf("\nFile to uncrypt [e.g: SECRETXT.COT]?   ");
     gets(buf);
     
     // try to open file
     fn=open(buf,O_BINARY|O_RDONLY);
     switch(fn)
        {
        case -1:printf("\nFILE NOT FOUND! (file to crack MUST be in SAME");
        printf("\ndirectory as CRACKCOT.EXE; file'name CAN NOT exceed");
        printf("\n8 characters; DO NOT USE accentuated characters;");
        printf("\nand last but not least: DO NOT forget file'extension!)\n");
        Wait_key(); exit(0);
        default: /*printf("\nOK, FILE FOUND")*/; return(fn);
        }
     }
     
     /**********************************************************************/
     /*  password'lenght is written WITHOUT ANY ENCRYPTION (!!!) in file,  */
     /*  always at same position, i.e between 3° and 4° slash              */
     /*                                                                    */
     /*  ********************** START  Crypt-o-Text *********************  */
     /*  CoT/0121/01/4/14                                                  */
     /*  CHiWyEBAtOSy0ROktxyEUGAuffQJb                                     */
     /*  CoT/2683                                                          */
     /*  **********************  END   Crypt-o-Text *********************  */
     /*                                                                    */
     /*  here, for instance, pwd lenght = 4 ( DECIMAL notation)            */
     
     int Read_pwd_lenght(int fn,int buf_size,char **buf,int *pos_buf)
     {
     int i,j;
     int pwd_lenght=0;
     int slash_nb=0;       // count "/"
     int digit_nb=0;       // pwd'lenght digit number
     int base=1;
     
     // read first 1000 characters of file
     read(fn,*buf,buf_size);
     close(fn);
     
     // find 3° "/"
     for(i=0;i<buf_size;i++)
        {
        if((*buf)[i]==0x2F)   // "/" ?
           {
           slash_nb++;
           if(slash_nb==3){break; /* OK, ready to read pwd lenght */}
           }
        }
     
     if(slash_nb<3){printf("\nSORRY, NOT A VALID FILE\n");Wait_key();exit(0);}
     
     // let's count nb of digit(s) betweeen 3° and 4° "/"
     i++;
     while((*buf)[i+digit_nb]!=0x2F) {digit_nb++;}
     
     for(j=1;j<digit_nb;j++) {base*=10;}
     
     // get pwd lenght
     for(j=0;j<digit_nb;j++)
        {
        pwd_lenght+=(((*buf)[i+j]-0x30)*base);  // conversion hex->dec
        base=(base/10);
        }
     
     // save our position in buffer
     *pos_buf=(i+digit_nb);
     
     //printf("\nPWD LENGHT : %d\n",pwd_lenght);
     return(pwd_lenght);
     
     }
     
     /**********************************************************************/
     /*    right_vec[pwd_lenght] is built using ascii values stored in     */
     /*    crypted file.                                                   */
     /*    those values are read by blocks of 4, and a block of 4          */
     /*    produces only 3 values to fill right_vec.                       */
     /*    ex: if we have, let's say, pwd lenght = 4, we'll need 4 values  */
     /*    in right_vec (2 groups of 3, values 5° and 6° are not used),    */
     /*    so we'll have to read: 2*4=8 values in file.                    */
     
     void Calc_right_vec(int pwd_lenght,char *buf,int buf_size,int pos_buf,
                        int **right_vec)
     {
     int ascii_nb;    // nb of values to read from file
     char *xor_ascii; // values taken from file
     int xor_nb;      // number of right_vec values built (xor_nb>=pwd_lenght)
     int *xor_temp;   // keep ALL right_vec values built
     int block_nb;    // number of blocks required to build right_vec[]
     int block;       // current block
     int *xor_pos;    // ascii'values positions in xor_table
     int i;
     int code;
     
     block_nb=(pwd_lenght/3)+1;
     
     ascii_nb=4*block_nb;
     xor_ascii=(char *)malloc(sizeof(char)*ascii_nb);
     Read_xor_ascii(ascii_nb,buf,buf_size,pos_buf,&xor_ascii);
     
     xor_nb=3*block_nb;
     xor_temp=(int *)malloc(sizeof(int)*xor_nb);
     xor_pos=(int *)malloc(sizeof(int)*4);   // 4 values in block
     
     for(block=0;block<block_nb;block++)
       {
       for(i=0;i<4;i++) {xor_pos[i]=Position(xor_ascii[4*block+i]);}
       code=(0x40000*xor_pos[0]+0x1000*xor_pos[1]+0x40*xor_pos[2]+xor_pos[3]);
       // now we extract 3 right_vec values from code
       xor_temp[3*block+2]=(code/0x10000);
       code=(code-0x10000*xor_temp[3*block+2]);
       xor_temp[3*block+1]=(code/0x100);
       code=(code-0x100*xor_temp[3*block+1]);
       xor_temp[3*block]=code;
       }
     
     // fill right_vec (pwd_lenght values to enter)
     for(i=0;i<pwd_lenght;i++)
        {
        (*right_vec)[i]=xor_temp[i];
        }
     
     }
     
     /**********************************************************************/
     /*     return position of "value" in xor_table                        */
     
     int Position(char value)
     {
     int i;
     
     for(i=0;i<xor_nb;i++)
        {
        if(xor_table[i]==value) {return(i);}
        }
     }
     
     /**********************************************************************/
     /*    ascii values are located after the second "C", just under "CoT" */
     /*    using precedent example, we'll read:   HiWy EBAt                */
     
     void Read_xor_ascii(int ascii_nb,char *buf,int buf_size,int pos_buf,
                         char **xor_ascii)
     {
     int i,j,k;
     int found=0;
     
     // we go on reading file, just after pwd_lenght, looking for "C"
     for(i=pos_buf;i<buf_size;i++)
        {
        if(buf[i]==0x43)   // "C" ?
           {
           found=1;
           break; /* OK, ready to read ascii_nb values */
           }
        }
     
     if(!found) {printf("\nSORRY, NOT A VALID FILE\n"); Wait_key(); exit(0);}
     
     // fill xor_ascii[]
     i++;
     k=0;
     for(j=i;j<(i+ascii_nb);j++)
        {
        (*xor_ascii)[k]=buf[j];
        k++;
        }
     
     }
     
     /**********************************************************************/
     /*   return code min and max as a function of pwd_lenght and set of   */
     /*   characters used                                                  */
     
     void Calc_min_max(int pwd_lenght,int *code_min,int *code_max)
     {
     // code_min=1*MIN(car)+2*MIN(car)+...+pwd_lenght*MIN(car)
     // code_min=1*MAX(car)+2*MAX(car)+...+pwd_lenght*MAX(car)
     // 1+2+..+n=0.5*n*(n+1)
     
     *code_min=0.5*(pwd_lenght)*(pwd_lenght+1)*car[0];
     *code_max=0.5*(pwd_lenght)*(pwd_lenght+1)*car[car_nb-1];
     }
     
     /**********************************************************************/
     /*   build left_vec[i]                                                */
     /*   also generate the new seed that will be used to build            */
     /*   left_vec[i+1]                                                    */
     
     void Calc_left_vec(unsigned int *seed,unsigned int *left_vec)
     {
     *seed=((*seed)*0x08088405+1);
     *left_vec=(*seed)/0x1000000;
     }
     
     /**********************************************************************/
     /*   check if uncrypted character belongs to character set            */
     /*   - yes => return(1) and character'position in set                 */
     /*   - no  => return(0)                                               */
     
     int In_list(int pwd_car,int *pos)
     {
     int i;
     
     for(i=0;i<car_nb;i++)
        {
        if(pwd_car==car[i]){*pos=i; return(1);}   /* character found */
        }
     
     return(0);  // character not found
     }
     
     /**********************************************************************/
     /*   Print password decrypted, along with equivalent "ALT" sequence   */
     
     void Print_pwd(int pwd_lenght,int *pos,time_t time_start)
     {
     int i;
     time_t t;
     t=time(NULL);
     printf("[%3.0lfs]",difftime(t,time_start));
     printf("\n\n ALT sequence: ");
     
     for(i=0;i<pwd_lenght;i++)
        {
        printf("[%d]",alt[pos[i]]);
        if((i+1)%10==0) {printf("\n               ");}
        }
     printf("\n\n     PWD: >>>");
     for(i=0;i<pwd_lenght;i++)
        {
        printf("%c",alt[pos[i]]);
        }
     printf("<<<\n\n");
     printf("\n\nbrought to You by CASIMIR!\n\n");
     printf("BONUS!!! to register CoT v1.21 enter : 111292*859100");
     Wait_key();
     
     }
     
     /**********************************************************************/
     /*                            chronometer                             */
     
     void Chrono(void)
     {
     static time_t t1;
     time_t t2;
     t2=time(NULL);
     
     if(difftime(t2,t1)>1){printf(".");t1=time(NULL);}  // ¤
     }
     
     /***********************************************************************/
     /*                   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");
     
     while(!kbhit()) {/* wait until key pressed */}
     }
     
     !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     
      

    THAT'S ALL, FOLKS !!!

    Here is the executable.

    If you have any questions, suggestions, metaphysical interrogations, feel free to contact Casimir

    Notes:




    You are not really deep inside fravia's page of reverse engineering, use your back button!