Merge pull request #1494 from gzzcoo/fix/ldap-lapsv2-kerberos-rpc

This commit is contained in:
Azox authored and GitHub committed 2026-10-29 16:54:46 +01:00
commit 30cf146578
2 files changed
+23 -13

No files matched your search

+4 -1
View File
@@ -45,12 +45,15 @@ class NXCModule:
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]}
if "mslaps-encryptedpassword" in values:
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, connection.dns_server)
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, connection.dns_server, connection.host, connection.remoteName, connection.aesKey)
try:
data = d.run()
except Exception as e:
context.log.fail(str(e))
continue
if not data:
context.log.fail(f"Failed to decrypt msLAPS-EncryptedPassword for {values['samaccountname']}")
continue
r = json.loads(data)
laps_computers.append((str(values["samaccountname"]), r["n"], str(r["p"])))
elif "mslaps-password" in values:
+19 -12
View File
@@ -7,7 +7,7 @@ from impacket.krb5.kerberosv5 import KerberosError
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.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.dpapi_ng import EncryptedPasswordBlob, KeyIdentifier, compute_kek, create_sd, decrypt_plaintext, unwrap_cek
from nxc.logger import NXCAdapter
@@ -169,7 +169,7 @@ class LDAPConnect:
class LAPSv2Extract:
def __init__(self, data, username, password, domain, ntlm_hash, do_kerberos, kdcHost, port, dns_server):
def __init__(self, data, username, password, domain, ntlm_hash, do_kerberos, kdcHost, port, dns_server, rpc_host=None, rpc_name=None, aesKey=""):
if ntlm_hash.find(":") != -1:
self.lmhash, self.nthash = ntlm_hash.split(":")
else:
@@ -182,8 +182,11 @@ class LAPSv2Extract:
self.domain = domain
self.do_kerberos = do_kerberos
self.kdcHost = kdcHost
self.aesKey = aesKey
self.logger = None
self.dns_server = dns_server
self.rpc_host = rpc_host or (dns_server if dns_server else domain)
self.rpc_name = rpc_name or domain
self.proto_logger(self.domain, port, self.domain)
def proto_logger(self, host, port, hostname):
@@ -215,29 +218,30 @@ class LAPSv2Extract:
gke = kds_cache[key_id["RootKeyId"]]
else:
# Connect on RPC over TCP to MS-GKDI to call opnum 0 GetKey
string_binding = hept_map(destHost=self.domain if not self.dns_server else self.dns_server, remoteIf=MSRPC_UUID_GKDI, protocol="ncacn_ip_tcp")
string_binding = hept_map(destHost=self.rpc_host, remoteIf=MSRPC_UUID_GKDI, protocol="ncacn_ip_tcp")
rpc_transport = transport.DCERPCTransportFactory(string_binding)
if hasattr(rpc_transport, "set_credentials"):
rpc_transport.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=self.lmhash, nthash=self.nthash)
rpc_transport.setRemoteName(self.rpc_name)
rpc_transport.set_credentials(username=self.username, password=self.password, domain=self.domain, lmhash=self.lmhash, nthash=self.nthash, aesKey=self.aesKey)
if self.do_kerberos:
self.logger.info("Connecting using kerberos")
rpc_transport.set_kerberos(self.do_kerberos, kdcHost=self.kdcHost)
dce = rpc_transport.get_dce_rpc()
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_INTEGRITY)
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
if self.do_kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
self.logger.info(f"Connecting to {string_binding}")
try:
dce.connect()
except Exception as e:
self.logger.error(f"Something went wrong, check error status => {e}")
return False
return None
self.logger.info("Connected")
try:
dce.bind(MSRPC_UUID_GKDI)
except Exception as e:
self.logger.error(f"Something went wrong, check error status => {e!s}")
return False
return None
self.logger.info("Successfully bound")
self.logger.info("Calling MS-GKDI GetKey")
@@ -248,15 +252,12 @@ class LAPSv2Extract:
kds_cache[gke["RootKeyId"]] = gke
kek = compute_kek(gke, key_id)
self.logger.info(f"KEK:\t{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(f"CEK:\t{cek}")
plaintext = decrypt_plaintext(cek, iv, remaining)
self.logger.info(plaintext[:-18].decode("utf-16le"))
return plaintext[:-18].decode("utf-16le")
@@ -325,13 +326,19 @@ def laps_search(self, username, password, cred_type, domain, dns_server):
self.kerberos,
self.kdcHost,
339,
dns_server
dns_server,
self.kdcHost,
self.kdcHost,
self.aesKey,
)
try:
data = d.run()
except Exception as e:
self.logger.fail(str(e))
return None, None, None
if not data:
self.logger.fail(f"Failed to decrypt msLAPS-EncryptedPassword for {self.hostname}")
return None, None, None
r = loads(data)
msMCSAdmPwd = r["p"]
username_laps = r["n"]