CuteFTP KeyFile Protection
advanced
Advanced reversing
30 October 1998
by The+Q
+cracker
Courtesy of Reverser's page of reverse engineering
slightly edited
by reverser+
fra_00xx
98xxxx
handle
1100
NA
PC
This is a very important essay. A very fine protection is here analyzed and explained by an outstanding +cracker. It's a reading that all protectors and all crackers should head and follow until they understand it. Since CuteFtp is a very good and widespread program, the keygenerators for this target have already been released by various scene and warez crackers months ago, so I don't believe we will damage Alex Kunadze publishing this. Actually the contrary should be true, at least I hope: reading how a good cracker understands a good protection, you can try to eliminate the 'weak' points of that same protection... provided you'r a good programmer, with enough clues.
I may add, as last observation, that CuteFtp is a really outstanding program. I'm using it personally on my windoze machines (actually I'm using its older version 2.0, because I don't like to change version every three minutes), and, believe me, I choose very carefully my programs. Therefore I do recommend to all my readers to get this target, crack it black and blue, following this extremely interesting essay by The+Q and learning a lot in the process, and then pay for it. A worthy program and a worthy protection, they all deserve it.
Alex should head the concluding words by The+Q, though:
As always, I searched for other parts in the target to study, and i found
that cuteftp encryptes the password for the ftps u put on the list. After a simple
look with HexEditor on TREE.DAT u see the password is encypted by a simple XOR.
Very disapointing after having seen the though protection for the key-file. 
(Oh well , it's no wonder the programmer doesn't give much when it comes 
to protect YOUR files..)
There is a crack, a crack in everything That's how the light gets in
Rating
( )Beginner ( )Intermediate (X)Advanced ( )Expert

CuteFTP has a good Key-File protection.
Here's we'll study it, and I'll show you a few techniques for reversing algorithms.
CuteFTP KeyFile Protection

Written by The+Q


Introduction
About half a year ago I tried to crack cuteftp key-file protection.. unsuccessfully.
I only got a cracking hang-over the following day (a cracking hang-over: when u wake up,
the first and only thing u see is the protection executed FAST and repeatedly. It's like
when ur stuck in a chess game , and u start to see ALL possible solutions at-once..
What can i say .. It's a cool feeling =)
Anyway , I've decided to give it another shoot (Never Say Never!;).
This time with different techniques to solve it - both mathematical and somewhat poetic.
I hope u'll enjoy and learn from this. :)



Tools required
SoftIce
W32Dasm
Hex-Editor (HIEW is my fav.)
And some backround music (Beethoven, Brahms, R.E.M .. =)

Target's URL/FTP
http://www.cuteftp.com (Any v2.x ; Newest is v2.6 , and I'll refere to it)

Program History
CuteFTP has been using a Key-File protection since the beginning (as far as i know).However the protection scheme has changed in ver2.x . We'll Study this protection .. and crack it ;)
Btw, have u noticed that all communication software targets have a good protection? (Terminate, CuteFTP, BulletProof =)

Essay
OK , let's start.
Once CuteFTP is running, fire up SoftIce and breakpoint on the CreateFileA Api, 
which is used to open files:Bpx CreateFileA do "d *(ss:esp+4)".
Help\About in CuteFTP menu, and SI pops. The data window holds the filename
parameter of the API. Our keyfile name is CuteFtp.Key . After a few P-RETs (F12)
we reach the read file procedure :

:00411DB3    push 100          ; 100h Bytes to read
:00411DB8    lea ecx, dword ptr [esp+34]
:00411DBC    push 01
:00411DBE    push ecx          ; Pointer to Buffer that gets the bytes
:00411DBF    call 00439BD0     ; Read Key-File
:00411DC4    add esp, 10
:00411DC7    mov edi, eax      ; Eax and edi with the acctualy bytes read
:00411DC9    push esi
:00411DCA    call 00439B20     ; Not importent
:00411DCF    add esp, 04
:00411DD2    cmp edi, 100
:00411DD8    jge 00411DE6      ; If length of file is 100h bytes (or more) then
                               ; continue with reg check

Ok,quickly make a file named CuteFtp.Key, and fill it with 00-FFh (100h bytes in 
total) (Don't do it manualy , code a little prog to do it..:)
Now we show this 'reporter clerence' to the program, so we can goto CS:00411DE6..

:00411DE6    push 19F          ; Seed value
:00411DEB    call 00439AE0     ; Set seed value
:00411DF0    add esp, 04
:00411DF3    xor esi, esi      ; Count=0
:00411DF5    lea edi, dword ptr [esp+0C] ; edi points to the table
:00411DF9    call 00439AF0     ; Play with seed , and produre a 16bit number
:00411DFE    movsx edx, ax     ; 16bit to 32bit value
:00411E01    xor edx, esi
:00411E03    inc esi           ; Inc Count
:00411E04    mov dword ptr [edi], edx  ; Update table with number produced in CALL above
:00411E06    add edi, 04       ; Inc table index
:00411E09    cmp esi, 08
:00411E0C    jl 00411DF9       ; Produce 8 numbers (32bit each one)

This routine produces a set of constant values . This Set is not linked to our file,
meaning everytime you run the protection scheme it will give out the same set of values
, regurdless of the data in the file.It's simple to test: Set a breakpoint on the file 
buffer, and see that this routine doesn't break on it. (Procedures deal with the 
parameters given to it, so if the pointer to our file buffer is not input, it's 
most likely this procedure won't touch it..).
Notice that we don't care HOW these values are driven.All we care is that it's constant.
It's a 'black box' that gives out the same numbers , if the input (seed) is the same.
Here is this table , which will be refered as Const_Set:
:DD (esp+0C) L 20
0167:0061F2F8 00000571  00002E55  00007277  00006A6B      q...U...wr..kj..
0167:0061F308 0000737F  00000BAB  00005646  000074BD      s......FV...t..
( ConstSet:Array[1..8] of longint =  $571,$2E55,$7277,$6A6B,$737F,$0BAB,$5646,$74BD; )

We continue with the following:
:00411E0E    lea eax, dword ptr [esp+0C]  ; Points to the Const_Set
:00411E12    lea ecx, dword ptr [esp+22C] ; Points to Table_Head
:00411E19    push eax
:00411E1A    call 00429660   ; Copy Const_Set (20h bytes) to [ECX] (-Head of table)
                             ; (The decryption uses this Table_Head and the Table 
                             ;  that follows)
; [ESP+2C] points to begining of our file buffer. It's maked as File[0]
; As u can guess, [ESP+4C] points to the 21h-st (4C-2C=20h) byte in the file - File[20]
:00411E1F    lea ecx, dword ptr [esp+2C]  ; File[0]
:00411E23    push 04                      ; Decrypt 04*08=20h bytes
:00411E25    push ecx
:00411E26    lea ecx, dword ptr [esp+234] ; Points to Table_Head
:00411E2D    call 00429630                ; ** Decryption Procedure
:00411E32    lea edx, dword ptr [esp+2C]  ; File[0] after decryption
:00411E36    lea ecx, dword ptr [esp+22C] ; Points to Table_Head
:00411E3D    push edx
:00411E3E    call 00429660   ; Update Table-head with 20h bytes from decrypted File[0]

:00411E43    lea eax, dword ptr [esp+4C]  ; File[20]
:00411E47    push 01                      ; Decrypt 01*08=08h bytes
:00411E49    push eax
:00411E4A    lea ecx, dword ptr [esp+234] ; Points to Table_Head
:00411E51    call 00429630                ; ** Decryption Procedure
:00411E56    mov ebp, dword ptr [esp+4C]  ; File[20] after decryption

:00411E5A    lea ecx, dword ptr [esp+54]  ; File[28]
:00411E5E    push 1B                      ; Decrypt 1B*08=D8h bytes
:00411E60    push ecx
:00411E61    lea ecx, dword ptr [esp+234] ; Points to Table_Head
:00411E68    call 00429630                ; ** Decryption Procedure
:00411E6D    lea edi, dword ptr [esp+54]  ; File[28] after decryption - UserName
:00411E71    or ecx, FFFFFFFF
:00411E74    xor eax, eax
:00411E76    lea edx, dword ptr [esp+12C]
:00411E7D    repnz                        ; Search NULL (indicates end of NAME string)
:00411E7E    scasb
:00411E7F    not ecx
:00411E81    sub edi, ecx
:00411E83    mov eax, ecx
:00411E85    mov esi, edi
:00411E87    mov edi, edx
:00411E89    shr ecx, 02
:00411E8C    repz                         ; Move 'clean' Name to another location
:00411E8D    movsd
:00411E8E    mov ecx, eax
:00411E90    mov eax, 28
:00411E95    and ecx, 03
:00411E98    repz                         ; Still moving ..
:00411E99    movsb
:00411E9A    xor ecx, ecx
:00411E9C    movsx edx, byte ptr [esp+eax+2C]    ; [ESP+28+2C] = [ESP+54] => File[28]
:00411EA1    add ecx, edx
:00411EA3    inc eax
:00411EA4    cmp eax, 78
:00411EA7    jl 00411E9C             ; Do CheckSum on 50h (78-28) bytes from File[28]
:00411EA9    cmp ecx, ebp
:00411EAB    jne 00411EFA            ; If ChecSum of Name (File[28]) is not equal
                                     ; to value in ebp (File[20]), then NOT REGISTERED
---------------------------------------------------
** Decryption Procedure :
:00429630    push ebx
:00429631    push edi
:00429632    mov edi, dword ptr [esp+10]  ; EDI with Number of 8_byte_blocks to decrypt
:00429636    mov ebx, ecx
:00429638    test edi, edi
:0042963A    jle 00429650
:0042963C    push esi
:0042963D    mov esi, dword ptr [esp+10]  ; ESI points to block of encrypted bytes
:00429641    push esi
:00429642    mov ecx, ebx
:00429644    call 00429400                ;  *** Main decryption routine
:00429649    add esi, 08                  ; Next 8_byte_block to decrypt
:0042964C    dec edi
:0042964D    jne 00429641
:0042964F    pop esi
:00429650    pop edi
:00429651    pop ebx
:00429652    ret 0008


Now that you've read it , go and read it again! Try to see the logic behind it, 
even before analyzing the decryption routine.(Some comments are added after analyzing,
so u might not understand them , but u can understand the logic in this code).
Before we summerize , lets do some simple checks to figure out general properties
of our decryption routine.It's adviced to do so before a deep analyze on the routine 
(which can lead to a cracking hang-over ;).. So analyze in layers.. like in graphics :)
 As u have propably figured out, the OUT buffer - where the decrypted bytes are 
written- is the IN buffer. Decrypted bytes are written over the input-encrypted 
bytes.
 Break-point with SI at location **CS:00429642 , and look at the file-buffer
:D DS:ESI
U'll see our data at File[0]: 00 01 02 03 04 05 06 07-08 09 0A 0B 0C 0D 0E 0F
New let it decrypt - trace over with SI (F10) until CS:00429649 .
Look at the data window :     04 55 04 05 00 11 9E 00-08 09 0A 0B 0C 0D 0E 0F
The main decryption routine (***CS:00429400) decrypts 8-bytes (64bit) each time.
The second parameter to the **Decryption procedure is the number of 8_byte_blocks to
decrypt , so when '1B' was pushed (CS:00411E5E), it ment decrypt D8 (1B*08) bytes.
 Next let's check if the decryption is also used for encryption .It's very rare, but
it does exist (Y=X xor 3B -> X=Y xor 3B ..).
Breakpoint as before , but now the input will be : 04 55 04 05 00 11 9E 00
Let it decrypt .. and look at the Data window , it's NOT 00 01 02 .. 07 , so
it's not going to be so easy ... GOOD! ;)
 Next check is to see if it's inferior to order of execution .
Input :                     00 01 02 03 04 05 06 07-00 01 02 03 04 05 06 07
After decryption (twice) :  04 55 04 05 00 11 9E 00-04 55 04 05 00 11 9E 00
Ok , it doen't matter when you decrypt it.

Summerize
1. Decryption procedure properties:
  -The main decryption routine decryptes 8_bytes each time.
  -Out buffer = In buffer , meaning Decrypted bytes are written over the 
   input-encrypted bytes.
  -Decryption routine is NOT the encryption routine.
  -Inferior to order of execution.
2. Keyfile properties:
  -Name: CuteFtp.Key
  -Length: 100h bytes.
  -Format:     -------------------------   -  -          -
                   New_Table_Head           20h

               -------------------------   -  -
                  UserName CheckSum         08
               -------------------------   -  -         100h
                UserName (Ends with null)

                                            D8h


               -------------------------   -  -          -
3. General work of the protection:
  -Create a Constant Set of values - 8 32bit (=20h bytes) numbers.
  -Copy Constant_Set to Table_Head.
  -Table_Head = Decrypted 20h bytes from File[0].
  -Correct CheckSum value = Decrypted 8h bytes from File[20] .(Now the decryption
    uses the updated Table_Head for decryption)
  -UserName = Decrypted D8h bytes from File[28]. Null indicates end of Name string.
  -Do CheckSum on UserName.
  -If ChecSum of Name (File[28]) is not equal to value in ebp (Correct 
   CheckSum-File[20]) then NOT REGISTERED.

Ok , so far so good. If u missed anything until here , i suggest u d/l the prog. and
play with it your-self .

Now comes the most importent part : Analyzing the decryption and reversing it. *** Main decryption routine : :00429400 push ebx :00429401 push ebp :00429402 mov ebp, dword ptr [esp+0C] :00429406 push esi :00429407 mov esi, ecx :00429409 push edi ; EBP points to 8_byte buffer , with the encrypted bytes. ; ESI points to Table_Head :0042940A mov edi, dword ptr [ebp+00] ; \ :0042940D mov ebx, dword ptr [ebp+04] ; Read the 8 encrypted bytes ; / :00429410 mov eax, dword ptr [esi+00] :00429412 add eax, edi :00429414 push eax :00429415 call 004293A0 ; ****Decrypt_Value_Function :0042941A mov edx, dword ptr [esi+04] :0042941D xor ebx, eax :0042941F mov ecx, ebx :00429421 add ecx, edx :00429423 push ecx :00429424 mov ecx, esi :00429426 call 004293A0 ; ****Decrypt_Value_Function :0042942B mov ecx, dword ptr [esi+08] :0042942E xor edi, eax .. Pattern repeats .. .......... :00429601 mov ecx, dword ptr [esi+04] :00429606 mov eax, edi :00429608 add eax, ecx :0042960A mov ecx, esi :0042960C push eax :0042960D call 004293A0 ; ***Decrypt_Value_Function :00429612 mov ecx, dword ptr [esi+00] :00429614 xor ebx, eax :00429616 add ecx, ebx :00429618 push ecx :00429619 mov ecx, esi :0042961B call 004293A0 ; ***Decrypt_Value_Function (This Func is Called 32 times) :00429620 xor edi, eax ; \ :00429622 mov dword ptr [ebp+00], ebx ; Write the 8 decrypted bytes :00429625 mov dword ptr [ebp+04], edi ; / --------------------------------------------------- ****Decrypt_Value_Function : :004293A0 mov eax, dword ptr [esp+04] ; The value pushed to the function :004293A4 push esi :004293A5 mov esi, ecx ; ESI points to Table_Head. ; ESI+20 -> Decryption_Table#1 ; ESI+120 -> Decryption_Tabel#2 .. :004293A7 mov ecx, eax :004293A9 shr ecx, 18 :004293AC movsx edx, byte ptr [ecx+esi+20] :004293B1 xor ecx, ecx :004293B3 mov cl, byte ptr [esp+0A] :004293B7 shl edx, 08 :004293BA movsx ecx, byte ptr [ecx+esi+120] :004293C2 or edx, ecx :004293C4 xor ecx, ecx :004293C6 mov cl, ah :004293C8 and eax, FF :004293CD shl edx, 08 :004293D0 movsx ecx, byte ptr [ecx+esi+220] :004293D8 movsx eax, byte ptr [eax+esi+320] :004293E0 or edx, ecx :004293E2 pop esi :004293E3 shl edx, 08 :004293E6 or edx, eax :004293E8 mov eax, edx :004293EA shl eax, 0B :004293ED shr edx, 15 :004293F0 or eax, edx :004293F2 ret 0004 Beutiful isn't it ? :) A little explaintion on the decryption table is in place. Breakpoint at CS:0042940A, and :D DS:ESI. The Table_Head is now at the Data window . This is the strcture of the Decryption Table: ------------------------- - - - [ESI] Table Head 20 ------------------------- - - [ESI+20] Decryption Tabel #1 100 ------------------------- - - [ESI+120] Decryption Tabel #2 100 ------------------------- - - 420h [ESI+220] Decryption Tabel #3 100 ------------------------- - - [ESI+320] Decryption Tabel #4 100 ------------------------- - - - As mentioned before , the Table_head is first the Constant_Set , that is used to decrypt the first 20h bytes from File[0] Then these 20h decrypted bytes are copied as the New_Table_Head , and it will be used to decrypt the other fields of the key-file. There is a function that is called 32 times during the 8_bytes decryption. I call it 'The survival of the fittest' function .. Let's see why . A 4byte number is pushed to the function : 03020671 Now each byte is used as INDEX, to get another byte from coresponding decryption table: 03 02 06 71 __ __ __ __ Input (EAX): |__|__|__|__| A=Table#1 [03] = EE B=Table#2 [02] = AE C=Table#3 [06] = 26 D=Table#4 [71] = D2 Now for the rules of the jungle : MovSx ecx, byte ptr [Table#X+Offset] Or edx,ecx This means that when a SIGNed number is loaded to ecx , he will wipe out the other numbers in edx (becouse of the OR). Example: EDX=Tabel#1 [03] = FFFFFFEE (All the other 'F' is because of extended sign) Now a new spices evolve.. but a strong property is not beeing able to run faster, get more food or be wizer to build tools. A strong property is being a SIGNED value.. ECX=Table#2 [02] = FFFFFFAE (EDX is shifed left .. = FFFFEE00) Now the struggle to survive.. If ecx is signed, will wipe out the old number, so OR EDX,ECX EDX=FFFFFFAE Another spices evolve.. this time a weak one .. ECX=Table#3 [06] = 00000026 (EDX is shifted left .. = FFFFAE00) The big fight for teritory .. only this time ECX is not signed , so he'll have to Share it .. OR EDX,ECX EDX=FFFFAE26 ........... After all this , who ever survives is being scrambled a bit , and this is the result of the function. It's nice to look at functions this way , isn't it ? ;) Before we're going to reverse the protection algorithm , let's summerize : - The main decryption routine decryptes 8_bytes each time . - EBX and EDI gets those encrypted bytes (CS:0042940A). - ESI points to the decryption table structure. - The Table_Head is used to calculate the Input number for the Function. - The Function uses the rest 4 decryption tables to calcualte result. It uses the 'survival of the fittest' rules ;) - The function is called 32 times in total. - There is a pattern the repeats in the main decryption routine.
Now it's time to carefuly study the decryption, so we could build our own encrypter, and create valid key-files. Where to start ? Do we need to revers the decrypt_value function ? If the decryption adds a value from the table_head to one of the encrypted bytes (ebx or edi), do we need to use FSUB as reverse ? This is where i got lost the other time .. Now however, we'll use a different techniqe , a more technical one. Lets transtale the decryption procedure (CS:00429400) to a higher leng. Buffer : Encrypted 8 bytes Define : A = EDI B = EBX Table_head:Arry[1..8] of longint X : a longint var. This simple code : :00429410 mov eax, dword ptr [esi+00] :00429412 add eax, edi :00429414 push eax :00429415 call 004293A0 :0042941A mov edx, dword ptr [esi+04] :0042941D xor ebx, eax Will be transtaled as : X:= A + Table_head[1]; B:= B Xor Func(X); ( Remember ESI points to table_head ? So dword ptr [ESI+0] is Table_head[1] , dword ptr [ESI+4] is Table_head[2] and so on.) We continue translating this procedure , and see a clear pattern appears : (CS:00429410) A:= Buffer[0]; {EDI} B:= Buffer[4]; {EBX} X:= A + Table_head[1]; B:= B Xor Func(X); X:= B + Table_head[2]; A:= A Xor Func(X); X:= A + Table_head[3]; B:= B Xor Func(X); X:= B + Table_head[4]; A:= A Xor Func(X); X:= A + Table_head[5]; B:= B Xor Func(X); X:= B + Table_head[6]; A:= A Xor Func(X); X:= A + Table_head[7]; B:= B Xor Func(X); X:= B + Table_head[8]; A:= A Xor Func(X); X:= A + Table_head[8]; B:= B Xor Func(X); X:= B + Table_head[7]; A:= A Xor Func(X); . . . X:= A + Table_head[2]; B:= B Xor Func(X); X:= B + Table_head[1]; A:= A Xor Func(X); Buffer[0]:=B; Buffer[4]:=A; (CS:00429628) Now all there is left to do is reverse this routine : X:= A + Table_head[1]; B:= B Xor Func(X); X:= B + Table_head[2]; A:= A Xor Func(X); But before we jump to it , lets look at some simple examples to reverse, and formaly explain the rules to reverse , and what reversing means.
Example 1 : A:=A+12; B:=B xor A; First , number the lines: 1. A:=A+12; 2. B:=B xor A; Next add the get() and Print() to show exactly what is input, and what is output: Get(A,B); 1. A:=A+12; 2. B:=B xor A; Print(A,B); Next change the Output vars to different names : Get(A,B); 1. A^:=A+12; 2. B^:=B xor A^; Print(A^,B^); What we want to do when reversing : __________ X | | Y --> | Box | --> Input | | Output ---------- __________ Y | Reversed | X --> | Box | --> Input | | Output ---------- Now to reverse it , go from the END of the routine . meaning first deal with line #2 , and then #1. Get(A^,B^); {It's now reversed ;) } 2. B^:=B Xor A^; ==> B:=B^ Xor A^; 1. A^:=A+12; ==> A:=A^-12; Print(A,B); And the revese is done =) This technique is a bit too technical .. but it works! Example 2 : X:=A ROL 2; B:=B Xor X; A:=Func(B) Xor X; Step#1-Number lines 1. X:=A ROL 2; 2. B:=B Xor X; 3. A:=Func(B) Xor X; Step#2-Show exactly what is input , and what is output Get(A,B); 1. X:=A ROL 2; 2. B:=B Xor X; 3. A:=Func(B) Xor X; Print(A,B); Step#3-Deal with output as if they are different vars (change names) Get(A,B); 1. X :=A ROL 2; 2. B^:=B Xor X; 3. A^:=Func(B^) Xor X; Print(A^,B^); Step#4-Minimum lines is better Get(A,B); 1. B^:=B Xor (A ROL 2); 2. A^:=Func(B^) Xor (A ROL 2); Print(A^,B^); Step#5-Reverse Get(A^,B^); 2. A^:=Func(B^) Xor (A ROL 2); || (A ROL 2)=A^ Xor Func(B^); ==> A:= ( A^ Xor Func(B^) ) ROR 2; 1. B^:=B Xor (A ROL 2); ==> B:= B^ Xor (A ROL 2); Print(A,B); As u can see, here we didn't have to reverse Func(X).. no need to build Un-Func(X)..
Reversing The Decrypion Ok , lets reverse : X:= A + Table_head[1]; B:= B Xor Func(X); X:= B + Table_head[2]; A:= A Xor Func(X); ---------------------------- Get(A,B); 1. X:= A + Table_head[1]; 2. B:= B Xor Func(X); 3. X:= B + Table_head[2]; 4. A:= A Xor Func(X); Print(A,B); ---------------------------- Get(A,B); 1. X := A + Table_head[1]; 2. B^:= B Xor Func(X); 3. X := B^ + Table_head[2]; 4. A^:= A Xor Func(X); Print(A^,B^); ---------------------------- Get(A,B); 1. B^:= B Xor Func(A + Table_head[1]); 2. A^:= A Xor Func(B^+ Table_head[2]); Print(A^,B^); ---------------------------- Get(A^,B^); 2. A^:= A Xor Func(B^+ Table_head[2]); ==> A:= A^ Xor Func(B^+ Table_head[2]); 1. B^:= B Xor Func(A + Table_head[1]); ==> B:= B^ Xor Func(A + Table_head[1]); Print(A,B); That's It =) Now the encrypter will be this pattern , appeared backwords than in the decryption: B:= Buffer[0]; A:= Buffer[4]; A:= A Xor Func(B + Table_head[1]); B:= B Xor Func(A + Table_head[2]); A:= A Xor Func(B + Table_head[3]); B:= B Xor Func(A + Table_head[4]); A:= A Xor Func(B + Table_head[5]); B:= B Xor Func(A + Table_head[6]); . . . A:= A Xor Func(B + Table_head[2]); B:= B Xor Func(A + Table_head[1]); Buffer[0]:=A; Buffer[4]:=B; (As u see, we don't need to reverse 'the survival of the fittest' function... Leave nature alone ;) After reversing the decryption, we now have the encryption procedure to create our valid key-files. Building a key-maker would be a matter of minutes of course, yet there's absolutely no need at all: there are infact already a series of keygenerators out there on the warez world, the best one being Phrozen Crew's PC_CUT2X.ZIP.


Final Notes
As always , I searched for other parts in the target to study, and i found
that cuteftp encryptes the password for the ftps u put on the list. After a simple
look with HexEditor on TREE.DAT u see the password is encypted by a simple XOR.
Very disapointing after  after having seen the though protection  for the key-file. 
(Oh well , it's no wonder the programmer doesn't give much when it comes to 
protect YOUR files..)

Anyway , i hope someone has learned from this.
Feel free to ask me anything (but crack-requests): Phrozen_q(at)CyberDude(point)Com

Greetings to all PC members, Reverser+ and all the crackers in the world =)

The+Q



Ob Duh
I wont even bother explaining you that you should BUY this target program if you intend to use it for a longer period than the allowed one. Should you want to STEAL this software instead, you don't need to crack its protection scheme at all: you'll find it on most Warez sites, complete and already regged, farewell.

You are deep inside reverser's page of reverse engineering, choose your way out:

redhomepage redlinks redsearch_forms red+ORC redstudents' essays redacademy database
redreality cracking redhow to search redjavascript wars
redtools redanonymity academy redcocktails redantismut CGI-scripts redmail_reverser
redIs reverse engineering legal?