The Cracking of MasterKey v1.02/1.05

by

CASIMIR

Part A

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 Keeper
  • The Cracking of Braun's Crypto 3.5
  • The Cracking of SecurityPlus
  • Caz presents : the Cracking of MasterKey v1.02/1.05 by Ph. MELLERET

    Table of contents:
    
    Abstract
    What do i need?
    Target's description
    
    PART A. REVERSING OF MASTERKEY
         A1. Input handling
         A2. Building of K1
         A3. Building of K2
         A4. Building of K3
         A5. Final cmp
         A6. Monitoring Registry 
    
    PART B. BREAKING OF MASTERKEY
         B1. General constraints
         B2. Splitting
             B21. Validity of splits
             B22. Sorting splits
         B3. Isolated column(s)
         B4. Modulo (K2 -> K3)
         B5. Solving method
             B51. Exhaustive search
             B52. Optimization and main algorithm
             B53. Password check
    
    PART C. BATCH FILE FOR REGISTRY'S KEY EXPORT
    
    PART D. 'C' SOURCE FOR CRACKER
    

    Abstract

    MasterKey uses a proprietary algorithm. The secret key (password) is stored in Windows's Registry in an encrypted form. Password's encryption consists in sums performed on password's "squared" characters, and a modulo is applied on result. Using cracker provided, passwords are recovered in a few minutes to a few hours.

    Warning : Cracker does not recover passwords with one or more extended characters ("é", "à""...).
    BTW, it would be a nice improvement, let me know if you do! (Casimir)


    What do I need?

    A target : MasterKey v1.02/1.05 by Ph. MELLERET (Kruptos Solutions)
               Win95 main exe : MKStarter.exe , 282624 bytes 
                MELLERET (Kruptos Solutions)
    
    Some tools : 
    
    +a system-level debugger : Winice 3.01 by Nu-Mega, as usual {;-)
    +an hexadecimal editor : HexWorkshop by BreakPoint Software
    +an ASCII table with hexadecimal values of characters
    +a good calculator : Excalibur 32-bit by David Bernazzani
     This calculator has an amazing amount of built-in functions, such as the
     unusual ROL and ROR asm features. Find it  here , 
     here, or  here. 
    
    +a Registry monitor : Regmon at Sysinternals 
    
    

    Target's description

    MasterKey (MK) encrypts dynamically directories on your hard drive. You must create your own secret key to activate MK (at least 5 characters long, extended characters allowed). Once activated, MK gives you access to your files.

    MK uses a proprietary algorithm, so we'll have to reverse it first (quite boring). Then we'll look for a crack (GREAT). Eventually, i'll write an essay to tell the whole story (absolute NIGHTMARE).

    PART A. REVERSING OF MASTERKEY

    OK, i do not like "dead listing" approach, so we'll use Winice first. Launch MK, choose CASIMIR (UPPERCASE) as a key. Select a few directories to protect, then exit MK. Now launch it again: it'll ask for the key. If key is invalid, we get an error msg: "Invalid key".

    We'll focus on the mechanism that allows MK to tell a valid key from a wrong one.

    =======================================

    A1. Input handling

    =======================================

    So we enter invalid key : 1234567 in window "Enter your access code". Before we hit the OK button, enter Winice and set a BPX GetWindowTextA (this function is often used to handle inputs such as pwd, SN...). Now we can press OK. We land right into the function, which is called from code location CS:4212D1 (module Mkstrate):

    
    :004212CF 50                      push eax
    :004212D0 56                      push esi
    
    * Reference To: USER32.GetWindowTextA, Ord:015Eh
                                      |
    :004212D1 FF15C8A44200            Call dword ptr [0042A4C8]
    :004212D7 8B4D10                  mov ecx, dword ptr [ebp+10]
    :004212DA 6AFF                    push FFFFFFFF
    :004212DC E8F6E0FFFF              call 0041F3D7
    :004212E1 EB0B                    jmp 004212EE
    
    
    Our input is stored at data location DS:EAX, so we must trace access (Read and Write) to this location to find out how our input is handled. Since i get EAX=6B4610 (this value may differ on your PC), i set a BPR DS:6B4610 DS:6B4610+7 RW. We discover that input is read by function Lstrlen (code location CS:41F3EA). This function is used to compute string's length (7). Length is returned is EAX register, and copied to location DS:6B4608. We set a BPR on this location, and find out that length is compared against 2 values:
    
    :004213F7    cmp eax,[ebp+10]=0x3F=63   // input's length must be 
                      7                     // lower or equal to 63 characters
    
    
    :0040CE57    cmp [edi-08],00000005      // input's length must be 
                        7                   // greater or equal to 5 characters
    
    
    Then we reach code location CS:40CE94 where input is copied from DS:6B4610 to DS:69F3EC. Then input is copied again from DS:69F3EC to DS:6B45C0 (code location CS:41075C). Then the real thing begins at code location CS:40C573.

    =======================================

    A2. Building of K1

    =======================================

        input = 1234567 = 31 32 33 34 35 36 37 (hex)
        i[0]=31 , i[1]=32 , ... , i[6]=37
    
    init : ecx=0
           edx=0
           ebx=0
           edi=0
    
        :0040C571   mov esi,dword ptr [esi]    // points to mem location DS:6B45C0
    +-->:0040C573   movsx eax,byte ptr [esi+ecx] //read one character from input
    |   :0040C577   imul eax,eax //eax=(eax*eax)=i[cl]^2
    |   :0040C57A   add edx,eax  //edx=i[0]^2+i[1]^2+...+i[6]^2
    | L :0040C57C   test cl,01   
    | O :0040C57F   je 0040C585  //jump only if cl is even
    | O :0040C581   add ebx,eax  //ebx=i[1]^2+i[3]^2+i[5]^2
    | P :0040C583   jmp 0040C587
    |   :0040C585   add edi,eax  //edi=i[0]^2+i[2]^2+i[4]^2+i[6]^2
    |   :0040C587   inc ecx //ecx=ecx+1, get ready to read next character from input
    |   :0040C588   cmp ecx,ebp //ebp=input's length
    +--<:0040C58A   jl 0040C573 //if end-of-input not reached then go on 
        :0040C58C   xor ecx,ecx
        :0040C58E   mov dword ptr [esp+10],edx //=31^2+32^2+33^2+34^2+35^2+36^2+37^2
                                               //=4A0C
                                               //=K1oe (odd and even indices)
        :0040C592   mov dword ptr [esp+14],ebx //=     32^2     +34^2     +36^2
                                               //=1FB8
                                               //=K1o (odd only)
        :0040C596   mov dword ptr [esp+18],edi //=31^2     +33^2     +35^2     +37^2
                                               //=2A54
                                               //=K1e (even only)
    
    So K1oe = K1o + K1e
    
    If we dump data location DS:esp+10 here's what we have:
    
            <-  K1oe -> <-  K1o  -> <-  K1e  -> 
    K1   =  0C 4A 00 00 B8 1F 00 00 54 2A 00 00     (12 bytes)
    
    K1 is the concatenation of K1oe, K1o and K1e.

    NB: Intel processors use "little endian" notation. It means that a number is always stored in memory with lower byte at lower memory location. For instance, number 159A34B8 is stored in memory like this:

    
    ---------->
    B8 34 9A 15          (B8 is at address DS:x , 15 at address DS:x+3)
    
    
    Before going any further, let's take a closer look at code location CS:40C573.
    Instead of a simple MOV (MOVe), we have a MOVSX (MOVe with Sign eXtended). It means that:
    
    -if we have character in eax < 80 (ex: 40), MOV and MOVSX work the same:  
    
    MOV     ebx , eax   -->   ebx = 00000040
    MOVSX   ebx , eax   -->   ebx = 00000040
    
    -if we have character in eax >= 80 (ex: 90), MOV and MOVSX differ:
    
    MOV     ebx , eax   -->   ebx = 00000090
    MOVSX   ebx , eax   -->   ebx = FFFFFF90
    
    That's the reason why we'll focus on non-extended characters (<80).
    
    =======================================

    A3. Building of K2

    =======================================

    K1 is used by MK just after its creation:

    
    init : esi=0
           edx=0
    
    +-->:0040C59C   mov bl, byte ptr [esp+esi+10] //read one character from K1
    |   :0040C5A0   test bl, bl //check if character is null (00)
    |   :0040C5A2   je 0040C5BF //if null then skip it
    |   :0040C5A4   mov eax, esi
    |   :0040C5A6   cdq
    |   :0040C5A7   idiv ebp
    | L :0040C5A9   mov eax, dword ptr [esp+0000021C]
    | O :0040C5B0   mov eax, dword ptr [eax]
    | O :0040C5B2   mov dl, byte ptr [edx+eax] //read one char from input
    | P :0040C5B5   add dl,bl  //bl=(char i from input + char j from K1)
    |   :0040C5B7   mov byte ptr [esp+ecx+00000114],dl //store result in K2
    |   :0040C5BE   inc ecx
    |   :0040C5BF   inc esi //esi=esi+1, get ready to read next char from K1
    |   :0040C5C0   cmp esi, 0000000C //0xC=12 , check if end-of-K1 reached 
    +--<:0040C5C3   jl 0040C59C  //if not reached then go on  
    If input is shorter than 12 characters then MK reads again the first characters from input. MK does not use null bytes from K1. For instance, with:
    
                          <--- input (original) --->
    input (extended)   =  31  32  33  34  35  36  37  31  32  33  34  35 
    K1                 =  0C  4A  00  00  B8  1F  00  00  54  2A  00  00            
                          <--  K1oe  -->  <--  K1o   -->  <--  K1e   -->
    
    we get:
    
    K2                 =  31  32          35  36          32  33    
                         +0C +4A         +B8 +1F         +54 +2A
    
                       =  3D  7C          ED  55          86  5D
                                     
                
    So K2 = 3D  7C  ED  55  86  5D    (6 bytes)
    
    K2 = K2oe K2o K2e 
    note: since K2's characters are stored in a 1-byte register, MK only keeps 8 low-bits for each character. For instance: 31+EC =10D, MK keeps OD.

    =======================================

    A4. Building of K3

    =======================================

    K2 is used by MK just after its creation:

    
    
    +-->:0040C5CB   xor eax, eax  // set eax to 0
    |   :0040C5CD   mov edi, 0000005E 
    |   :0040C5D2   mov al, byte ptr [esp+esi+00000114] //read one char from K2
    | L :0040C5D9   cdq
    | O :0040C5DA   idiv edi // al = (al modulo 5E) = remainder of (al/5E) = (al%5E)
    | O :0040C5DC   add dl, 20 // al = al +20
    | P :0040C5DF   mov byte ptr [esp+esi+00000114],dl // store result in K3 
    |                                                  // (overwrite K2)
    |   :0040C5E6   inc esi //esi=esi+1, get ready to read next char from K1
    |   :0040C5E7   cmp esi, ecx  // ecx=length of K2
    +--<:0040C5E9   jl 0040C5CB   // if end-of-K2 not reached then go on 
    
    
    With K2 = 3D 7C ED 55 86 5D   we get:
    
         K3 = (3D%5E)+20 (7C%5E)+20 (ED%5E)+20 (55%5E)+20 (86%5E)+20 (5D%5E)+20
    
                  
    So   K3 = 5D  3E  51  75  48  7D    (6 bytes)
    
    K3 = K3oe K3o K3e 
    Building K3 this way, MK makes sure that each character from K3 is printable. Each character will have an ASCII value between 20 and 7E. For instance, here we have K3: ]>QuH}

    On my PC, K3 is at memory location DS:69F168. We set a BPR to learn how MK uses it. We land at code location CS:41F148 where K3's length is computed. Then we land at code location CS:41075C where K3 is copied to DS:6B46B0. So we set one more BPR on this new data location.

    =======================================

    A5. Final cmp

    =======================================

    Yes, we're nearly done! We land at code location CS:412A70 where K3 is read:

    
    
    +-->:00412A70   mov eax,dword ptr [edx] //read 4 characters from K3
    |   :00412A72   cmp al,byte ptr [ecx]   //compare 1st char from K3 against
    |                                       //1st char from a mysterious string  
    |                                       //stored at DS:ECX (KEY_CHK)
    |   :00412A74   jne 00412AA4 //if not the same then jump         
    |   :00412A76   or al,al     //check if al=0 (end-of-K3 reached?)
    |   :00412A78   je 00412AA0  //if not reached then go on, else jump
    |   :00412A7A   cmp ah,byte ptr [ecx+01] //compare 2nd char from K3 against
    | L                                      //2nd char from KEY_CHK     
    |   :00412A7D   jne 00412AA4
    |   :00412A7F   or ah,ah
    | O :00412A81   je 00412AA0
    |   :00412A83   shr eax,00000010 // shift eax 10 times on the rigth 
    |                                // ABCDEFGH --> 0000ABCD
    | O :00412A86   cmp al,byte ptr [ecx+02] //compare 3rd char from K3 against 
    |                                        //3rd char from KEY_CHK
    |   :00412A89   jne 00412AA4
    | P :00412A8B   or al,al
    |   :00412A8D   je 00412AA0
    |   :00412A8F   cmp ah,byte ptr [ecx+03] //compare 4th char from K3 against 
    |                                        //4th char from KEY_CHK
    |   :00412A92   jne 00412AA4
    |   :00412A94   add ecx,00000004 // get ready to read next 4-byte value
    |   :00412A97   add edx,00000004 // ditto
    |   :00412A9A   or ah,ah
    +--<:00412A9C   jne 00412A70 //if end-of-K3 not reached then go on 
    
    If K3 is the same as KEY_CHK then we land here:
    :00412A9E   mov edi,edi
    :00412AA0   xor eax,eax // set eax to 0...
    :00412AA2   ret //...then return
    
    If K3 differs from KEY_CHK then we land here:
    :00412AA4   sbb eax,eax
    :00412AA6   shl eax,1
    :00412AA8   inc eax // set eax to 1...
    :00412AA9   ret //...then return
    
    We have: KEY_CHK  e=2E@r
             K3       ]>QuH}
    
    Since strings are not the same, we have eax=1.

    We follow the return and we find out that the final test takes place at CS:40C473. If eax=0 then MK lets you in, otherwise it displays an error msg: invalid key! Naughty boy, do not do it again or i call the police, etc...

    But if instead of 1234567 we enter the correct password then K3=KEY_CHK, and eax is set to 0.

    A6. Monitoring Registry

    OK, we're done with reversing (thank you Winice). But one big question remains: where does MK store KEY_CHK??? Well, it could be in some hidden file, somewhere on the HD, or stored in some ini file... To be honest, i had no idea where the f**k it could be, so i tried the easy way once again: i launched a registry monitor (Regmon, see "What do i need?" part), then i launched MK, entered wrong pwd, and closed MK. Then i looked at Registry activities recorded: BINGO! Here it was:

    
    Mkstarte	
    QueryValueEx	
    HKCU\software\Kruptos Solutions\MKSTARTER\Security\Access	SUCCESS	"e=2E@r"	
    This is where KEY_CHK is stored.
    
    note: there is a MS-DOS command to extract a Registry's value to a file:
    
    @regedit /E file_for_storage "HKEY_USERS\.Default\Software\Kruptos Solutions\MkStarter\Security" 
    PART B. BREAKING OF MASTERKEY

    Copyright October, 2000 by Casimir.

    Mail Casimir

    Converted to hypertext by Joe Peschel October 19, 2000.