NetExec/crackmapexec.py

249 lines
13 KiB
Python

#!/usr/bin/env python2
#This must be one of the first imports or else we get threading error on completion
from gevent import monkey
monkey.patch_all()
from gevent.pool import Pool
from gevent import joinall, sleep
from core.connector import connector
from core.database import CMEDatabase
from core.cmeserver import CMEServer
from threading import Thread
from logging import getLogger
from argparse import RawTextHelpFormatter
from core.logger import setup_logger, setup_debug_logger, CMEAdapter
from core.context import Context
from core.helpers import highlight
from core.targetparser import parse_targets
import getpass
import sqlite3
import imp
import argparse
import os
import logging
import sys
VERSION = '3.0'
CODENAME = '\'So looong gay boy!\''
parser = argparse.ArgumentParser(description="""
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
/ || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / |
| ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----'
| | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | |
| `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----.
\______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______|
Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r
Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino)
Inspired by:
@ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap
@gojhonny's CredCrack https://github.com/gojhonny/CredCrack
@pentestgeek's smbexec https://github.com/pentestgeek/smbexec
{}: {}
{}: {}
""".format(highlight('Version', 'red'),
highlight(VERSION),
highlight('Codename', 'red'),
highlight(CODENAME)),
formatter_class=RawTextHelpFormatter,
version='{} - {}'.format(VERSION, CODENAME),
epilog='HA! Made you look!')
parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets")
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)")
parser.add_argument('-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication')
parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames")
parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
msgroup = parser.add_mutually_exclusive_group()
msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords")
msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
parser.add_argument("-m", metavar='MODULE', dest='module', help='Payload module to use')
parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options')
parser.add_argument('--module-info', action='store_true', dest='module_info', help='Display module info')
parser.add_argument("--share", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)")
parser.add_argument("--smb-port", dest='smb_port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
parser.add_argument("--mssql-port", dest='mssql_port', default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)')
parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)')
parser.add_argument("--server-port", dest='server_port', metavar='PORT', type=int, help='Start the server on the specified port')
parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate locally to each target')
parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)')
parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output")
rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')
rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems')
rgroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)")
rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
rgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')
rgroup.add_argument("--wdigest", choices={'enable', 'disable'}, help="Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1")
egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
egroup.add_argument("--shares", action="store_true", dest="enum_shares", help="Enumerate shares and access")
egroup.add_argument('--uac', action='store_true', help='Checks UAC status')
egroup.add_argument("--sessions", action='store_true', dest='enum_sessions', help='Enumerate active sessions')
egroup.add_argument('--disks', action='store_true', dest='enum_disks', help='Enumerate disks')
egroup.add_argument("--users", action='store_true', dest='enum_users', help='Enumerate users')
egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s (default: 4000)')
egroup.add_argument("--pass-pol", action='store_true', dest='pass_pol', help='Dump password policy')
egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users')
egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query')
egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', dest='wmi_namespace', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)')
sgroup = parser.add_argument_group("Spidering", "Options for spidering shares")
sgroup.add_argument("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (default: root directory)')
sgroup.add_argument("--content", dest='search_content', action='store_true', help='Enable file content searching')
sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', dest='exclude_dirs', help='Directories to exclude from spidering')
esgroup = sgroup.add_mutually_exclusive_group()
esgroup.add_argument("--pattern", nargs='*', help='Pattern(s) to search for in folders, filenames and file content')
esgroup.add_argument("--regex", nargs='*', help='Regex(s) to search for in folders, filenames and file content')
sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)')
cgroup = parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default="wmiexec", help="Method to execute the command. Ignored if in MSSQL mode (default: wmiexec)")
cgroup.add_argument('--force-ps32', action='store_true', help='Force the PowerShell command to run in a 32-bit process')
cgroup.add_argument('--no-output', action='store_true', dest='no_output', help='Do not retrieve command output')
cgroup.add_argument("-x", metavar="COMMAND", dest='command', help="Execute the specified command")
cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Execute the specified PowerShell command')
mgroup = parser.add_argument_group("MSSQL Interaction", "Options for interacting with MSSQL DBs")
mgroup.add_argument("--mssql", action='store_true', help='Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs')
mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB')
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
module = None
server = None
context = None
targets = []
server_port_dict = {'http': 80, 'https': 443}
args = parser.parse_args()
if args.verbose:
setup_debug_logger()
logger = CMEAdapter(setup_logger())
if os.geteuid() is not 0:
logger.error("I'm sorry {}, I'm afraid I can't let you do that".format(getpass.getuser()))
sys.exit(1)
if not args.server_port:
args.server_port = server_port_dict[args.server]
try:
# set the database connection to autocommit w/ isolation level
db_connection = sqlite3.connect('data/cme.db', check_same_thread=False)
db_connection.text_factory = str
db_connection.isolation_level = None
db = CMEDatabase(db_connection)
except Exception as e:
logger.error("Could not connect to CME database: {}".format(e))
sys.exit(1)
if args.cred_id:
try:
c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0]
args.username = [username]
if not args.domain:
args.domain = domain
if credtype == 'hash':
args.hash = [password]
elif credtype == 'plaintext':
args.password = [password]
except IndexError:
logger.error("Invalid database credential ID!")
sys.exit(1)
else:
for user, passw, ntlm_hash in zip(args.username, args.password, args.hash):
if os.path.exists(user):
args.username.remove(user)
args.username.append(open(user, 'r'))
if os.path.exists(passw):
args.password.remove(passw)
args.password.append(open(passw, 'r'))
if os.path.exists(ntlm_hash):
args.hash.remove(ntlm_hash)
args.hash.append(open(ntlm_hash, 'r'))
if args.module:
if not os.path.exists(args.module):
logger.error('Path to module invalid!')
sys.exit(1)
else:
module = imp.load_source('payload_module', args.module).CMEModule()
if not hasattr(module, 'name'):
logger.error('Module missing the name variable!')
sys.exit(1)
elif not hasattr(module, 'options'):
logger.error('Module missing the options function!')
sys.exit(1)
elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'):
logger.error('Module missing the on_login/on_admin_login function(s)!')
sys.exit(1)
if args.module_info:
logger.info('{} module description:'.format(module.name))
print module.__doc__
logger.info('{} module options:'.format(module.name))
print module.options.__doc__
sys.exit(0)
module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper()})
context = Context(db, module_logger, args)
module_options = {}
for option in args.module_options:
key, value = option.split('=')
module_options[str(key).upper()] = value
module.options(context, module_options)
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
server = CMEServer(module, context, args.server_port, args.server)
server.start()
for target in args.target:
if os.path.exists(target):
with open(target, 'r') as target_file:
for target_entry in target_file:
targets.extend(parse_targets(target_entry))
else:
targets.extend(parse_targets(target))
try:
'''
Open all the greenlet (as supposed to redlet??) threads
Whoever came up with that name has a fetish for traffic lights
'''
pool = Pool(args.threads)
jobs = [pool.spawn(connector, str(target), args, db, module, context, server) for target in targets]
#Dumping the NTDS.DIT can take a long time, so we ignore the thread timeout
if args.ntds:
joinall(jobs)
elif not args.ntds:
for job in jobs:
job.join(timeout=args.timeout)
except KeyboardInterrupt:
pass
if server:
server.shutdown()
logger.info('KTHXBYE!')