by
CASIMIR
Part A
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
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)
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
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).
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.
=======================================
=======================================
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 004212EEOur 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 charactersThen 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.
=======================================
=======================================
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).=======================================
=======================================
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 onIf 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 5Dnote: 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.So K2 = 3D 7C ED 55 86 5D (6 bytes) K2 = K2oe K2o K2e
=======================================
=======================================
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)+20Building 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}So K3 = 5D 3E 51 75 48 7D (6 bytes) K3 = K3oe K3o K3e
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.
=======================================
=======================================
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.
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.