CRACKING OF WinXFiles

by

CASIMIR

Part B

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
  • 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
  • Software by PepSoft
    (You might need to use FTP Search to find the software.)
    Try searching for WXF.

    This essay presents in-extenso the reverse-engineering and cracking of an encryption utility distributed by PEPSOFTWARE Ltd, WinXFiles(TM). Map of the essay:

       Introduction
       Strategy
       Getting prepared
    
       PART A - REVERSING OF WINXFILES 
            1. First contact
            2. Where is the fork?
            3. The Check routine
            4. The making of Check_string
            5. The Remainder
            6. Decryption process
    
       PART B - ANALYSIS AND BREAKING OF WINXFILES
            Method 1: let's suppose Tail = 000...
                   Step 1: Solving
                   Step 2: Checking password found
                   Notes on Method 1
            Method 2: Tail = garbage
                   Step 1: How to find 2 characters from pwd
                   Step 2: System(s) solving
                   Step 3: Checking password found
                   Bad luck!
                   Notes on Method 2
    
       PART C - 'C' SOURCE OF CRACKER
    

    PART B - ANALYSIS AND BREAKING OF WINXFILES

    As with former cracks on Crypt-o-Text and Encrypt-It for Windows, we take advantage of the fact that WinXFiles uses ALWAYS the SAME structure to store original file name and password into Header (see below). Separators (| and \) are always there. And we also know that "sometimes" (not always, because of WXF's weird attitude) the Tail is filled with 0s.

    
        <---------------- 255 bytes -------------->
        file_name|password\00000000000000000000...0
        <name_  > <pwd_  > <-------- TAIL --------> 
         length    length      
        <---------------- HEADER ----------------->
    
    First of all, let's sum up actions performed by WXF (chronological order):

    In order to crack WXF, we're going to mimic it, using similar functions. But we must make 3 assumptions before we can do anything:

    Yes, unfortunatly none of those 3 precious values is stored in Header {:-(

    It would have spared us a lot of search time, but anyway there are not too many combinations to try out. Look:

    So we have: 100*25*255 = 637500 combinations. Small beer! {:-) We proceed this way (pseudo-code):
    Depending on what WXF puts in Tail (0s or garbage), we can use 2 methods to find a password. We try method 1 first, if it doesn't work we try method 2.

    Method 1: let's suppose Tail = 000...

    If we SUPPOSE that Tail is filled up with zeros (instead of garbage, see Chapter 4, note 3), then cracking is very easy.

    For instance: 
    With name_len = 9 characters 
          pwd_len = 8 characters 
          remainder fixed to any value in [00 ; FE]
    we must find each character from password, i.e: Pwd[0],Pwd[1],... and Pwd[7].
    

    Step 1: Solving

    
       KNOWN        KNOWN       UNKNOWN
      Header[0]  + Remain[0]  - Pwd[0] = ?                    F
      Header[1]  + Remain[1]  - Pwd[1] = ?                    I 
      Header[2]  + Remain[2]  - Pwd[2] = ?                    L
      Header[3]  + Remain[3]  - Pwd[3] = ?                    E 
      Header[4]  + Remain[4]  - Pwd[4] = ?                    N 
      Header[5]  + Remain[5]  - Pwd[5] = ?                    A
      Header[6]  + Remain[6]  - Pwd[6] = ?                    M
      Header[7]  + Remain[7]  - Pwd[7] = ?                    E
      Header[8]  + Remain[8]  - Pwd[0] = ?
      Header[9]  + Remain[9]  - Pwd[1] = | <-------------- first separator  
      Header[10] + Remain[10] - Pwd[2] = Pwd[0]               P         
      Header[11] + Remain[11] - Pwd[3] = Pwd[1]               A
      Header[12] + Remain[12] - Pwd[4] = Pwd[2]               S
      Header[13] + Remain[13] - Pwd[5] = Pwd[3]               S
      Header[14] + Remain[14] - Pwd[6] = Pwd[4]               W
      Header[15] + Remain[15] - Pwd[7] = Pwd[5]               O
      Header[16] + Remain[16] - Pwd[0] = Pwd[6]               R 
      Header[17] + Remain[17] - Pwd[1] = Pwd[7]               D
      Header[18] + Remain[18] - Pwd[2] = \ <-------------- second separator
      Header[19] + Remain[19] - Pwd[3] = 0 => Pwd[3] found
      Header[20] + Remain[20] - Pwd[4] = 0 => Pwd[4] found
      Header[21] + Remain[21] - Pwd[5] = 0 => Pwd[5] found
      Header[22] + Remain[22] - Pwd[6] = 0 => Pwd[6] found    T
      Header[23] + Remain[23] - Pwd[7] = 0 => Pwd[7] found    A
      Header[24] + Remain[24] - Pwd[0] = 0 => Pwd[0] found    I  
      Header[25] + Remain[25] - Pwd[1] = 0 => Pwd[1] found    L
      Header[26] + Remain[26] - Pwd[2] = 0 => Pwd[2] found
                 .            .        .  
                 .            .        .
                 .            .        .
    
    
    We found successively 3°,4°,5°,6°,7°,0°,1° and 2° characters from password.

    Step 2: Checking password found

    Remember we made assumptions on:

    So the password we found must be checked to be certain that assumptions we made were correct. There are various tests performed:
    We check if the WHOLE Tail is null, not only the beginning:
    Tail begins here ->  Header[19]  + Remain[19]  - Pwd[3] = 0 ?
                         Header[20]  + Remain[20]  - Pwd[4] = 0 ?
                         Header[21]  + Remain[21]  - Pwd[5] = 0 ?
                         Header[22]  + Remain[22]  - Pwd[6] = 0 ?
                         Header[23]  + Remain[23]  - Pwd[7] = 0 ?
                         Header[24]  + Remain[24]  - Pwd[0] = 0 ?
                         Header[25]  + Remain[25]  - Pwd[1] = 0 ?
                         Header[26]  + Remain[26]  - Pwd[2] = 0 ?
                         Header[27]  + Remain[27]  - Pwd[3] = 0 ?
    
                                     .             .        .  
                                     .             .        .
                                     .             .        .
    
                         Header[254] + Remain[254] - Pwd[6] = 0 ?
    Tail ends here   ->  Header[255] + Remain[255] - Pwd[7] = 0 ?
                    
    
    We check if remainder R is correct:
    
                 Pwd[0]+Pwd[1]+Pwd[2]+Pwd[3]+Pwd[4]+Pwd[5]+Pwd[6]+Pwd[7]
    remaider of: ------------------------------------------------------- = R ?
                                           FF
    
    We check if separators ( | and \ ) are present:
    
          Header[9]  + Remain[9]  - Pwd[1] = | ?
       
          Header[18] + Remain[18] - Pwd[2] = \ ?
    
    We perform the same test as WXF with Check_string. If password is correct, we must have :
           Header[10] + Remain[10] - Pwd[2] = Pwd[0] ?
           Header[11] + Remain[11] - Pwd[3] = Pwd[1] ?
           Header[12] + Remain[12] - Pwd[4] = Pwd[2] ?
           Header[13] + Remain[13] - Pwd[5] = Pwd[3] ?
           Header[14] + Remain[14] - Pwd[6] = Pwd[4] ?
           Header[15] + Remain[15] - Pwd[7] = Pwd[5] ?
           Header[16] + Remain[16] - Pwd[0] = Pwd[6] ?
           Header[17] + Remain[17] - Pwd[1] = Pwd[7] ?
    
    If a password passes every test, it means that it is the GOOD password! So we can stop searching and have some rest!

    Notes on Method 1

    If WXF effectively filled the Tail with 0s, then this method works very well, you'll always find the good password in a very short time (search time < 30 sec on a P150 32MB).

    If this method gives nothing, we use the second one.

    Method 2: Tail = garbage

    Well, it won't be as easy as with Method 1, because this time we can NOT use the Tail. Instead, we'll use the separators ( | and \ ) to find out 2 *distinct* characters from password. Then we'll try to find out missing characters.

    !!!Hey, YOU, if you're still reading this crap and plan to mail the author, use the word 'cactus' in your letter (don't worry, just for statistical purposes)!!!

    For instance:

    With name_len = 9 characters 
          pwd_len = 8 characters 
          remainder fixed to any value in [00 ; FE]
    We must find each character from password, i.e: Pwd[0],Pwd[1],... and Pwd[7].

    Step 1: How to find 2 characters from pwd

    We use separators in Header to guess 2 characters from password. We have (refer to Method 1, Step 1):

    Header[9]  + Remain[9]  - Pwd[1] = | => Pwd[1] = (Header[ 9]+Remain[ 9]-0x7C)
     
    Header[18] + Remain[18] - Pwd[2] = \ => Pwd[2] = (Header[18]+Remain[18]-0x5C)
    
    Now we know Pwd[1] and Pwd[2].

    Step 2: System(s) solving

    OK, now take a deap breath, it gets a little harder {;-)

    We are going to use following equations to find Pwd[0], Pwd[3], Pwd[4], Pwd[5], Pwd[6] and Pwd[7]:

    
      Header[10] + Remain[10] - Pwd[2] = Pwd[0]  |
      Header[11] + Remain[11] - Pwd[3] = Pwd[1]  |
      Header[12] + Remain[12] - Pwd[4] = Pwd[2]  |
      Header[13] + Remain[13] - Pwd[5] = Pwd[3]  | ----> system S0
      Header[14] + Remain[14] - Pwd[6] = Pwd[4]  |
      Header[15] + Remain[15] - Pwd[7] = Pwd[5]  |
      Header[16] + Remain[16] - Pwd[0] = Pwd[6]  |
      Header[17] + Remain[17] - Pwd[1] = Pwd[7]  |
    
    Those 8 equations are equivalent to system S0:
     
              | P0 +      P2                          = H10 + R10
              |      P1 +      P3                     = H11 + R11
              |           P2 +      P4                = H12 + R12
    system S0 |                P3 +      P5           = H13 + R13
              |                     P4 +      P6      = H14 + R14
              |                          P5 +      P7 = H15 + R15   
              | P0 +                          P6      = H16 + R16
              |      P1 +                          P7 = H17 + R17  
    In fact, we can cut system S0 into 2 independent sub-systems S1 and S2:
    
    S1:
    -> given P1, we can calculate successively : P3,P5 and P7. Last equation is used for checking.
     
                   | P1========>P3                    = H11+R11    => P3=H11+R11-P1
                   |            | 
                   |           P3========>P5          = H13+R13    => P5=H13+R13-P3
    system S1      |                       |
                   |                      P5======>P7 = H15+R15    => P7=H15+R15-P5
                   |                                |
                   | P1                            P7 = H17+R17 check P1=H17+R17-P7?  
    
    S2:
    -> given P2, we can calculate successively : P0,P4 and P6. Last equation is used for checking.
    
              | P0<=======P2                          = H10+R10    => P0=H10+R10-P2
              |            |   
              |           P2=======>P4                = H12+R12    => P4=H12+R12-P2
    system S2 |                      |
              |                     P4=======>P6      = H14+R14    => P6=H14+R14-P4
              |                                |
              | P0                            P6      = H16+R16 check P0=H16+R16-P6?
    
    
    So, by solving those 2 sub-systems above, we can recover the whole pwd.

    Step 3: Checking password found

    As with Method 1, we made assumptions on:

    So the password we found must be checked to be certain that assumptions we made were correct. Tests are the same as with Method 1, but, of course, we can NOT (and don't need to, anyway) perform test on Tail.

    Note


    But what will happen if we have more than 2 subsystems to solve?

    Bad luck!

    For instance:

    With name_len = 8 characters 
          pwd_len = 6 characters 
          remainder fixed to any value in [00 ; FE]
    We must find each character from password, i.e: Pwd[0],Pwd[1],... and Pwd[5].
     
      KNOWN        KNOWN       UNKNOWN
      Header[0]  + Remain[0]  - Pwd[0] = ?                    F
      Header[1]  + Remain[1]  - Pwd[1] = ?                    I 
      Header[2]  + Remain[2]  - Pwd[2] = ?                    L
      Header[3]  + Remain[3]  - Pwd[3] = ?                    E 
      Header[4]  + Remain[4]  - Pwd[4] = ?                    N 
      Header[5]  + Remain[5]  - Pwd[5] = ?                    A
      Header[6]  + Remain[6]  - Pwd[0] = ?                    M
      Header[7]  + Remain[7]  - Pwd[1] = ?                    E
      Header[8]  + Remain[8]  - Pwd[2] = | <-------------- first separator 
      Header[9]  + Remain[9]  - Pwd[3] = Pwd[0]                  |
      Header[10] + Remain[10] - Pwd[4] = Pwd[1]               P  |       
      Header[11] + Remain[11] - Pwd[5] = Pwd[2]               W  |
      Header[12] + Remain[12] - Pwd[0] = Pwd[3]               D  | ----> system S0
      Header[13] + Remain[13] - Pwd[1] = Pwd[4]                  |
      Header[14] + Remain[14] - Pwd[2] = Pwd[5]                  | 
      Header[15] + Remain[15] - Pwd[3] = \ <-------------- second separator 
      Header[16] + Remain[16] - Pwd[4] = ?                            
      Header[17] + Remain[17] - Pwd[5] = ?                    T     
      Header[18] + Remain[18] - Pwd[0] = ?                    A
                 .            .        .                      I
                 .            .        .                      L
                 .            .        .
    
    Using separators we find Pwd[2] and Pwd[3]:
    Header[8]  + Remain[8]  - Pwd[2] = | => Pwd[2] = (Header[ 8]+Remain[ 8]-0x7C)
    Header[15] + Remain[15] - Pwd[3] = \ => Pwd[3] = (Header[15]+Remain[15]-0x5C)
    
    This time, system S0 looks like this:
    
      Header[9]  + Remain[9]  - Pwd[3] = Pwd[0]                  |
      Header[10] + Remain[10] - Pwd[4] = Pwd[1]               P  |       
      Header[11] + Remain[11] - Pwd[5] = Pwd[2]               W  |
      Header[12] + Remain[12] - Pwd[0] = Pwd[3]               D  | ----> system S0
      Header[13] + Remain[13] - Pwd[1] = Pwd[4]                  |
      Header[14] + Remain[14] - Pwd[2] = Pwd[5]
    
    
              | P0 +           P3                     = H9  + R9
              |      P1 +           P4                = H10 + R10
              |           P2 +           P5           = H11 + R11
    system S0 | P0 +           P3                     = H12 + R12
              |      P1 +           P4                = H13 + R13
              |           P2 +           P5           = H14 + R14   
    
      
    
    We can cut system S0 into 3 sub-systems S1, S2 and S3:
    
    S1:
    -> given P3, we can calculate P0 with first equation. Second equation is used for checking.
     
              | P0<============P3                   = H9  + R9    =>  P0=H9+R9-P3
              |
    system S1 |
              | P0             P3                   = H12 + R12 check P0=H12+R12-P3?
     
             
    S2:
    -> given P2, we can calculate P5 with first equation. Second equation is used for checking.
    
                      | P2============>P5           = H11 + R11   =>  P5=H11+R11-P2
    system S2         |
                      |
                      | P2             P5           = H14 + R14 check P5=H14+R14-P2?
      
    
    BUT for S3 : as we don't know P1 nor P4 we can't do anything. BTW, WXF will neither be able to check correctly input when there are 3 or more sub-systems.

    For instance: with:

    We get: We can find various solutions to S3 (compatible with remainder value):
    Try it: encrypt file SIX6.BMP using pwd 666666, then decrypt it using pwd 656676. WXF will let you in (!), but decryption will be erroneous.

    If original text was: ABCDEFGHIJKL...
    then you will get: A?CD?FG?IJ?L... ('?' means: wrong character)
    I had fun playing with pictures too! When decrypted with a wrong pwd, you can observe weird (and sometimes beautiful) transformations. BTW, you need to edit file's header once decrypted, so a viewer can handle picture. Otherwise, you just get a 'Bad/Unknown Format' message when try to open picture.

    Notes on Method 2

    Solvability depends on number of sub-systems you obtain:

    It means that IF you choose a combination of name_len/pwd_len that produces more than 2 sub-systems THEN your password can NOT be found using Method 2 (but of course we can still try to recover it using Method 1). Kind of 'natural protection'! For instance, {name_len=8/6=pwd_len}, or {name_len=11/9=pwd_len} are secure combinations.

    By looking at various combinations of name_len/pwd_len, i found out that about 25% of files are auto-immune to Method 2 {:-( . Hey, don't cry, that means we gonna crack 3 files out of 4, not that bad! {:-)

    In terms of solving speed, Method 2 is equivalent to Method 1 (search time < 30 sec on a P150 32MB). As Cracker try Method 1 first, then Method 2, the whole search time may not exceed one minute.

    Click here for Part C. It includes C cracking source code and an executable.

    Copyright December 1998 by Casimir.

    Mail Casimir

    Converted to hypertext by Joe Peschel December 16, 1998.