dissect.target 3.19.dev55__py3-none-any.whl → 3.19.dev57__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.
- dissect/target/plugin.py +10 -3
- dissect/target/plugins/os/windows/adpolicy.py +4 -1
- dissect/target/plugins/os/windows/credential/__init__.py +0 -0
- dissect/target/plugins/os/windows/credential/lsa.py +174 -0
- dissect/target/plugins/os/windows/{sam.py → credential/sam.py} +5 -2
- dissect/target/plugins/os/windows/dpapi/blob.py +3 -0
- dissect/target/plugins/os/windows/dpapi/crypto.py +61 -23
- dissect/target/plugins/os/windows/dpapi/dpapi.py +127 -133
- dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py +0 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +21 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +17 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +20 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py +8 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +38 -0
- dissect/target/plugins/os/windows/dpapi/master_key.py +3 -0
- dissect/target/plugins/os/windows/regf/shimcache.py +2 -2
- dissect/target/plugins/os/windows/task_helpers/tasks_xml.py +1 -1
- dissect/target/tools/query.py +2 -2
- {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/METADATA +1 -1
- {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/RECORD +26 -18
- /dissect/target/plugins/os/windows/{credhist.py → credential/credhist.py} +0 -0
- {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/WHEEL +0 -0
- {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/top_level.txt +0 -0
dissect/target/plugin.py
CHANGED
@@ -142,7 +142,7 @@ def get_nonprivate_attributes(cls: Type[Plugin]) -> list[Any]:
|
|
142
142
|
|
143
143
|
def get_nonprivate_methods(cls: Type[Plugin]) -> list[Callable]:
|
144
144
|
"""Retrieve all public methods of a :class:`Plugin`."""
|
145
|
-
return [attr for attr in get_nonprivate_attributes(cls) if not isinstance(attr, property)]
|
145
|
+
return [attr for attr in get_nonprivate_attributes(cls) if not isinstance(attr, property) and callable(attr)]
|
146
146
|
|
147
147
|
|
148
148
|
def get_descriptors_on_nonprivate_methods(cls: Type[Plugin]) -> list[RecordDescriptor]:
|
@@ -1020,7 +1020,7 @@ class NamespacePlugin(Plugin):
|
|
1020
1020
|
continue
|
1021
1021
|
|
1022
1022
|
# The method needs to output records
|
1023
|
-
if getattr(subplugin_func, "__output__", None)
|
1023
|
+
if getattr(subplugin_func, "__output__", None) not in ["record", "yield"]:
|
1024
1024
|
continue
|
1025
1025
|
|
1026
1026
|
# The method may not be part of a parent class.
|
@@ -1106,6 +1106,9 @@ class NamespacePlugin(Plugin):
|
|
1106
1106
|
cls.__init_subclass_namespace__(cls, **kwargs)
|
1107
1107
|
|
1108
1108
|
|
1109
|
+
__COMMON_PLUGIN_METHOD_NAMES__ = {attr.__name__ for attr in get_nonprivate_methods(Plugin)}
|
1110
|
+
|
1111
|
+
|
1109
1112
|
class InternalPlugin(Plugin):
|
1110
1113
|
"""Parent class for internal plugins.
|
1111
1114
|
|
@@ -1115,13 +1118,17 @@ class InternalPlugin(Plugin):
|
|
1115
1118
|
|
1116
1119
|
def __init_subclass__(cls, **kwargs):
|
1117
1120
|
for method in get_nonprivate_methods(cls):
|
1118
|
-
if callable(method):
|
1121
|
+
if callable(method) and method.__name__ not in __COMMON_PLUGIN_METHOD_NAMES__:
|
1119
1122
|
method.__internal__ = True
|
1120
1123
|
|
1121
1124
|
super().__init_subclass__(**kwargs)
|
1122
1125
|
return cls
|
1123
1126
|
|
1124
1127
|
|
1128
|
+
class InternalNamespacePlugin(NamespacePlugin, InternalPlugin):
|
1129
|
+
pass
|
1130
|
+
|
1131
|
+
|
1125
1132
|
@dataclass(frozen=True, eq=True)
|
1126
1133
|
class PluginFunction:
|
1127
1134
|
name: str
|
@@ -69,7 +69,10 @@ class ADPolicyPlugin(Plugin):
|
|
69
69
|
xml = task_file.read_text()
|
70
70
|
tree = ElementTree.fromstring(xml)
|
71
71
|
for task in tree.findall(".//{*}Task"):
|
72
|
-
|
72
|
+
# https://github.com/python/cpython/issues/83122
|
73
|
+
if (properties := task.find("Properties")) is None:
|
74
|
+
properties = task
|
75
|
+
|
73
76
|
task_data = ElementTree.tostring(task)
|
74
77
|
yield ADPolicyRecord(
|
75
78
|
last_modification_time=task_file_stat.st_mtime,
|
File without changes
|
@@ -0,0 +1,174 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import hashlib
|
4
|
+
from functools import cached_property
|
5
|
+
from typing import Iterator
|
6
|
+
|
7
|
+
from dissect.target.exceptions import RegistryKeyNotFoundError, UnsupportedPluginError
|
8
|
+
from dissect.target.helpers.record import TargetRecordDescriptor
|
9
|
+
from dissect.target.plugin import Plugin, export
|
10
|
+
|
11
|
+
try:
|
12
|
+
from Crypto.Cipher import AES, ARC4, DES
|
13
|
+
|
14
|
+
HAS_CRYPTO = True
|
15
|
+
except ImportError:
|
16
|
+
HAS_CRYPTO = False
|
17
|
+
|
18
|
+
|
19
|
+
LSASecretRecord = TargetRecordDescriptor(
|
20
|
+
"windows/credential/lsa",
|
21
|
+
[
|
22
|
+
("datetime", "ts"),
|
23
|
+
("string", "name"),
|
24
|
+
("string", "value"),
|
25
|
+
],
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
class LSAPlugin(Plugin):
|
30
|
+
"""Windows Local Security Authority (LSA) plugin.
|
31
|
+
|
32
|
+
Resources:
|
33
|
+
- https://learn.microsoft.com/en-us/windows/win32/secauthn/lsa-authentication
|
34
|
+
- https://moyix.blogspot.com/2008/02/decrypting-lsa-secrets.html (Windows XP)
|
35
|
+
- https://github.com/fortra/impacket/blob/master/impacket/examples/secretsdump.py
|
36
|
+
"""
|
37
|
+
|
38
|
+
__namespace__ = "lsa"
|
39
|
+
|
40
|
+
SECURITY_POLICY_KEY = "HKEY_LOCAL_MACHINE\\SECURITY\\Policy"
|
41
|
+
SYSTEM_KEY = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\LSA"
|
42
|
+
|
43
|
+
def check_compatible(self) -> None:
|
44
|
+
if not HAS_CRYPTO:
|
45
|
+
raise UnsupportedPluginError("Missing pycryptodome dependency")
|
46
|
+
|
47
|
+
if not self.target.has_function("registry") or not list(self.target.registry.keys(self.SYSTEM_KEY)):
|
48
|
+
raise UnsupportedPluginError("Registry key not found: %s", self.SYSTEM_KEY)
|
49
|
+
|
50
|
+
@cached_property
|
51
|
+
def syskey(self) -> bytes:
|
52
|
+
"""Return byte value of Windows system SYSKEY, also called BootKey."""
|
53
|
+
lsa = self.target.registry.key(self.SYSTEM_KEY)
|
54
|
+
syskey_keys = ["JD", "Skew1", "GBG", "Data"]
|
55
|
+
# This magic value rotates the order of the data
|
56
|
+
alterator = [0x8, 0x5, 0x4, 0x2, 0xB, 0x9, 0xD, 0x3, 0x0, 0x6, 0x1, 0xC, 0xE, 0xA, 0xF, 0x7]
|
57
|
+
|
58
|
+
r = bytes.fromhex("".join([lsa.subkey(key).class_name for key in syskey_keys]))
|
59
|
+
return bytes(r[i] for i in alterator)
|
60
|
+
|
61
|
+
@cached_property
|
62
|
+
def lsakey(self) -> bytes:
|
63
|
+
"""Decrypt and return the LSA key of the Windows system."""
|
64
|
+
security_pol = self.target.registry.key(self.SECURITY_POLICY_KEY)
|
65
|
+
|
66
|
+
try:
|
67
|
+
# Windows Vista or newer
|
68
|
+
enc_key = security_pol.subkey("PolEKList").value("(Default)").value
|
69
|
+
lsa_key = _decrypt_aes(enc_key, self.syskey)
|
70
|
+
return lsa_key[68:100]
|
71
|
+
except RegistryKeyNotFoundError:
|
72
|
+
pass
|
73
|
+
|
74
|
+
try:
|
75
|
+
# Windows XP
|
76
|
+
enc_key = security_pol.subkey("PolSecretEncryptionKey").value("(Default)").value
|
77
|
+
lsa_key = _decrypt_rc4(enc_key, self.syskey)
|
78
|
+
return lsa_key[16:32]
|
79
|
+
except RegistryKeyNotFoundError:
|
80
|
+
pass
|
81
|
+
|
82
|
+
raise ValueError("Unable to determine LSA policy key location in registry")
|
83
|
+
|
84
|
+
@cached_property
|
85
|
+
def _secrets(self) -> dict[str, bytes] | None:
|
86
|
+
"""Return dict of Windows system decrypted LSA secrets."""
|
87
|
+
if not self.target.ntversion:
|
88
|
+
raise ValueError("Unable to determine Windows NT version")
|
89
|
+
|
90
|
+
result = {}
|
91
|
+
for subkey in self.target.registry.key(self.SECURITY_POLICY_KEY).subkey("Secrets").subkeys():
|
92
|
+
enc_data = subkey.subkey("CurrVal").value("(Default)").value
|
93
|
+
|
94
|
+
# Windows Vista or newer
|
95
|
+
if float(self.target.ntversion) >= 6.0:
|
96
|
+
secret = _decrypt_aes(enc_data, self.lsakey)
|
97
|
+
|
98
|
+
# Windows XP
|
99
|
+
else:
|
100
|
+
secret = _decrypt_des(enc_data, self.lsakey)
|
101
|
+
|
102
|
+
result[subkey.name] = secret
|
103
|
+
|
104
|
+
return result
|
105
|
+
|
106
|
+
@export(record=LSASecretRecord)
|
107
|
+
def secrets(self) -> Iterator[LSASecretRecord]:
|
108
|
+
"""Yield decrypted LSA secrets from a Windows target."""
|
109
|
+
for key, value in self._secrets.items():
|
110
|
+
yield LSASecretRecord(
|
111
|
+
ts=self.target.registry.key(f"{self.SECURITY_POLICY_KEY}\\Secrets\\{key}").ts,
|
112
|
+
name=key,
|
113
|
+
value=value.hex(),
|
114
|
+
_target=self.target,
|
115
|
+
)
|
116
|
+
|
117
|
+
|
118
|
+
def _decrypt_aes(data: bytes, key: bytes) -> bytes:
|
119
|
+
ctx = hashlib.sha256()
|
120
|
+
ctx.update(key)
|
121
|
+
for _ in range(1, 1000 + 1):
|
122
|
+
ctx.update(data[28:60])
|
123
|
+
|
124
|
+
ciphertext = data[60:]
|
125
|
+
plaintext = []
|
126
|
+
|
127
|
+
for i in range(0, len(ciphertext), 16):
|
128
|
+
cipher = AES.new(ctx.digest(), AES.MODE_CBC, iv=b"\x00" * 16)
|
129
|
+
plaintext.append(cipher.decrypt(ciphertext[i : i + 16].ljust(16, b"\x00")))
|
130
|
+
|
131
|
+
return b"".join(plaintext)
|
132
|
+
|
133
|
+
|
134
|
+
def _decrypt_rc4(data: bytes, key: bytes) -> bytes:
|
135
|
+
md5 = hashlib.md5()
|
136
|
+
md5.update(key)
|
137
|
+
for _ in range(1000):
|
138
|
+
md5.update(data[60:76])
|
139
|
+
rc4_key = md5.digest()
|
140
|
+
|
141
|
+
cipher = ARC4.new(rc4_key)
|
142
|
+
return cipher.decrypt(data[12:60])
|
143
|
+
|
144
|
+
|
145
|
+
def _decrypt_des(data: bytes, key: bytes) -> bytes:
|
146
|
+
plaintext = []
|
147
|
+
|
148
|
+
enc_size = int.from_bytes(data[:4], "little")
|
149
|
+
data = data[len(data) - enc_size :]
|
150
|
+
|
151
|
+
key0 = key
|
152
|
+
for _ in range(0, len(data), 8):
|
153
|
+
ciphertext = data[:8]
|
154
|
+
block_key = _transform_key(key0[:7])
|
155
|
+
|
156
|
+
cipher = DES.new(block_key, DES.MODE_ECB)
|
157
|
+
plaintext.append(cipher.decrypt(ciphertext))
|
158
|
+
|
159
|
+
key0 = key0[7:]
|
160
|
+
data = data[8:]
|
161
|
+
|
162
|
+
if len(key0) < 7:
|
163
|
+
key0 = key[len(key0) :]
|
164
|
+
|
165
|
+
return b"".join(plaintext)
|
166
|
+
|
167
|
+
|
168
|
+
def _transform_key(key: bytes) -> bytes:
|
169
|
+
new_key = []
|
170
|
+
new_key.append(((key[0] >> 0x01) << 1) & 0xFE)
|
171
|
+
for i in range(0, 6):
|
172
|
+
new_key.append((((key[i] & ((1 << (i + 1)) - 1)) << (6 - i) | (key[i + 1] >> (i + 2))) << 1) & 0xFE)
|
173
|
+
new_key.append(((key[6] & 0x7F) << 1) & 0xFE)
|
174
|
+
return bytes(new_key)
|
@@ -210,7 +210,7 @@ struct SAM_HASH_AES { /* size: >=24 */
|
|
210
210
|
c_sam = cstruct().load(sam_def)
|
211
211
|
|
212
212
|
SamRecord = TargetRecordDescriptor(
|
213
|
-
"windows/
|
213
|
+
"windows/credential/sam",
|
214
214
|
[
|
215
215
|
("uint32", "rid"),
|
216
216
|
("string", "fullname"),
|
@@ -303,6 +303,9 @@ class SamPlugin(Plugin):
|
|
303
303
|
if not HAS_CRYPTO:
|
304
304
|
raise UnsupportedPluginError("Missing pycryptodome dependency")
|
305
305
|
|
306
|
+
if not self.target.has_function("lsa"):
|
307
|
+
raise UnsupportedPluginError("LSA plugin is required for SAM plugin")
|
308
|
+
|
306
309
|
if not len(list(self.target.registry.keys(self.SAM_KEY))) > 0:
|
307
310
|
raise UnsupportedPluginError(f"Registry key not found: {self.SAM_KEY}")
|
308
311
|
|
@@ -374,7 +377,7 @@ class SamPlugin(Plugin):
|
|
374
377
|
nt (string): Parsed NT-hash.
|
375
378
|
"""
|
376
379
|
|
377
|
-
syskey = self.target.
|
380
|
+
syskey = self.target.lsa.syskey # aka. bootkey
|
378
381
|
samkey = self.calculate_samkey(syskey) # aka. hashed bootkey or hbootkey
|
379
382
|
|
380
383
|
almpassword = b"LMPASSWORD\0"
|
@@ -2,17 +2,16 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import hashlib
|
4
4
|
import hmac
|
5
|
-
from typing import Optional, Union
|
6
5
|
|
7
6
|
try:
|
8
|
-
from Crypto.Cipher import AES, ARC4
|
7
|
+
from Crypto.Cipher import AES, ARC4, DES3
|
9
8
|
|
10
9
|
HAS_CRYPTO = True
|
11
10
|
except ImportError:
|
12
11
|
HAS_CRYPTO = False
|
13
12
|
|
14
|
-
CIPHER_ALGORITHMS: dict[
|
15
|
-
HASH_ALGORITHMS: dict[
|
13
|
+
CIPHER_ALGORITHMS: dict[int | str, CipherAlgorithm] = {}
|
14
|
+
HASH_ALGORITHMS: dict[int | str, HashAlgorithm] = {}
|
16
15
|
|
17
16
|
|
18
17
|
class CipherAlgorithm:
|
@@ -35,17 +34,27 @@ class CipherAlgorithm:
|
|
35
34
|
return CIPHER_ALGORITHMS[name]()
|
36
35
|
|
37
36
|
def derive_key(self, key: bytes, hash_algorithm: HashAlgorithm) -> bytes:
|
38
|
-
"""Mimics the corresponding native Microsoft function.
|
37
|
+
"""Mimics the corresponding native Microsoft function.
|
38
|
+
|
39
|
+
Resources:
|
40
|
+
- https://github.com/tijldeneut/DPAPIck3/blob/main/dpapick3/crypto.py#L185
|
41
|
+
"""
|
42
|
+
|
39
43
|
if len(key) > hash_algorithm.block_length:
|
40
44
|
key = hashlib.new(hash_algorithm.name, key).digest()
|
41
45
|
|
42
|
-
if len(key) >=
|
46
|
+
if len(key) >= self.key_length:
|
43
47
|
return key
|
44
48
|
|
45
49
|
key = key.ljust(hash_algorithm.block_length, b"\x00")
|
46
|
-
pad1 = bytes(c ^ 0x36 for c in key)
|
47
|
-
pad2 = bytes(c ^ 0x5C for c in key)
|
48
|
-
|
50
|
+
pad1 = bytes(c ^ 0x36 for c in key)[: hash_algorithm.block_length]
|
51
|
+
pad2 = bytes(c ^ 0x5C for c in key)[: hash_algorithm.block_length]
|
52
|
+
key = hashlib.new(hash_algorithm.name, pad1).digest() + hashlib.new(hash_algorithm.name, pad2).digest()
|
53
|
+
key = self.fixup_key(key)
|
54
|
+
return key
|
55
|
+
|
56
|
+
def fixup_key(self, key: bytes) -> bytes:
|
57
|
+
return key
|
49
58
|
|
50
59
|
def decrypt_with_hmac(
|
51
60
|
self, data: bytes, key: bytes, iv: bytes, hash_algorithm: HashAlgorithm, rounds: int
|
@@ -55,7 +64,7 @@ class CipherAlgorithm:
|
|
55
64
|
|
56
65
|
return self.decrypt(data, key, iv)
|
57
66
|
|
58
|
-
def decrypt(self, data: bytes, key: bytes, iv:
|
67
|
+
def decrypt(self, data: bytes, key: bytes, iv: bytes | None = None) -> bytes:
|
59
68
|
raise NotImplementedError()
|
60
69
|
|
61
70
|
|
@@ -66,7 +75,7 @@ class _AES(CipherAlgorithm):
|
|
66
75
|
iv_length = 128 // 8
|
67
76
|
block_length = 128 // 8
|
68
77
|
|
69
|
-
def decrypt(self, data: bytes, key: bytes, iv:
|
78
|
+
def decrypt(self, data: bytes, key: bytes, iv: bytes | None = None) -> bytes:
|
70
79
|
if not HAS_CRYPTO:
|
71
80
|
raise RuntimeError("Missing pycryptodome dependency")
|
72
81
|
|
@@ -100,7 +109,7 @@ class _RC4(CipherAlgorithm):
|
|
100
109
|
iv_length = 128 // 8
|
101
110
|
block_length = 1 // 8
|
102
111
|
|
103
|
-
def decrypt(self, data: bytes, key: bytes, iv:
|
112
|
+
def decrypt(self, data: bytes, key: bytes, iv: bytes | None = None) -> bytes:
|
104
113
|
if not HAS_CRYPTO:
|
105
114
|
raise RuntimeError("Missing pycryptodome dependency")
|
106
115
|
|
@@ -108,6 +117,34 @@ class _RC4(CipherAlgorithm):
|
|
108
117
|
return cipher.decrypt(data)
|
109
118
|
|
110
119
|
|
120
|
+
class _DES3(CipherAlgorithm):
|
121
|
+
id = 0x6603
|
122
|
+
name = "DES3"
|
123
|
+
key_length = 192 // 8
|
124
|
+
iv_length = 64 // 8
|
125
|
+
block_length = 64 // 8
|
126
|
+
|
127
|
+
def fixup_key(self, key: bytes) -> bytes:
|
128
|
+
nkey = []
|
129
|
+
for byte in key:
|
130
|
+
parity_bit = 0
|
131
|
+
for i in range(8):
|
132
|
+
parity_bit ^= (byte >> i) & 1
|
133
|
+
|
134
|
+
nkey.append(byte if parity_bit == 0 else byte | 1)
|
135
|
+
return bytes(nkey[: self.key_length])
|
136
|
+
|
137
|
+
def decrypt(self, data: bytes, key: bytes, iv: bytes | None = None) -> bytes:
|
138
|
+
if not HAS_CRYPTO:
|
139
|
+
raise RuntimeError("Missing pycryptodome dependency")
|
140
|
+
|
141
|
+
if len(key) != 24:
|
142
|
+
raise ValueError(f"Invalid DES3 CBC key length {len(key)}")
|
143
|
+
|
144
|
+
cipher = DES3.new(key, DES3.MODE_CBC, iv=iv if iv else b"\x00" * 8)
|
145
|
+
return cipher.decrypt(data)
|
146
|
+
|
147
|
+
|
111
148
|
class HashAlgorithm:
|
112
149
|
id: int
|
113
150
|
name: str
|
@@ -123,7 +160,7 @@ class HashAlgorithm:
|
|
123
160
|
return HASH_ALGORITHMS[id]()
|
124
161
|
|
125
162
|
@classmethod
|
126
|
-
def from_name(cls, name: str) ->
|
163
|
+
def from_name(cls, name: str) -> HashAlgorithm | None:
|
127
164
|
return HASH_ALGORITHMS[name]()
|
128
165
|
|
129
166
|
|
@@ -148,7 +185,7 @@ class _HMAC(_SHA1):
|
|
148
185
|
|
149
186
|
|
150
187
|
class _SHA256(HashAlgorithm):
|
151
|
-
id =
|
188
|
+
id = 0x800C
|
152
189
|
name = "sha256"
|
153
190
|
digest_length = 256 // 8
|
154
191
|
block_length = 512 // 8
|
@@ -197,12 +234,12 @@ def dpapi_hmac(pwd_hash: bytes, hmac_salt: bytes, value: bytes, hash_algorithm:
|
|
197
234
|
|
198
235
|
def crypt_session_key_type1(
|
199
236
|
master_key: bytes,
|
200
|
-
nonce:
|
237
|
+
nonce: bytes | None,
|
201
238
|
hash_algorithm: HashAlgorithm,
|
202
|
-
entropy:
|
203
|
-
strong_password:
|
204
|
-
smart_card_secret:
|
205
|
-
verify_blob:
|
239
|
+
entropy: bytes | None = None,
|
240
|
+
strong_password: str | None = None,
|
241
|
+
smart_card_secret: bytes | None = None,
|
242
|
+
verify_blob: bytes | None = None,
|
206
243
|
) -> bytes:
|
207
244
|
"""Computes the decryption key for Type1 DPAPI blob, given the master key and optional information.
|
208
245
|
|
@@ -218,6 +255,7 @@ def crypt_session_key_type1(
|
|
218
255
|
strong_password: Optional password used for decryption or the blob itself.
|
219
256
|
smart_card_secret: Optional MS Next Gen Crypto secret (e.g. from PIN code).
|
220
257
|
verify_blob: Optional encrypted blob used for integrity check.
|
258
|
+
|
221
259
|
Returns:
|
222
260
|
decryption key
|
223
261
|
"""
|
@@ -258,10 +296,10 @@ def crypt_session_key_type2(
|
|
258
296
|
masterkey: bytes,
|
259
297
|
nonce: bytes,
|
260
298
|
hash_algorithm: HashAlgorithm,
|
261
|
-
entropy:
|
262
|
-
strong_password:
|
263
|
-
smart_card_secret:
|
264
|
-
verify_blob:
|
299
|
+
entropy: bytes | None = None,
|
300
|
+
strong_password: str | None = None,
|
301
|
+
smart_card_secret: bytes | None = None,
|
302
|
+
verify_blob: bytes | None = None,
|
265
303
|
) -> bytes:
|
266
304
|
"""Computes the decryption key for Type2 DPAPI blob, given the masterkey and optional information.
|
267
305
|
|
@@ -1,18 +1,11 @@
|
|
1
|
-
import
|
1
|
+
from __future__ import annotations
|
2
|
+
|
2
3
|
import re
|
3
4
|
from functools import cache, cached_property
|
4
5
|
from pathlib import Path
|
5
|
-
|
6
|
-
try:
|
7
|
-
from Crypto.Cipher import AES
|
8
|
-
|
9
|
-
HAS_CRYPTO = True
|
10
|
-
except ImportError:
|
11
|
-
HAS_CRYPTO = False
|
12
|
-
|
6
|
+
from typing import Iterator
|
13
7
|
|
14
8
|
from dissect.target.exceptions import UnsupportedPluginError
|
15
|
-
from dissect.target.helpers import keychain
|
16
9
|
from dissect.target.plugin import InternalPlugin
|
17
10
|
from dissect.target.plugins.os.windows.dpapi.blob import Blob as DPAPIBlob
|
18
11
|
from dissect.target.plugins.os.windows.dpapi.master_key import CredSystem, MasterKeyFile
|
@@ -20,175 +13,176 @@ from dissect.target.target import Target
|
|
20
13
|
|
21
14
|
|
22
15
|
class DPAPIPlugin(InternalPlugin):
|
23
|
-
|
16
|
+
"""Windows Data Protection API (DPAPI) plugin.
|
24
17
|
|
25
|
-
|
18
|
+
Resources:
|
19
|
+
- Reversing ``Crypt32.dll``
|
20
|
+
- https://learn.microsoft.com/en-us/windows/win32/api/dpapi/
|
21
|
+
- https://github.com/fortra/impacket/blob/master/examples/dpapi.py
|
22
|
+
- https://github.com/tijldeneut/DPAPIck3
|
23
|
+
- https://www.passcape.com/index.php?section=docsys&cmd=details&id=28
|
24
|
+
"""
|
26
25
|
|
27
|
-
|
28
|
-
SYSTEM_KEY = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\LSA"
|
26
|
+
__namespace__ = "dpapi"
|
29
27
|
|
30
|
-
|
28
|
+
RE_MASTER_KEY = re.compile("^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$")
|
29
|
+
SYSTEM_SID = "S-1-5-18"
|
31
30
|
|
32
31
|
def __init__(self, target: Target):
|
33
32
|
super().__init__(target)
|
34
33
|
self.keychain = cache(self.keychain)
|
35
34
|
|
36
35
|
def check_compatible(self) -> None:
|
37
|
-
if not
|
38
|
-
raise UnsupportedPluginError("
|
39
|
-
|
40
|
-
if not list(self.target.registry.keys(self.SYSTEM_KEY)):
|
41
|
-
raise UnsupportedPluginError(f"Registry key not found: {self.SYSTEM_KEY}")
|
36
|
+
if not self.target.has_function("lsa"):
|
37
|
+
raise UnsupportedPluginError("Windows registry and LSA plugins are required for DPAPI decryption")
|
42
38
|
|
43
39
|
def keychain(self) -> set:
|
44
|
-
|
45
|
-
|
46
|
-
for key in keychain.get_keys_for_provider("user") + keychain.get_keys_without_provider():
|
47
|
-
if key.key_type == keychain.KeyType.PASSPHRASE:
|
48
|
-
passwords.add(key.value)
|
49
|
-
|
50
|
-
# It is possible to encrypt using an empty passphrase.
|
51
|
-
passwords.add("")
|
52
|
-
return passwords
|
53
|
-
|
54
|
-
@cached_property
|
55
|
-
def syskey(self) -> bytes:
|
56
|
-
lsa = self.target.registry.key(self.SYSTEM_KEY)
|
57
|
-
syskey_keys = ["JD", "Skew1", "GBG", "Data"]
|
58
|
-
# This magic value rotates the order of the data
|
59
|
-
alterator = [0x8, 0x5, 0x4, 0x2, 0xB, 0x9, 0xD, 0x3, 0x0, 0x6, 0x1, 0xC, 0xE, 0xA, 0xF, 0x7]
|
60
|
-
|
61
|
-
r = bytes.fromhex("".join([lsa.subkey(key).class_name for key in syskey_keys]))
|
62
|
-
return bytes(r[i] for i in alterator)
|
63
|
-
|
64
|
-
@cached_property
|
65
|
-
def lsakey(self) -> bytes:
|
66
|
-
policy_key = "PolEKList"
|
67
|
-
|
68
|
-
encrypted_key = self.target.registry.key(self.SECURITY_POLICY_KEY).subkey(policy_key).value("(Default)").value
|
69
|
-
|
70
|
-
lsa_key = _decrypt_aes(encrypted_key, self.syskey)
|
71
|
-
|
72
|
-
return lsa_key[68:100]
|
73
|
-
|
74
|
-
@cached_property
|
75
|
-
def secrets(self) -> dict[str, bytes]:
|
76
|
-
result = {}
|
77
|
-
|
78
|
-
reg_secrets = self.target.registry.key(self.SECURITY_POLICY_KEY).subkey("Secrets")
|
79
|
-
for subkey in reg_secrets.subkeys():
|
80
|
-
enc_data = subkey.subkey("CurrVal").value("(Default)").value
|
81
|
-
secret = _decrypt_aes(enc_data, self.lsakey)
|
82
|
-
result[subkey.name] = secret
|
83
|
-
|
84
|
-
return result
|
40
|
+
return set(self.target._dpapi_keyprovider.keys())
|
85
41
|
|
86
42
|
@cached_property
|
87
43
|
def master_keys(self) -> dict[str, dict[str, MasterKeyFile]]:
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
44
|
+
"""Returns dict of found DPAPI master keys on the Windows target for SYSTEM and regular users."""
|
45
|
+
master_keys = {}
|
46
|
+
|
47
|
+
# Search for SYSTEM master keys
|
48
|
+
master_keys[self.SYSTEM_SID] = {}
|
92
49
|
|
93
|
-
system_master_key_path = self.target.fs.path("sysvol/Windows/System32/Microsoft/Protect/
|
50
|
+
system_master_key_path = self.target.fs.path(f"sysvol/Windows/System32/Microsoft/Protect/{self.SYSTEM_SID}")
|
94
51
|
system_user_master_key_path = system_master_key_path.joinpath("User")
|
95
52
|
|
96
53
|
for dir in [system_master_key_path, system_user_master_key_path]:
|
97
|
-
user_mks = self._load_master_keys_from_path(self.
|
98
|
-
|
54
|
+
user_mks = self._load_master_keys_from_path(self.SYSTEM_SID, dir)
|
55
|
+
master_keys[self.SYSTEM_SID].update(user_mks)
|
56
|
+
|
57
|
+
# Search for user master keys, generally located at $HOME/AppData/Roaming/Microsoft/Protect/{user_sid}/{mk_guid}
|
58
|
+
PROTECT_DIRS = [
|
59
|
+
# Windows Vista and newer
|
60
|
+
"AppData/Roaming/Microsoft/Protect",
|
61
|
+
# Windows XP
|
62
|
+
"Application Data/Microsoft/Protect",
|
63
|
+
]
|
99
64
|
|
100
65
|
for user in self.target.user_details.all_with_home():
|
101
|
-
|
102
|
-
|
103
|
-
if user_mks:
|
104
|
-
result[user.user.name] = user_mks
|
66
|
+
sid = user.user.sid
|
67
|
+
master_keys.setdefault(sid, {})
|
105
68
|
|
106
|
-
|
69
|
+
for protect_dir in PROTECT_DIRS:
|
70
|
+
path = user.home_path.joinpath(protect_dir).joinpath(sid)
|
71
|
+
if user_mks := self._load_master_keys_from_path(sid, path):
|
72
|
+
master_keys[sid] |= user_mks
|
107
73
|
|
108
|
-
|
109
|
-
|
110
|
-
|
74
|
+
return master_keys
|
75
|
+
|
76
|
+
def _load_master_keys_from_path(self, sid: str, path: Path) -> Iterator[tuple[str, MasterKeyFile]]:
|
77
|
+
"""Iterate over the provided ``path`` and search for master key files for the given user SID."""
|
111
78
|
|
112
|
-
def _load_master_keys_from_path(self, username: str, path: Path) -> dict[str, MasterKeyFile]:
|
113
79
|
if not path.exists():
|
114
|
-
|
80
|
+
self.target.log.info("Unable to load master keys from path as it does not exist: %s", path)
|
81
|
+
return
|
115
82
|
|
116
|
-
result = {}
|
117
83
|
for file in path.iterdir():
|
118
|
-
if self.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
84
|
+
if not self.RE_MASTER_KEY.findall(file.name):
|
85
|
+
continue
|
86
|
+
|
87
|
+
with file.open() as fh:
|
88
|
+
mkf = MasterKeyFile(fh)
|
89
|
+
|
90
|
+
# Decrypt SYSTEM master key using the DPAPI_SYSTEM LSA secret.
|
91
|
+
if sid == self.SYSTEM_SID:
|
92
|
+
if "DPAPI_SYSTEM" not in self.target.lsa._secrets:
|
93
|
+
self.target.log.warning("Unable to decrypt SYSTEM master key: LSA secret missing")
|
94
|
+
continue
|
95
|
+
|
96
|
+
# Windows XP
|
97
|
+
if float(self.target.ntversion) < 6.0:
|
98
|
+
secret_offset = 8
|
99
|
+
|
100
|
+
# Windows Vista and newer
|
101
|
+
else:
|
102
|
+
secret_offset = 16
|
103
|
+
|
104
|
+
dpapi_system = CredSystem(self.target.lsa._secrets["DPAPI_SYSTEM"][secret_offset:])
|
105
|
+
mkf.decrypt_with_key(dpapi_system.machine_key)
|
106
|
+
mkf.decrypt_with_key(dpapi_system.user_key)
|
107
|
+
|
108
|
+
# Decrypting the System master key should always succeed
|
109
|
+
if not mkf.decrypted:
|
110
|
+
self.target.log.error("Failed to decrypt SYSTEM master key!")
|
111
|
+
continue
|
112
|
+
|
113
|
+
yield file.name, mkf
|
114
|
+
|
115
|
+
# Decrypt user master key
|
116
|
+
else:
|
117
|
+
# Iterate over every master key password we have from the keychain
|
118
|
+
for provider, mk_pass in self.keychain():
|
119
|
+
try:
|
120
|
+
if mkf.decrypt_with_password(sid, mk_pass):
|
121
|
+
self.target.log.info(
|
122
|
+
"Decrypted user master key with password '%s' from provider %s", mk_pass, provider
|
123
|
+
)
|
124
|
+
break
|
125
|
+
except ValueError:
|
126
|
+
pass
|
127
|
+
|
128
|
+
try:
|
129
|
+
if mkf.decrypt_with_hash(sid, bytes.fromhex(mk_pass)):
|
130
|
+
self.target.log.info(
|
131
|
+
"Decrypted SID %s master key with hash '%s' from provider %s", sid, mk_pass, provider
|
132
|
+
)
|
133
|
+
break
|
134
|
+
except ValueError:
|
135
|
+
pass
|
124
136
|
|
125
|
-
|
126
|
-
|
137
|
+
if not mkf.decrypted:
|
138
|
+
self.target.log.warning("Could not decrypt master key '%s' for SID '%s'", file.name, sid)
|
127
139
|
|
128
|
-
|
129
|
-
if not mkf.decrypted:
|
130
|
-
raise Exception("Failed to decrypt System master key")
|
140
|
+
yield file.name, mkf
|
131
141
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
142
|
+
@cached_property
|
143
|
+
def _users(self) -> dict[str, str]:
|
144
|
+
"""Cached map of username to SID."""
|
145
|
+
return {user.name: user.sid for user in self.target.users()}
|
136
146
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
except ValueError:
|
141
|
-
pass
|
147
|
+
def decrypt_system_blob(self, data: bytes) -> bytes:
|
148
|
+
"""Decrypt the given bytes using the SYSTEM master key."""
|
149
|
+
return self.decrypt_user_blob(data, sid=self.SYSTEM_SID)
|
142
150
|
|
143
|
-
|
144
|
-
|
151
|
+
def decrypt_user_blob(self, data: bytes, username: str | None = None, sid: str | None = None) -> bytes:
|
152
|
+
"""Decrypt the given bytes using the master key of the given SID or username."""
|
145
153
|
|
146
|
-
|
154
|
+
if not sid and not username:
|
155
|
+
raise ValueError("Either sid or username argument is required")
|
147
156
|
|
148
|
-
|
157
|
+
if not sid and username:
|
158
|
+
sid = self._users.get(username)
|
149
159
|
|
150
|
-
|
151
|
-
|
152
|
-
return self.decrypt_user_blob(data, self.SYSTEM_USERNAME)
|
160
|
+
if not sid:
|
161
|
+
raise ValueError("No SID provided or no SID found")
|
153
162
|
|
154
|
-
|
155
|
-
|
156
|
-
|
163
|
+
try:
|
164
|
+
blob = DPAPIBlob(data)
|
165
|
+
except EOFError as e:
|
166
|
+
raise ValueError(f"Failed to parse DPAPI blob: {e}")
|
157
167
|
|
158
|
-
if not (mk := self.master_keys.get(
|
159
|
-
raise ValueError(f"Blob
|
168
|
+
if not (mk := self.master_keys.get(sid, {}).get(blob.guid)):
|
169
|
+
raise ValueError(f"Blob is encrypted using master key {blob.guid} that we do not have for SID {sid}")
|
160
170
|
|
161
171
|
if not blob.decrypt(mk.key):
|
162
|
-
raise ValueError(f"Failed to decrypt blob for
|
172
|
+
raise ValueError(f"Failed to decrypt blob for SID {sid}")
|
163
173
|
|
164
174
|
return blob.clear_text
|
165
175
|
|
166
176
|
def decrypt_blob(self, data: bytes) -> bytes:
|
167
177
|
"""Attempt to decrypt the given bytes using any of the available master keys."""
|
168
|
-
|
178
|
+
try:
|
179
|
+
blob = DPAPIBlob(data)
|
180
|
+
except EOFError as e:
|
181
|
+
raise ValueError(f"Failed to parse DPAPI blob: {e}")
|
169
182
|
|
170
183
|
for user in self.master_keys:
|
171
184
|
for mk in self.master_keys[user].values():
|
172
185
|
if blob.decrypt(mk.key):
|
173
186
|
return blob.clear_text
|
174
187
|
|
175
|
-
raise ValueError("Failed to decrypt blob")
|
176
|
-
|
177
|
-
|
178
|
-
def _decrypt_aes(data: bytes, key: bytes) -> bytes:
|
179
|
-
ctx = hashlib.sha256()
|
180
|
-
ctx.update(key)
|
181
|
-
|
182
|
-
tmp = data[28:60]
|
183
|
-
for _ in range(1, 1000 + 1):
|
184
|
-
ctx.update(tmp)
|
185
|
-
|
186
|
-
aeskey = ctx.digest()
|
187
|
-
iv = b"\x00" * 16
|
188
|
-
|
189
|
-
result = []
|
190
|
-
for i in range(60, len(data), 16):
|
191
|
-
cipher = AES.new(aeskey, AES.MODE_CBC, iv)
|
192
|
-
result.append(cipher.decrypt(data[i : i + 16].ljust(16, b"\x00")))
|
193
|
-
|
194
|
-
return b"".join(result)
|
188
|
+
raise ValueError("Failed to decrypt blob using any available master key")
|
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
4
|
+
from dissect.target.plugin import export
|
5
|
+
from dissect.target.plugins.os.windows.dpapi.keyprovider.keyprovider import (
|
6
|
+
KeyProviderPlugin,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
class CredHistKeyProviderPlugin(KeyProviderPlugin):
|
11
|
+
__namespace__ = "_dpapi_keyprovider_credhist"
|
12
|
+
|
13
|
+
def check_compatible(self) -> None:
|
14
|
+
if not self.target.has_function("credhist"):
|
15
|
+
raise UnsupportedPluginError("CREDHIST plugin not available on target")
|
16
|
+
|
17
|
+
@export(output="yield")
|
18
|
+
def keys(self) -> Iterator[tuple[str, str]]:
|
19
|
+
for credhist in self.target.credhist():
|
20
|
+
if value := getattr(credhist, "sha1"):
|
21
|
+
yield self.__namespace__, value
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.plugin import export
|
4
|
+
from dissect.target.plugins.os.windows.dpapi.keyprovider.keyprovider import (
|
5
|
+
KeyProviderPlugin,
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
class EmptyKeyProviderPlugin(KeyProviderPlugin):
|
10
|
+
__namespace__ = "_dpapi_keyprovider_empty"
|
11
|
+
|
12
|
+
def check_compatible(self) -> None:
|
13
|
+
return
|
14
|
+
|
15
|
+
@export(output="yield")
|
16
|
+
def keys(self) -> Iterator[tuple[str, str]]:
|
17
|
+
yield self.__namespace__, ""
|
@@ -0,0 +1,20 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.helpers import keychain
|
4
|
+
from dissect.target.plugin import export
|
5
|
+
from dissect.target.plugins.os.windows.dpapi.keyprovider.keyprovider import (
|
6
|
+
KeyProviderPlugin,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
class KeychainKeyProviderPlugin(KeyProviderPlugin):
|
11
|
+
__namespace__ = "_dpapi_keyprovider_keychain"
|
12
|
+
|
13
|
+
def check_compatible(self) -> None:
|
14
|
+
return
|
15
|
+
|
16
|
+
@export(output="yield")
|
17
|
+
def keys(self) -> Iterator[tuple[str, str]]:
|
18
|
+
for key in keychain.get_keys_for_provider("user") + keychain.get_keys_without_provider():
|
19
|
+
if key.key_type == keychain.KeyType.PASSPHRASE:
|
20
|
+
yield self.__namespace__, key.value
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.cstruct import cstruct
|
4
|
+
|
5
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
6
|
+
from dissect.target.plugin import export
|
7
|
+
from dissect.target.plugins.os.windows.dpapi.keyprovider.keyprovider import (
|
8
|
+
KeyProviderPlugin,
|
9
|
+
)
|
10
|
+
|
11
|
+
defaultpassword_def = """
|
12
|
+
struct DefaultPassword {
|
13
|
+
DWORD length;
|
14
|
+
char flags[4*3];
|
15
|
+
WCHAR data[length/2];
|
16
|
+
char checksum_or_guid[0x10];
|
17
|
+
};
|
18
|
+
"""
|
19
|
+
|
20
|
+
c_defaultpassword = cstruct().load(defaultpassword_def)
|
21
|
+
|
22
|
+
|
23
|
+
class LSADefaultPasswordKeyProviderPlugin(KeyProviderPlugin):
|
24
|
+
__namespace__ = "_dpapi_keyprovider_lsa_defaultpassword"
|
25
|
+
|
26
|
+
def check_compatible(self) -> None:
|
27
|
+
if not self.target.has_function("lsa"):
|
28
|
+
raise UnsupportedPluginError("LSA plugin not available on target")
|
29
|
+
|
30
|
+
@export(output="yield")
|
31
|
+
def keys(self) -> Iterator[tuple[str, str]]:
|
32
|
+
if default_pass := self.target.lsa._secrets.get("DefaultPassword"):
|
33
|
+
try:
|
34
|
+
value = c_defaultpassword.DefaultPassword(default_pass).data
|
35
|
+
except Exception:
|
36
|
+
self.target.log.warning("Failed to parse LSA DefaultPassword value")
|
37
|
+
return
|
38
|
+
yield self.__namespace__, value
|
@@ -95,6 +95,9 @@ class MasterKey:
|
|
95
95
|
|
96
96
|
def decrypt_with_password(self, user_sid: str, pwd: str) -> bool:
|
97
97
|
"""Decrypts the master key with the given user's password and SID."""
|
98
|
+
if self.decrypted:
|
99
|
+
return True
|
100
|
+
|
98
101
|
pwd = pwd.encode("utf-16-le")
|
99
102
|
|
100
103
|
for algo in ["sha1", "md4"]:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import binascii
|
2
2
|
from datetime import datetime
|
3
|
-
from enum import IntEnum
|
3
|
+
from enum import IntEnum
|
4
4
|
from io import BytesIO
|
5
5
|
from typing import Callable, Generator, Optional, Tuple, Union
|
6
6
|
|
@@ -116,7 +116,7 @@ class SHIMCACHE_WIN_TYPE(IntEnum):
|
|
116
116
|
VERSION_NT61 = 0x0601
|
117
117
|
VERSION_NT52 = 0x0502
|
118
118
|
|
119
|
-
VERSION_WIN81_NO_HEADER = auto()
|
119
|
+
VERSION_WIN81_NO_HEADER = 0x1002 # auto()
|
120
120
|
|
121
121
|
|
122
122
|
def win_10_path(ed: Structure) -> str:
|
@@ -189,7 +189,7 @@ class XmlTask:
|
|
189
189
|
bytes: The raw XML data as string of the element if found, otherwise None.
|
190
190
|
"""
|
191
191
|
data = self.task_element.find(xml_path) if xml_path else self.task_element
|
192
|
-
if data:
|
192
|
+
if data is not None:
|
193
193
|
return ElementTree.tostring(data, encoding="utf-8").strip()
|
194
194
|
|
195
195
|
def get_triggers(self) -> Iterator[GroupedRecord]:
|
dissect/target/tools/query.py
CHANGED
@@ -5,7 +5,7 @@ import argparse
|
|
5
5
|
import logging
|
6
6
|
import pathlib
|
7
7
|
import sys
|
8
|
-
from datetime import datetime
|
8
|
+
from datetime import datetime, timezone
|
9
9
|
from typing import Callable
|
10
10
|
|
11
11
|
from flow.record import RecordPrinter, RecordStreamWriter, RecordWriter
|
@@ -390,7 +390,7 @@ def main():
|
|
390
390
|
log.debug("", exc_info=e)
|
391
391
|
parser.exit(1)
|
392
392
|
|
393
|
-
timestamp = datetime.
|
393
|
+
timestamp = datetime.now(tz=timezone.utc)
|
394
394
|
|
395
395
|
execution_report.set_plugin_stats(PLUGINS)
|
396
396
|
log.debug("%s", execution_report.get_formatted_report())
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.19.
|
3
|
+
Version: 3.19.dev57
|
4
4
|
Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
6
6
|
License: Affero General Public License v3
|
@@ -3,7 +3,7 @@ dissect/target/container.py,sha256=0YcwcGmfJjhPXUB6DEcjWEoSuAtTDxMDpoTviMrLsxM,9
|
|
3
3
|
dissect/target/exceptions.py,sha256=ULi7NXlqju_d8KENEL3aimmfKTFfbNssfeWhAnOB654,2972
|
4
4
|
dissect/target/filesystem.py,sha256=__p2p72B6mKgIiAOj85EC3ESdZ8gjgkm2pt1KKym3h0,60743
|
5
5
|
dissect/target/loader.py,sha256=I8WNzDA0SMy42F7zfyBcSKj_VKNv64213WUvtGZ77qE,7374
|
6
|
-
dissect/target/plugin.py,sha256=
|
6
|
+
dissect/target/plugin.py,sha256=xbvmjZMFNwJXZYMBE4KI93-nWL9Zhum8x39ydcO6s2o,50578
|
7
7
|
dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
|
8
8
|
dissect/target/target.py,sha256=m4bAKgPLUJERKgxRZFevKvEBNaz77wIC5mVrDe6eI8o,32438
|
9
9
|
dissect/target/volume.py,sha256=aQZAJiny8jjwkc9UtwIRwy7nINXjCxwpO-_UDfh6-BA,15801
|
@@ -264,12 +264,11 @@ dissect/target/plugins/os/unix/log/utmp.py,sha256=1nPHIaBUHt_9z6PDrvyqg4huKLihUa
|
|
264
264
|
dissect/target/plugins/os/windows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
265
265
|
dissect/target/plugins/os/windows/_os.py,sha256=uBa0dVkFxDsxHAU3T23UEIOCgAx5R6cIpCgbGq3fflY,13131
|
266
266
|
dissect/target/plugins/os/windows/activitiescache.py,sha256=Q2aILnhJ2rp2AwEbWwyBuSLjMbGqaYJTsavSbfkcFKE,6741
|
267
|
-
dissect/target/plugins/os/windows/adpolicy.py,sha256=
|
267
|
+
dissect/target/plugins/os/windows/adpolicy.py,sha256=qjv0s-gAIGKCznWdVOARJbLXnCKYgvzoFNWoXnq3m1M,7102
|
268
268
|
dissect/target/plugins/os/windows/amcache.py,sha256=ZZNOs3bILTf0AGkDkhoatndl0j39DXkstN7oOyxJECU,27188
|
269
269
|
dissect/target/plugins/os/windows/catroot.py,sha256=QVwMF5nuMzCkWnoOMs5BkwYoKN61HKmlxo8mKMoD3w8,10937
|
270
270
|
dissect/target/plugins/os/windows/cim.py,sha256=jsrpu6TZpBUh7VWI9AV2Ib5bebTwsvqOwRfa5gjJd7c,3056
|
271
271
|
dissect/target/plugins/os/windows/clfs.py,sha256=begVsZ-CY97Ksh6S1g03LjyBgu8ERY2hfNDWYPj0GXI,4872
|
272
|
-
dissect/target/plugins/os/windows/credhist.py,sha256=YSjuyd53Augdy_lKKzZHtx5Ozt0HzF6LDYIOb-8P1Pw,7058
|
273
272
|
dissect/target/plugins/os/windows/datetime.py,sha256=YKHUZU6lkKJocq15y0yCwvIIOb1Ej-kfvEBmHbrdIGw,9467
|
274
273
|
dissect/target/plugins/os/windows/defender.py,sha256=zh3brEvJmknD5ef0PGuLZ1G95Fgdh-dlgi-ZEbADKXo,32716
|
275
274
|
dissect/target/plugins/os/windows/env.py,sha256=-u9F9xWy6PUbQmu5Tv_MDoVmy6YB-7CbHokIK_T3S44,13891
|
@@ -281,7 +280,6 @@ dissect/target/plugins/os/windows/notifications.py,sha256=T1CIvQgpW__qDR0Rq5zpeW
|
|
281
280
|
dissect/target/plugins/os/windows/prefetch.py,sha256=v4OgSKMwcihz0SOuA0o0Ec8wsAKuiuEmJolqZmHFgJA,10491
|
282
281
|
dissect/target/plugins/os/windows/recyclebin.py,sha256=zx58hDCvcrD_eJl9nJmr_i80krSN03ya8nQzWFr2Tw0,4917
|
283
282
|
dissect/target/plugins/os/windows/registry.py,sha256=EfqUkgbzaqTuq1kIPYNG1TfvJxhJE5X-TEjV3K_xsPU,12814
|
284
|
-
dissect/target/plugins/os/windows/sam.py,sha256=NwKzfP_ae8SXgCoj_apa-29ZeFxeQsGidJ6llF1khP8,15468
|
285
283
|
dissect/target/plugins/os/windows/services.py,sha256=MoVPJ1GKpPaJrGd2DYtuHEmKqC2uOKRc5SZKB12goSs,6068
|
286
284
|
dissect/target/plugins/os/windows/sru.py,sha256=sOM7CyMkW8XIXzI75GL69WoqUrSK2X99TFIfdQR2D64,17767
|
287
285
|
dissect/target/plugins/os/windows/startupinfo.py,sha256=kl8Y7M4nVfmJ71I33VCegtbHj-ZOeEsYAdlNbgwtUOA,3406
|
@@ -291,14 +289,24 @@ dissect/target/plugins/os/windows/thumbcache.py,sha256=23YjOjTNoE7BYITmg8s9Zs8Wi
|
|
291
289
|
dissect/target/plugins/os/windows/ual.py,sha256=TYF-R46klEa_HHb86UJd6mPrXwHlAMOUTzC0pZ8uiq0,9787
|
292
290
|
dissect/target/plugins/os/windows/wer.py,sha256=ogecvKYxAvDXLptQj4cn0JLn1FxaXjeSuJWs4JgkoZs,8656
|
293
291
|
dissect/target/plugins/os/windows/wua_history.py,sha256=GrSmnEbPYUrQzixG5JWElMRBoCrUPnZKpsVIwpfPQfg,54785
|
292
|
+
dissect/target/plugins/os/windows/credential/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
293
|
+
dissect/target/plugins/os/windows/credential/credhist.py,sha256=YSjuyd53Augdy_lKKzZHtx5Ozt0HzF6LDYIOb-8P1Pw,7058
|
294
|
+
dissect/target/plugins/os/windows/credential/lsa.py,sha256=bo5zS4gDvMDU0c4456ZJ4FrDkcTnWdpmLaQZnZ33_fI,5638
|
295
|
+
dissect/target/plugins/os/windows/credential/sam.py,sha256=iRqMNPLqrObJG2h6brzvAyeVBnIIgHVX_p_Hw_Jfa3A,15599
|
294
296
|
dissect/target/plugins/os/windows/defender_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
295
297
|
dissect/target/plugins/os/windows/defender_helpers/defender_patterns.py,sha256=xsZH0WqMX_mC1q55jgp4RDHBRh2UQBXZVhJD0DRiwZU,9329
|
296
298
|
dissect/target/plugins/os/windows/defender_helpers/defender_records.py,sha256=_azaY5Y1cH-WPmkA5k94PMktZGYXmWJG8addFQxQ554,5177
|
297
299
|
dissect/target/plugins/os/windows/dpapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
298
|
-
dissect/target/plugins/os/windows/dpapi/blob.py,sha256=
|
299
|
-
dissect/target/plugins/os/windows/dpapi/crypto.py,sha256=
|
300
|
-
dissect/target/plugins/os/windows/dpapi/dpapi.py,sha256=
|
301
|
-
dissect/target/plugins/os/windows/dpapi/master_key.py,sha256=
|
300
|
+
dissect/target/plugins/os/windows/dpapi/blob.py,sha256=OcCdsQBJ0s6yJCjpv_scd8XW8dtzBNfPBpRLvEcrEP0,5148
|
301
|
+
dissect/target/plugins/os/windows/dpapi/crypto.py,sha256=0dUMQkhaI_bg7ccMxde18q6YvcY2Mjru2AmHLrgfVH4,10378
|
302
|
+
dissect/target/plugins/os/windows/dpapi/dpapi.py,sha256=Rz9VHLnMDVJqbrarfD9O_u9HOQWLeGpl0nXjsM_qoo0,7486
|
303
|
+
dissect/target/plugins/os/windows/dpapi/master_key.py,sha256=sQYMWJ66DvqTLWQX5U0y0dTOg6LhOBokd3EjRM-ykPM,6163
|
304
|
+
dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
305
|
+
dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py,sha256=25W3Zl9fNPYmbracPaZ5mHlkGlJUNaAB6yXrKyqTHP4,741
|
306
|
+
dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py,sha256=K39KXg16lPuyO_aZVqGG1WcMf7SrQLTwvruLQQKX6LU,442
|
307
|
+
dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py,sha256=3c4R3eg0ZQpRzpt62848gSwZvv71A0CmZiQQBV6X10g,665
|
308
|
+
dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py,sha256=L_q4jYNMuUkIb3OoPSWO1WIFjZyre4cAE9J3nhzRPKg,212
|
309
|
+
dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py,sha256=gM-UXuA8qBzgzpr3YsEib1YspWQ7At8pcGBjOGomI4I,1218
|
302
310
|
dissect/target/plugins/os/windows/exchange/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
303
311
|
dissect/target/plugins/os/windows/exchange/exchange.py,sha256=ofoapuDQXefIX4sTzwNboyk5RztN2JEyw1OWl5cx-wo,1564
|
304
312
|
dissect/target/plugins/os/windows/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -323,14 +331,14 @@ dissect/target/plugins/os/windows/regf/recentfilecache.py,sha256=goS6ajLIh6ZU-Gq
|
|
323
331
|
dissect/target/plugins/os/windows/regf/regf.py,sha256=D1GrljF-sV8cWIjWJ3zH7k52i1OWD8poEC_PIeZMEis,3419
|
324
332
|
dissect/target/plugins/os/windows/regf/runkeys.py,sha256=-2HcdnVytzCt1xwgAI8rHDnwk8kwLPWURumvhrGnIHU,4278
|
325
333
|
dissect/target/plugins/os/windows/regf/shellbags.py,sha256=hXAqThFkHmGPmhNRSXwMNzw25kAyIC6OOZivgpPEwTQ,25679
|
326
|
-
dissect/target/plugins/os/windows/regf/shimcache.py,sha256=
|
334
|
+
dissect/target/plugins/os/windows/regf/shimcache.py,sha256=TY7GEFnxb8h99q12CzM0SwVlUymi4hFPae3uuM0M6kY,9998
|
327
335
|
dissect/target/plugins/os/windows/regf/trusteddocs.py,sha256=3yvpBDM-Asg0rvGN2TwALGRm9DYogG6TxRau9D6FBbw,3700
|
328
336
|
dissect/target/plugins/os/windows/regf/usb.py,sha256=nSAHB4Cdd0wF2C1EK_XYOfWCyqOgTZCLfDhuSmr7rdM,9709
|
329
337
|
dissect/target/plugins/os/windows/regf/userassist.py,sha256=bSioEQdqUxdGwkdgMUfDIY2_pzrl9PdxPjmzmMaIwHs,5490
|
330
338
|
dissect/target/plugins/os/windows/task_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
331
339
|
dissect/target/plugins/os/windows/task_helpers/tasks_job.py,sha256=7w3UGOiTAUQkP3xQ3sj4X3MTgHUJmmfdgiEadWmYquI,21197
|
332
340
|
dissect/target/plugins/os/windows/task_helpers/tasks_records.py,sha256=vpCyKqLQSzI5ymD1h5P6RncLEE47YtmjDFwKA16dVZ4,4046
|
333
|
-
dissect/target/plugins/os/windows/task_helpers/tasks_xml.py,sha256=
|
341
|
+
dissect/target/plugins/os/windows/task_helpers/tasks_xml.py,sha256=fKwh9jtOP_gzWC_QTyuNScAvjJzWJphSz436aPknXzQ,15280
|
334
342
|
dissect/target/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
335
343
|
dissect/target/tools/build_pluginlist.py,sha256=5fomcuMwsVzcnYx5Htf5f9lSwsLeUUvomLUXNA4t7m4,849
|
336
344
|
dissect/target/tools/dd.py,sha256=rTM-lgXxrYBpVAtJqFqAatDz45bLoD8-mFt_59Q3Lio,1928
|
@@ -339,7 +347,7 @@ dissect/target/tools/fsutils.py,sha256=dyAdp2fzydcozaIZ1mFTpdUeVcibYNJCHN8AFw5Fo
|
|
339
347
|
dissect/target/tools/info.py,sha256=8nnbqFUYeo4NLPE7ORcTBcDL-TioGB2Nqc1TKcu5qdY,5715
|
340
348
|
dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
|
341
349
|
dissect/target/tools/mount.py,sha256=L_0tSmiBdW4aSaF0vXjB0bAkTC0kmT2N1hrbW6s5Jow,3254
|
342
|
-
dissect/target/tools/query.py,sha256=
|
350
|
+
dissect/target/tools/query.py,sha256=XgMDSfaN4SivJmIIEntYJOXcOEwWrUp_tYt5AjEtB4k,15602
|
343
351
|
dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
|
344
352
|
dissect/target/tools/shell.py,sha256=dmshIriwdd_UwrdUcTfWkcYD8Z0mjzbDqwyZG-snDdM,50482
|
345
353
|
dissect/target/tools/utils.py,sha256=nnhjNW8v99eVZQ-CgxTbsi8Wa6Z2XKDFr1aWakgq9jc,12191
|
@@ -356,10 +364,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
356
364
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
357
365
|
dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
|
358
366
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
359
|
-
dissect.target-3.19.
|
360
|
-
dissect.target-3.19.
|
361
|
-
dissect.target-3.19.
|
362
|
-
dissect.target-3.19.
|
363
|
-
dissect.target-3.19.
|
364
|
-
dissect.target-3.19.
|
365
|
-
dissect.target-3.19.
|
367
|
+
dissect.target-3.19.dev57.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
368
|
+
dissect.target-3.19.dev57.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
369
|
+
dissect.target-3.19.dev57.dist-info/METADATA,sha256=T_FmZVCuN_qBLZIKsTREmYorw0NdcdiG0JtrrnYii5k,12897
|
370
|
+
dissect.target-3.19.dev57.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
371
|
+
dissect.target-3.19.dev57.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
|
372
|
+
dissect.target-3.19.dev57.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
373
|
+
dissect.target-3.19.dev57.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|