Skip to content

Commit c18eb4f

Browse files
zblurxDidierA
andauthored
3.0.0 Release (#25)
* add LOCAL triage and bug fixes Co-authored-by: DidierA <[email protected]> * add possibility to bypass shared violation error * add explicit target flag * kill browser functionality * replace copy by esentutl in shared violation bypass * added callback functions * added proper looted_files use * wifi : fix cred dump (#23) Co-authored-by: DidierA <[email protected]> * fix target option (#24) Co-authored-by: DidierA <[email protected]> * wam dump * Fix mobaxterm (#22) Co-authored-by: DidierA <[email protected]> Co-authored-by: zblurx <[email protected]> * added masterkey hashes generation * add blob action * allow user to specify dpapi_system_key in machinemasterkeys --------- Co-authored-by: DidierA <[email protected]>
1 parent eea24b0 commit c18eb4f

38 files changed

+4883
-2247
lines changed

README.md

Lines changed: 133 additions & 49 deletions
Large diffs are not rendered by default.

dploot/action/backupkey.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,50 +8,65 @@
88
from dploot.lib.smb import DPLootSMBConnection
99
from dploot.triage.backupkey import BackupkeyTriage
1010

11-
NAME = 'backupkey'
11+
NAME = "backupkey"
12+
1213

1314
class BackupkeyAction:
14-
1515
def __init__(self, options: argparse.Namespace) -> None:
1616
self.options = options
1717
self.target = Target.from_options(options)
18-
18+
1919
self.conn = None
2020
self._is_admin = None
2121
self.dce = None
2222
self.outputfile = None
2323
self.legacy = self.options.legacy
2424

25-
if self.options.outputfile is not None and self.options.outputfile != '':
25+
if self.options.outputfile is not None and self.options.outputfile != "":
2626
self.outputfile = self.options.outputfile
2727
else:
28-
self.outputfile = 'key.pvk'
28+
self.outputfile = "key.pvk"
2929

3030
def connect(self) -> None:
3131
self.conn = DPLootSMBConnection(self.target)
3232
if self.conn.connect() is None:
3333
logging.error("Could not connect to %s" % self.target.address)
3434
sys.exit(1)
35+
if self.conn.local_session:
36+
logging.error("Backup key is not implemented with LOCAL target.")
37+
sys.exit(1)
3538

3639
def run(self) -> None:
3740
self.connect()
38-
logging.info("Connected to %s as %s\\%s %s\n" % (self.target.address, self.target.domain, self.target.username, ( "(admin)"if self.is_admin else "")))
41+
logging.info(
42+
"Connected to {} as {}\\{} {}\n".format(
43+
self.target.address,
44+
self.target.domain,
45+
self.target.username,
46+
("(admin)" if self.is_admin else ""),
47+
)
48+
)
3949
triage = BackupkeyTriage(target=self.target, conn=self.conn)
4050
backupkey = triage.triage_backupkey()
4151
if backupkey.backupkey_v1 is not None and self.legacy:
4252
if not self.options.quiet:
4353
print("Legacy key:")
44-
print("0x%s" % hexlify(backupkey.backupkey_v1).decode('latin-1'))
54+
print("0x%s" % hexlify(backupkey.backupkey_v1).decode("latin-1"))
4555
print("\n")
46-
logging.info("Exporting key to file {}".format(self.outputfile + ".key"))
47-
open(self.outputfile + ".key", 'wb').write(backupkey.backupkey_v1)
56+
logging.info("Exporting key to file {}".format(self.outputfile + ".key"))
57+
open(self.outputfile + ".key", "wb").write(backupkey.backupkey_v1)
4858
if not self.options.quiet:
4959
print("[DOMAIN BACKUPKEY V2]")
5060
backupkey.pvk_header.dump()
51-
print("PRIVATEKEYBLOB:{%s}" % (hexlify(backupkey.backupkey_v2).decode('latin-1')))
61+
print(
62+
"PRIVATEKEYBLOB:{%s}"
63+
% (hexlify(backupkey.backupkey_v2).decode("latin-1"))
64+
)
5265
print("\n")
53-
logging.critical("Exporting domain backupkey to file {}".format(self.outputfile ))
54-
open(self.outputfile, 'wb').write(backupkey.backupkey_v2)
66+
logging.critical(
67+
f"Exporting domain backupkey to file {self.outputfile}"
68+
)
69+
open(self.outputfile, "wb").write(backupkey.backupkey_v2)
5570

5671
@property
5772
def is_admin(self) -> bool:
@@ -61,10 +76,12 @@ def is_admin(self) -> bool:
6176
self._is_admin = self.conn.is_admin()
6277
return self._is_admin
6378

79+
6480
def entry(options: argparse.Namespace) -> None:
6581
a = BackupkeyAction(options)
6682
a.run()
6783

84+
6885
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
6986
subparser = subparsers.add_parser(NAME, help="Backup Keys from domain controller")
7087

@@ -73,19 +90,13 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable
7390
group.add_argument(
7491
"-outputfile",
7592
action="store",
76-
help=(
77-
"Export keys to specific filename (default key.pvk)"
78-
),
93+
help=("Export keys to specific filename (default key.pvk)"),
7994
)
8095

8196
group.add_argument(
82-
'-legacy',
83-
action='store_true',
84-
help=(
85-
"Get also backupkey v1 (legacy)"
86-
)
97+
"-legacy", action="store_true", help=("Get also backupkey v1 (legacy)")
8798
)
8899

89100
add_target_argument_group(subparser)
90101

91-
return NAME, entry
102+
return NAME, entry

dploot/action/blob.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import argparse
2+
import base64
3+
import logging
4+
import os
5+
import sys
6+
from typing import Callable, Tuple
7+
from dploot.action.masterkeys import (
8+
add_masterkeys_argument_group,
9+
parse_masterkeys_options,
10+
)
11+
12+
from impacket.dpapi import DPAPI_BLOB
13+
14+
from dploot.lib.dpapi import decrypt_blob, find_masterkey_for_blob
15+
from dploot.lib.smb import DPLootSMBConnection
16+
from dploot.lib.target import Target, add_target_argument_group
17+
from dploot.lib.utils import dump_looted_files_to_disk, find_guid, find_sha1, handle_outputdir_option
18+
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file, Masterkey
19+
20+
NAME = "blob"
21+
22+
23+
class BlobAction:
24+
def __init__(self, options: argparse.Namespace) -> None:
25+
self.options = options
26+
self.target = Target.from_options(options)
27+
28+
self.conn = None
29+
self._is_admin = None
30+
self.outputdir = None
31+
self.masterkeys = None
32+
self.pvkbytes = None
33+
self.passwords = None
34+
self.nthashes = None
35+
36+
if not self.handle_blob_option(self.options.blob):
37+
sys.exit(1)
38+
39+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
40+
41+
if self.options.mkfile is not None:
42+
try:
43+
self.masterkeys = parse_masterkey_file(self.options.mkfile)
44+
except Exception as e:
45+
logging.error(str(e))
46+
sys.exit(1)
47+
48+
if self.options.masterkey is not None:
49+
guid, sha1 = self.options.masterkey.split(":")
50+
self.masterkeys[Masterkey(
51+
guid=find_guid(guid),
52+
sha1=find_sha1(sha1),
53+
)]
54+
55+
self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(
56+
self.options, self.target
57+
)
58+
59+
def connect(self) -> None:
60+
self.conn = DPLootSMBConnection(self.target)
61+
if self.conn.connect() is None:
62+
logging.error("Could not connect to %s" % self.target.address)
63+
sys.exit(1)
64+
65+
def run(self) -> None:
66+
self.connect()
67+
logging.info(
68+
"Connected to {} as {}\\{} {}\n".format(
69+
self.target.address,
70+
self.target.domain,
71+
self.target.username,
72+
("(admin)" if self.is_admin else ""),
73+
)
74+
)
75+
if self.is_admin:
76+
if self.masterkeys is None:
77+
78+
def masterkey_triage(masterkey):
79+
masterkey.dump()
80+
81+
masterkeytriage = MasterkeysTriage(
82+
target=self.target,
83+
conn=self.conn,
84+
pvkbytes=self.pvkbytes,
85+
nthashes=self.nthashes,
86+
passwords=self.passwords,
87+
per_masterkey_callback=masterkey_triage
88+
if not self.options.quiet
89+
else None,
90+
)
91+
logging.info("Triage ALL USERS masterkeys\n")
92+
self.masterkeys = masterkeytriage.triage_masterkeys()
93+
print()
94+
if self.outputdir is not None:
95+
dump_looted_files_to_disk(self.outputdir, masterkeytriage.looted_files)
96+
97+
logging.info("Trying to decrypt DPAPI blob\n")
98+
DPAPI_BLOB(self.blob).dump()
99+
masterkey = find_masterkey_for_blob(self.blob, masterkeys=self.masterkeys)
100+
if masterkey is not None:
101+
cleartext = decrypt_blob(blob_bytes=self.blob, masterkey=masterkey, entropy=self.options.entropy if self.options.entropy != "" else None)
102+
print("Data decrypted: %s" % cleartext)
103+
else:
104+
logging.info("Not an admin, exiting...")
105+
106+
@property
107+
def is_admin(self) -> bool:
108+
if self._is_admin is not None:
109+
return self._is_admin
110+
111+
self._is_admin = self.conn.is_admin()
112+
return self._is_admin
113+
114+
def handle_blob_option(self, blob_argument):
115+
if os.path.isfile(blob_argument):
116+
with open(blob_argument, "rb") as f:
117+
self.blob = f.read()
118+
return True
119+
else:
120+
try:
121+
self.blob = base64.b64decode(blob_argument)
122+
return True
123+
except Exception:
124+
logging.error(f"{blob_argument} does not seems to be a file nor a b64 encoded blob.")
125+
return False
126+
127+
def entry(options: argparse.Namespace) -> None:
128+
a = BlobAction(options)
129+
a.run()
130+
131+
132+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
133+
subparser = subparsers.add_parser(
134+
NAME, help="Decrypt DPAPI blob. Can fetch masterkeys on target"
135+
)
136+
137+
group = subparser.add_argument_group("vaults options")
138+
139+
group.add_argument(
140+
"-blob",
141+
action="store",
142+
required=True,
143+
help=("Blob base64 encoded or in file"),
144+
)
145+
146+
group.add_argument(
147+
"-masterkey",
148+
action="store",
149+
help=("{GUID}:SHA1 masterkey"),
150+
)
151+
152+
group.add_argument(
153+
"-entropy",
154+
action="store",
155+
help=("Entropy value"),
156+
)
157+
158+
group.add_argument(
159+
"-mkfile",
160+
action="store",
161+
help=("File containing {GUID}:SHA1 masterkeys mappings"),
162+
)
163+
164+
add_masterkeys_argument_group(group)
165+
add_target_argument_group(subparser)
166+
167+
return NAME, entry

0 commit comments

Comments
 (0)