                 How to crack "uncrackable" test4 by LordByte

                            by Crook, 14 June 1997
                           updated on 8 August 1997


                                What is test4?


     Test4  is  fourth  in  row of small challanges from LordByte. This one is
1251 bytes long and it was uncrackable for some time (I don't know when it was
written).  Quoting author of this test "the only purpose of it is to challenge
superior Crackers. ONLY password can be accepted. NO PATCH can qualify!".
     The  technique  used  in  this  test  is  called  one-time  pad. If every
variation of this method is so easily cracked, you can forget about it.

                       Target audience of this tutorial


     Everyone  wishing  to  learn  something. You MUST know assembly and basic
math before trying to read this. Of course you can read without even trying to
think  how  was it done, but it's just wasted time. The knowledge of C is also
an advantage, because I'm going to show you program in C which does whole work
for us.


                                 Brute force


     When there are passwords around there is also a brute force approach. But
after short calculations I've started to think about some smarter method.
     There  are  2^96  possible  passwords.  Assuming  that  you've  written a
program,  which can check 1 million passwords per second, it means you have to
wait 2^49 years, until it finishes.
     After  I've  cracked  test4  I  realized  that  there  are  2^60 possible
passwords.  But  even knowing this brute force is slow: statisticaly for every
2^36 wrong passwords, there is one good. Checking million passwords per second
it would take about 18 hours. My method takes about 1 minute on 486DX/33... ;)
                                         [This paragraph was dedicated to ACP]

                                 Let's start


     The  first  thing  you  must  do is to understand the test4.com. Run your
favourite  debugger  (Turbo  Debugger will be enough) and look deeply into the
code,  trying  to  understand  what's  doing  inside  of  it.  Without perfect
understanding  of method password is processed you won't be able to understand
further  parts  of  this  tut. However, below is disassembly of vital parts of
test4.com to help you with this task.

     mov dx, offset text1
     call show_text
     call hook_int
     mov dx, offset text2
     call show_text
     mov dx, offset buffer
     mov ax, 0A00h         ; get password
     int 21h

     This  is  the start of TEST4. You're writing some messages on the screen,
hooking  time interrupt to blink the frame (patched by me, so there will be no
such   effect),  and  getting  password  into  the  buffer.  As  far,  nothing
complicated.

start:
     mov ebx, 0
get_4_bytes_out_of_password_into_eax:
     lea esi, [ebx+offset password]
     lea edi, table
     mov eax, [esi]
     cmp ah, 0Dh
     jz manipulate_with_password
do_xor:
     xor [edi], eax
     rol eax, 2
     inc edi
     cmp dword ptr [edi+4], 0
     jnz do_xor
     inc ebx
     jmp short get_4_bytes_out_of_password_into_eax

     Let me tell you what's going there. In start you're zeroing ebx, which is
index register from now on - it's char number in password. Next, load into esi
address  of  char  number  ebx in password, and into edi address of the table,
which will be xored by the values depending on password. Then you're getting 4
bytes  of  the  password  into  eax  and  checking if you're on the end of the
password. If you are, then you must change password (of course by xoring it by
some values).
     Code  after  do_xor label is the main part of this program. Four bytes in
the  table  are xored by the eax. Then eax is rolled 2 bits to the left, index
in  the table (edi) is increased by one. You don't want to xor anything except
the  table, so there must be checking if you're out of table boundaries, which
is  done  in the next line. If you've whole table xored, the ebx (index in the
password) is increased by one, and the whole story begins once again.

manipulate_with_password:
     lea esi, table
     mov eax, [esi]
     lea esi, password
     xor [esi], eax
     xor [esi+4], eax
     xor [esi+8], eax
     inc pass_count
     cmp pass_count, 3
     jnz not_3_pass
     mov xor_key, eax
not_3_pass:
     cmp pass_count, 5
     jnz start

     This part is easy to understand: get first four bytes from the table into
eax,  then xor password with it. Next increase pass count. If it's third pass,
you're storing eax for later use (to decrypt ciphered message). If it's fifth,
you can go further.

     mov ebx, 0
check:
     lea esi, [ebx+offset table]
     lea edi, [ebx+offset target_table]
     mov eax, [esi]
     xor [edi], eax
     jnz bad_password
     add ebx, 4
     cmp dword ptr [esi+4], 0
     jnz check

     This  piece of code is also pretty obvious - the good password transforms
original  table  into  target_table  (also  stored  in  file). Checking if the
password  is good is reduced to check if both, table and target_table, are the
same.  Above  snippet  of code does just that - if the tables are different it
means the password is bad. If they're the same - OK, user is a good cracker ;)

     mov si, good_password_message
     mov eax, xor_key
xor_message:
     xor [si], al
     rol eax, 4
     inc si
     cmp byte ptr [si], 24h
     jnz xor_message
     mov dx, good_password_message
     call show_text
     jmp short end_program

     Here  the  text  is  decrypted. The algorithm is very simple, however you
should study it, because it's important part of the crack.
     If you perfectly understand what is going on you can read furher - if you
have  any  doubts  then  read this again and again until you understand. It is
strongly recommended to know exactly how it works.


               Main idea of crack (or, should I say, croock ;)


     The  target_table  can  be  presented  as a row of bits. Nothing exciting
about  this. But it can be also presented as value (0 or 1) and number of bits
from the password it is xored. And that's exciting, because it's the main idea
which  allowed  me to make C program to crack this program. Don't worry if you
don't understand anything - I'll show you an example.
     Let's  assume  you've got target value: 0xe9, and the source value: 0x5f.
You've got also a "magic box" which transforms source value into target value:

source value (0x5f) --> [MAGIC BOX] --> target_value (0xe9)

     You  also know that magic box uses just xor to transform values. You even
know  the  schema  of this xors, but we don't know the password. Let's say the
password has 4 bits.

0 bit from source xored by 0, 1, 3 bits from password gives 0 bit from target
1 bit from source xored by 1, 2, 3 bits from password gives 1 bit from target
2 bit from source xored by 0 bit from password gives 2 bit from target
3 bit from source xored by 1, 2 bits from password gives 3 bit from target
4 bit from source xored by 3 bit from password gives 4 bit from target
5 bit from source xored by 2, 3 bits from password gives 5 bit from target
6 bit from source xored by 0, 3 bits from password gives 6 bit from target
7 bit from source xored by 1, 3 bits from password gives 7 bit from target

     This  allows  us  to  recover the password. How? By creating eight linear
equations. Symbol 0s means bit 0 from source, 7t - seventh bit from target, 2p
- second bit from password. Above 8 conditions can be written as:

0s xor 0p xor 1p xor 3p = 0t
1s xor 1p xor 2p xor 3p = 1t
2s xor 0p = 2t
3s xor 1p xor 2p = 3t
4s xor 3p = 4t
5s xor 2p xor 3p = 5t
6s xor 0p xor 3p = 6t
7s xor 1p xor 3p = 7t

     You  know source and targets bits so you can change 0s, 1s and so on into
values:

(0x5f)           (0xe9)

1 xor 0p, 1p, 3p = 1
1 xor 1p, 2p, 3p = 0
1 xor 0p =         0
1 xor 1p, 2p =     1
1 xor 3p =         0
0 xor 2p, 3p =     1
1 xor 0p, 3p =     1
0 xor 1p, 3p =     1

     By xoring both sides of equations by the values on the left side (this is
basic math I've written earlier) you get:

0p xor 1p xor 3p = 0
1p xor 2p xor 3p = 1
0p               = 1
1p xor 2p        = 0
3p               = 1
2p xor 3p        = 1
0p xor 3p        = 0
1p xor 3p        = 1

     Solving  this set of equations (using whichever method - e.g. using sheet
of paper and a pencil ;) you get:

0p = 1
1p = 0
2p = 0
3p = 1, so the password is 1001.

     I  think the main idea is clear now: you must build up a schema of xoring
for every bit in the table, on then solve the equations. This is not so simple
as  in the above examples - the table is 122 bytes long, so it's 976 bits, and
for  each  you must build up an equations. Then you must solve these equations
using some smart method, because solving 976 equations on sheet of paper would
take  ages. Using some programming language (e.g. C) you are now able to write
a program which will do crack for us.


                              Hey, wait a minute


     Above  explanations  are true, but you're forgetting about one thing - in
the  third  pass  (if you don't remember - please study the code again) you're
storing  eax into memory, and then this value is used to decipher some crypted
text.  Who  can  guearantee us, that password which will transform target into
target_table will also generate valid key for decrypting this text? That's why
you  must build 32 equations extra for this condition. Finally you've got 1008
instead of 976 equations.


                               Finding xor_key


     All  right,  but  how  can  you find this key? The answer is simple - the
decrypted  text  should end with three bytes 0xd, 0xa, 0x24 (if you don't know
why,  study  some info about int21/ah=9 function). The layout of the test4.com
in memory looks like this:

0100 code
02c4 buffer for password
02d3 "Enter password" text
02ea table
036a ciphered text
03b5 target_table

     target_table  is  zeroed  after comparing function (check label) if table
and  target_table  are  the same, so the last three bytes of the ciphered text
are  stored  in  03b2,  03b3  and 03b4 offsets. It's 0x9e, 0x40 and 0xfe. 0x9e
should be xored to 0xa, 0x40 to 0xd and 0xfe to 0x24. That means 0x9e is xored
by 0x94, 0x40 by 0x4d and 0xfe by 0xda.

0x9e xor 0x94 = 0xa
0x40 xor 0x4d = 0xd
0xfe xor 0xda = 0x24

     In  eax  you've  got  the  key.  I'm assuming every letter is a nibble (4
bits):

eax: abcdefgh
al: gh

     So, first byte is xored by gh. Then eax is rolled 4 bits left:

eax: bcdefgha
al: ha

     Second  byte  is  xored  by  ha, and so on. Look what happens when you're
xoring ninth byte:

1: abcdefgh
2: bcdefgha
3: cdefghab
4: defghabc
5: efghabcd
6: fghabcde
7: ghabcdef
8: habcdefg
9: abcdefgh

     It  means  that key is same for bytes which are in the same column of the
dump pane in TD.

DS:036A D7 22 B4 C1 13 71 70 EC <-- This is dump panel from TD
DS:0372 F8 2C AE .. .. .. .. ..
...
DS:03B2 9E 40 FE .. .. .. .. ..

     Xoring  D7,22,B4  by  94,4D,DA you get "Con". This should be beginning of
the  "Congratulations"  string.  Let's go further: xor D7,22,B4,C1,13,71,70,EC
with  "Congratu".  You're getting 8 bytes: 94,4D,DA,A6,61,10,09,99. You've got
whole  information,  but  you  must  "pack" 8 bytes into 4. The lowest byte is
0x94.  Then, after ROL EAX,4 you should get 0x4d in al. That means the highest
nibble is 0xd. After next ROL al is 0xda, so the second highest nibble is 0xa.
Going  further,  you  can reconstruct the key, which is 0xda610994. Using this
value, you can decrypt text:
"Congratulations. You have done the what cannot be done .. Tell me how ?!"
     Wow! First success! You haven't done anything spectacular by now, but you
will, don't worry ;)


                                   Program


Whole source code for testcrk.c which is the main part of my work is included
in ZIP file, UUENCODED at the end of this file. Below are the main parts of
the code, commented as good as I can.

------------------------------------------------------------------------------
#define KEYCHARS 16
#define KEYBITS KEYCHARS * 8
#define XORCHARS 122
#define XORBITS XORCHARS * 8
#define EQUATIONS XORBITS + 32
#define KEYSIZE KEYBITS * (KEYBITS + 1)
#define XORSIZE XORBITS * (KEYBITS + 1)
------------------------------------------------------------------------------

KEYCHARS - number of chars in the buffer
KEYBITS - number of bits in the buffer
XORCHARS - size of table
XORBITS - number of bits in the table
EQUATIONS - number of equations to solve
KEYSIZE - size of whole TabKey table
XORSIZE - size of whole TabCipher table

------------------------------------------------------------------------------
char huge TabKey[KEYBITS][KEYBITS+1];         // password
char huge TabCipher[EQUATIONS][KEYBITS+1];    // table for XOR
char AktKey[32][KEYBITS+1];                   // current key
char Temp[2][KEYBITS+1];                      // temporary for ROL
char TabPass[KEYBITS];                        // password divided into bits
char password[KEYCHARS];
char TempEqu[12 * 8 + 1];                     // temp for swap_equ
unsigned char solved[EQUATIONS];
------------------------------------------------------------------------------

TabKey - table where the key (whole 16-char buffer) will be stored, divided
         into bits. If the n-th bit is set to 1 (0 < n < KEYBITS), that means
         this bit is XORed by n-th from the buffer. If the KEYBITS bit is set
         it means it is XORed by 1.
TabCipher - table where the table is stored, divided into bits. Format same as
            above.
AktKey - key divided into bits (value stored in eax, used to XOR bytes from
         table). Format same as above.
Temp - temporary table to perform ROL EAX,2 on AktKey
TabPass - password divided into bits. If the n-th bit is set, it means the
          n-th bit in the password is 1. If not - the bit is 0. WARNING: I've
          implemented other bit numbering system: bit 0 is the most
          significant bit, and the (KEYBITS-1) is the least significant.
password - password in bytes. Used by do_test() routine. Just for testing
           purposes. Left for historical reasons ;)
TempEqu - temporary swap space for swap_equ functions, which swaps places of
          two equations
solved - if the n-th element of this table is set, that means it was used to
         calculate some bit in password. More details on solve() functions
         comment

------------------------------------------------------------------------------
FILE *org;
char passsize;
char size_pass[] = {11, 14, 14, 14};
char passchars = 0, passbits = 0;
char yesorno;
------------------------------------------------------------------------------

org - file struct, used on file operations
passsize - current password size, more details on create_table() function
           comment
size_pass - table to get passsize from, more details on create_table()
            function comment
passchars - number of letters in password. Set by user.
passbits - number of bits in password. Set by user.

------------------------------------------------------------------------------
void create_table()
------------------------------------------------------------------------------

Function which is the alfa & omega here. Creates the XOR schema.

------------------------------------------------------------------------------
int i, j;
unsigned char mask, chr, byte[XORCHARS];
long cipherpos;
int pass, keychar, bits, xor_char;
unsigned char buffer[KEYBITS + 1];
------------------------------------------------------------------------------

i, j - counters, used in various for loops
mask, chr, byte, cipherpos - used when loading ORG.DAT
pass - pass index
keychar - char index from the password processed
bits - bit index when xoring table by eax
xor_char - char index when xoring table by eax
buffer - buffer used when reading from CIPHER.DAT

------------------------------------------------------------------------------
if ((org = fopen("CIPHER.DAT", "rb")) != NULL)
{
  fread(byte, 1, 1, org);
  if (byte[0] == passchars)
  {
    printf("reading from CIPHER.DAT...\n");
    for (i = 0; i < EQUATIONS; i++)
    {
      fread(buffer, KEYBITS+1, 1, org);
      // we cannot use _fmemcpy, coz it fails sometimes on huge tables
      for (j = 0; j <= KEYBITS; j++)
        TabCipher[i][j] = buffer[j];
    }
    fclose(org);
    return;
  }
}
------------------------------------------------------------------------------

     Above  piece  of  code  loads  the  equations from CIPHER.DAT, instead of
creating them from scratch. This file is created on the end of create_table().
Speeds up the process of cracking after first time.

------------------------------------------------------------------------------
memset(TabPass, 0, KEYBITS);
_fmemset(TabKey, 0, KEYSIZE);   // initiate TabKey
for (i = 0; i < passbits; i++)  // set passbits bits
  TabKey[i][i] = 1;
TabKey[passchars*8][KEYBITS] = 0;   // char after password is 0x0d
...
TabKey[13*8][KEYBITS] = 0;         // 13th char is 0x0a
...
TabKey[14*8][KEYBITS] = 0;         // on 14th pos we've got 0x0d
...
TabKey[15*8][KEYBITS] = 0;         // on 15th there's is 0x20
...
passsize = passchars;
------------------------------------------------------------------------------

Initialization code. Sets up bits like in the original test4.com:

x|x|x|x|x|x|x|x|x|x|x|x|0xd|0xa|0x0d|0x20|
this is the case when the password has 12 chars.

x|x|x|x|x|x|x|x|x|x|0xd|0|0|0xa|0x0d|0x20|
it looks like this, when the password has 10 chars.

------------------------------------------------------------------------------
if ((org = fopen("ORG.DAT", "rb")) == NULL)
{
  printf("ORG.DAT not found!\n");
  exit(255);
}

_fmemset(TabCipher, 0, XORSIZE);  // initiate TabCipher
cipherpos = 0;
fread(byte, XORCHARS, 1, org);
fclose(org);
for (i = 0; i < XORCHARS; i++)
  for (mask = 0x80; mask != 0; mask >>= 1)
  {
    if ((byte[i] & mask) == mask) TabCipher[cipherpos][KEYBITS] = 1;
    cipherpos++;
  }
------------------------------------------------------------------------------

     Loads  the  ORG.DAT  into  memory and transforms it into bits. ORG.DAT is
table stored in separate file.

------------------------------------------------------------------------------
for (pass = 0; pass < 5; pass++)
{
  for (keychar = 0; keychar < (passsize - 1); keychar++)
  {
    putch('.');
    memcpy(AktKey, TabKey[keychar*8+24], 8*(KEYBITS+1)); // get current key
    memcpy(AktKey[8], TabKey[keychar*8+16], 8*(KEYBITS+1)); // but reversed
    memcpy(AktKey[16], TabKey[keychar*8+8], 8*(KEYBITS+1));
    memcpy(AktKey[24], TabKey[keychar*8], 8*(KEYBITS+1));
------------------------------------------------------------------------------

When you execute instruction
  MOV EAX,[ESI]
and in [ESI] you've got
  ABCD
where A, B, C, D are bytes, in EAX you get
  DCBA
That's why you must copy to AktKey using this strange-looking code.

------------------------------------------------------------------------------
for (xor_char = 0; xor_char <= (XORCHARS - 4); xor_char++)
{
  for (bits = 24; bits < 32; bits++)
    do_xor(TabCipher[xor_char * 8 + bits - 24], AktKey[bits]);
  for (bits = 16; bits < 24; bits++)
    do_xor(TabCipher[xor_char * 8 + bits - 8], AktKey[bits]);
  for (bits = 8; bits < 16; bits++)
    do_xor(TabCipher[xor_char * 8 + bits + 8], AktKey[bits]);
  for (bits = 0; bits < 8; bits++)
    do_xor(TabCipher[xor_char * 8 + bits + 24], AktKey[bits]);
  memcpy(Temp, AktKey, 2 * (KEYBITS + 1));
  memmove(AktKey, AktKey[2], 30 * (KEYBITS + 1));
  memcpy(AktKey[30], Temp, 2 * (KEYBITS + 1));
}
------------------------------------------------------------------------------

     Does  right  XORing.  Because  the XOR [ESI], EAX behaves like written in
above  comment  we  have  to  do  it  also  in  reverse byte order. The next 3
instructions are equivalent to ROL EAX,2

------------------------------------------------------------------------------
for (bits = 0; bits < 32; bits++)
{
  do_xor(TabKey[bits], TabCipher[bits]);
  do_xor(TabKey[32 + bits], TabCipher[bits]);
  do_xor(TabKey[64 + bits], TabCipher[bits]);
}
------------------------------------------------------------------------------

After finished pass you must XOR password with the beginning of table.

------------------------------------------------------------------------------
if (pass == 2) set_eax();
if (passsize < 12) passsize = 14;
------------------------------------------------------------------------------

     When  it's  third  pass  (first pass has number 0 assigned) we must build
that 32 additional equations (done in set_eax()).
     Remember  when  the  process  of  XORing  was  stopped, and next pass was
begining?  When  the  AH  was  0dh.  So,  when  the  0dh put after password by
int21h/ah=0a  function  is  inside  the 12-byte snip, it's being XORed by some
values  from  table and there's no 0dh to stop. The nearest 0dh is on the 14th
position,  and  that's why when we have less then 12 chars in password we must
set passsize after first pass to 14.


------------------------------------------------------------------------------
create_equations()
------------------------------------------------------------------------------

     This function converts data stored in TabCipher to equations (also stored
in  TabCipher).  The  target_table,  used  for  creating  right  hand  side of
equations is store in XOR.DAT file, which is a dump from TEST4.COM

------------------------------------------------------------------------------
solve()
------------------------------------------------------------------------------

     This   function   solves   the   equations   created   in   TabCipher  by
create_equations() using Gauss-Jordan method. Let me show you the algorithm on
example.

Let's say we've got following set of equations

a xor b xor d = 1
c xor d = 0
a xor c xor d = 1
a xor c = 0

It can be written also as

1*a xor 1*b xor 0*c xor 1*d = 1
0*a xor 0*b xor 1*c xor 1*d = 0
1*a xor 0*b xor 1*c xor 1*d = 1
1*a xor 0*b xor 1*c xor 0*d = 0

Writing it in compact form:

1101 1
0011 0
1011 1
1010 0

It's the format the equations are stored in TabCipher after create_equations()
function was called. The Gauss-Jordan algorithm works like this:

Let's eliminate 1 from the first column, leaving it just in first equation.
How? By XORing all equations with 1 in the first column by first equation.

1101 1
0011 0
0110 0 <- 1101 xor 1011 = 0110, 1 xor 1 = 0
1010 0

And XOR also fourth equation:

1101 1
0011 0
0110 0
0111 1 <- 1101 xor 1010 = 0111, 1 xor 0 = 1

We haven't got 1 on second column and second row, so let's exchange second
equation with third.

1101 1
0110 0 \
0011 0 /
0111 1

Let's eliminate 1 from second column (except second row), firstly XOR first
equation with second

1011 1 <- 1101 xor 0110 = 1011, 1 xor 0 = 1
0110 0
0011 0
0111 1

Now XOR fourth equation by second

1011 1
0110 0
0011 0
0001 1 <- 0111 xor 0110 = 0001, 1 xor 0 = 1

Do the same with third column.

1000 1
0101 0
0011 0
0001 1

And, of course with last, fourth.

1000 1
0100 1
0010 1
0001 1

     The  result is on the right hand side - first bit in first row, second in
second,  and  so  on. In my solve() I'm using a variation of this algorithm. I
don't  swap the equations, but I set appropriate bit in solved table. When bit
is  set  in the solved table, that means this equation was used as a "base" to
calculate  one of the bits. More details in the source code - if you want know
how it works - study it.


                               After testcrk.c


     After  running  testcrk  you'll get two new files - test.out and equ.dat.
Reading  the  first one will bring first success - second and third bytes are,
respectively,  'a'  and  't'.  EVERY valid, 12-char, password has to have "at"
string  on the second and third chars. I've written a showequ.c which converts
equ.dat  into  equ.txt  - human readable form. If you have thoroughly examined
testcrk.c  you'll  also be able to understand showequ.c with ease. Run showequ
and read equ.txt. The only two additional (to "at" string) conditions are:

1. password[1] = password[12] xor 6
2. password[4] = password[5] xor password[6] xor ... xor password[11] xor
   password[1] xor 57

     Just  these  three conditions should be met to generate a VALID password!
(Look   into   genpass.c   for   details)  BTW:  The original password used by
LordByte was "saturn404cpu" (try typing rn404cpu into genpass ;)


              Hey! Password generated by genpass.c doesn't work!


     The reason for this is very simple. Remember how test4 checks if it's the
end of the password and it should begin next pass? It's checking for 0dh in ah
register.  When  0dh appears in some strange place (after xoring password with
values  from  the  table),  the  schema of xoring changes dramatically, so the
equations  produced  by  testcrk.c  are  useless, what makes password bad. Try
entering  "KurwaMac"  (pozdrowienia  dla wszystkich Polakow <- sorry, a bit of
Polish here) into genpass, and then generated password into test4. Then run TD
and look where this mysterious 0dh is appearing in the password.



                              Counting password


     I  don't  know  the  exact  number  of passwords, that can be accepted by
test4.   However,   using   calculus   of  probability,  I  give  you  a  good
approximation.  As  shown  in  the  previous paragraph, there is 8 significant
(changeable)  chars in password, 2 of chars are "checksums" of this eight, and
2 are constant ("at"). So, theoretically, there is 2^(8*8) passwords, which is
2^64.  But  you must remember about 0dh appearing in the password after XORing
it.  Let's assume (I don't know if it's right assumption. If you can give me a
strict  mathemathical  proof,  I  would  be  pleased.  Send all your tries to:
croock@priv.onet.pl)  that  appearing  0dh  on  each  of 12 positions has same
likelihood.  There  are  4  passes  where  0dh  can  appear, so it gives us 48
possible  positions. The likelihood of appearing 0dh in one position is like 1
to  256. So the sumary likelihood of appearing at least one 0dh in password is
48/256, which is equal to 3/16. So, 3/16 of passwords has 0dh inside.

3             3 * 2^64
--  * 2^64 = --------- = 3 * 2^60
16              2^4

     So, the probable number of passwords equals to:

2^64 - 3 * 2^60 = 2^60 * (2^4 - 3) = 13 * 2^60 ~= 2^64

     As  stated  above,  this is only good approximation. Why? Maybe there are
some passwords with 0dh, which are good ones. Maybe the the likelihood on each
position is not the same. Maybe... However, the number of passwords is so big,
it makes no difference ;)

                                 Final words


     I  hope  you understand this whole crap I wrote above, and I think you'll
find  it  useful.  If  you have any comments, questions or some remarks please
write to croock@priv.onet.pl.
     Message  to  all  lamahows:  don't  steal my code, it is supposed to be a
learning  assistance,  not the occasion to change CR00CK to your nick and tell
the whole world how good you are.
     Greetz  must  go  to  LordByte  for  his test4 and Razzia for his (moral)
support ;)

                              Really final words


     Look  out!  TEST5 is going out. Search for another tutorial by me, now on
cracking test5 :-)))

                                                            mat=ByCr00ck
                                                            1at:14061997
