Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
565 lines (519 sloc) 21 KB
/**
* crack.c
* A small C program that allows to break passwords hashed through the DES algorithms (as by the crypt() function from unistd.h) given
* the result of the hash, either through brute force or a dictionary attack.
*
* By : Ismail Harrando
* harrismatik@gmail.com
*
* Log (rounding the minutes to the closest 5 multiple)
* 22/01/2015 11:30 > 16:40 - Started working on the file from scratch
* 22/01/2015 16:40 > 17:15 - Fixed a memory allocation bug (did you know that realloc only works on dynamically allocated variables ?)
* 22/01/2015 17:15 > 17:45 - First fully working version with -f, -s, -o and -c (weeeee..)
* 24/01/2015 17:50 > 19:10 - Finished the description (who knew a bunch of printfs would take so long ..)
* 25/01/2015 16:40 > 17:50 - Added the dictionary attack option
* 26/01/2015 17:50 > 20:05 - Remade generate_and_check() to use a counter instead of an array
* 26/01/2015 20:05 > 22:45 - Added the last comments to the code, a few tweaks .. ready to ship !
*
* Bugs (that I know of)
* Not a bug as much as it is an inconvenience: if you want to use a purely numeric format such as (15 for the alphabet '12345'
* it will be taken for the numeric argument corresponding to the max length (N = 15). There is no obvious way around it.
*
*/
#define DEFAULT_LEN 8
#define START 1
#define STOP 0
#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
// === Prototypes ===
void print_help(void);
void append_to(char** alphabet, char* to_append);
int is_valid_format(char* s, char* e);
int compare(char* string1, char* string2);
char arg_to_char(char* arg);
int length(char* string1);
int is_number(char* string);
int to_number(char* string, int len);
void generate_alphabet(char** alphabet, char* format, int freq, int spc, int sample);
int dictionary_attack(FILE* dict, char** password, char* hash, int output);
int old_generate_and_check(char** pass, char* hash, char* alpha, int max, int cstl, int out);
double chrono(int flag);
unsigned long long generate_and_check(char** pass, char* hash, char* alpha, int max, int cstl, int out);
// === Main ===
int main(int argc, char* argv[])
{
if (argc < 10 && argc > 1)
{
// Check if a correct hash was provided as the first argument
if (length(argv[1]) != 13)
{
printf("Invalid password format\n");
return 1;
}
// Initialize the arguments
char *hash = argv[1];
char *dictfile = "", *format = "az";
char *alphabet = NULL, *password = NULL;
long long counter = 0;
int freq = 0, spec = 0, cstlen = 0, sample = 0, output = 0;
int maxlen = DEFAULT_LEN;
for (int i = 2; i < argc; i++)
{
switch (arg_to_char(argv[i]))
{
case 'f': freq = 1; break;
case 's': spec = 1; break;
case 'c': cstlen = 1; break;
case 'a': sample = 1; break;
case 'o': output = 1; break;
case 'd': dictfile = (i + 1 < argc) ? argv[++i] : ""; break;
case 'N': maxlen = to_number(argv[i], length(argv[i])); break;
case 'F': format = argv[i];
}
}
// if '-d' passed correctly then dictfile != ""
if (length(dictfile))
{
// Load the file passed as an argument after -d
printf("Opening %s ...\n", dictfile);
FILE* fdic = fopen(dictfile, "r");
if (fdic)
{
chrono(START);
if(dictionary_attack(fdic, &password, hash, output))
{
printf("Hash : %s\n", hash);
printf("Password : %s\n", password);
printf("CPU Time : %.2lf ms\n", chrono(STOP));
}
else
{
printf("All possibilities using the dictionary \"%s\" ", dictfile);
printf("were exhausted.\nThe password wasn't found.\n");
}
fclose(fdic);
}
else
{
printf("Error loading the file %s: premission denied or wrong path.\n", dictfile);
printf("Please retry\n");
}
}
// No dictionary. Generating passwords from an alphabet
else
{
generate_alphabet(&alphabet, format, freq, spec, sample);
chrono(START);
if((counter = generate_and_check(&password, hash, alphabet, maxlen, cstlen, output)))
{
printf("Hash : %s\n", hash);
printf("Alphabet : %s\n", alphabet);
printf("Password : %s\n", password);
printf("Tries : %lld\n", counter);
printf("CPU Time : %.2lf ms\n", chrono(STOP));
}
else
{
printf("All possibilities using the alphabet \"%s\" ", alphabet);
printf("of length [%d-%d]", cstlen ? maxlen : 1, maxlen);
printf(" exhausted.\nThe password wasn't found.\n");
}
free(alphabet);
}
free(password);
}
else
{
// Showing proper usage of ./crack
print_help();
return 1;
}
}
// === Fuctions ===
// compare to strings (unlike strcmp, returns 1 if the two strings are equal and non-null)
int compare(char* s1, char* s2)
{
if (!s1 || !s2)
return 0;
for ( ; *s1 && *s2 && *s1 == *s2; s1++, s2++);
return *s1 == *s2;
}
// strlen, basically
int length(char* s)
{
if(!s || !*s)
return 0;
return 1 + length(s + 1);
}
// returns the positive number in s, for converting the numerical argument for MAXLEN
int is_number(char* s)
{
// so that a parameter such as 09 will be interpreted as an alphabet and not a MAXLEN
if (*s == '0') return 0;
while(*s && *s >= '0' && *s <= '9' && s++);
return !*s;
}
// a clean-up function for argument retrieval
char arg_to_char(char* arg)
{
if (arg[0] == '-')
return arg[1]; // for flags
else if (is_number(arg))
return 'N'; // for maxlen
else
return 'F'; // for format
}
// Timing the process, flag can take START = 1 or STOP = 0, in that order
// No verifying is done because the function is literally called twice
double chrono(int flag)
{
static struct timeval start, end;
if(flag)
{
gettimeofday(&start, NULL);
return 0.0;
}
else
{
gettimeofday(&end, NULL);
return (double) ((end.tv_sec - start.tv_sec)*1000.0 + (end.tv_usec - start.tv_usec) / 1000.0);
}
}
// convert a positive number string into a number (input verified via the is_number call before)
int to_number(char* s, int len)
{
if(len == 1)
return s[0] - '0';
return s[len - 1] - '0' + 10 * to_number(s, len - 1);
}
// Join two alphabets, or create one if *alphabet is null
void append_to(char** alphabet, char* to_append)
{
// Go through each character of the alphabet to append and add it if it's not a duplicate
for (int i = 0, j, al = length(*alphabet), l = length(to_append); i < l; i++)
{
for(j = 0; j < al && (*alphabet)[j] != to_append[i]; j++);
if (j == al)
{
al += 1;
*alphabet = (char*) realloc(*alphabet, al + 1);
(*alphabet)[j] = to_append[i];
(*alphabet)[j+1] = '\0';
}
}
}
// checks for valid formats such as ka or QT or 13
int is_valid_format(char* s, char* e)
{
if (((*s >= '0' && *s <= '9') || (*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z')) &&
((*e >= '0' && *e <= '9') || (*e >= 'a' && *e <= 'z') || (*e >= 'A' && *e <= 'Z')))
{
if (*s > *e)
{
char temp = *s;
*s = *e;
*e = temp;
}
return 1;
}
return 0;
}
// given the user's arguments, generate the appropriate alphabet
void generate_alphabet(char** alphabet, char* format, int freq, int spc, int sample)
{
// Frequency of characters in password (source: goo.gl/90VBb7)
char* fall = "aeorisn1tl2md0cp3hbuk45g9687yfwjvzxqASERBTMLNPOIDCHGKFJUW.!Y*@V-ZQX_$#,/+?;^ %~=&`\\)][:<(>\"|{'}";
// added those two to decluster the function a bit
char* falp = "aeorisn1tl2md0cp3hbuk45g9687yfwjvzxqASERBTMLNPOIDCHGKFJUWYVZQX";
char* fspc = ".!*@-_$#,/+?;^ %~=&`\\)][:<(>\"|{'}";
// if -a is passed, then use the argument format as an alphabet, otherwise go through the options
if (sample)
{
append_to(alphabet, format);
}
else
{
if (compare(format, "all"))
{
if (freq)
{
// append the appropriate frequency alphabet and return
append_to(alphabet, (spc) ? fall : falp);
return;
}
else
format = "azAZ09";
}
for (int i = 0, l = length(format); i < l; i++)
{
// "protective" measure to accept only formats that make sense
if (is_valid_format(&format[i], &format[i+1]))
{
// generate the corresponding alphabet
int malen = format[i+1] - format[i] + 1;
char mini_alphabet[malen + 1];
if (freq)
{
// fill the alphabet from 'falp' to conserve order
for (int j = 0, k = 0, l = length(falp); j < l; j++)
{
if(falp[j] >= format[i] && falp[j] <= format[i+1])
mini_alphabet[k++] = falp[j];
}
}
else
{
// fill the alphabet alphabetically
for (int j = 0; j < malen; j++)
mini_alphabet[j] = format[i] + j;
}
mini_alphabet[malen] = '\0';
append_to(alphabet, mini_alphabet);
// we've taken two letters
i++;
}
else
{
// if the output is not recognized as valid, use it as an alphabet
// e.g. "m5{Q" will be treated as if -a is passed
append_to(alphabet, format + i);
break;
}
}
}
// just because I wanted the special characters to always be appended in the end
if(spc)
append_to(alphabet, fspc);
}
/*
dropped, too shabby.
although this one can handle possibility spaces great than 2^sizeof(long long)
(as if this measly program would be used for such cases *shrugs*)
instead of using a counter and adjusting the positions of the letters accordingly,
make only the necessary amount of setting and resetting
- salt is the second argument of the crypt function
- pwpos[] holds the indices of the letters of the alphabet that make up the password
- tail is the position of the last character (could've been the first really, I just
liked this one better because it looks cooler and more natural when using crack -a 09)
- curlen is to gauge the progress made and see if every possible combination is exhausted
*/
int old_generate_and_check(char** pw, char* hash, char* alphabet, int maxlen, int cstl, int output)
{
char salt[] = {hash[0], hash[1], '\0'};
int curlen = (cstl) ? maxlen : 1;
int tail = curlen - 1;
int pwpos[maxlen];
int alphalen = length(alphabet);
(*pw) = (char*) malloc(curlen + 1);
for(int i = 0; i < curlen; i++)
(*pw)[i] = alphabet[pwpos[i] = 0];
(*pw)[curlen] = '\0';
while (compare(crypt(*pw, salt), hash) == 0)
{
// to optimize, this is an extra check for every loop
// could go outside of the loop but I didn't feel like writing another function
if(output)
printf("[%d/%d] %*s | %s\n", maxlen, curlen,
maxlen, *pw, crypt(*pw, salt));
// update the current configuration of indices
pwpos[tail]++;
// update the tail or ripple the excess through the lower indices
if (pwpos[tail] < alphalen)
(*pw)[tail] = alphabet[pwpos[tail]];
else
{
pwpos[tail] = 0;
(*pw)[tail] = alphabet[0];
int i = tail - 1;
// zero out every pos that reached the limit alphalen
for( ; i >= 0 && pwpos[i] + 1 == alphalen; i--)
(*pw)[i] = alphabet[pwpos[i] = 0];
// there are still other combination at this curlen
if(i >= 0)
(*pw)[i] = alphabet[++pwpos[i]];
// maxed out the possibilities, move to the next len
else
{
curlen++;
tail = curlen - 1;
if (curlen <= maxlen)
{
*pw = (char*) realloc(*pw, curlen + 1);
pwpos[curlen - 1] = 0;
(*pw)[curlen - 1] = alphabet[0];
(*pw)[curlen] = '\0';
}
else
return 0;
}
}
}
return 1; // quit with a break
}
/*
new version of generate_and_check using an 8-byte counter
(that's a 2^64 possibilities, around 95^10, which means almost every
possible 10 character long passwords with 'all -s' passed)
it's "capped" at 10 (or less, if you're not using all 95 possibles chars)
but it's much easier to deploy and it can return the number of tries!
*/
unsigned long long generate_and_check(char** pw, char* hash, char* alphabet, int maxlen, int cstl, int output)
{
/*
counter : the variable that identifies every combination
alphalen : the length of the alphabet, useful to decompose the counter
curlen : stores the current length of generated passwords (so that we can generate every
possible password whose length is <= maxlen
set to maxlen directly if -c is passed
powers : stores the powers of alphalen so that we don't have to compute them at every loop cycle
(used to decompose the counter into unique combinations of the alphabet)
accumpow : stores the accumulative sum of the the powers. Used only to format the output in case -o is passed
(so that we get the passwords in "alphabetical order": "aaa" then "aab" then "aac" ..)
also can be used whether counter reached its limits in curlen (which is coincidentially the accumulated
sum of alphalen powers <= curlen)
*/
unsigned long long counter;
int alphalen = length(alphabet);
char salt[] = {hash[0], hash[1], '\0'};
int i, curlen = (cstl) ? maxlen : 1;
unsigned long long powers[maxlen + 1];
unsigned long long accumpow[maxlen];
// initialize everything properly
*pw = (char*) malloc(maxlen + 1);
(*pw)[curlen] = '\0';
for(powers[0] = 1, accumpow[0] = 0, i = 1; i <= maxlen; i++)
{
powers[i] = alphalen * powers[i - 1];
accumpow[i] = powers[i] + accumpow[i - 1];
}
for(i = 1; i <= curlen; i++)
{
(*pw)[i - 1] = alphabet[0];
}
counter = accumpow[curlen - 1] - 1;
// just to avoid that pesky check in every loop (minimizing the opartions per loop)
if(output)
{
// increase the counter after every unsuccessful loop, and check for overflow
do
{
counter++;
(*pw)[curlen - 1] = alphabet[counter % alphalen];
for(i = 1; i < curlen; i++)
(*pw)[curlen - 1 - i] = alphabet[((counter - accumpow[curlen - 1]) / powers[i]) % alphalen];
/* ^^^^
absolutely unnecessary complication to make the results look better
you might as well use (*pw)[i] = alphabet[(counter / powers[i]) % alphalen]
the no-output version doesn't use this formula (because it demands more calculations per cycle)
*/
if(counter == accumpow[curlen]) // all combinations at curlen exhausted
{
curlen++;
if (curlen > maxlen) // and we're done !
return 0;
else
{
counter--; // backtrack so that the first curlen-long combination is tested
(*pw)[curlen] = '\0';
continue;
}
}
printf("[%d/%d] %*s | %s\n", maxlen, curlen, maxlen, *pw, crypt(*pw, salt));
}
while (compare(crypt(*pw, salt), hash) == 0);
}
// repeated the same code except for the printf
else
{
do
{
counter++;
for(i = 0; i < curlen; i++)
(*pw)[i] = alphabet[(counter / powers[i]) % alphalen];
if(counter == accumpow[curlen])
{
curlen++;
if (curlen > maxlen)
return 0;
else
{
counter--;
(*pw)[curlen] = '\0';
continue;
}
}
}
while (compare(crypt(*pw, salt), hash) == 0);
}
return counter; // quit through the loop condition
}
// load the file, read every word and compare its hash to the given hash
int dictionary_attack(FILE* dict, char** password, char* hash, int output)
{
char salt[] = {hash[0], hash[1], '\0'};
char* result;
*password = (char*) malloc(30*sizeof(char));
// Embarrassing way to get away with one less comparison per cycle
if (output)
{
for (int i = 1; fscanf(dict, "%s", *password) != EOF; i++)
{
result = crypt(*password, salt);
printf("%d : %s | %s\n", i, result, *password);
if (compare(result, hash))
return 1;
}
}
else
{
for (int i = 0; fscanf(dict, "%s", *password) != EOF; i++)
{
if (compare(crypt(*password, salt), hash))
return 1;
}
}
return 0;
}
// Cleaning up the main
void print_help(void)
{
printf("\nHow to use crack (not *that* crack): \n");
printf(" $ crack hash frmt NMAX -f -s -c -a -o -f \n");
printf(" hash : the DES-hashed password (13 characters) e.g. HA50uI.k/2Pa2\n");
printf(" frmt : the format of the alphabet to use, as follows\n");
printf(" az for lowercase letters / AZ for uppercase letters / 09 for digits\n");
printf(" all for uppercase, lowercase and digits\n");
printf(" any two caracters, e.g. bf to use the alphabet 'bcdef'\n");
printf(" a combination of the previous possibilities (examples below)\n");
printf(" (if -a is passed, then this holds the alphabet sample requested)\n");
printf(" NMAX : the max length of the password to look for\n");
printf(" if -c is passed, then the length of the password to look for\n");
printf(" -f : order the alphabet by frequency of occurence instead of alphabetically\n");
printf(" (speeds up checking non-random passwords)\n");
printf(" -s : add 33 printable special characters to the alphabet\n");
printf(" -c : generate only passwords of length NMAX (by default 8)\n");
printf(" -a : use a sample alphabet (passed in format)\n");
printf(" -o : output the generated passwords and their hashes\n");
printf(" (kinda looks cool, but hella slows down the process)\n");
printf(" -d : use a dictionary attack (prioritized over all other options)\n");
printf(" the argument right after this option is taken to be the filename\n\n");
printf("Examples of usage : \n");
printf(" $ crack HA50uI.k/2Pa2\n");
printf(" will try all possible combinations of lowercase letters (default alphabet)\n");
printf(" for passwords of length DEFAULT_LEN (by default 8) and outputs only the result, if found.\n");
printf(" $ crack HA50uI.k/2Pa2 -f ac 5\n");
printf(" will try all possible combinations of passwords that are at most 5 characters long\n");
printf(" containing only letters from a to i, ordered by frequency (aeidchbgf)\n");
printf(" so, it wll generate every sequence from 'a', 'e' .. to 'ffffe', 'fffff'\n");
printf(" $ crack HA50uI.k/2Pa2 all -s -o -f -t -c 8\n");
printf(" set a timer and use all characters (azAZ09 and special chars) ordered by frequency to\n");
printf(" generate every possible password that is of length 7 or less (good luck with that ..)\n");
printf(" $ crack HA50uI.k/2Pa2 -a ilmkzt123{*0 7\n");
printf(" try every possible combination of the alphabet 'ilmkzt123{*0' that are 7 characters long or less\n");
printf(" $ crack HA50uI.k/2Pa2 -d wurds.txt\n");
printf(" try every word on the file 'wurds.txt' for a potential match\n\n");
printf("Well.. hopefully you get the idea.\n");
}