dissect.target 3.19.dev56__py3-none-any.whl → 3.19.dev58__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) hide show
  1. dissect/target/plugin.py +10 -3
  2. dissect/target/plugins/os/windows/credential/__init__.py +0 -0
  3. dissect/target/plugins/os/windows/credential/lsa.py +174 -0
  4. dissect/target/plugins/os/windows/{sam.py → credential/sam.py} +5 -2
  5. dissect/target/plugins/os/windows/dpapi/blob.py +3 -0
  6. dissect/target/plugins/os/windows/dpapi/crypto.py +61 -23
  7. dissect/target/plugins/os/windows/dpapi/dpapi.py +127 -133
  8. dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py +0 -0
  9. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +21 -0
  10. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +17 -0
  11. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +20 -0
  12. dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py +8 -0
  13. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +38 -0
  14. dissect/target/plugins/os/windows/dpapi/master_key.py +3 -0
  15. {dissect.target-3.19.dev56.dist-info → dissect.target-3.19.dev58.dist-info}/METADATA +2 -2
  16. {dissect.target-3.19.dev56.dist-info → dissect.target-3.19.dev58.dist-info}/RECORD +22 -14
  17. /dissect/target/plugins/os/windows/{credhist.py → credential/credhist.py} +0 -0
  18. {dissect.target-3.19.dev56.dist-info → dissect.target-3.19.dev58.dist-info}/COPYRIGHT +0 -0
  19. {dissect.target-3.19.dev56.dist-info → dissect.target-3.19.dev58.dist-info}/LICENSE +0 -0
  20. {dissect.target-3.19.dev56.dist-info → dissect.target-3.19.dev58.dist-info}/WHEEL +0 -0
  21. {dissect.target-3.19.dev56.dist-info → dissect.target-3.19.dev58.dist-info}/entry_points.txt +0 -0
  22. {dissect.target-3.19.dev56.dist-info → dissect.target-3.19.dev58.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
@@ -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
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.19.dev56
3
+ Version: 3.19.dev58
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
@@ -31,7 +31,7 @@ Requires-Dist: dissect.ntfs <4,>=3.4
31
31
  Requires-Dist: dissect.regf <4,>=3.3
32
32
  Requires-Dist: dissect.util <4,>=3
33
33
  Requires-Dist: dissect.volume <4,>=2
34
- Requires-Dist: flow.record ~=3.15.0
34
+ Requires-Dist: flow.record ~=3.16.0
35
35
  Requires-Dist: structlog
36
36
  Provides-Extra: cb
37
37
  Requires-Dist: dissect.target[full] ; extra == 'cb'
@@ -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
@@ -269,7 +269,6 @@ dissect/target/plugins/os/windows/amcache.py,sha256=ZZNOs3bILTf0AGkDkhoatndl0j39
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
@@ -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.dev56.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
360
- dissect.target-3.19.dev56.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
361
- dissect.target-3.19.dev56.dist-info/METADATA,sha256=L-B6HjmylvgtkkqzQNrLepKXEn8hhjbxaW8R8xvb-3Y,12897
362
- dissect.target-3.19.dev56.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
363
- dissect.target-3.19.dev56.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
364
- dissect.target-3.19.dev56.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
365
- dissect.target-3.19.dev56.dist-info/RECORD,,
367
+ dissect.target-3.19.dev58.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
368
+ dissect.target-3.19.dev58.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
369
+ dissect.target-3.19.dev58.dist-info/METADATA,sha256=ds6L0Tl8rGiXP9RJGdype6uHycYg9TBhK2_CuQKPo9c,12897
370
+ dissect.target-3.19.dev58.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
371
+ dissect.target-3.19.dev58.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
372
+ dissect.target-3.19.dev58.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
373
+ dissect.target-3.19.dev58.dist-info/RECORD,,