#!/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 =<&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> - /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 = ; 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+?()?\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'); }