Table of Contents
Previous Section Next Section

0x460 Password Cracking

Passwords aren't generally stored in plaintext form. A file containing all the passwords in plaintext form would be far too attractive a target, so instead a one- way hash function is used. The most well known of these functions is based on DES and is called crypt(). Other popular password-hashing algorithms are MD5 and Blowfish.

A one-way hash function expects a plaintext password and a salt value for input and then outputs a hash with the inputted salt value prepended to it. This hash is mathematically irreversible, meaning that it is impossible to determine the original password using only the hash. Perl has a crypt() function built in, making it a useful demonstration tool.


File: hash.pl

#!/usr/bin/perl
$plaintext = "test"; $salt = "je";
$hash = crypt($plaintext, $salt);
print "crypt($plaintext, $salt) = $hash\n";

The following output uses the preceding Perl script and then just uses command- line execution to hash values with the crypt() function, using various salt values.

$ ./hash.pl
crypt(test, je) = jeHEAX1m66RV.
$ perl -e '$hash = crypt("test", "je"); print "$hash\n";'
jeHEAX1m66RV.
$ perl -e '$hash = crypt("test", "xy"); print "$hash\n";'
xyVSuHLjceD92
$

The salt value is used to perturb the algorithm further, so there can be multiple hash values for the same plaintext value if different salt values are used. The hash value (including the prepended salt) is stored in the password file under the premise that if an attacker were to steal the password file, the hashes would be useless.

When a legitimate user actually needs to authenticate using the password hash, that user's hash is looked up in the password file. The user is prompted to enter her password, the original salt value is extracted from the password file, and whatever the user types is sent through the same one-way hash function with the salt value. If the text entered at the password prompt is the correct password, the one-way hashing function will produce the same hash output as is stored in the password file. This allows authentication to function as expected, while never having to store the plaintext password.

0x461 Dictionary Attacks

It turns out, however, that the encrypted passwords in the password file aren't so useless after all. Sure it's mathematically impossible to reverse the hash, but it is possible to just quickly try hashing every word in the dictionary, using the salt value for a specific hash, and then compare the results with that hash. If the hashes match, then that word from the dictionary must be the plaintext password.

A simple dictionary-attack program can be whipped up in Perl with relative ease. The following Perl script simply reads words from standard input and tries to hash them all with the proper salt. If there is a match, the matching word is displayed and the script exits.

File: crack.pl


#!/usr/bin/perl
# Get the hash to crack from the first command-line argument
$hash = shift;
$salt = substr($hash,0,2);       # The salt is the first 2 chars

print "Cracking the hash '$hash' using words from standard input..\n";
while(defined($in = <STDIN>)) # Read from standard input
{
   chomp $in;                     # Remove the hard return
   if(crypt($in, $salt) eq $hash) # If the hashes match...
   {
      print "Password is: $in\n"; # Print the password
      exit;                       # and exit.
   }
}
print "The password wasn't found in the words from standard input.\n";

The following output shows this Perl script being executed.

$ perl -e '$hash = crypt("test", "je"); print "$hash\n";'
jeHEAX1m66RV.
$ cat /usr/share/dict/words | crack.pl jeHEAX1m66RV.
Cracking the hash 'jeHEAX1m66RV.' using words from standard input..
Password is: test
$ grep "^test$" /usr/share/dict/words
test
$

In this example, the many words provided by /usr/share/dict/words are piped into the cracking script. Because the word "test" was the original password, and it is also found in the words file, the password hash will eventually be cracked. This is why it's considered poor security practice to use passwords that are also dictionary words or that are based on dictionary words.

The downside to this attack is that if the original password isn't a word found in the dictionary file, the password won't be found. For example, if a non- dictionary word like "h4R%" is used as a password, the dictionary attack won't be able to find it, as shown here:

$ perl -e '$hash = crypt("h4R%", "je"); print "$hash\n";'
jeMqqfIfPNNTE
$ cat /usr/share/dict/words | crack.pl jeMqqfIfPNNTE
Cracking the hash 'jeMqqfIfPNNTE' using words from standard input..
The password wasn't found in the words from standard input.
$

Custom dictionary files are often made using different languages, standard modifications of words (such as transforming letters to numbers), or simply appending numbers to the end of each word. While a bigger dictionary will yield more passwords, it will also take more time to process.

0x462 Exhaustive Brute-Force Attacks

A dictionary attack that tries every single possible combination is an exhaustive brute-force attack. While this type of attack will technically be able to crack every conceivable password, it will probably take longer than your grandchildren's grandchildren would be willing to wait.

With 95 possible input characters for crypt() style passwords, there are 958 possible passwords for an exhaustive search of all eight-character passwords, which works out to be over seven quadrillion possible passwords. This number gets so big so quickly because as another character is added to the password length, the number of possible passwords grows exponentially. Assuming 10,000 cracks per second, it would take about 22,875 years to try every password. Distributing this effort across many machines and processors is one possible approach; however, it is important to remember that this will only achieve a linear speed-up. If one thousand machines were combined, each capable of 10,000 cracks per second, the effort would still take over 22 years. The linear speed-up achieved by adding another machine is marginal compared to the growth in keyspace if another character were added to the password length.

Luckily, the inverse of the exponential growth is also true; as characters are removed from the password length, the number of possible passwords decreases exponentially. This means that a four-character password only has 954 possible passwords. This keyspace has only about 84 million possible passwords, which can be exhaustively cracked (assuming 10,000 cracks per second) in a little over two hours. This means that even though a password like "h4R%" isn't in any dictionary, it can be cracked in a reasonable amount of time.

This means that in addition to avoiding dictionary words, password length is also important. Because the complexity scales up exponentially, doubling the length to produce an eight-character password should bring the level of effort required to crack the password into the unreasonable time frame.

Solar Designer has developed a password-cracking program called John the Ripper that uses both a dictionary attack and then an exhaustive brute-force attack. This program is probably the most popular program of its kind, and it should be available at http://www.openwall.com/john/.

# john

John the Ripper Version 1.6 Copyright (c) 1996-98 by Solar Designer

Usage: john [OPTIONS] [PASSWORD-FILES]
-single                  "single crack" mode
-wordfile:FILE -stdin    wordlist mode, read words from FILE or stdin
-rules                   enable rules for wordlist mode
-incremental[:MODE]      incremental mode [using section MODE]
-external:MODE           external mode or word filter
-stdout[:LENGTH]         no cracking, just write words to stdout
-restore[:FILE]          restore an interrupted session [from FILE]
-session:FILE            set session file name to FILE
-status[:FILE]           print status of a session [from FILE]
-makechars:FILE          make a charset, FILE will be overwritten
-show                    show cracked passwords
-test                    perform a benchmark
-users:[-]LOGIN|UID[,..] load this (these) user(s) only
-groups:[-]GID[,..]      load users of this (these) group(s) only
-shells:[-]SHELL[,..]    load users with this (these) shell(s) only
-salts:[-]COUNT          load salts with at least COUNT passwords only
-format:NAME             force ciphertext format NAME (DES/BSDI/MD5/BF/AFS/LM)
-savemem:LEVEL           enable memory saving, at LEVEL 1..3
# john /etc/shadow
Loaded 44 passwords with 44 different salts (FreeBSD MD5 [32/32])
guesses: 0 time: 0:00:00:19 8% (1) c/s: 248 trying: orez8
guesses: 0 time: 0:00:00:59 13% (1) c/s: 242 trying: darkcube[
guesses: 0 time: 0:00:04:09 55% (1) c/s: 236 trying: ghost93
guesses: 0 time: 0:00:06:29 78% (1) c/s: 237 trying: ereiamjh9999984
guesses: 0 time: 0:00:07:29 90% (1) c/s: 238 trying: matrix1979
guesses: 0 time: 0:00:07:59 94% (1) c/s: 238 trying: kyoorius1919
guesses: 0 time: 0:00:08:09 95% (1) c/s: 238 trying: jigga9979
guesses: 0 time: 0:00:08:39 0% (2) c/s: 238 trying: qwerty
guesses: 0 time: 0:00:14:49 1% (2) c/s: 239 trying: dolphins
guesses: 0 time: 0:00:16:49 3% (2) c/s: 240 trying: Michelle
guesses: 0 time: 0:00:18:19 4% (2) c/s: 240 trying: Sadie
guesses: 0 time: 0:00:23:19 5% (2) c/s: 239 trying: kokos
guesses: 0 time: 0:00:48:09 12% (2) c/s: 233 trying: fugazifugazi
guesses: 0 time: 0:01:02:19 16% (2) c/s: 239 trying: MONSTER
guesses: 0 time: 0:01:32:09 23% (2) c/s: 237 trying: legend7
testing7        (ereiamjh)
guesses: 1 time: 0:01:37:29 24% (2) c/s: 237 trying: molly9
Session aborted
#

In this output, the account "ereiamjh" is shown to have the password of "testing7".

0x463 Hash Lookup Table

Another interesting idea for password cracking is using a giant hash lookup table. If all the hashes for all possible passwords were precomputed and stored in a searchable data structure somewhere, any password could be cracked in the time it takes to search. Assuming a binary search, this time would be about O(log2 N) where N is the number of entries. Because N is 958 in the case of eight- character passwords, this works out to about O(8 log2 95), which is quite fast.

However, a hash lookup table like this would require about a hundred thousand terabytes of storage. In addition, the design of the password-hashing algorithm takes this type of attack into consideration and mitigates it with the salt value. Because multiple plaintext passwords will hash to different password hashes with different salt values, a separate lookup table would have to be created for each salt. With the DES-based crypt() function, there are 4,096 possible salt values, which means that even a hash lookup table for a smaller keyspace, like all possible four-character passwords, becomes impractical. The storage space needed for a single lookup table for a fixed salt for all possible four-character passwords is about one gigabyte, but because of the salt values, there are 4,096 possible hashes for a single plaintext password, necessitating 4,096 different tables. This raises the needed storage space up to about 4.6 terabytes, which greatly dissuades such an attack.

0x464 Password Probability Matrix

There is a trade-off between computational power and storage space that exists everywhere. This is seen in the most elementary forms of computer science and everyday life. MP3 files use compression to store a high-quality sound file in a relatively small amount of space, but the demand for computational resources increases. Pocket calculators use this trade-off in the other direction by maintaining a lookup table for functions like sine and cosine to save the calculator from doing heavy computations.

This trade-off can also be applied to cryptography in what has become known as a time/space trade-off attack. While Hellman's methods for this type of attack are probably more efficient, the following source code should be easier to understand. The general principal is always the same, though; try to find the sweet spot between computational power and storage space, so that an exhaustive brute-force attack can be completed in a short amount of time, using a reasonable amount of space. Unfortunately, the dilemma of salts will still present itself, because this method still requires some form of storage. However, there are only 4096 possible salts with crypt() style password hashes, so the effect of this problem can be diminished by reducing the needed storage space far enough to remain reasonable despite the 4096 multiplier.

This method uses a form of lossy compression. Instead of having an exact hash lookup table, several thousand possible plaintext values will be returned when a password hash is entered. These values can be checked quickly to converge on the original plaintext password, and the lossy compression allows for a major space reduction. In the demonstration code that follows, the keyspace for all possible four-character passwords (with a fixed salt) is used. The storage space needed is reduced by 88 percent when compared with a hash lookup table (with a fixed salt), and the keyspace that must be brute-forced through is reduced by about 1018 times. Under the assumption of 10,000 cracks per second, this method can crack any four-character password (with a fixed salt) in under eight seconds, which is a considerable speed-up when compared to the two hours needed for an exhaustive brute-force attack of the same keyspace.

This method builds a three-dimensional binary matrix that correlates parts of the hash values with parts of the plaintext values. On the X-axis, the plaintext is split into two pairs; the first two characters and the second two characters. The possible values are enumerated into a binary vector that is 952, or 9025, bits long (about 1129 bytes). On the Y-axis, the ciphertext is split into four three-character chunks. These are enumerated the same way down the columns, but only four bits of the third character are actually used. This means there are 642 · 4, or 16,384, columns. The Z-axis exists simply to maintain eight different two- dimensional matrices, so four exist for each of the plaintext pairs.

The basic idea is to split the plaintext into two paired values that are enumerated along a vector. Every possible plaintext is hashed into ciphertext, and the ciphertext is used to find the appropriate column of the matrix. Then the plaintext enumeration bit across the row of the matrix is turned on. When the ciphertext values are reduced into smaller chunks, collisions are inevitable.

Plaintext

Hash


test

jeHEAX1m66RV.

!J)h

jeHEA38vqlkkQ

".F+

jeHEA1Tbde5FE

"8,J

jeHEAnX8kQK3I


In this case, the column for HEA would have the bits corresponding to the plaintext pairs te, !J, "., and "8 turned on, as these plaintext/hash pairs are added to the matrix.

After the matrix is completely filled out, when a hash such as jeHEA38vqlkkQ is entered, the column for HEA will be looked up, and the two-dimensional matrix will return the values te, !J, "., and "8 for the first two characters of the plaintext. There are four matrices like this for the first two characters, using ciphertext substring from characters two through four, four through six, six though eight, and eight though ten, each with a different vector of possible first two-character plaintext values. Each vector is pulled, and they are combined with a bitwise AND. This will only leave bits turned on corresponding to plaintext pairs that were listed as possibilities for each substring of ciphertext. There are also four matrices like this for the last two characters of plaintext.

The sizes of the matrices were determined by the pigeonhole principle. This is a simple principle that states if k+1 objects are put into k boxes, at least one of the boxes will contain two objects. So, to get the best results, the goal is for each vector to be a little bit less than half full of 1s. Because 954, or 81,450,625, entries will be put in the matrices, there need to be about twice as many holes to achieve 50 percent saturation. Because each vector has 9,025 entries, there should be about columns. This works out to be about 18 thousand columns. Because ciphertext substrings of three characters are being used for the columns, the first two characters and four bits from the third character are used to provide 642 · 4, or about 16 thousand columns (there are only 64 possible values for each character of ciphertext hash). This should be close enough, because when a bit is added twice, the overlap is ignored. In practice, each vector turns out to be about 42 percent saturated with 1s.

Because four vectors are pulled for a single ciphertext, the probability of any one enumeration position having a 1 value in each vector is about 0.424 or about 3.11 percent. This means that, on average, the 9,025 possibilities for the first two characters of plaintext are reduced by about 97 percent to 280 possibilities. This is done for the last two characters also, providing about 2802, or 78,400, possible plaintext values. Under the assumption of 10,000 cracks per second, this reduced keyspace would take under eight seconds to check.

Of course, there are downsides. First, it takes at least as long to create the matrix as the original brute-force attack would have taken; however, this is a one time cost. Also, the salts still tend to prohibit any type of storage attack, even with the reduced storage-space requirements.

The following two source code listings can be used to create a password probability matrix and crack passwords with them. The first listing will generate a matrix that can be used to crack all possible four-character passwords salted with je. The second listing will use the generated matrix to actually do the password cracking.

File: ppm_gen.c

/*****************************************************************\
*    Password Probability Matrix    * File: ppm_gen.c             *
*******************************************************************
*                                                                 *
*                                                                 *
* Author:       Jon Erickson <matrix@phiral.com>                  *
* Organization: Phiral Research Laboratories                      *
*                                                                 *
* This is the generate program for the PPM proof of               *
* concept. It generates a file called 4char.ppm, which            *
* contains information regarding all possible 4                   *
* character passwords salted with 'je'. This file can             *
* used to quickly crack passwords found within this               *
* keyspace with the corresponding ppm_crack.c program.            *
\*****************************************************************/
#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define HEIGHT 16384
#define WIDTH 1129
#define DEPTH 8
#define SIZE HEIGHT * WIDTH * DEPTH
int singleval(char a)
{
   int i, j;
   i = (int)a;
   if((i >= 46) && (i <= 57))
     j = i - 46;
   else if ((i >= 65) && (i <= 90))
     j = i - 53;
   else if ((i >= 97) && (i <= 122))
     j = i - 59;
   return j;
}

int tripleval(char a, char b, char c)
{
   return (((singleval(c)%4)*4096)+(singleval(a)*64)+singleval(b));
}

main()
{
   char *plain;
   char *code;
   char *data;
   int i, j, k, l;
   unsigned int charval, val;
   FILE *handle;
   if (!(handle = fopen("4char.ppm", "w")))
   {
      printf("Error: Couldn't open file '4char.ppm' for writing.\n");
      exit(1);
   }
   data = (char *) malloc(SIZE+19);
   if (!(data))
   {
      printf("Error: Couldn't allocate memory.\n");
      exit(1);
   }
   plain = data+SIZE;
   code = plain+5;

   for(i=32; i<127; i++)
   {
      for(j=32; j<127; j++)
      {
         printf("Adding %c%c** to 4char.ppm..\n", i, j);
         for(k=32; k<127; k++)
         {
            for(l=32; l<127; l++)
            {

               plain[0] = (char)i;
               plain[1] = (char)j;
               plain[2] = (char)k;
               plain[3] = (char)l;
               plain[4] = 0;
               code = crypt(plain, "je");

               val = tripleval(code[2], code[3], code[4]);
               charval = (i-32)*95 + (j-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));
               val += (HEIGHT * 4);
               charval = (k-32)*95 + (l-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));

               val = HEIGHT + tripleval(code[4], code[5], code[6]);
               charval = (i-32)*95 + (j-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));
               val += (HEIGHT * 4);
               charval = (k-32)*95 + (l-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));

               val = (2 * HEIGHT) + tripleval(code[6], code[7], code[8]);
               charval = (i-32)*95 + (j-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));
               val += (HEIGHT * 4);
               charval = (k-32)*95 + (l-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));

               val = (3 * HEIGHT) + tripleval(code[8], code[9], code[10]);
               charval = (i-32)*95 + (j-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));
               val += (HEIGHT * 4);
               charval = (k-32)*95 + (l-32);
               data[(val*WIDTH)+(charval/8)] |= (1<<(charval%8));
            }
         }
      }
   }
   printf("finished.. saving..\n");
   fwrite(data, SIZE, 1, handle);
   free(data); fclose(handle);
}

File: ppm_crack.c


/*********************************************************\
*  Password Probability Matrix    *    File: ppm_crack.c  *
***********************************************************
*                                                         *
* Author:       Jon Erickson <matrix@phiral.com>          *
* Organization: Phiral Research Laboratories              *
*                                                         *
* This is the crack program for the PPM proof of concept  *
* It uses an existing file called 4char.ppm, which        *
* contains information regarding all possible 4           *
* character passwords salted with 'je'. This file can     *
* be generated with the corresponding ppm_gen.c program.  *
*                                                         *
\*********************************************************/

#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define HEIGHT 16384
#define WIDTH 1129
#define DEPTH 8
#define SIZE HEIGHT * WIDTH * DEPTH
#define DCM HEIGHT * WIDTH

int singleval(char a)
{
   int i, j;
   i = (int)a;
   if((i >= 46) && (i <= 57))
     j = i - 46;
   else if ((i >= 65) && (i <= 90))
     j = i - 53;
   else if ((i >= 97) && (i <= 122))
     j = i - 59;
   return j;
}

int tripleval(char a, char b, char c)
{
   return (((singleval(c)%4)*4096)+(singleval(a)*64)+singleval(b));
}
void merge(char *vector1, char *vector2)
{
   int i;
   for(i=0; i < WIDTH; i++)
     vector1[i] &= vector2[i];
   }
   int length(char *vector)
   {
      int i, j, count=0;
      for(i=0; i < 9025; i++)
        count += ((vector[(i/8)]&(1<<(i%8)))>>(i%8));
      return count;
   }

   int grab(char *vector, int index)
   {
      char val;
      int a, b;
      int word = 0;

      val = ((vector[(index/8)]&(1<<(index%8)))>>(index%8));
      if (!val)
        index = 31337;
      return index;
   }
   void show(char *vector)
   {
      int i, a, b;
      int val; for(i=0; i < 9025; i++)
      {
         val = grab(vector, i);
         if(val != 31337)
         {
            a = val / 95;
            b = val - (a * 95);
            printf("%c%c ",a+32, b+32);
         }
      }
      printf("\n");
   }
   main()
   {
      char plain[5];
      char pass[14];
      char bin_vector1[WIDTH];
      char bin_vector2[WIDTH];
      char temp_vector[WIDTH];
      char prob_vector1[2][9025];
      char prob_vector2[2][9025];
      int a, b, i, j, len, pv1_len=0, pv2_len=0;
      FILE *fd;

      if(!(fd = fopen("4char.ppm", "r")))
      {
         printf("Error: Couldn't open PPM file for reading.\n");
         exit(1);
      }

      printf("Input encrypted password (salted with 'je') : ");
      scanf("%s", &pass);

      printf("First 2 characters: \tSaturation\n");

      fseek(fd,(DCM*0)+tripleval(pass[2], pass[3], pass[4])*WIDTH, SEEK_SET);
      fread(bin_vector1, WIDTH, 1, fd);

      len = length(bin_vector1);
      printf("sing length = %d\t%f%\n", len, len*100.0/9025.0);

      fseek(fd,(DCM*1)+tripleval(pass[4], pass[5], pass[6])*WIDTH, SEEK_SET);
      fread(temp_vector, WIDTH, 1, fd);
      merge(bin_vector1, temp_vector);

      len = length(bin_vector1);
      printf("dual length = %d\t%f%\n", len, len*100.0/9025.0);

      fseek(fd,(DCM*2)+tripleval(pass[6], pass[7], pass[8])*WIDTH, SEEK_SET);
      fread(temp_vector, WIDTH, 1, fd);
      merge(bin_vector1, temp_vector);

      len = length(bin_vector1);
      printf("trip length = %d\t%f%\n", len, len*100.0/9025.0);

      fseek(fd,(DCM*3)+tripleval(pass[8], pass[9],pass[10])*WIDTH, SEEK_SET);
      fread(temp_vector, WIDTH, 1, fd);
      merge(bin_vector1, temp_vector);

      len = length(bin_vector1);
      printf("quad length = %d\t%f%\n", len, len*100.0/9025.0);
      show(bin_vector1);
      printf("Last 2 characters: \tSaturation\n");

      fseek(fd,(DCM*4)+tripleval(pass[2], pass[3], pass[4])*WIDTH, SEEK_SET);
      fread(bin_vector2, WIDTH, 1, fd);

      len = length(bin_vector2);
      printf("sing length = %d\t%f%\n", len, len*100.0/9025.0);

      fseek(fd,(DCM*5)+tripleval(pass[4], pass[5], pass[6])*WIDTH, SEEK_SET);
      fread(temp_vector, WIDTH, 1, fd);
      merge(bin_vector2, temp_vector);

      len = length(bin_vector2);
      printf("dual length = %d\t%f%\n", len, len*100.0/9025.0);

      fseek(fd,(DCM*6)+tripleval(pass[6], pass[7], pass[8])*WIDTH, SEEK_SET);
      fread(temp_vector, WIDTH, 1, fd);
      merge(bin_vector2, temp_vector);

      len = length(bin_vector2);
      printf("trip length = %d\t%f%\n", len, len*100.0/9025.0);

      fseek(fd,(DCM*7)+tripleval(pass[8], pass[9],pass[10])*WIDTH, SEEK_SET);
      fread(temp_vector, WIDTH, 1, fd);
      merge(bin_vector2, temp_vector);

      len = length(bin_vector2);
      printf("quad length = %d\t%f%\n", len, len*100.0/9025.0);
      show(bin_vector2);

      printf("Building probability vectors...\n");
      for(i=0; i < 9025; i++)
      {
         j = grab(bin_vector1, i);
         if(j != 31337)
         {
            prob_vector1[0][pv1_len] = j / 95;
            prob_vector1[1][pv1_len] = j - (prob_vector1[0][pv1_len] * 95);
            pv1_len++;
         }
      }
      for(i=0; i < 9025; i++)
      {
         j = grab(bin_vector2, i);
         if(j != 31337)
         {
            prob_vector2[0][pv2_len] = j / 95;
            prob_vector2[1][pv2_len] = j - (prob_vector2[0][pv2_len] * 95);
            pv2_len++;
         }
      }

      printf("Cracking remaining %d possibilites..\n", pv1_len*pv2_len);
      for(i=0; i < pv1_len; i++)
      {
         for(j=0; j < pv2_len; j++)
         {
            plain[0] = prob_vector1[0][i] + 32;
            plain[1] = prob_vector1[1][i] + 32;
            plain[2] = prob_vector2[0][j] + 32;
            plain[3] = prob_vector2[1][j] + 32;
            plain[4] = 0;
            if(strcmp(crypt(plain, "je"), pass) == 0)
            {
               printf("Password : %s\n", plain);
               i = 31337;
               j = 31337;
            }
         }
      }
      if(i < 31337)
         printf("Password wasn't salted with 'je' or is not 4 chars long.\n");

         fclose(fd);
}

The first piece of code, ppm_gen.c, can be used to generate a four-character password probability matrix, as shown here:

$ gcc -O3 -o gen ppm_gen.c -lcrypt
$ ./gen
Adding    ** to 4char.ppm..
Adding   !** to 4char.ppm..
Adding   "** to 4char.ppm..
Adding   #** to 4char.ppm..
Adding   $** to 4char.ppm..
 [Output snipped]
$ ls -lh 4char.ppm
-rw-r--r--    1 matrix    users       141M Dec 19 18:52 4char.ppm
$

The second piece of code, ppm_crack.c, can be used to crack the troublesome password of "h4R%" in a matter of seconds:

$ gcc -O3 -o crack ppm_crack.c -lcrypt
$ perl -e '$hash = crypt("h4R%", "je"); print "$hash\n";'
jeMqqfIfPNNTE
$ ./crack
Input encrypted password (salted with 'je') : jeMqqfIfPNNTE
First 2 characters:    Saturation
sing length = 3801     42.116343%
dual length = 1666     18.459834%
trip length = 695      7.700831%
quad length = 287      3.180055%
4 9 N !& !M !Q "/ "5 "W #K #d #g #p $K $O $s %) %Z %\ %r &( &T '- '0 '7 'D 'F (
(v (| )+ ). )E )W *c *p *q *t *x +C -5 -A -[ -a .% .D .S .f /t 02 07 0? 0e 0{ 0| 1A
1U 1V 1Z 1d 2V 2e 2q 3P 3a 3k 3m 4E 4M 4P 4X 4f 6 6, 6C 7: 7@ 7S 7z 8F 8H 9R 9U 9_
9~ :- :q :s ;G ;J ;Z ;k <! <8 =! =3 =H =L =N =Y >V >X ?1 @# @W @v @| AO B/ B0 BO Bz
C( D8 D> E8 EZ F@ G& G? Gj Gy H4 I@ J JN JT JU Jh Jq Ks Ku M) M{ N, N: NC NF NQ Ny
O/ O[ P9 Pc Q! QA Qi Qv RA Sg Sv T0 Te U& U> UO VT V[ V] Vc Vg Vi W: WG X" X6 XZ X'
Xp YT YV Y^ Yl Yy Y{ Za [$ [* [9 [m [z \" \+ \C \O \w ]( ]: ]@ ]w _K _j 'q a. aN a^
ae au b: bG bP cE cP dU d] e! fI fv g! gG h+ h4 hc iI iT iV iZ in k. kp l5 l' lm lq
m, m= mE n0 nD nQ n~ o# o: o^ p0 p1 pC pc q* q0 qQ q{ rA rY s" sD sz tK tw u- v$ v.
v3 v; v_ vi vo wP wt x" x& x+ x1 xQ xX xi yN yo zO zP zU z[ z^ zf zi zr zt {- {B {a
|s }) }+ }? }y ~L ~m
Last 2 characters:    Saturation
sing length = 3821    42.337950%
dual length = 1677    18.581717%
trip length = 713     7.900277%
quad length = 297     3.290859%
! & != !H !I !K !P !X !o !~ "r "{ "} #% #0 $5 $] %K %M %T &" &% &( &0 &4 &I &q &}
'B 'Q 'd )j )w *I *] *e *j *k *o *w *| +B +W ,' ,J ,V -z . .$ .T /' /_ 0Y 0i 0s 1!
1= 1l 1v 2- 2/ 2g 2k 3n 4K 4Y 4\ 4y 5- 5M 5O 5} 6+ 62 6E 6j 7* 74 8E 9Q 9\ 9a 9b :8
:; :A :H :S :w ;" ;& ;L <L <m <r <u =, =4 =v >v >x ?& ?' ?j ?w @0 A* B B@ BT C8 CF
CJ CN C} D+ D? DK Dc EM EQ FZ GO GR H) Hj I: I> J( J+ J3 J6 Jm K# K) K@ L, L1 LT N*
NW N' O= O[ Ot P: P\ Ps Q- Qa R% RJ RS S3 Sa T! T$ T@ TR T_ Th U" U1 V* V{ W3 Wy Wz
X% X* Y* Y? Yw Z7 Za Zh Zi Zm [F \( \3 \5 \_ \a \b \| ]$ ]. ]2 ]? ]d ^[ ^~ '1 'F 'f
'y a8 a= aI aK az b, b- bS bz c( cg dB e, eF eJ eK eu fT fW fo g( g> gW g\ h$ h9 h:
h@ hk i? jN ji jn k= kj l7 lo m< m= mT me m| m} n% n? n~ o oF oG oM p" p9 p\ q} r6
r= rB sA sN s{ s~ tX tp u u2 uQ uU uk v# vG vV vW vl w* w> wD wv x2 xA y: y= y? yM
yU yX zK zv {# {) {= {O {m |I |Z }. }; }d ~+ ~C ~a
Building probability vectors...
Cracking remaining 85239 possibilites..
Password :    h4R%
$

Table of Contents
Previous Section Next Section