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

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 :
Seed = 1*ch_1 + 2*ch_2 + ... + n*ch_n
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: