dploot 3.1.3__tar.gz → 3.2.0__tar.gz
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-3.1.3 → dploot-3.2.0}/PKG-INFO +8 -14
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/browser.py +17 -0
- dploot-3.2.0/dploot/action/cng.py +129 -0
- dploot-3.2.0/dploot/action/machinecng.py +120 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/entry.py +4 -0
- dploot-3.2.0/dploot/lib/consts.py +30 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/crypto.py +104 -1
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/dpapi.py +2 -2
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/masterkey.py +2 -2
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/smb.py +51 -20
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/target.py +2 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/utils.py +1 -1
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/__init__.py +6 -7
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/browser.py +72 -49
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/certificates.py +62 -40
- dploot-3.2.0/dploot/triage/cng.py +165 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/credentials.py +1 -2
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/masterkeys.py +7 -8
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/mobaxterm.py +1 -2
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/rdg.py +1 -2
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/vaults.py +3 -4
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/wam.py +6 -3
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/wifi.py +13 -18
- {dploot-3.1.3 → dploot-3.2.0}/pyproject.toml +14 -14
- dploot-3.1.3/dploot/lib/consts.py +0 -11
- {dploot-3.1.3 → dploot-3.2.0}/LICENSE +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/README.md +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/__init__.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/__init__.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/backupkey.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/blob.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/certificates.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/credentials.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/machinecertificates.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/machinecredentials.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/machinemasterkeys.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/machinetriage.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/machinevaults.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/masterkeys.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/mobaxterm.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/rdg.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/sccm.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/triage.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/vaults.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/wam.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/action/wifi.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/__init__.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/lib/wmi.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/backupkey.py +0 -0
- {dploot-3.1.3 → dploot-3.2.0}/dploot/triage/sccm.py +0 -0
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dploot
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: DPAPI looting remotely in Python
|
|
5
|
-
License: MIT
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Keywords: DPAPI,credentials,Windows
|
|
7
|
-
Author:
|
|
8
|
+
Author: Thomas Seigneuret
|
|
8
9
|
Author-email: seigneuret.thomas@protonmail.com
|
|
9
|
-
Requires-Python: >=3.10.0,<4.0.0
|
|
10
|
-
Classifier: Environment :: Console
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
17
10
|
Classifier: Topic :: Security
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Environment :: Console
|
|
18
14
|
Requires-Dist: cryptography (>=40.0.1)
|
|
19
15
|
Requires-Dist: impacket (>=0.12.0)
|
|
20
16
|
Requires-Dist: lxml (>=5.0)
|
|
21
17
|
Requires-Dist: pyasn1 (>=0.4.8)
|
|
22
|
-
Project-URL: Homepage, https://github.com/zblurx/dploot
|
|
23
|
-
Project-URL: Repository, https://github.com/zblurx/dploot
|
|
24
18
|
Description-Content-Type: text/markdown
|
|
25
19
|
|
|
26
20
|
# dploot
|
|
@@ -11,6 +11,7 @@ from dploot.lib.smb import DPLootSMBConnection
|
|
|
11
11
|
from dploot.lib.target import Target, add_target_argument_group
|
|
12
12
|
from dploot.lib.utils import dump_looted_files_to_disk, handle_outputdir_option
|
|
13
13
|
from dploot.triage.browser import BrowserTriage, Cookie
|
|
14
|
+
from dploot.triage.cng import CngTriage
|
|
14
15
|
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
15
16
|
|
|
16
17
|
NAME = "browser"
|
|
@@ -95,6 +96,21 @@ class BrowserAction:
|
|
|
95
96
|
else:
|
|
96
97
|
secret.dump()
|
|
97
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
|
+
|
|
98
114
|
triage = BrowserTriage(
|
|
99
115
|
target=self.target,
|
|
100
116
|
conn=self.conn,
|
|
@@ -107,6 +123,7 @@ class BrowserAction:
|
|
|
107
123
|
)
|
|
108
124
|
triage.triage_browsers(
|
|
109
125
|
gather_cookies=self.options.show_cookies,
|
|
126
|
+
cng_chromekey=cng_chromekey,
|
|
110
127
|
bypass_shared_violation=self.options.bypass_shared_violation,
|
|
111
128
|
)
|
|
112
129
|
if self.outputdir is not None:
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Callable, Tuple
|
|
5
|
+
|
|
6
|
+
from dploot.action.masterkeys import add_masterkeys_argument_group, parse_masterkeys_options
|
|
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.cng import CngTriage
|
|
11
|
+
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
NAME = "cng"
|
|
15
|
+
|
|
16
|
+
class CngAction:
|
|
17
|
+
def __init__(self, options: argparse.Namespace) -> None:
|
|
18
|
+
self.options = options
|
|
19
|
+
self.target = Target.from_options(options)
|
|
20
|
+
|
|
21
|
+
self.conn = None
|
|
22
|
+
self._is_admin = None
|
|
23
|
+
self.outputdir = None
|
|
24
|
+
self.masterkeys = None
|
|
25
|
+
self.pvkbytes = None
|
|
26
|
+
self.passwords = None
|
|
27
|
+
self.nthashes = None
|
|
28
|
+
|
|
29
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
30
|
+
|
|
31
|
+
if self.options.mkfile is not None:
|
|
32
|
+
try:
|
|
33
|
+
self.masterkeys = parse_masterkey_file(self.options.mkfile)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
logging.error(str(e))
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(
|
|
39
|
+
self.options, self.target
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def connect(self) -> None:
|
|
43
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
44
|
+
if self.conn.connect() is None:
|
|
45
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
def run(self) -> None:
|
|
49
|
+
self.connect()
|
|
50
|
+
logging.info(
|
|
51
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
52
|
+
self.target.address,
|
|
53
|
+
self.target.domain,
|
|
54
|
+
self.target.username,
|
|
55
|
+
("(admin)" if self.is_admin else ""),
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
if self.is_admin:
|
|
59
|
+
if self.masterkeys is None:
|
|
60
|
+
|
|
61
|
+
def masterkey_triage(masterkey):
|
|
62
|
+
masterkey.dump()
|
|
63
|
+
|
|
64
|
+
masterkeytriage = MasterkeysTriage(
|
|
65
|
+
target=self.target,
|
|
66
|
+
conn=self.conn,
|
|
67
|
+
pvkbytes=self.pvkbytes,
|
|
68
|
+
nthashes=self.nthashes,
|
|
69
|
+
passwords=self.passwords,
|
|
70
|
+
per_masterkey_callback=masterkey_triage
|
|
71
|
+
if not self.options.quiet
|
|
72
|
+
else None,
|
|
73
|
+
)
|
|
74
|
+
logging.info("Triage ALL USERS masterkeys\n")
|
|
75
|
+
self.masterkeys = masterkeytriage.triage_masterkeys()
|
|
76
|
+
print()
|
|
77
|
+
if self.outputdir is not None:
|
|
78
|
+
dump_looted_files_to_disk(self.outputdir, masterkeytriage.looted_files)
|
|
79
|
+
|
|
80
|
+
def cng_callback(cng):
|
|
81
|
+
if self.options.quiet:
|
|
82
|
+
cng.dump_quiet()
|
|
83
|
+
else:
|
|
84
|
+
cng.dump()
|
|
85
|
+
|
|
86
|
+
triage = CngTriage(
|
|
87
|
+
target=self.target,
|
|
88
|
+
conn=self.conn,
|
|
89
|
+
masterkeys=self.masterkeys,
|
|
90
|
+
per_cng_callback=cng_callback,
|
|
91
|
+
)
|
|
92
|
+
logging.info("Triage CNG files for ALL USERS\n")
|
|
93
|
+
triage.triage_cng()
|
|
94
|
+
if self.outputdir is not None:
|
|
95
|
+
dump_looted_files_to_disk(self.outputdir, triage.looted_files)
|
|
96
|
+
else:
|
|
97
|
+
logging.info("Not an admin, exiting...")
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def is_admin(self) -> bool:
|
|
101
|
+
if self._is_admin is not None:
|
|
102
|
+
return self._is_admin
|
|
103
|
+
|
|
104
|
+
self._is_admin = self.conn.is_admin()
|
|
105
|
+
return self._is_admin
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def entry(options: argparse.Namespace) -> None:
|
|
109
|
+
a = CngAction(options)
|
|
110
|
+
a.run()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
114
|
+
subparser = subparsers.add_parser(
|
|
115
|
+
NAME, help="Dump users CNG files blob from local or remote target"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
group = subparser.add_argument_group("cng options")
|
|
119
|
+
|
|
120
|
+
group.add_argument(
|
|
121
|
+
"-mkfile",
|
|
122
|
+
action="store",
|
|
123
|
+
help=("File containing {GUID}:SHA1 masterkeys mappings"),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
add_masterkeys_argument_group(group)
|
|
127
|
+
add_target_argument_group(subparser)
|
|
128
|
+
|
|
129
|
+
return NAME, entry
|
|
@@ -0,0 +1,120 @@
|
|
|
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.cng import CngTriage
|
|
10
|
+
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
NAME = "machinecng"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MachineCngAction:
|
|
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.masterkeys = None
|
|
25
|
+
self.outputdir = None
|
|
26
|
+
|
|
27
|
+
self.outputdir = handle_outputdir_option(directory=self.options.export_dir)
|
|
28
|
+
|
|
29
|
+
if self.options.mkfile is not None:
|
|
30
|
+
try:
|
|
31
|
+
self.masterkeys = parse_masterkey_file(self.options.mkfile)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logging.error(str(e))
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
def connect(self) -> None:
|
|
37
|
+
self.conn = DPLootSMBConnection(self.target)
|
|
38
|
+
if self.conn.connect() is None:
|
|
39
|
+
logging.error("Could not connect to %s" % self.target.address)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
def run(self) -> None:
|
|
43
|
+
self.connect()
|
|
44
|
+
logging.info(
|
|
45
|
+
"Connected to {} as {}\\{} {}\n".format(
|
|
46
|
+
self.target.address,
|
|
47
|
+
self.target.domain,
|
|
48
|
+
self.target.username,
|
|
49
|
+
("(admin)" if self.is_admin else ""),
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
if self.is_admin:
|
|
53
|
+
if self.masterkeys is None:
|
|
54
|
+
|
|
55
|
+
def masterkey_triage(masterkey):
|
|
56
|
+
masterkey.dump()
|
|
57
|
+
|
|
58
|
+
masterkeytriage = MasterkeysTriage(
|
|
59
|
+
target=self.target,
|
|
60
|
+
conn=self.conn,
|
|
61
|
+
per_masterkey_callback=masterkey_triage
|
|
62
|
+
if not self.options.quiet
|
|
63
|
+
else None,
|
|
64
|
+
)
|
|
65
|
+
logging.info("Triage SYSTEM masterkeys\n")
|
|
66
|
+
self.masterkeys = masterkeytriage.triage_system_masterkeys()
|
|
67
|
+
print()
|
|
68
|
+
if self.outputdir is not None:
|
|
69
|
+
dump_looted_files_to_disk(self.outputdir, masterkeytriage.looted_files)
|
|
70
|
+
|
|
71
|
+
def cng_callback(cng_file):
|
|
72
|
+
if self.options.quiet:
|
|
73
|
+
cng_file.dump_quiet()
|
|
74
|
+
else:
|
|
75
|
+
cng_file.dump()
|
|
76
|
+
|
|
77
|
+
triage = CngTriage(
|
|
78
|
+
target=self.target,
|
|
79
|
+
conn=self.conn,
|
|
80
|
+
masterkeys=self.masterkeys,
|
|
81
|
+
per_cng_callback=cng_callback,
|
|
82
|
+
)
|
|
83
|
+
logging.info("Triage SYSTEM CNG files\n")
|
|
84
|
+
triage.triage_system_cng()
|
|
85
|
+
if self.outputdir is not None:
|
|
86
|
+
dump_looted_files_to_disk(self.outputdir, triage.looted_files)
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
logging.info("Not an admin, exiting...")
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def is_admin(self) -> bool:
|
|
93
|
+
if self._is_admin is not None:
|
|
94
|
+
return self._is_admin
|
|
95
|
+
|
|
96
|
+
self._is_admin = self.conn.is_admin()
|
|
97
|
+
return self._is_admin
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def entry(options: argparse.Namespace) -> None:
|
|
101
|
+
a = MachineCngAction(options)
|
|
102
|
+
a.run()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
|
|
106
|
+
subparser = subparsers.add_parser(
|
|
107
|
+
NAME, help="Dump system CNG files from local or remote target"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
group = subparser.add_argument_group("machinecng options")
|
|
111
|
+
|
|
112
|
+
group.add_argument(
|
|
113
|
+
"-mkfile",
|
|
114
|
+
action="store",
|
|
115
|
+
help=("File containing {GUID}:SHA1 masterkeys mappings"),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
add_target_argument_group(subparser)
|
|
119
|
+
|
|
120
|
+
return NAME, entry
|
|
@@ -11,8 +11,10 @@ from dploot.action import (
|
|
|
11
11
|
blob,
|
|
12
12
|
browser,
|
|
13
13
|
certificates,
|
|
14
|
+
cng,
|
|
14
15
|
credentials,
|
|
15
16
|
machinecertificates,
|
|
17
|
+
machinecng,
|
|
16
18
|
machinecredentials,
|
|
17
19
|
machinemasterkeys,
|
|
18
20
|
machinetriage,
|
|
@@ -32,8 +34,10 @@ ENTRY_PARSERS = [
|
|
|
32
34
|
blob,
|
|
33
35
|
browser,
|
|
34
36
|
certificates,
|
|
37
|
+
cng,
|
|
35
38
|
credentials,
|
|
36
39
|
machinecertificates,
|
|
40
|
+
machinecng,
|
|
37
41
|
machinecredentials,
|
|
38
42
|
machinemasterkeys,
|
|
39
43
|
machinetriage,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Define some dploot consts.
|
|
2
|
+
|
|
3
|
+
FALSE_POSITIVES = [
|
|
4
|
+
".",
|
|
5
|
+
"..",
|
|
6
|
+
"desktop.ini",
|
|
7
|
+
"Public",
|
|
8
|
+
"Default",
|
|
9
|
+
"Default User",
|
|
10
|
+
"All Users",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
# simple class to check for false positives, case insensitive
|
|
14
|
+
class FalsePositives(list):
|
|
15
|
+
def __init__(self,
|
|
16
|
+
false_positives: list[str] = None
|
|
17
|
+
) -> None:
|
|
18
|
+
if false_positives is None:
|
|
19
|
+
false_positives = FALSE_POSITIVES
|
|
20
|
+
|
|
21
|
+
super().__init__(map (lambda x: x.lower(), false_positives))
|
|
22
|
+
|
|
23
|
+
def __contains__(self, name):
|
|
24
|
+
return super().__contains__(str(name).lower())
|
|
25
|
+
|
|
26
|
+
def __setitem__(self, key, value):
|
|
27
|
+
return super().__setitem__(key,str(value).lower())
|
|
28
|
+
|
|
29
|
+
def append(self, element):
|
|
30
|
+
return super().append(str(element).lower())
|
|
@@ -43,6 +43,107 @@ class CERTBLOB:
|
|
|
43
43
|
print()
|
|
44
44
|
print("DER : %s " % (self.der))
|
|
45
45
|
|
|
46
|
+
# CNG PART
|
|
47
|
+
class CNG_PROPERTY(Structure):
|
|
48
|
+
structure = (
|
|
49
|
+
("StructLen", "<L=0"),
|
|
50
|
+
("Type", "<L=0"),
|
|
51
|
+
("Unknown", "<L=0"),
|
|
52
|
+
("NameLen", "<L=0"),
|
|
53
|
+
("PropertyLen", "<L=0"),
|
|
54
|
+
("_Name", "_-Name", 'self["NameLen"]'),
|
|
55
|
+
("Name", ":"),
|
|
56
|
+
("_Property", "_-Property", 'self["PropertyLen"]'),
|
|
57
|
+
("Property", ":"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def dump(self):
|
|
61
|
+
print("[CNG PROPERTY]")
|
|
62
|
+
print("StructLen : %8x (%d)" % (self["StructLen"], self["StructLen"]))
|
|
63
|
+
print("Type : %8x (%d)" % (self["Type"], self["Type"]))
|
|
64
|
+
print("Unknown : %8x (%d)" % (self["Unknown"], self["Unknown"]))
|
|
65
|
+
print("NameLen : %8x (%d)" % (self["NameLen"], self["NameLen"]))
|
|
66
|
+
print("PropertyLen : %8x (%d)" % (self["PropertyLen"], self["PropertyLen"]))
|
|
67
|
+
print("Name : %s" % (self["Name"].decode("utf-16le").rstrip("\0")))
|
|
68
|
+
print("Property : %s" % hexlify(self["Property"]))
|
|
69
|
+
|
|
70
|
+
class CNG_PROPERTIES:
|
|
71
|
+
def __init__(self, data=None, alignment=0):
|
|
72
|
+
remaining = data
|
|
73
|
+
self.properties = []
|
|
74
|
+
while len(remaining) > 0:
|
|
75
|
+
prop = CNG_PROPERTY(remaining)
|
|
76
|
+
self.properties.append(prop)
|
|
77
|
+
remaining = remaining[len(prop) :]
|
|
78
|
+
|
|
79
|
+
def __len__(self):
|
|
80
|
+
length = 0
|
|
81
|
+
for prop in self.properties:
|
|
82
|
+
length += len(prop)
|
|
83
|
+
return length
|
|
84
|
+
|
|
85
|
+
def dump(self):
|
|
86
|
+
for prop in self.properties:
|
|
87
|
+
prop.dump()
|
|
88
|
+
|
|
89
|
+
class CNG_BLOB(Structure):
|
|
90
|
+
structure = (
|
|
91
|
+
("Version", "<L=1"),
|
|
92
|
+
("Unknown", "<L=0"),
|
|
93
|
+
("NameLen", "<L=0"),
|
|
94
|
+
("Type", "<L=0"),
|
|
95
|
+
("PublicPropLen", "<L=0"),
|
|
96
|
+
("PrivatePropLen", "<L=0"),
|
|
97
|
+
("PrivateKeyLen", "<L=0"),
|
|
98
|
+
("Unknown2", "16s=b'\\x00'*16"),
|
|
99
|
+
("_Name", "_-Name", 'self["NameLen"]'),
|
|
100
|
+
("Name", ":"),
|
|
101
|
+
("_PublicProperties", "_-PublicProperties", 'self["PublicPropLen"]'),
|
|
102
|
+
("PublicProperties", ":", CNG_PROPERTIES),
|
|
103
|
+
("_PrivateProperties", "_-PrivateProperties", 'self["PrivatePropLen"]'),
|
|
104
|
+
("PrivateProperties", ":", DPAPI_BLOB),
|
|
105
|
+
("_PrivateKey", "_-PrivateKey", 'self["PrivateKeyLen"]'),
|
|
106
|
+
("PrivateKey", ":", DPAPI_BLOB),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def dump(self):
|
|
110
|
+
print("[CNG BLOB]")
|
|
111
|
+
print("Version : %8x (%d)" % (self["Version"], self["Version"]))
|
|
112
|
+
print("Unknown : %8x (%d)" % (self["Unknown"], self["Unknown"]))
|
|
113
|
+
print("NameLen : %8x (%d)" % (self["NameLen"], self["NameLen"]))
|
|
114
|
+
print("Type : %8x (%d)" % (self["Type"], self["Type"]))
|
|
115
|
+
print("PublicPropLen : %8x (%d)" % (self["PublicPropLen"], self["PublicPropLen"]))
|
|
116
|
+
print("PrivatePropLey : %8x (%d)" % (self["PrivatePropLen"], self["PrivatePropLen"]))
|
|
117
|
+
print("PrivateKeyLen : %8x (%d)" % (self["PrivateKeyLen"], self["PrivateKeyLen"]))
|
|
118
|
+
print("Unknown2 : %s " % (self["Unknown2"]))
|
|
119
|
+
print("Name : %s" % (self["Name"].decode("utf-16le").rstrip("\0")))
|
|
120
|
+
print()
|
|
121
|
+
print("PublicProperties:")
|
|
122
|
+
self["PublicProperties"].dump()
|
|
123
|
+
print()
|
|
124
|
+
print("PrivateProperties:")
|
|
125
|
+
self["PrivateProperties"].dump()
|
|
126
|
+
print()
|
|
127
|
+
print("PrivateKey:")
|
|
128
|
+
self["PrivateKey"].dump()
|
|
129
|
+
|
|
130
|
+
class CHROME_KEY_DATA_BLOB(Structure):
|
|
131
|
+
structure = (
|
|
132
|
+
("Magic", "<I"),
|
|
133
|
+
("Version", "<I"),
|
|
134
|
+
("KeyLen", "<I"),
|
|
135
|
+
("_Key", "_-Key", 'self["KeyLen"]'),
|
|
136
|
+
("Key", ":"),
|
|
137
|
+
("Remaining", ":")
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def dump(self):
|
|
141
|
+
print("[BCRYPT KEY DATA]")
|
|
142
|
+
print("Magic : %4x (%s)" % (self["Magic"], self["Magic"]))
|
|
143
|
+
print("Version : %4x (%d)" % (self["Version"], self["Version"]))
|
|
144
|
+
print("KeyLen : %4x (%d)" % (self["KeyLen"], self["KeyLen"]))
|
|
145
|
+
print("Key : %s" % hexlify(self["Key"]))
|
|
146
|
+
print("Remaining : %s" % hexlify(self["Remaining"]))
|
|
46
147
|
|
|
47
148
|
# https://github.com/SecureAuthCorp/impacket/pull/1120
|
|
48
149
|
# Private Decrypted Private Key
|
|
@@ -258,7 +359,7 @@ class PVKHeader(Structure):
|
|
|
258
359
|
|
|
259
360
|
def pvkblob_to_pkcs1(key):
|
|
260
361
|
"""
|
|
261
|
-
|
|
362
|
+
Modified from impacket dpapi.py
|
|
262
363
|
parse private key into pkcs#1 format
|
|
263
364
|
:param key:
|
|
264
365
|
:return:
|
|
@@ -298,6 +399,8 @@ def deriveKeysFromUser(sid, password):
|
|
|
298
399
|
|
|
299
400
|
return key1, key2, key3
|
|
300
401
|
|
|
402
|
+
def byte_xor(ba1, ba2):
|
|
403
|
+
return bytes([_a ^ _b for _a, _b in zip(ba1, ba2, strict=False)])
|
|
301
404
|
|
|
302
405
|
def deriveKeysFromUserkey(sid, nthash):
|
|
303
406
|
key1 = HMAC.new(nthash, (sid + "\0").encode("utf-16le"), SHA1).digest() # SHA1
|
|
@@ -141,7 +141,7 @@ def find_masterkey_for_credential_blob(
|
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
def decrypt_privatekey(
|
|
144
|
-
privatekey_bytes: bytes, masterkey: Any
|
|
144
|
+
privatekey_bytes: bytes, masterkey: Any
|
|
145
145
|
) -> RSA.RsaKey:
|
|
146
146
|
blob = PVKHeader(privatekey_bytes)
|
|
147
147
|
blob = PVKFile_SIG(privatekey_bytes) if blob["SigHeadLen"] > 0 else PVKFile(privatekey_bytes)
|
|
@@ -152,7 +152,7 @@ def decrypt_privatekey(
|
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
def find_masterkey_for_privatekey_blob(
|
|
155
|
-
privatekey_bytes: bytes, masterkeys: List[Any]
|
|
155
|
+
privatekey_bytes: bytes, masterkeys: List[Any]
|
|
156
156
|
) -> "Any | None":
|
|
157
157
|
blob = PVKHeader(privatekey_bytes)
|
|
158
158
|
if len(blob["Remaining"]) == 0:
|
|
@@ -11,10 +11,10 @@ from dploot.lib.dpapi import decrypt_masterkey
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Masterkey:
|
|
14
|
-
def __init__(self, guid, blob = None, sid = None, key = None, sha1 = None, user: str = "None") -> None:
|
|
14
|
+
def __init__(self, guid, blob = None, sid:str = None, key = None, sha1 = None, user: str = "None") -> None:
|
|
15
15
|
self.guid = guid
|
|
16
16
|
self.blob = blob
|
|
17
|
-
self.sid = sid
|
|
17
|
+
self.sid = str(sid).upper()
|
|
18
18
|
self.user = user
|
|
19
19
|
|
|
20
20
|
self.key = key
|