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.
Files changed (26) hide show
  1. dissect/target/plugin.py +10 -3
  2. dissect/target/plugins/os/windows/adpolicy.py +4 -1
  3. dissect/target/plugins/os/windows/credential/__init__.py +0 -0
  4. dissect/target/plugins/os/windows/credential/lsa.py +174 -0
  5. dissect/target/plugins/os/windows/{sam.py → credential/sam.py} +5 -2
  6. dissect/target/plugins/os/windows/dpapi/blob.py +3 -0
  7. dissect/target/plugins/os/windows/dpapi/crypto.py +61 -23
  8. dissect/target/plugins/os/windows/dpapi/dpapi.py +127 -133
  9. dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py +0 -0
  10. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +21 -0
  11. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +17 -0
  12. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +20 -0
  13. dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py +8 -0
  14. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +38 -0
  15. dissect/target/plugins/os/windows/dpapi/master_key.py +3 -0
  16. dissect/target/plugins/os/windows/regf/shimcache.py +2 -2
  17. dissect/target/plugins/os/windows/task_helpers/tasks_xml.py +1 -1
  18. dissect/target/tools/query.py +2 -2
  19. {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/METADATA +1 -1
  20. {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/RECORD +26 -18
  21. /dissect/target/plugins/os/windows/{credhist.py → credential/credhist.py} +0 -0
  22. {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/COPYRIGHT +0 -0
  23. {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/LICENSE +0 -0
  24. {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/WHEEL +0 -0
  25. {dissect.target-3.19.dev55.dist-info → dissect.target-3.19.dev57.dist-info}/entry_points.txt +0 -0
  26. {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) != "record":
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
- properties = task.find("Properties") or task
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,
@@ -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/registry/sam",
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.dpapi.syskey # aka. bootkey
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"
@@ -90,6 +90,9 @@ class Blob:
90
90
  if self.decrypted:
91
91
  return True
92
92
 
93
+ if not master_key:
94
+ raise ValueError("No master key provided to decrypt blob with")
95
+
93
96
  for algo in [crypt_session_key_type1, crypt_session_key_type2]:
94
97
  session_key = algo(
95
98
  master_key,
@@ -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[Union[int, str], CipherAlgorithm] = {}
15
- HASH_ALGORITHMS: dict[Union[int, str], HashAlgorithm] = {}
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) >= hash_algorithm.digest_length:
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
- return hashlib.new(hash_algorithm.name, pad1).digest() + hashlib.new(hash_algorithm.name, pad2).digest()
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: Optional[bytes] = None) -> bytes:
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: Optional[bytes] = None) -> bytes:
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: Optional[bytes] = None) -> bytes:
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) -> Optional[HashAlgorithm]:
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 = 0x8004
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: Optional[bytes],
237
+ nonce: bytes | None,
201
238
  hash_algorithm: HashAlgorithm,
202
- entropy: Optional[bytes] = None,
203
- strong_password: Optional[str] = None,
204
- smart_card_secret: Optional[bytes] = None,
205
- verify_blob: Optional[bytes] = None,
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: Optional[bytes] = None,
262
- strong_password: Optional[str] = None,
263
- smart_card_secret: Optional[bytes] = None,
264
- verify_blob: Optional[bytes] = None,
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 hashlib
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
- __namespace__ = "dpapi"
16
+ """Windows Data Protection API (DPAPI) plugin.
24
17
 
25
- MASTER_KEY_REGEX = re.compile("^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$")
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
- SECURITY_POLICY_KEY = "HKEY_LOCAL_MACHINE\\SECURITY\\Policy"
28
- SYSTEM_KEY = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\LSA"
26
+ __namespace__ = "dpapi"
29
27
 
30
- SYSTEM_USERNAME = "System"
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 HAS_CRYPTO:
38
- raise UnsupportedPluginError("Missing pycryptodome dependency")
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
- passwords = set()
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
- # This assumes that there is no user named System.
89
- # As far as I can tell, the name "System" is saved for the actual System user
90
- # Therefore the user can't actually exist in `all_with_home`
91
- result = {self.SYSTEM_USERNAME: {}}
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/S-1-5-18")
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.SYSTEM_USERNAME, dir)
98
- result[self.SYSTEM_USERNAME].update(user_mks)
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
- path = user.home_path.joinpath("AppData/Roaming/Microsoft/Protect").joinpath(user.user.sid)
102
- user_mks = self._load_master_keys_from_path(user.user.name, path)
103
- if user_mks:
104
- result[user.user.name] = user_mks
66
+ sid = user.user.sid
67
+ master_keys.setdefault(sid, {})
105
68
 
106
- return result
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
- @cached_property
109
- def _users(self) -> dict[str, dict[str, str]]:
110
- return {u.name: {"sid": u.sid} for u in self.target.users()}
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
- return {}
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.MASTER_KEY_REGEX.findall(file.name):
119
- with file.open() as fh:
120
- mkf = MasterKeyFile(fh)
121
-
122
- if username == self.SYSTEM_USERNAME:
123
- dpapi_system = CredSystem(self.secrets["DPAPI_SYSTEM"][16:])
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
- if not mkf.decrypt_with_key(dpapi_system.machine_key):
126
- mkf.decrypt_with_key(dpapi_system.user_key)
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
- # This should not be possible, decrypting the System master key should always succeed
129
- if not mkf.decrypted:
130
- raise Exception("Failed to decrypt System master key")
140
+ yield file.name, mkf
131
141
 
132
- if user := self._users.get(username):
133
- for mk_pass in self.keychain():
134
- if mkf.decrypt_with_password(user["sid"], mk_pass):
135
- break
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
- try:
138
- if mkf.decrypt_with_hash(user["sid"], bytes.fromhex(mk_pass)) is True:
139
- break
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
- if not mkf.decrypted:
144
- self.target.log.warning("Could not decrypt DPAPI master key for username '%s'", username)
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
- result[file.name] = mkf
154
+ if not sid and not username:
155
+ raise ValueError("Either sid or username argument is required")
147
156
 
148
- return result
157
+ if not sid and username:
158
+ sid = self._users.get(username)
149
159
 
150
- def decrypt_system_blob(self, data: bytes) -> bytes:
151
- """Decrypt the given bytes using the System master key."""
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
- def decrypt_user_blob(self, data: bytes, username: str) -> bytes:
155
- """Decrypt the given bytes using the master key of the given user."""
156
- blob = DPAPIBlob(data)
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(username, {}).get(blob.guid)):
159
- raise ValueError(f"Blob UUID is unknown to {username} master keys")
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 user {username}")
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
- blob = DPAPIBlob(data)
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")
@@ -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,8 @@
1
+ from dissect.target.plugin import InternalNamespacePlugin
2
+
3
+
4
+ class KeyProviderPlugin(InternalNamespacePlugin):
5
+ __namespace__ = "_dpapi_keyprovider"
6
+
7
+ def check_compatible(self) -> None:
8
+ return None
@@ -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, auto
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]:
@@ -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.utcnow()
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.dev55
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=k9xWNnIGQG0DQsq6DKYJ6_DAX1aIA0SjzniWmOwX8O4,50317
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=fULRFO_I_QxAn6G9SCwlLL-TLVliS13JEGnGotf7lSA,6983
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=j3MMROXroes7pr_VLt8Xv6WEpv19hlgDpOxOJyZMRvo,5044
299
- dissect/target/plugins/os/windows/dpapi/crypto.py,sha256=_F1F2j1chQw-KLqfWvgL2mCkF3HSvdVnM78OZ0ph9hc,9337
300
- dissect/target/plugins/os/windows/dpapi/dpapi.py,sha256=NrLtx61m8PXsB3CzxUQgc1BKkaAVBOre1oEfGvqgtuw,7130
301
- dissect/target/plugins/os/windows/dpapi/master_key.py,sha256=oUuUfvMXmhRrgIs1CXTR6CdETKNYZwoStXSqtDdil78,6111
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=no78i0nxbnfgDJ5TpDZNAJggCigD_zLrXNYss7gdg2Q,9994
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=oOsYse2-BrliVQRXlHD1-89hsmNrJqg42DJy681AW0U,15268
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=ONHu2FVomLccikb84qBrlhNmEfRoHYFQMcahk_y2c9A,15580
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.dev55.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
360
- dissect.target-3.19.dev55.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
361
- dissect.target-3.19.dev55.dist-info/METADATA,sha256=kEnsyXUwLaOo42y56XYs1I9yAA2gKy6KY8DIKqONfU4,12897
362
- dissect.target-3.19.dev55.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
363
- dissect.target-3.19.dev55.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
364
- dissect.target-3.19.dev55.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
365
- dissect.target-3.19.dev55.dist-info/RECORD,,
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,,