Merge branch 'master' into protocol_db_marshall

main
Marshall Hallenbeck 2023-05-19 19:02:17 -04:00
commit ecb68637b6
4 changed files with 153 additions and 46 deletions

View File

@ -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')

View File

@ -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:

View File

@ -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 == "":

View File

@ -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 = "*"