#!/usr/bin/perl 

# Nutcracker version 1.9 (beta test stage for 2.0) 

# Released under the GNU GPL (http://www.fsf.org/copyleft/gpl.html)

# Copyleft 2001 by Ryan T. Rhea (this is open source, hack away...)
#  ryan@northernlightsgroup.hypermart.net

# Check http://northernlights.hypermart.net for new versions of this 
# program and for other computer security related tools and information

# Modified on Sept 13, 2000 by Jason Burke
# Simplified some logic, removed curses routines, and added
#  some minor statistics information when the program ends

# Modified again Sept 18, 2000 by Ryan T. Rhea
# Kept Jason's changes, but abstracted the different crack routines
# This was in part done in order to fix a major logic error in the suffix 
#  modification algorithm (this bug was present in Nutcracker 1.5 but
#  suprisingly it went undetected)

# Added the prefix_mod_crack() routine (why not?)

# Added the option to specify the high value for suffix_mod_crack()
#  and prefix_mod_crack() at the command line

# Added to the statistics info and abstracted a new procedure for
#  generating the statistics 


print "\n";
print "Nutcracker version 1.9\n";
print "Copyleft 2001 by Ryan T. Rhea\n";
print "\n";


# Check our command-line arguments; make sure we at least have file names

if($#ARGV<1) {
	print "\n";
	print "USAGE: nutcracker passwd_file dictionary_file [-p(X)] [-s(X)]\n";
	print "\n";
	print "-p  : turn on the prefix mod algorithm with default range 0-99\n";
	print "-pX : turn on the prefix mod algorithm using specified range 0-X\n";
	print "-s  : turn on the suffix mod algorithm with default range 0-99\n"; 
	print "-pX : turn on the suffix mod algorithm using specified range 0-X\n";
	print "\n";
	print "\a";
	exit;
}


# Set filenames for password file and dictionary file

$passwd=$ARGV[0];
$dict=$ARGV[1];


# Find the number of command-line options

$num_args=$#ARGV-1;


# By default set all options to 'no'; process command-line options

$prefix_mod="no";
$suffix_mod="no";

for (1 .. $num_args) {
	$curr=$_ + 1;
	SWITCH: { 
		if ($ARGV[$curr] =~ /^-p/) {
			$prefix_mod="yes";
			$high_prefix=substr($ARGV[$curr], 2);
			if($high_prefix eq "") { $high_prefix = "99"; } 
			last SWITCH;
		}
		if ($ARGV[$curr] =~ /^-s/) { 
			$suffix_mod="yes";
			$high_suffix=substr($ARGV[$curr], 2);
			if($high_suffix eq "") { $high_suffix = "99"; }
			last SWITCH; 
		}
	}
}


# Here is the "main()" ;) 

load_dict_file();

$start_time=time;

load_and_crack_passwd_file();

$secs=(time - $start_time);

generate_stats();

# End of "main()"



# Routine to process dictionary file
# Dictionary words get pushed into @words

sub load_dict_file {
    open(DICT, $dict) or die "error: can't open $dict";
	print "Loading dictionary file... ";
	while(<DICT>) {
        @_words=split;
        push @words, [@_words];
    }
    print "Done!\n";
	print "\n";
    close(DICT);
}


# Routine to process our encrypted password file
# Check here for disabled (or no password) accounts
# Use the crypt function(s) on any active (remaining) accounts

sub load_and_crack_passwd_file {
    open(PASSWD, $passwd) or die "error: can't open $passwd";
    print "user name        ";
    print "status           ";
    print "password         \n";
    print "---------------  ";
    print "---------------  ";
    print "---------------  \n";
    $count=0;
    while(<PASSWD>) {
        $count++;
        ($user, $enc, $uid, $gid, $gecos, $home, $shell)=split(/:/);
        print "$user", ' ' x (17 - length($user));
        if($enc eq "\*" or $enc eq "x" or $enc eq "!" or $enc eq "!!") {
            $status="disabled";
            $password="-";
            print "$status", ' ' x (17 - length($status));
            print "$password\n";
        }
        elsif($enc eq "") {
            $status="NO PASSWORD\a";
            $password=" ";
            print "$status", ' ' x (17 - length($status));
            print "$password\n";
        }
        else {
            $crk="no";
            crack();
			if($crk eq "no" && $prefix_mod eq "yes") {
				prefix_mod_crack();
			}
			if($crk eq "no" && $suffix_mod eq "yes") {
				suffix_mod_crack();
			}
			if($crk eq "no") {
                $status="unable to crack";
                $password="X";
                print "$status", ' ' x (17 - length($status));
                print "$password\n";
            }
        }
    }
    close(PASSWD);
}


# Routine to crypt() dictionary words and then compare with our encrypted passwords

sub crack {
    for $word(@words) {
        $try=crypt(@$word[0], $enc);
		$attempt_total++;
        if($try eq $enc) {
            $status="CRACKED";
            $password=@$word[0];
            print "\a";
            print "$status", ' ' x (17 - length($status));
            print "$password\n";
            $crk="yes";
            $crk_total++;
            last;
        }
	}
}


# Routine to crypt() our prefix-modified dictionary words and then compare with our
#  encrypted passwords

sub prefix_mod_crack { 
	for $word(@words) {	
		for (0 .. $high_prefix) {
        	$prefix = $_;
            $modword=$prefix.@$word[0];
            $try=crypt($modword, $enc);
            $attempt_total++;
			if($try eq $enc) {
            	$status="CRACKED";
                print "\a";
                print "$status", ' ' x (17 - length($status));
                print "$modword\n";
                $crk="yes";
                $crk_total++;
                last;
            }
        }
		if($crk eq "yes") { last; }
	}
}


# Routine to crypt() our suffix-modified dictionary words and then compare with our
#  encrypted passwords

sub suffix_mod_crack { 
	for $word(@words) {	
		for (0 .. $high_suffix) {
        	$suffix = $_;
            $modword=@$word[0].$suffix;
            $try=crypt($modword, $enc);
            $attempt_total++;
			if($try eq $enc) {
            	$status="CRACKED";
                print "\a";
                print "$status", ' ' x (17 - length($status));
                print "$modword\n";
                $crk="yes";
                $crk_total++;
                last;
            }
        }
		if($crk eq "yes") { last; }
	}
}


# Routine to generate some statistical info

sub generate_stats {
	if($secs eq "0") { $secs = "1"; }

	print "\n";
	if($crk_total ne "") {
		print "$crk_total passwords cracked out of $count ";
		print "(", sprintf("%.0f", ($crk_total / $count) * 100), "\%)";
		print "\n";
	}

	print "$attempt_total crypts in ";
	if($secs > 3600) {
		print int($secs / 3600);
		print " hr ";
		print int(($secs % 3600) / 60);
		print " min ";
		print ($secs % 60);
		print " sec ";
	}
	elsif($secs > 60) {
		print int($secs / 60);
		print " min ";
		print ($secs % 60);
		print " sec ";
	}
	else {
		print $secs;
		print " sec ";
	}

	print "(", sprintf("%.0f", $attempt_total / $secs), " c/s)";
	print "\n";
}

