Merge branch 'master' into protocol_db_marshall
commit
ecb68637b6
|
@ -1,10 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pyasn1.codec.der import decoder
|
||||
from pyasn1_modules import rfc5652
|
||||
|
||||
from impacket.ldap import ldap as ldap_impacket
|
||||
from impacket.krb5.kerberosv5 import KerberosError
|
||||
from cme.logger import CMEAdapter
|
||||
from impacket.dcerpc.v5 import transport
|
||||
from impacket.dcerpc.v5.epm import hept_map
|
||||
from impacket.dcerpc.v5.gkdi import MSRPC_UUID_GKDI, GkdiGetKey, GroupKeyEnvelope
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
from impacket.dpapi_ng import EncryptedPasswordBlob, KeyIdentifier, compute_kek, create_sd, decrypt_plaintext, unwrap_cek
|
||||
|
||||
from cme.logger import CMEAdapter
|
||||
|
||||
ldap_error_status = {
|
||||
"1": "STATUS_NOT_SUPPORTED",
|
||||
|
@ -29,16 +37,7 @@ class LDAPConnect:
|
|||
def proto_logger(self, host, port, hostname):
|
||||
self.logger = CMEAdapter(extra={"protocol": "LDAP", "host": host, "port": port, "hostname": hostname})
|
||||
|
||||
def kerberos_login(
|
||||
self,
|
||||
domain,
|
||||
username,
|
||||
password="",
|
||||
ntlm_hash="",
|
||||
aesKey="",
|
||||
kdcHost="",
|
||||
useCache=False,
|
||||
):
|
||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
||||
lmhash = ""
|
||||
nthash = ""
|
||||
|
||||
|
@ -176,3 +175,94 @@ class LDAPConnect:
|
|||
except OSError as e:
|
||||
self.logger.debug(f"{domain}\\{username}:{password if password else ntlm_hash} {'Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller'}")
|
||||
return False
|
||||
|
||||
class LAPSv2Extract:
|
||||
def __init__(self, data, username, password, domain, ntlm_hash, do_kerberos, kdcHost, port):
|
||||
if ntlm_hash.find(":") != -1:
|
||||
self.lmhash, self.nthash = ntlm_hash.split(":")
|
||||
else:
|
||||
self.nthash = ntlm_hash
|
||||
self.lmhash = ''
|
||||
|
||||
self.data = data
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.domain = domain
|
||||
self.do_kerberos = do_kerberos
|
||||
self.kdcHost = kdcHost
|
||||
self.logger = None
|
||||
self.proto_logger(self.domain, port, self.domain)
|
||||
|
||||
def proto_logger(self, host, port, hostname):
|
||||
self.logger = CMEAdapter(extra={"protocol": "LDAP", "host": host, "port": port, "hostname": hostname})
|
||||
|
||||
def run(self):
|
||||
KDSCache = {}
|
||||
self.logger.info('[-] Unpacking blob')
|
||||
try:
|
||||
encryptedLAPSBlob = EncryptedPasswordBlob(self.data)
|
||||
parsed_cms_data, remaining = decoder.decode(encryptedLAPSBlob['Blob'], asn1Spec=rfc5652.ContentInfo())
|
||||
enveloped_data_blob = parsed_cms_data['content']
|
||||
parsed_enveloped_data, _ = decoder.decode(enveloped_data_blob, asn1Spec=rfc5652.EnvelopedData())
|
||||
|
||||
recipient_infos = parsed_enveloped_data['recipientInfos']
|
||||
kek_recipient_info = recipient_infos[0]['kekri']
|
||||
kek_identifier = kek_recipient_info['kekid']
|
||||
key_id = KeyIdentifier(bytes(kek_identifier['keyIdentifier']))
|
||||
tmp,_ = decoder.decode(kek_identifier['other']['keyAttr'])
|
||||
sid = tmp['field-1'][0][0][1].asOctets().decode("utf-8")
|
||||
target_sd = create_sd(sid)
|
||||
except Exception as e:
|
||||
logging.error('Cannot unpack msLAPS-EncryptedPassword blob due to error %s' % str(e))
|
||||
return
|
||||
|
||||
# Check if item is in cache
|
||||
if key_id['RootKeyId'] in KDSCache:
|
||||
self.logger.info("Got KDS from cache")
|
||||
gke = KDSCache[key_id['RootKeyId']]
|
||||
else:
|
||||
# Connect on RPC over TCP to MS-GKDI to call opnum 0 GetKey
|
||||
stringBinding = hept_map(destHost=self.domain, remoteIf=MSRPC_UUID_GKDI, protocol='ncacn_ip_tcp')
|
||||
rpctransport = transport.DCERPCTransportFactory(stringBinding)
|
||||
if hasattr(rpctransport, 'set_credentials'):
|
||||
rpctransport.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=self.lmhash, nthash=self.nthash)
|
||||
if self.do_kerberos:
|
||||
self.logger.info("Connecting using kerberos")
|
||||
rpctransport.set_kerberos(self.do_kerberos, kdcHost=self.kdcHost)
|
||||
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_INTEGRITY)
|
||||
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
||||
self.logger.info("Connecting to %s" % stringBinding)
|
||||
try:
|
||||
dce.connect()
|
||||
except Exception as e:
|
||||
logging.error("Something went wrong, check error status => %s" % str(e))
|
||||
return False
|
||||
self.logger.info("Connected")
|
||||
try:
|
||||
dce.bind(MSRPC_UUID_GKDI)
|
||||
except Exception as e:
|
||||
logging.error("Something went wrong, check error status => %s" % str(e))
|
||||
return False
|
||||
self.logger.info("Successfully bound")
|
||||
|
||||
|
||||
self.logger.info("Calling MS-GKDI GetKey")
|
||||
resp = GkdiGetKey(dce, target_sd=target_sd, l0=key_id['L0Index'], l1=key_id['L1Index'], l2=key_id['L2Index'], root_key_id=key_id['RootKeyId'])
|
||||
self.logger.info("Decrypting password")
|
||||
# Unpack GroupKeyEnvelope
|
||||
gke = GroupKeyEnvelope(b''.join(resp['pbbOut']))
|
||||
KDSCache[gke['RootKeyId']] = gke
|
||||
|
||||
kek = compute_kek(gke, key_id)
|
||||
self.logger.info("KEK:\t%s" % kek)
|
||||
enc_content_parameter = bytes(parsed_enveloped_data["encryptedContentInfo"]["contentEncryptionAlgorithm"]["parameters"])
|
||||
iv, _ = decoder.decode(enc_content_parameter)
|
||||
iv = bytes(iv[0])
|
||||
|
||||
cek = unwrap_cek(kek, bytes(kek_recipient_info['encryptedKey']))
|
||||
self.logger.info("CEK:\t%s" % cek)
|
||||
plaintext = decrypt_plaintext(cek, iv, remaining)
|
||||
self.logger.info(plaintext[:-18].decode('utf-16le'))
|
||||
return plaintext[:-18].decode('utf-16le')
|
|
@ -41,7 +41,7 @@ from cme.protocols.smb.smbspider import SMBSpider
|
|||
from cme.protocols.smb.passpol import PassPolDump
|
||||
from cme.protocols.smb.samruser import UserSamrDump
|
||||
from cme.protocols.smb.samrfunc import SamrFunc
|
||||
from cme.protocols.ldap.smbldap import LDAPConnect
|
||||
from cme.protocols.ldap.laps import LDAPConnect, LAPSv2Extract
|
||||
from cme.helpers.logger import highlight
|
||||
from cme.helpers.misc import *
|
||||
from cme.helpers.bloodhound import add_user_bh
|
||||
|
@ -568,23 +568,35 @@ class smb(connection):
|
|||
|
||||
msMCSAdmPwd = ""
|
||||
sAMAccountName = ""
|
||||
username = ""
|
||||
username_laps = ""
|
||||
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
|
||||
results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)]
|
||||
if len(results) != 0:
|
||||
for host in results:
|
||||
values = {str(attr["type"]).lower(): str(attr["vals"][0]) for attr in host["attributes"]}
|
||||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in host["attributes"]}
|
||||
if "mslaps-encryptedpassword" in values:
|
||||
self.logger.fail("LAPS password is encrypted and currently CrackMapExec doesn't support the decryption...")
|
||||
return False
|
||||
elif "mslaps-password" in values:
|
||||
r = loads(values["mslaps-password"])
|
||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||
d = LAPSv2Extract(
|
||||
bytes(msMCSAdmPwd),
|
||||
username[0] if username else "",
|
||||
password[0] if password else "",
|
||||
domain,
|
||||
ntlm_hash[0] if ntlm_hash else "",
|
||||
self.args.kerberos,
|
||||
self.args.kdcHost,
|
||||
339)
|
||||
data = d.run()
|
||||
r = loads(data)
|
||||
msMCSAdmPwd = r["p"]
|
||||
username = r["n"]
|
||||
username_laps = r["n"]
|
||||
elif "mslaps-password" in values:
|
||||
r = loads(str(values["mslaps-password"]))
|
||||
msMCSAdmPwd = r["p"]
|
||||
username_laps = r["n"]
|
||||
elif "ms-mcs-admpwd" in values:
|
||||
msMCSAdmPwd = values["ms-mcs-admpwd"]
|
||||
msMCSAdmPwd = str(values["ms-mcs-admpwd"])
|
||||
else:
|
||||
self.logger.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password")
|
||||
logging.debug(f"Host: {sAMAccountName:<20} Password: {msMCSAdmPwd} {self.hostname}")
|
||||
|
@ -593,7 +605,7 @@ class smb(connection):
|
|||
|
||||
return False
|
||||
|
||||
self.username = self.args.laps if not username else username
|
||||
self.username = self.args.laps if not username_laps else username_laps
|
||||
self.password = msMCSAdmPwd
|
||||
|
||||
if msMCSAdmPwd == "":
|
||||
|
@ -615,16 +627,7 @@ class smb(connection):
|
|||
return self.laps_search(self.args.username, self.args.password, self.args.hash, self.domain)
|
||||
return True
|
||||
|
||||
def kerberos_login(
|
||||
self,
|
||||
domain,
|
||||
username,
|
||||
password="",
|
||||
ntlm_hash="",
|
||||
aesKey="",
|
||||
kdcHost="",
|
||||
useCache=False,
|
||||
):
|
||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
||||
logging.getLogger("impacket").disabled = True
|
||||
# Re-connect since we logged off
|
||||
if not self.no_ntlm:
|
||||
|
|
|
@ -2,19 +2,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import binascii
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
import os
|
||||
import requests
|
||||
|
||||
from datetime import datetime
|
||||
from pypsrp.client import Client
|
||||
|
||||
from impacket.smbconnection import SMBConnection
|
||||
from impacket.examples.secretsdump import LocalOperations, LSASecrets, SAMHashes
|
||||
|
||||
from cme.config import process_secret
|
||||
from cme.connection import *
|
||||
from cme.helpers.bloodhound import add_user_bh
|
||||
from cme.protocols.ldap.smbldap import LDAPConnect
|
||||
from cme.protocols.ldap.laps import LDAPConnect, LAPSv2Extract
|
||||
from cme.logger import CMEAdapter
|
||||
from pypsrp.client import Client
|
||||
from impacket.examples.secretsdump import LocalOperations, LSASecrets, SAMHashes
|
||||
|
||||
|
||||
class winrm(connection):
|
||||
def __init__(self, args, db, host):
|
||||
|
@ -213,25 +214,37 @@ class winrm(connection):
|
|||
|
||||
msMCSAdmPwd = ""
|
||||
sAMAccountName = ""
|
||||
username = ""
|
||||
username_laps = ""
|
||||
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
|
||||
results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)]
|
||||
if len(results) != 0:
|
||||
for host in results:
|
||||
values = {str(attr["type"]).lower(): str(attr["vals"][0]) for attr in host["attributes"]}
|
||||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in host["attributes"]}
|
||||
if "mslaps-encryptedpassword" in values:
|
||||
self.logger.fail("LAPS password is encrypted and currently CrackMapExec doesn't" " support the decryption...")
|
||||
return False
|
||||
from json import loads
|
||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||
d = LAPSv2Extract(
|
||||
bytes(msMCSAdmPwd),
|
||||
username[0] if username else "",
|
||||
password[0] if password else "",
|
||||
domain,
|
||||
ntlm_hash[0] if ntlm_hash else "",
|
||||
self.args.kerberos,
|
||||
self.args.kdcHost,
|
||||
339)
|
||||
data = d.run()
|
||||
r = loads(data)
|
||||
msMCSAdmPwd = r["p"]
|
||||
username_laps = r["n"]
|
||||
elif "mslaps-password" in values:
|
||||
from json import loads
|
||||
|
||||
r = loads(values["mslaps-password"])
|
||||
r = loads(str(values["mslaps-password"]))
|
||||
msMCSAdmPwd = r["p"]
|
||||
username = r["n"]
|
||||
username_laps = r["n"]
|
||||
elif "ms-mcs-admpwd" in values:
|
||||
msMCSAdmPwd = values["ms-mcs-admpwd"]
|
||||
msMCSAdmPwd = str(values["ms-mcs-admpwd"])
|
||||
else:
|
||||
self.logger.fail("No result found with attribute ms-MCS-AdmPwd or" " msLAPS-Password")
|
||||
self.logger.debug("Host: {:<20} Password: {} {}".format(sAMAccountName, msMCSAdmPwd, self.hostname))
|
||||
|
@ -239,7 +252,7 @@ class winrm(connection):
|
|||
self.logger.fail("msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS" " property for {}".format(self.hostname))
|
||||
return False
|
||||
|
||||
self.username = self.args.laps if not username else username
|
||||
self.username = self.args.laps if not username_laps else username_laps
|
||||
self.password = msMCSAdmPwd
|
||||
|
||||
if msMCSAdmPwd == "":
|
||||
|
|
|
@ -35,7 +35,7 @@ neo4j = "^4.1.1"
|
|||
pylnk3 = "^0.4.2"
|
||||
pypsrp = "^0.7.0"
|
||||
paramiko = "^2.7.2"
|
||||
impacket = { git = "https://github.com/mpgn/impacket.git", branch = "master" }
|
||||
impacket = { git = "https://github.com/mpgn/impacket.git", branch = "gkdi" }
|
||||
dsinternals = "^1.2.4"
|
||||
xmltodict = "^0.12.0"
|
||||
terminaltables = "^3.1.0"
|
||||
|
@ -51,6 +51,7 @@ masky = "^0.2.0"
|
|||
sqlalchemy = "^2.0.4"
|
||||
aiosqlite = "^0.18.0"
|
||||
pytest = "^7.2.2"
|
||||
pyasn1-modules = "^0.3.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "*"
|
||||
|
|
Loading…
Reference in New Issue