Merge branch 'master' of https://github.com/mpgn/CrackMapExec
commit
6d4731cf93
|
@ -9,7 +9,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
name: CrackMapExec Tests on ${{ matrix.os }}
|
||||
name: CrackMapExec Tests for Py${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
|
@ -22,9 +22,14 @@ jobs:
|
|||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install librairies
|
||||
- name: Install poetry
|
||||
run: |
|
||||
pip install .
|
||||
pipx install poetry --python python${{ matrix.python-version }}
|
||||
poetry --version
|
||||
poetry env info
|
||||
- name: Install librairies with dev group
|
||||
run: |
|
||||
poetry install --with dev
|
||||
- name: Run the e2e test
|
||||
run: |
|
||||
pytest tests
|
||||
poetry run pytest tests
|
||||
|
|
|
@ -8,10 +8,11 @@ from cme.loaders.protocolloader import ProtocolLoader
|
|||
from cme.helpers.logger import highlight
|
||||
from termcolor import colored
|
||||
from cme.logger import cme_logger
|
||||
import importlib.metadata
|
||||
|
||||
|
||||
def gen_cli_args():
|
||||
VERSION = "6.0.0"
|
||||
VERSION = importlib.metadata.version("crackmapexec")
|
||||
CODENAME = "Bane"
|
||||
|
||||
parser = argparse.ArgumentParser(description=f"""
|
||||
|
@ -112,7 +113,7 @@ def gen_cli_args():
|
|||
std_parser = argparse.ArgumentParser(add_help=False)
|
||||
std_parser.add_argument(
|
||||
"target",
|
||||
nargs="+" if not module_parser.parse_known_args()[0].list_modules else "*",
|
||||
nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options) else "*",
|
||||
type=str,
|
||||
help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)",
|
||||
)
|
||||
|
|
|
@ -51,8 +51,8 @@ class connection(object):
|
|||
self.admin_privs = False
|
||||
self.password = ""
|
||||
self.username = ""
|
||||
self.kerberos = True if self.args.kerberos or self.args.use_kcache else False
|
||||
self.aesKey = None if not self.args.aesKey else self.args.aesKey
|
||||
self.kerberos = True if self.args.kerberos or self.args.use_kcache or self.args.aesKey else False
|
||||
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
|
||||
self.kdcHost = None if not self.args.kdcHost else self.args.kdcHost
|
||||
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
|
||||
self.failed_logins = 0
|
||||
|
@ -236,6 +236,7 @@ class connection(object):
|
|||
secret.append(secret_single)
|
||||
cred_type.append(cred_type_single)
|
||||
|
||||
if len(secret) != len(data): data = [None] * len(secret)
|
||||
return domain, username, owned, secret, cred_type, data
|
||||
|
||||
def parse_credentials(self):
|
||||
|
@ -324,6 +325,10 @@ class connection(object):
|
|||
return False
|
||||
if self.args.continue_on_success and owned:
|
||||
return False
|
||||
# Enforcing FQDN for SMB if not using local authentication. Related issues/PRs: #26, #28, #24, #38
|
||||
if self.args.protocol == 'smb' and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "":
|
||||
self.logger.error(f"Domain {domain} for user {username.rstrip()} need to be FQDN ex:domain.local, not domain")
|
||||
return False
|
||||
|
||||
with sem:
|
||||
if cred_type == 'plaintext':
|
||||
|
|
|
@ -28,6 +28,18 @@ from sys import exit
|
|||
import logging
|
||||
import sqlalchemy
|
||||
from rich.progress import Progress
|
||||
from sys import platform
|
||||
|
||||
# Increase file_limit to prevent error "Too many open files"
|
||||
if platform != "win32":
|
||||
import resource
|
||||
file_limit = list(resource.getrlimit(resource.RLIMIT_NOFILE))
|
||||
if file_limit[1] > 10000:
|
||||
file_limit[0] = 10000
|
||||
else:
|
||||
file_limit[0] = file_limit[1]
|
||||
file_limit = tuple(file_limit)
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, file_limit)
|
||||
|
||||
try:
|
||||
import librlers
|
||||
|
|
|
@ -13,7 +13,7 @@ from impacket.dcerpc.v5.dcomrt import IObjectExporter
|
|||
|
||||
class CMEModule:
|
||||
name = "ioxidresolver"
|
||||
description = "Thie module helps you to identify hosts that have additional active interfaces"
|
||||
description = "This module helps you to identify hosts that have additional active interfaces"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = False
|
||||
|
|
|
@ -27,13 +27,17 @@ class CMEModule:
|
|||
def options(self, context, module_options):
|
||||
"""
|
||||
SERVER PKI Enrollment Server to enumerate templates for. Default is None, use CN name
|
||||
BASE_DN The base domain name for the LDAP query
|
||||
"""
|
||||
self.context = context
|
||||
self.regex = re.compile("(https?://.+)")
|
||||
|
||||
self.server = None
|
||||
self.base_dn = None
|
||||
if module_options and "SERVER" in module_options:
|
||||
self.server = module_options["SERVER"]
|
||||
if module_options and "BASE_DN" in module_options:
|
||||
self.base_dn = module_options["BASE_DN"]
|
||||
|
||||
def on_login(self, context, connection):
|
||||
"""
|
||||
|
@ -49,7 +53,7 @@ class CMEModule:
|
|||
|
||||
try:
|
||||
sc = ldap.SimplePagedResultsControl()
|
||||
base_dn_root = ",".join(connection.ldapConnection._baseDN.split(",")[-2:])
|
||||
base_dn_root = connection.ldapConnection._baseDN if self.base_dn is None else self.base_dn
|
||||
|
||||
if self.server is None:
|
||||
resp = connection.ldapConnection.search(
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ldap3
|
||||
from impacket.dcerpc.v5 import samr, epm, transport
|
||||
|
||||
class CMEModule:
|
||||
'''
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
Thanks to the guys at impacket for the original code
|
||||
'''
|
||||
|
||||
name = 'add-computer'
|
||||
description = 'Adds or deletes a domain computer'
|
||||
supported_protocols = ['smb']
|
||||
opsec_safe = True
|
||||
multiple_hosts = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
add-computer: Specify add-computer to call the module using smb
|
||||
NAME: Specify the NAME option to name the Computer to be added
|
||||
PASSWORD: Specify the PASSWORD option to supply a password for the Computer to be added
|
||||
DELETE: Specify DELETE to remove a Computer
|
||||
CHANGEPW: Specify CHANGEPW to modify a Computer password
|
||||
Usage: cme smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password1"
|
||||
cme smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" DELETE=True
|
||||
cme smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
|
||||
'''
|
||||
|
||||
self.__baseDN = None
|
||||
self.__computerGroup = None
|
||||
self.__method = "SAMR"
|
||||
self.__noAdd = False
|
||||
self.__delete = False
|
||||
self.noLDAPRequired = False
|
||||
|
||||
if 'DELETE' in module_options:
|
||||
self.__delete = True
|
||||
|
||||
if 'CHANGEPW' in module_options and ('NAME' not in module_options or 'PASSWORD' not in module_options):
|
||||
context.log.error('NAME and PASSWORD options are required!')
|
||||
elif 'CHANGEPW' in module_options:
|
||||
self.__noAdd = True
|
||||
|
||||
if 'NAME' in module_options:
|
||||
self.__computerName = module_options['NAME']
|
||||
if self.__computerName[-1] != '$':
|
||||
self.__computerName += '$'
|
||||
else:
|
||||
context.log.error('NAME option is required!')
|
||||
exit(1)
|
||||
|
||||
if 'PASSWORD' in module_options:
|
||||
self.__computerPassword = module_options['PASSWORD']
|
||||
elif 'PASSWORD' not in module_options and not self.__delete:
|
||||
context.log.error('PASSWORD option is required!')
|
||||
exit(1)
|
||||
|
||||
def on_login(self, context, connection):
|
||||
|
||||
#Set some variables
|
||||
self.__domain = connection.domain
|
||||
self.__domainNetbios = connection.domain
|
||||
self.__kdcHost = connection.hostname + "." + connection.domain
|
||||
self.__target = self.__kdcHost
|
||||
self.__username = connection.username
|
||||
self.__password = connection.password
|
||||
self.__targetIp = connection.host
|
||||
self.__port = context.smb_server_port
|
||||
self.__aesKey = context.aesKey
|
||||
self.__hashes = context.hash
|
||||
self.__doKerberos = connection.kerberos
|
||||
self.__nthash = ""
|
||||
self.__lmhash = ""
|
||||
|
||||
if context.hash and ":" in context.hash[0]:
|
||||
hashList = context.hash[0].split(":")
|
||||
self.__nthash = hashList[-1]
|
||||
self.__lmhash = hashList[0]
|
||||
elif context.hash and ":" not in context.hash[0]:
|
||||
self.__nthash = context.hash[0]
|
||||
self.__lmhash = "00000000000000000000000000000000"
|
||||
|
||||
# First try to add via SAMR over SMB
|
||||
self.doSAMRAdd(context)
|
||||
|
||||
# If SAMR fails now try over LDAPS
|
||||
if not self.noLDAPRequired:
|
||||
self.doLDAPSAdd(connection,context)
|
||||
else:
|
||||
exit(1)
|
||||
|
||||
def doSAMRAdd(self,context):
|
||||
|
||||
if self.__targetIp is not None:
|
||||
stringBinding = epm.hept_map(self.__targetIp, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
|
||||
else:
|
||||
stringBinding = epm.hept_map(self.__target, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
|
||||
rpctransport = transport.DCERPCTransportFactory(stringBinding)
|
||||
rpctransport.set_dport(self.__port)
|
||||
|
||||
if self.__targetIp is not None:
|
||||
rpctransport.setRemoteHost(self.__targetIp)
|
||||
rpctransport.setRemoteName(self.__target)
|
||||
|
||||
if hasattr(rpctransport, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
self.__nthash, self.__aesKey)
|
||||
|
||||
rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
|
||||
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
servHandle = None
|
||||
domainHandle = None
|
||||
userHandle = None
|
||||
try:
|
||||
dce.connect()
|
||||
dce.bind(samr.MSRPC_UUID_SAMR)
|
||||
|
||||
samrConnectResponse = samr.hSamrConnect5(dce, '\\\\%s\x00' % self.__target,
|
||||
samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN )
|
||||
servHandle = samrConnectResponse['ServerHandle']
|
||||
|
||||
samrEnumResponse = samr.hSamrEnumerateDomainsInSamServer(dce, servHandle)
|
||||
domains = samrEnumResponse['Buffer']['Buffer']
|
||||
domainsWithoutBuiltin = list(filter(lambda x : x['Name'].lower() != 'builtin', domains))
|
||||
|
||||
if len(domainsWithoutBuiltin) > 1:
|
||||
domain = list(filter(lambda x : x['Name'].lower() == self.__domainNetbios, domains))
|
||||
if len(domain) != 1:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'This domain does not exist: "' + self.__domainNetbios + '"'))
|
||||
logging.critical("Available domain(s):")
|
||||
for domain in domains:
|
||||
logging.error(" * %s" % domain['Name'])
|
||||
raise Exception()
|
||||
else:
|
||||
selectedDomain = domain[0]['Name']
|
||||
else:
|
||||
selectedDomain = domainsWithoutBuiltin[0]['Name']
|
||||
|
||||
samrLookupDomainResponse = samr.hSamrLookupDomainInSamServer(dce, servHandle, selectedDomain)
|
||||
domainSID = samrLookupDomainResponse['DomainId']
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.info("Opening domain %s..." % selectedDomain)
|
||||
samrOpenDomainResponse = samr.hSamrOpenDomain(dce, servHandle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER , domainSID)
|
||||
domainHandle = samrOpenDomainResponse['DomainHandle']
|
||||
|
||||
if self.__noAdd or self.__delete:
|
||||
try:
|
||||
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000073:
|
||||
context.log.highlight(u'{}'.format(
|
||||
self.__computerName + ' not found in domain ' + selectedDomain))
|
||||
self.noLDAPRequired = True
|
||||
raise Exception()
|
||||
else:
|
||||
raise
|
||||
|
||||
userRID = checkForUser['RelativeIds']['Element'][0]
|
||||
if self.__delete:
|
||||
access = samr.DELETE
|
||||
message = "delete"
|
||||
else:
|
||||
access = samr.USER_FORCE_PASSWORD_CHANGE
|
||||
message = "set the password for"
|
||||
try:
|
||||
openUser = samr.hSamrOpenUser(dce, domainHandle, access, userRID)
|
||||
userHandle = openUser['UserHandle']
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000022:
|
||||
context.log.highlight(u'{}'.format(
|
||||
self.__username + ' does not have the right to ' + message + " " + self.__computerName))
|
||||
self.noLDAPRequired = True
|
||||
raise Exception()
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
if self.__computerName is not None:
|
||||
try:
|
||||
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
|
||||
self.noLDAPRequired = True
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Computer account already exists with the name: "' + self.__computerName + '"'))
|
||||
raise Exception()
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code != 0xc0000073:
|
||||
raise
|
||||
else:
|
||||
foundUnused = False
|
||||
while not foundUnused:
|
||||
self.__computerName = self.generateComputerName()
|
||||
try:
|
||||
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000073:
|
||||
foundUnused = True
|
||||
else:
|
||||
raise
|
||||
try:
|
||||
createUser = samr.hSamrCreateUser2InDomain(dce, domainHandle, self.__computerName, samr.USER_WORKSTATION_TRUST_ACCOUNT, samr.USER_FORCE_PASSWORD_CHANGE,)
|
||||
self.noLDAPRequired = True
|
||||
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xc0000022:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'The following user does not have the right to create a computer account: "' + self.__username + '"'))
|
||||
raise Exception()
|
||||
elif e.error_code == 0xc00002e7:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'The following user exceeded their machine account quota: "' + self.__username + '"'))
|
||||
raise Exception()
|
||||
else:
|
||||
raise
|
||||
userHandle = createUser['UserHandle']
|
||||
|
||||
if self.__delete:
|
||||
samr.hSamrDeleteUser(dce, userHandle)
|
||||
context.log.highlight(u'{}'.format('Successfully deleted the "' + self.__computerName + '" Computer account'))
|
||||
self.noLDAPRequired=True
|
||||
userHandle = None
|
||||
else:
|
||||
samr.hSamrSetPasswordInternal4New(dce, userHandle, self.__computerPassword)
|
||||
if self.__noAdd:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Successfully set the password of machine "' + self.__computerName + '" with password "' + self.__computerPassword + '"'))
|
||||
self.noLDAPRequired=True
|
||||
else:
|
||||
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
|
||||
userRID = checkForUser['RelativeIds']['Element'][0]
|
||||
openUser = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, userRID)
|
||||
userHandle = openUser['UserHandle']
|
||||
req = samr.SAMPR_USER_INFO_BUFFER()
|
||||
req['tag'] = samr.USER_INFORMATION_CLASS.UserControlInformation
|
||||
req['Control']['UserAccountControl'] = samr.USER_WORKSTATION_TRUST_ACCOUNT
|
||||
samr.hSamrSetInformationUser2(dce, userHandle, req)
|
||||
if not self.noLDAPRequired:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
|
||||
self.noLDAPRequired = True
|
||||
|
||||
except Exception as e:
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
if userHandle is not None:
|
||||
samr.hSamrCloseHandle(dce, userHandle)
|
||||
if domainHandle is not None:
|
||||
samr.hSamrCloseHandle(dce, domainHandle)
|
||||
if servHandle is not None:
|
||||
samr.hSamrCloseHandle(dce, servHandle)
|
||||
dce.disconnect()
|
||||
|
||||
def doLDAPSAdd(self, connection, context):
|
||||
ldap_domain = connection.domain.replace(".", ",dc=")
|
||||
spns = [
|
||||
'HOST/%s' % self.__computerName,
|
||||
'HOST/%s.%s' % (self.__computerName, connection.domain),
|
||||
'RestrictedKrbHost/%s' % self.__computerName,
|
||||
'RestrictedKrbHost/%s.%s' % (self.__computerName, connection.domain),
|
||||
]
|
||||
ucd = {
|
||||
'dnsHostName': '%s.%s' % (self.__computerName, connection.domain),
|
||||
'userAccountControl': 0x1000,
|
||||
'servicePrincipalName': spns,
|
||||
'sAMAccountName': self.__computerName,
|
||||
'unicodePwd': ('"%s"' % self.__computerPassword).encode('utf-16-le')
|
||||
}
|
||||
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2, ciphers='ALL:@SECLEVEL=0')
|
||||
ldapServer = ldap3.Server(connection.host, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
|
||||
c = Connection(ldapServer, connection.username + '@' + connection.domain, connection.password)
|
||||
c.bind()
|
||||
|
||||
if (self.__delete):
|
||||
result = c.delete("cn=" + self.__computerName + ",cn=Computers,dc=" + ldap_domain)
|
||||
if result:
|
||||
context.log.highlight(u'{}'.format('Successfully deleted the "' + self.__computerName + '" Computer account'))
|
||||
elif result == False and c.last_error == "noSuchObject":
|
||||
context.log.highlight(u'{}'.format('Computer named "' + self.__computerName + '" was not found'))
|
||||
elif result == False and c.last_error == "insufficientAccessRights":
|
||||
context.log.highlight(
|
||||
u'{}'.format('Insufficient Access Rights to delete the Computer "' + self.__computerName + '"'))
|
||||
else:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Unable to delete the "' + self.__computerName + '" Computer account. The error was: ' + c.last_error))
|
||||
else:
|
||||
result = c.add("cn=" + self.__computerName + ",cn=Computers,dc=" + ldap_domain,
|
||||
['top', 'person', 'organizationalPerson', 'user', 'computer'], ucd)
|
||||
if result:
|
||||
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
|
||||
context.log.highlight(u'{}'.format('You can try to verify this with the CME command:'))
|
||||
context.log.highlight(u'{}'.format(
|
||||
'cme ldap ' + connection.host + ' -u ' + connection.username + ' -p ' + connection.password + ' -M group-mem -o GROUP="Domain Computers"'))
|
||||
elif result == False and c.last_error == "entryAlreadyExists":
|
||||
context.log.highlight(u'{}'.format('The Computer account "' + self.__computerName + '" already exists'))
|
||||
elif not result:
|
||||
context.log.highlight(u'{}'.format(
|
||||
'Unable to add the "' + self.__computerName + '" Computer account. The error was: ' + c.last_error))
|
||||
c.unbind()
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import socket
|
||||
import sys
|
||||
|
||||
class CMEModule:
|
||||
'''
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
'''
|
||||
|
||||
name = 'comp-desc'
|
||||
description = 'Retrieves computers containing the specified description'
|
||||
supported_protocols = ['ldap']
|
||||
opsec_safe = True
|
||||
multiple_hosts = False
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
comp-desc: Specify comp-desc to call the module
|
||||
DESC: Specify the DESC option to enter your description text to search for
|
||||
Usage: cme ldap $DC-IP -u Username -p Password -M comp-desc -o DESC="server"
|
||||
cme ldap $DC-IP -u Username -p Password -M comp-desc -o DESC="XP"
|
||||
'''
|
||||
|
||||
self.DESC = ''
|
||||
|
||||
if 'DESC' in module_options:
|
||||
self.DESC = module_options['DESC']
|
||||
else:
|
||||
context.log.error('DESC option is required!')
|
||||
exit(1)
|
||||
|
||||
def on_login(self, context, connection):
|
||||
|
||||
# Building the search filter
|
||||
searchFilter = "(&(objectCategory=computer)(operatingSystem=*"+self.DESC+"*))"
|
||||
|
||||
try:
|
||||
context.log.debug('Search Filter=%s' % searchFilter)
|
||||
resp = connection.ldapConnection.search(searchFilter=searchFilter,
|
||||
attributes=['dNSHostName','operatingSystem'],
|
||||
sizeLimit=0)
|
||||
except ldap_impacket.LDAPSearchError as e:
|
||||
if e.getErrorString().find('sizeLimitExceeded') >= 0:
|
||||
context.log.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
|
||||
resp = e.getAnswers()
|
||||
pass
|
||||
else:
|
||||
logging.debug(e)
|
||||
return False
|
||||
|
||||
answers = []
|
||||
context.log.debug('Total no. of records returned %d' % len(resp))
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
dNSHostName = ''
|
||||
operatingSystem = ''
|
||||
try:
|
||||
for attribute in item['attributes']:
|
||||
if str(attribute['type']) == 'dNSHostName':
|
||||
dNSHostName = str(attribute['vals'][0])
|
||||
elif str(attribute['type']) == 'operatingSystem':
|
||||
operatingSystem = attribute['vals'][0]
|
||||
if dNSHostName != '' and operatingSystem != '':
|
||||
answers.append([dNSHostName,operatingSystem])
|
||||
except Exception as e:
|
||||
context.log.debug("Exception:", exc_info=True)
|
||||
context.log.debug('Skipping item, cannot process due to error %s' % str(e))
|
||||
pass
|
||||
if len(answers) > 0:
|
||||
context.log.success('Found the following computers: ')
|
||||
for answer in answers:
|
||||
try:
|
||||
IP = socket.gethostbyname(answer[0])
|
||||
context.log.highlight(u'{} ({}) ({})'.format(answer[0],answer[1],IP))
|
||||
context.log.debug('IP found')
|
||||
except socket.gaierror as e:
|
||||
context.log.debug('Missing IP')
|
||||
context.log.highlight(u'{} ({}) ({})'.format(answer[0],answer[1],"No IP Found"))
|
||||
else:
|
||||
context.log.success('Unable to find any computers with the description "' + self.DESC + '"')
|
|
@ -41,6 +41,7 @@ class CMEModule:
|
|||
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
|
||||
doKerberos=connection.kerberos,
|
||||
dcHost=connection.kdcHost,
|
||||
aesKey=connection.aesKey,
|
||||
)
|
||||
|
||||
if dce is not None:
|
||||
|
@ -103,7 +104,7 @@ class NetrDfsAddRootResponse(NDRCALL):
|
|||
|
||||
|
||||
class TriggerAuth:
|
||||
def connect(self, username, password, domain, lmhash, nthash, target, doKerberos, dcHost):
|
||||
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost):
|
||||
rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\netdfs]" % target)
|
||||
if hasattr(rpctransport, "set_credentials"):
|
||||
rpctransport.set_credentials(
|
||||
|
@ -112,6 +113,7 @@ class TriggerAuth:
|
|||
domain=domain,
|
||||
lmhash=lmhash,
|
||||
nthash=nthash,
|
||||
aesKey=aesKey,
|
||||
)
|
||||
|
||||
if doKerberos:
|
||||
|
|
|
@ -46,6 +46,7 @@ class CMEModule:
|
|||
connection.domain,
|
||||
connection.lmhash,
|
||||
connection.nthash,
|
||||
connection.aesKey,
|
||||
)
|
||||
dce, rpctransport = lsa.connect()
|
||||
policyHandle = lsa.open_policy(dce)
|
||||
|
@ -54,7 +55,7 @@ class CMEModule:
|
|||
for service in product["services"]:
|
||||
try:
|
||||
lsa.LsarLookupNames(dce, policyHandle, service["name"])
|
||||
context.log.display(f"Detected installed service on {connection.host}: {product['name']} {service['description']}")
|
||||
context.log.info(f"Detected installed service on {connection.host}: {product['name']} {service['description']}")
|
||||
if product["name"] not in results:
|
||||
results[product["name"]] = {"services": []}
|
||||
results[product["name"]]["services"].append(service)
|
||||
|
@ -64,7 +65,7 @@ class CMEModule:
|
|||
except Exception as e:
|
||||
context.log.fail(str(e))
|
||||
|
||||
context.log.display(f"Detecting running processes on {connection.host} by enumerating pipes...")
|
||||
context.log.info(f"Detecting running processes on {connection.host} by enumerating pipes...")
|
||||
try:
|
||||
for f in connection.conn.listPath("IPC$", "\\*"):
|
||||
fl = f.get_longname()
|
||||
|
@ -124,6 +125,7 @@ class LsaLookupNames:
|
|||
kdcHost="",
|
||||
lmhash="",
|
||||
nthash="",
|
||||
aesKey="",
|
||||
):
|
||||
self.domain = domain
|
||||
self.username = username
|
||||
|
@ -133,6 +135,7 @@ class LsaLookupNames:
|
|||
self.doKerberos = k
|
||||
self.lmhash = lmhash
|
||||
self.nthash = nthash
|
||||
self.aesKey = aesKey
|
||||
self.dcHost = kdcHost
|
||||
|
||||
def connect(self, string_binding=None, iface_uuid=None):
|
||||
|
@ -154,7 +157,7 @@ class LsaLookupNames:
|
|||
# Authenticate if specified
|
||||
if self.authn and hasattr(rpc_transport, "set_credentials"):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash)
|
||||
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey)
|
||||
|
||||
if self.doKerberos:
|
||||
rpc_transport.set_kerberos(self.doKerberos, kdcHost=self.dcHost)
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
|
||||
class CMEModule:
|
||||
'''
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
'''
|
||||
|
||||
name = 'group-mem'
|
||||
description = 'Retrieves all the members within a Group'
|
||||
supported_protocols = ['ldap']
|
||||
opsec_safe = True
|
||||
multiple_hosts = False
|
||||
primaryGroupID = ''
|
||||
answers = []
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
group-mem: Specify group-mem to call the module
|
||||
GROUP: Specify the GROUP option to query for that group's members
|
||||
Usage: cme ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain admins"
|
||||
cme ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain controllers"
|
||||
'''
|
||||
|
||||
self.GROUP = ''
|
||||
|
||||
if 'GROUP' in module_options:
|
||||
self.GROUP = module_options['GROUP']
|
||||
else:
|
||||
context.log.error('GROUP option is required!')
|
||||
exit(1)
|
||||
|
||||
def on_login(self, context, connection):
|
||||
|
||||
#First look up the SID of the group passed in
|
||||
searchFilter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
|
||||
attribute = "objectSid"
|
||||
|
||||
searchResult = doSearch(self, context, connection, searchFilter, attribute)
|
||||
#If no SID for the Group is returned exit the program
|
||||
if searchResult is None:
|
||||
context.log.success('Unable to find any members of the "' + self.GROUP + '" group')
|
||||
return True
|
||||
|
||||
# Convert the binary SID to a primaryGroupID string to be used further
|
||||
sidString = connection.sid_to_str(searchResult).split("-")
|
||||
self.primaryGroupID = sidString[-1]
|
||||
|
||||
#Look up the groups DN
|
||||
searchFilter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
|
||||
attribute = "distinguishedName"
|
||||
distinguishedName = (doSearch(self, context, connection, searchFilter, attribute)).decode("utf-8")
|
||||
|
||||
# Carry out the search
|
||||
searchFilter = "(|(memberOf="+distinguishedName+")(primaryGroupID="+self.primaryGroupID+"))"
|
||||
attribute = "sAMAccountName"
|
||||
searchResult = doSearch(self, context, connection, searchFilter, attribute)
|
||||
|
||||
if len(self.answers) > 0:
|
||||
context.log.success('Found the following members of the ' + self.GROUP + ' group:')
|
||||
for answer in self.answers:
|
||||
context.log.highlight(u'{}'.format(answer[0]))
|
||||
|
||||
# Carry out an LDAP search for the Group with the supplied Group name
|
||||
def doSearch(self,context, connection,searchFilter,attributeName):
|
||||
try:
|
||||
context.log.debug('Search Filter=%s' % searchFilter)
|
||||
resp = connection.ldapConnection.search(searchFilter=searchFilter,
|
||||
attributes=[attributeName],
|
||||
sizeLimit=0)
|
||||
context.log.debug('Total no. of records returned %d' % len(resp))
|
||||
for item in resp:
|
||||
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
attributeValue = '';
|
||||
try:
|
||||
for attribute in item['attributes']:
|
||||
if str(attribute['type']) == attributeName:
|
||||
if attributeName == "objectSid":
|
||||
attributeValue = bytes(attribute['vals'][0])
|
||||
return attributeValue;
|
||||
elif attributeName == "distinguishedName":
|
||||
attributeValue = bytes(attribute['vals'][0])
|
||||
return attributeValue;
|
||||
else:
|
||||
attributeValue = str(attribute['vals'][0])
|
||||
if attributeValue is not None:
|
||||
self.answers.append([attributeValue])
|
||||
except Exception as e:
|
||||
context.log.debug("Exception:", exc_info=True)
|
||||
context.log.debug('Skipping item, cannot process due to error %s' % str(e))
|
||||
pass
|
||||
except Exception as e:
|
||||
context.log.debug("Exception:", e)
|
||||
return False
|
|
@ -147,7 +147,15 @@ class CMEModule:
|
|||
self.reset = None
|
||||
self.reset_dumped = None
|
||||
self.method = None
|
||||
|
||||
@staticmethod
|
||||
def save_credentials(context, connection, domain, username, password, lmhash, nthash):
|
||||
host_id = context.db.get_computers(connection.host)[0][0]
|
||||
if password is not None:
|
||||
credential_type = 'plaintext'
|
||||
else:
|
||||
credential_type = 'hash'
|
||||
password = ':'.join(h for h in [lmhash, nthash] if h is not None)
|
||||
context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
METHOD Method to use to dump lsass.exe with lsassy
|
||||
|
@ -222,6 +230,7 @@ class CMEModule:
|
|||
]
|
||||
)
|
||||
credentials_output.append(cred)
|
||||
self.save_credentials(context, connection, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
|
||||
global credentials_data
|
||||
credentials_data = credentials_output
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import json
|
||||
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
|
||||
from cme.protocols.ldap.laps import LDAPConnect, LAPSv2Extract
|
||||
|
||||
class CMEModule:
|
||||
"""
|
||||
|
@ -49,21 +49,35 @@ class CMEModule:
|
|||
for computer in results:
|
||||
msMCSAdmPwd = ""
|
||||
sAMAccountName = ""
|
||||
values = {str(attr["type"]).lower(): str(attr["vals"][0]) for attr in computer["attributes"]}
|
||||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]}
|
||||
if "mslaps-encryptedpassword" in values:
|
||||
context.log.fail("LAPS password is encrypted and currently CrackMapExec doesn't" " support the decryption...")
|
||||
|
||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||
d = LAPSv2Extract(
|
||||
bytes(msMCSAdmPwd),
|
||||
connection.username if connection.username else "",
|
||||
connection.password if connection.password else "",
|
||||
connection.domain,
|
||||
connection.nthash if connection.nthash else "",
|
||||
connection.kerberos,
|
||||
connection.kdcHost,
|
||||
339)
|
||||
try:
|
||||
data = d.run()
|
||||
except Exception as e:
|
||||
self.logger.fail(str(e))
|
||||
return
|
||||
r = json.loads(data)
|
||||
laps_computers.append((str(values["samaccountname"]), r["n"], str(r["p"])))
|
||||
elif "mslaps-password" in values:
|
||||
r = json.loads(values["mslaps-password"])
|
||||
laps_computers.append((values["samaccountname"], r["n"], r["p"]))
|
||||
r = json.loads(str(values["mslaps-password"]))
|
||||
laps_computers.append((str(values["samaccountname"]), r["n"], str(r["p"])))
|
||||
elif "ms-mcs-admpwd" in values:
|
||||
laps_computers.append((values["samaccountname"], "", values["ms-mcs-admpwd"]))
|
||||
laps_computers.append((str(values["samaccountname"]), "", str(values["ms-mcs-admpwd"])))
|
||||
else:
|
||||
context.log.fail("No result found with attribute ms-MCS-AdmPwd or" " msLAPS-Password")
|
||||
context.log.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password")
|
||||
|
||||
laps_computers = sorted(laps_computers, key=lambda x: x[0])
|
||||
for sAMAccountName, user, msMCSAdmPwd in laps_computers:
|
||||
context.log.highlight("Computer: {:<20} User: {:<15} Password: {}".format(sAMAccountName, user, msMCSAdmPwd))
|
||||
for sAMAccountName, user, password in laps_computers:
|
||||
context.log.highlight("Computer:{} User:{:<15} Password:{}".format(sAMAccountName, user, password))
|
||||
else:
|
||||
context.log.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password !")
|
||||
|
|
|
@ -128,6 +128,8 @@ class CMEModule:
|
|||
credz_bh = []
|
||||
domain = None
|
||||
for cred in credentials:
|
||||
if cred["domain"] == None:
|
||||
cred["domain"] = ""
|
||||
domain = cred["domain"]
|
||||
if "." not in cred["domain"] and cred["domain"].upper() in connection.domain.upper():
|
||||
domain = connection.domain # slim shady
|
||||
|
|
|
@ -45,6 +45,7 @@ class CMEModule:
|
|||
domain=connection.domain,
|
||||
lmhash=connection.lmhash,
|
||||
nthash=connection.nthash,
|
||||
aesKey=connection.aesKey,
|
||||
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
|
||||
pipe=self.pipe,
|
||||
do_kerberos=connection.kerberos,
|
||||
|
@ -195,6 +196,7 @@ def coerce(
|
|||
domain,
|
||||
lmhash,
|
||||
nthash,
|
||||
aesKey,
|
||||
target,
|
||||
pipe,
|
||||
do_kerberos,
|
||||
|
@ -232,6 +234,7 @@ def coerce(
|
|||
domain=domain,
|
||||
lmhash=lmhash,
|
||||
nthash=nthash,
|
||||
aesKey=aesKey,
|
||||
)
|
||||
|
||||
if target_ip:
|
||||
|
|
|
@ -56,6 +56,7 @@ class CMEModule:
|
|||
connection.domain,
|
||||
connection.lmhash,
|
||||
connection.nthash,
|
||||
connection.aesKey,
|
||||
)
|
||||
|
||||
rpctransport.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
|
||||
|
|
|
@ -45,6 +45,7 @@ class CMEModule:
|
|||
domain=connection.domain,
|
||||
lmhash=connection.lmhash,
|
||||
nthash=connection.nthash,
|
||||
aesKey=connection.aesKey,
|
||||
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
|
||||
pipe="FssagentRpc",
|
||||
doKerberos=connection.kerberos,
|
||||
|
@ -62,6 +63,7 @@ class CMEModule:
|
|||
domain=connection.domain,
|
||||
lmhash=connection.lmhash,
|
||||
nthash=connection.nthash,
|
||||
aesKey=connection.aesKey,
|
||||
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
|
||||
pipe="FssagentRpc",
|
||||
)
|
||||
|
@ -194,6 +196,7 @@ class CoerceAuth:
|
|||
domain,
|
||||
lmhash,
|
||||
nthash,
|
||||
aesKey,
|
||||
target,
|
||||
pipe,
|
||||
doKerberos,
|
||||
|
@ -215,6 +218,7 @@ class CoerceAuth:
|
|||
domain=domain,
|
||||
lmhash=lmhash,
|
||||
nthash=nthash,
|
||||
aesKey=aesKey,
|
||||
)
|
||||
|
||||
dce.set_credentials(*rpctransport.get_credentials())
|
||||
|
|
|
@ -27,6 +27,7 @@ class CMEModule:
|
|||
"""
|
||||
|
||||
self.showservers = True
|
||||
self.base_dn = None
|
||||
|
||||
if module_options and "SHOWSERVERS" in module_options:
|
||||
if module_options["SHOWSERVERS"].lower() == "true" or module_options["SHOWSERVERS"] == "1":
|
||||
|
@ -35,6 +36,8 @@ class CMEModule:
|
|||
self.showservers = False
|
||||
else:
|
||||
print("Could not parse showservers option.")
|
||||
if module_options and "BASE_DN" in module_options:
|
||||
self.base_dn = module_options["BASE_DN"]
|
||||
|
||||
name = "subnets"
|
||||
description = "Retrieves the different Sites and Subnets of an Active Directory"
|
||||
|
@ -43,16 +46,20 @@ class CMEModule:
|
|||
multiple_hosts = False
|
||||
|
||||
def on_login(self, context, connection):
|
||||
dn = ",".join(["DC=%s" % part for part in connection.domain.split(".")][-2:])
|
||||
dn = connection.ldapConnection._baseDN if self.base_dn is None else self.base_dn
|
||||
|
||||
context.log.display("Getting the Sites and Subnets from domain")
|
||||
|
||||
try:
|
||||
list_sites = connection.ldapConnection.search(
|
||||
searchBase="CN=Configuration,%s" % dn,
|
||||
searchFilter="(objectClass=site)",
|
||||
attributes=["distinguishedName", "name", "description"],
|
||||
sizeLimit=999,
|
||||
)
|
||||
except LDAPSearchError as e:
|
||||
context.log.fail(str(e))
|
||||
exit()
|
||||
for site in list_sites:
|
||||
if isinstance(site, ldapasn1_impacket.SearchResultEntry) is not True:
|
||||
continue
|
||||
|
|
|
@ -292,8 +292,7 @@ class ldap(connection):
|
|||
|
||||
# Re-connect since we logged off
|
||||
self.create_conn_obj()
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
|
||||
self.output_filename = self.output_filename.replace(":", "-")
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
|
||||
def print_host_info(self):
|
||||
self.logger.debug("Printing host info for LDAP")
|
||||
|
@ -738,17 +737,20 @@ class ldap(connection):
|
|||
try:
|
||||
if self.ldapConnection:
|
||||
self.logger.debug(f"Search Filter={searchFilter}")
|
||||
|
||||
# Microsoft Active Directory set an hard limit of 1000 entries returned by any search
|
||||
paged_search_control = ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000)
|
||||
resp = self.ldapConnection.search(
|
||||
searchFilter=searchFilter,
|
||||
attributes=attributes,
|
||||
sizeLimit=sizeLimit,
|
||||
searchControls=[paged_search_control],
|
||||
)
|
||||
return resp
|
||||
except ldap_impacket.LDAPSearchError as e:
|
||||
if e.getErrorString().find("sizeLimitExceeded") >= 0:
|
||||
# We should never reach this code as we use paged search now
|
||||
self.logger.fail("sizeLimitExceeded exception caught, giving up and processing the data received")
|
||||
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
|
||||
# paged queries
|
||||
resp = e.getAnswers()
|
||||
pass
|
||||
else:
|
||||
|
|
|
@ -29,7 +29,7 @@ class MSSQLEXEC:
|
|||
if output:
|
||||
cme_logger.debug(f"Output is enabled")
|
||||
for row in command_output:
|
||||
cme_logger.display(row)
|
||||
cme_logger.debug(row)
|
||||
# self.mssql_conn.printReplies()
|
||||
# self.mssql_conn.colMeta[0]["TypeData"] = 80 * 2
|
||||
# self.mssql_conn.printRows()
|
||||
|
|
|
@ -133,8 +133,7 @@ class rdp(connection):
|
|||
self.server_os = info_domain["os_guess"] + " Build " + str(info_domain["os_build"])
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
|
||||
self.output_filename = self.output_filename.replace(":", "-")
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
break
|
||||
|
||||
if self.args.domain:
|
||||
|
|
|
@ -234,8 +234,7 @@ class smb(connection):
|
|||
pass
|
||||
|
||||
self.os_arch = self.get_os_arch()
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
|
||||
self.output_filename = self.output_filename.replace(":", "-")
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
|
||||
if not self.domain:
|
||||
self.domain = self.hostname
|
||||
|
@ -322,7 +321,11 @@ class smb(connection):
|
|||
self.args.kerberos,
|
||||
self.args.kdcHost,
|
||||
339)
|
||||
try:
|
||||
data = d.run()
|
||||
except Exception as e:
|
||||
self.logger.fail(str(e))
|
||||
return
|
||||
r = loads(data)
|
||||
msMCSAdmPwd = r["p"]
|
||||
username_laps = r["n"]
|
||||
|
@ -405,8 +408,8 @@ class smb(connection):
|
|||
|
||||
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
||||
else:
|
||||
self.plaintext_login(username, password, self.host)
|
||||
return
|
||||
self.plaintext_login(self.hostname, username, password)
|
||||
return True
|
||||
|
||||
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
|
||||
self.logger.success(out)
|
||||
|
@ -603,7 +606,11 @@ class smb(connection):
|
|||
)
|
||||
self.smbv1 = False
|
||||
except socket.error as e:
|
||||
# This should not happen anymore!!!
|
||||
if str(e).find("Too many open files") != -1:
|
||||
if not self.logger:
|
||||
print("DEBUG ERROR: logger not set, please open an issue on github: " + str(self) + str(self.logger))
|
||||
self.proto_logger()
|
||||
self.logger.fail(f"SMBv3 connection error on {self.host if not kdc else kdc}: {e}")
|
||||
return False
|
||||
except (Exception, NetBIOSTimeout) as e:
|
||||
|
@ -626,7 +633,10 @@ class smb(connection):
|
|||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
dce.bind(scmr.MSRPC_UUID_SCMR)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
# 0xF003F - SC_MANAGER_ALL_ACCESS
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
|
||||
|
@ -689,7 +699,8 @@ class smb(connection):
|
|||
self.password,
|
||||
self.domain,
|
||||
self.conn,
|
||||
self.hash,
|
||||
self.args.share,
|
||||
self.hash
|
||||
)
|
||||
self.logger.info("Executed command via mmcexec")
|
||||
break
|
||||
|
@ -709,6 +720,7 @@ class smb(connection):
|
|||
self.aesKey,
|
||||
self.kdcHost,
|
||||
self.hash,
|
||||
self.logger
|
||||
) # self.args.share)
|
||||
self.logger.info("Executed command via atexec")
|
||||
break
|
||||
|
@ -731,6 +743,7 @@ class smb(connection):
|
|||
self.kdcHost,
|
||||
self.hash,
|
||||
self.args.share,
|
||||
self.args.port,
|
||||
self.logger
|
||||
)
|
||||
self.logger.info("Executed command via smbexec")
|
||||
|
@ -1001,7 +1014,7 @@ class smb(connection):
|
|||
groups = SamrFunc(self).get_local_groups()
|
||||
if groups:
|
||||
self.logger.success("Enumerated local groups")
|
||||
self.logger.display(f"Local groups: {groups}")
|
||||
self.logger.debug(f"Local groups: {groups}")
|
||||
|
||||
for group_name, group_rid in groups.items():
|
||||
self.logger.highlight(f"rid => {group_rid} => {group_name}")
|
||||
|
@ -1274,7 +1287,7 @@ class smb(connection):
|
|||
|
||||
if hasattr(rpc_transport, "set_credentials"):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash)
|
||||
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey)
|
||||
|
||||
if self.kerberos:
|
||||
rpc_transport.set_kerberos(self.kerberos, self.kdcHost)
|
||||
|
|
|
@ -5,7 +5,7 @@ import os
|
|||
import logging
|
||||
from impacket.dcerpc.v5 import tsch, transport
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
from cme.helpers.misc import gen_random_string
|
||||
from cme.logger import cme_logger
|
||||
from time import sleep
|
||||
|
@ -23,6 +23,7 @@ class TSCH_EXEC:
|
|||
aesKey=None,
|
||||
kdcHost=None,
|
||||
hashes=None,
|
||||
logger=cme_logger
|
||||
):
|
||||
self.__target = target
|
||||
self.__username = username
|
||||
|
@ -36,6 +37,7 @@ class TSCH_EXEC:
|
|||
self.__aesKey = aesKey
|
||||
self.__doKerberos = doKerberos
|
||||
self.__kdcHost = kdcHost
|
||||
self.logger = logger
|
||||
|
||||
if hashes is not None:
|
||||
# This checks to see if we didn't provide the LM Hash
|
||||
|
@ -147,6 +149,7 @@ class TSCH_EXEC:
|
|||
dce.set_credentials(*self.__rpctransport.get_credentials())
|
||||
dce.connect()
|
||||
# dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
||||
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
||||
dce.bind(tsch.MSRPC_UUID_TSCHS)
|
||||
tmpName = gen_random_string(8)
|
||||
tmpFileName = tmpName + ".tmp"
|
||||
|
@ -156,7 +159,11 @@ class TSCH_EXEC:
|
|||
logging.info(f"Task XML: {xml}")
|
||||
taskCreated = False
|
||||
logging.info(f"Creating task \\{tmpName}")
|
||||
try:
|
||||
tsch.hSchRpcRegisterTask(dce, f"\\{tmpName}", xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
|
||||
except Exception as e:
|
||||
self.logger.fail(str(e))
|
||||
return
|
||||
taskCreated = True
|
||||
|
||||
logging.info(f"Running task \\{tmpName}")
|
||||
|
|
|
@ -60,7 +60,7 @@ from impacket.dcerpc.v5.dtypes import NULL
|
|||
|
||||
|
||||
class MMCEXEC:
|
||||
def __init__(self, host, share_name, username, password, domain, smbconnection, hashes=None):
|
||||
def __init__(self, host, share_name, username, password, domain, smbconnection, share, hashes=None):
|
||||
self.__host = host
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
|
@ -76,10 +76,12 @@ class MMCEXEC:
|
|||
self.__quit = None
|
||||
self.__executeShellCommand = None
|
||||
self.__retOutput = True
|
||||
self.__share = share
|
||||
self.__dcom = None
|
||||
if hashes is not None:
|
||||
self.__lmhash, self.__nthash = hashes.split(":")
|
||||
|
||||
dcom = DCOMConnection(
|
||||
self.__dcom = DCOMConnection(
|
||||
self.__host,
|
||||
self.__username,
|
||||
self.__password,
|
||||
|
@ -90,7 +92,7 @@ class MMCEXEC:
|
|||
oxidResolver=True,
|
||||
)
|
||||
try:
|
||||
iInterface = dcom.CoCreateInstanceEx(string_to_bin("49B2791A-B1AE-4C90-9B8E-E860BA07F889"), IID_IDispatch)
|
||||
iInterface = self.__dcom.CoCreateInstanceEx(string_to_bin("49B2791A-B1AE-4C90-9B8E-E860BA07F889"), IID_IDispatch)
|
||||
iMMC = IDispatch(iInterface)
|
||||
|
||||
resp = iMMC.GetIDsOfNames(("Document",))
|
||||
|
@ -117,20 +119,20 @@ class MMCEXEC:
|
|||
except Exception as e:
|
||||
self.exit()
|
||||
logging.error(str(e))
|
||||
dcom.disconnect()
|
||||
self.__dcom.disconnect()
|
||||
|
||||
def getInterface(self, interface, resp):
|
||||
# Now let's parse the answer and build an Interface instance
|
||||
objRefType = OBJREF("".join(resp))["flags"]
|
||||
objRefType = OBJREF(b"".join(resp))["flags"]
|
||||
objRef = None
|
||||
if objRefType == FLAGS_OBJREF_CUSTOM:
|
||||
objRef = OBJREF_CUSTOM("".join(resp))
|
||||
objRef = OBJREF_CUSTOM(b"".join(resp))
|
||||
elif objRefType == FLAGS_OBJREF_HANDLER:
|
||||
objRef = OBJREF_HANDLER("".join(resp))
|
||||
objRef = OBJREF_HANDLER(b"".join(resp))
|
||||
elif objRefType == FLAGS_OBJREF_STANDARD:
|
||||
objRef = OBJREF_STANDARD("".join(resp))
|
||||
objRef = OBJREF_STANDARD(b"".join(resp))
|
||||
elif objRefType == FLAGS_OBJREF_EXTENDED:
|
||||
objRef = OBJREF_EXTENDED("".join(resp))
|
||||
objRef = OBJREF_EXTENDED(b"".join(resp))
|
||||
else:
|
||||
logging.error("Unknown OBJREF Type! 0x%x" % objRefType)
|
||||
|
||||
|
@ -150,6 +152,7 @@ class MMCEXEC:
|
|||
self.__retOutput = output
|
||||
self.execute_remote(command)
|
||||
self.exit()
|
||||
self.__dcom.disconnect()
|
||||
return self.__outputBuffer
|
||||
|
||||
def exit(self):
|
||||
|
@ -163,12 +166,11 @@ class MMCEXEC:
|
|||
return True
|
||||
|
||||
def execute_remote(self, data):
|
||||
self.__output = gen_random_string(6)
|
||||
local_ip = self.__smbconnection.getSMBServer().get_socket().getsockname()[0]
|
||||
self.__output = "\\Windows\\Temp\\" + gen_random_string(6)
|
||||
|
||||
command = "/Q /c " + data
|
||||
command = self.__shell + " /Q /c " + data
|
||||
if self.__retOutput is True:
|
||||
command += " 1> " + f"\\\\{local_ip}\\{self.__share_name}\\{self.__output}" + " 2>&1"
|
||||
command += " 1> " + f"{self.__output}" + " 2>&1"
|
||||
|
||||
dispParams = DISPPARAMS(None, False)
|
||||
dispParams["rgdispidNamedArgs"] = NULL
|
||||
|
@ -203,7 +205,7 @@ class MMCEXEC:
|
|||
dispParams["rgvarg"].append(arg0)
|
||||
|
||||
self.__executeShellCommand[0].Invoke(self.__executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
|
||||
self.get_output_fileless()
|
||||
self.get_output_remote()
|
||||
|
||||
def output_callback(self, data):
|
||||
self.__outputBuffer += data
|
||||
|
@ -219,3 +221,22 @@ class MMCEXEC:
|
|||
break
|
||||
except IOError:
|
||||
sleep(2)
|
||||
|
||||
def get_output_remote(self):
|
||||
if self.__retOutput is False:
|
||||
self.__outputBuffer = ""
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
|
||||
break
|
||||
except Exception as e:
|
||||
if str(e).find("STATUS_SHARING_VIOLATION") >= 0:
|
||||
# Output not finished, let's wait
|
||||
sleep(2)
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
self.__smbconnection.deleteFile(self.__share, self.__output)
|
||||
|
|
|
@ -80,7 +80,7 @@ class PassPolDump:
|
|||
self.hash = connection.hash
|
||||
self.lmhash = ""
|
||||
self.nthash = ""
|
||||
self.aesKey = None
|
||||
self.aesKey = connection.aesKey
|
||||
self.doKerberos = connection.kerberos
|
||||
self.protocols = PassPolDump.KNOWN_PROTOCOLS.keys()
|
||||
self.pass_pol = {}
|
||||
|
|
|
@ -25,7 +25,7 @@ class SamrFunc:
|
|||
self.hash = connection.hash
|
||||
self.lmhash = ""
|
||||
self.nthash = ""
|
||||
self.aesKey = (None,)
|
||||
self.aesKey = connection.aesKey
|
||||
self.doKerberos = connection.kerberos
|
||||
|
||||
if self.hash is not None:
|
||||
|
@ -40,16 +40,20 @@ class SamrFunc:
|
|||
self.samr_query = SAMRQuery(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
domain=self.domain,
|
||||
remote_name=self.addr,
|
||||
remote_host=self.addr,
|
||||
kerberos=self.doKerberos,
|
||||
aesKey=self.aesKey,
|
||||
)
|
||||
self.lsa_query = LSAQuery(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
domain=self.domain,
|
||||
remote_name=self.addr,
|
||||
remote_host=self.addr,
|
||||
kerberos=self.doKerberos,
|
||||
aesKey=self.aesKey,
|
||||
logger=self.logger
|
||||
)
|
||||
|
||||
|
@ -107,13 +111,14 @@ class SAMRQuery:
|
|||
remote_name="",
|
||||
remote_host="",
|
||||
kerberos=None,
|
||||
aesKey="",
|
||||
):
|
||||
self.__username = username
|
||||
self.__password = password
|
||||
self.__domain = domain
|
||||
self.__lmhash = ""
|
||||
self.__nthash = ""
|
||||
self.__aesKey = None
|
||||
self.__aesKey = aesKey
|
||||
self.__port = port
|
||||
self.__remote_name = remote_name
|
||||
self.__remote_host = remote_host
|
||||
|
@ -207,6 +212,7 @@ class LSAQuery:
|
|||
port=445,
|
||||
remote_name="",
|
||||
remote_host="",
|
||||
aesKey="",
|
||||
kerberos=None,
|
||||
logger=None
|
||||
):
|
||||
|
@ -215,7 +221,7 @@ class LSAQuery:
|
|||
self.__domain = domain
|
||||
self.__lmhash = ""
|
||||
self.__nthash = ""
|
||||
self.__aesKey = None
|
||||
self.__aesKey = aesKey
|
||||
self.__port = port
|
||||
self.__remote_name = remote_name
|
||||
self.__remote_host = remote_host
|
||||
|
|
|
@ -24,7 +24,7 @@ class UserSamrDump:
|
|||
self.hash = connection.hash
|
||||
self.lmhash = ""
|
||||
self.nthash = ""
|
||||
self.aesKey = None
|
||||
self.aesKey = connection.aesKey
|
||||
self.doKerberos = connection.kerberos
|
||||
self.protocols = UserSamrDump.KNOWN_PROTOCOLS.keys()
|
||||
self.users = []
|
||||
|
|
|
@ -91,8 +91,7 @@ class winrm(connection):
|
|||
|
||||
self.db.add_host(self.host, self.port, self.hostname, self.domain, self.server_os)
|
||||
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
|
||||
self.output_filename = self.output_filename.replace(":", "-")
|
||||
self.output_filename = os.path.expanduser(f"~/.cme/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
|
||||
def laps_search(self, username, password, ntlm_hash, domain):
|
||||
ldapco = LDAPConnect(self.domain, "389", self.domain)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "crackmapexec"
|
||||
version = "6.0.0"
|
||||
version = "6.0.1"
|
||||
description = "A swiss army knife for pentesting networks"
|
||||
authors = ["Marcello Salvati <byt3bl33d3r@pm.com>", "Martial Puygrenier <mpgn@protonmail.com>"]
|
||||
readme = "README.md"
|
||||
|
@ -50,17 +50,18 @@ asyauth = "~0.0.13"
|
|||
masky = "^0.2.0"
|
||||
sqlalchemy = "^2.0.4"
|
||||
aiosqlite = "^0.18.0"
|
||||
pytest = "^7.2.2"
|
||||
pyasn1-modules = "^0.3.0"
|
||||
rich = "^13.3.5"
|
||||
python-libnmap = "^0.7.3"
|
||||
resource = "^0.2.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
flake8 = "*"
|
||||
pylint = "*"
|
||||
shiv = "*"
|
||||
black = "^20.8b1"
|
||||
pytest = "^7.2.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.2.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
|
Loading…
Reference in New Issue