dissect.target 3.17.dev34__py3-none-any.whl → 3.17.dev35__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.
@@ -0,0 +1,210 @@
1
+ import hashlib
2
+ import logging
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import BinaryIO, Iterator, Optional
6
+ from uuid import UUID
7
+
8
+ from dissect.cstruct import cstruct
9
+ from dissect.util.sid import read_sid
10
+
11
+ from dissect.target.exceptions import UnsupportedPluginError
12
+ from dissect.target.helpers import keychain
13
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
14
+ from dissect.target.helpers.record import create_extended_descriptor
15
+ from dissect.target.plugin import Plugin, export
16
+ from dissect.target.plugins.general.users import UserDetails
17
+ from dissect.target.plugins.os.windows.dpapi.crypto import (
18
+ CipherAlgorithm,
19
+ HashAlgorithm,
20
+ derive_password_hash,
21
+ )
22
+ from dissect.target.target import Target
23
+
24
+ log = logging.getLogger(__name__)
25
+
26
+
27
+ CredHistRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
28
+ "windows/credential/history",
29
+ [
30
+ ("string", "guid"),
31
+ ("boolean", "decrypted"),
32
+ ("string", "sha1"),
33
+ ("string", "nt"),
34
+ ],
35
+ )
36
+
37
+
38
+ credhist_def = """
39
+ struct entry {
40
+ DWORD dwVersion;
41
+ CHAR guidLink[16];
42
+ DWORD dwNextLinkSize;
43
+ DWORD dwCredLinkType;
44
+ DWORD algHash; // ALG_ID
45
+ DWORD dwPbkdf2IterationCount;
46
+ DWORD dwSidSize;
47
+ DWORD algCrypt; // ALG_ID
48
+ DWORD dwShaHashSize;
49
+ DWORD dwNtHashSize;
50
+ CHAR pSalt[16];
51
+ CHAR pSid[dwSidSize];
52
+ CHAR encrypted[0];
53
+ };
54
+ """
55
+
56
+ c_credhist = cstruct()
57
+ c_credhist.load(credhist_def)
58
+
59
+
60
+ @dataclass
61
+ class CredHistEntry:
62
+ version: int
63
+ guid: str
64
+ user_sid: str
65
+ sha1: Optional[bytes]
66
+ nt: Optional[bytes]
67
+ hash_alg: HashAlgorithm = field(repr=False)
68
+ cipher_alg: CipherAlgorithm = field(repr=False)
69
+ raw: c_credhist.entry = field(repr=False)
70
+ decrypted: bool = False
71
+
72
+ def decrypt(self, password_hash: bytes) -> None:
73
+ """Decrypt this CREDHIST entry using the provided password hash. Modifies ``CredHistEntry.sha1``
74
+ and ``CredHistEntry.nt`` values.
75
+
76
+ If the decrypted ``nt`` value is 16 bytes we assume the decryption was successful.
77
+
78
+ Args:
79
+ password_hash: Bytes of SHA1 password hash digest.
80
+
81
+ Raises:
82
+ ValueError: If the decryption seems to have failed.
83
+ """
84
+ data = self.cipher_alg.decrypt_with_hmac(
85
+ data=self.raw.encrypted,
86
+ key=derive_password_hash(password_hash, self.user_sid),
87
+ iv=self.raw.pSalt,
88
+ hash_algorithm=self.hash_alg,
89
+ rounds=self.raw.dwPbkdf2IterationCount,
90
+ )
91
+
92
+ sha_size = self.raw.dwShaHashSize
93
+ nt_size = self.raw.dwNtHashSize
94
+
95
+ sha1 = data[:sha_size]
96
+ nt = data[sha_size : sha_size + nt_size].rstrip(b"\x00")
97
+
98
+ if len(nt) != 16:
99
+ raise ValueError("Decrypting failed, invalid password hash?")
100
+
101
+ self.decrypted = True
102
+ self.sha1 = sha1
103
+ self.nt = nt
104
+
105
+
106
+ class CredHistFile:
107
+ def __init__(self, fh: BinaryIO) -> None:
108
+ self.fh = fh
109
+ self.entries = list(self._parse())
110
+
111
+ def __repr__(self) -> str:
112
+ return f"<CredHistFile fh='{self.fh}' entries={len(self.entries)}>"
113
+
114
+ def _parse(self) -> Iterator[CredHistEntry]:
115
+ self.fh.seek(0)
116
+ try:
117
+ while True:
118
+ entry = c_credhist.entry(self.fh)
119
+
120
+ # determine size of encrypted data and add to entry
121
+ cipher_alg = CipherAlgorithm.from_id(entry.algCrypt)
122
+ enc_size = entry.dwShaHashSize + entry.dwNtHashSize
123
+ enc_size += enc_size % cipher_alg.block_length
124
+ entry.encrypted = self.fh.read(enc_size)
125
+
126
+ yield CredHistEntry(
127
+ version=entry.dwVersion,
128
+ guid=UUID(bytes_le=entry.guidLink),
129
+ user_sid=read_sid(entry.pSid),
130
+ hash_alg=HashAlgorithm.from_id(entry.algHash),
131
+ cipher_alg=cipher_alg,
132
+ sha1=None,
133
+ nt=None,
134
+ raw=entry,
135
+ )
136
+ except EOFError:
137
+ pass
138
+
139
+ def decrypt(self, password_hash: bytes) -> None:
140
+ """Decrypt a CREDHIST chain using the provided password SHA1 hash."""
141
+
142
+ for entry in reversed(self.entries):
143
+ try:
144
+ entry.decrypt(password_hash)
145
+ except ValueError as e:
146
+ log.warning("Could not decrypt entry %s with password %s", entry.guid, password_hash.hex())
147
+ log.debug("", exc_info=e)
148
+ continue
149
+ password_hash = entry.sha1
150
+
151
+
152
+ class CredHistPlugin(Plugin):
153
+ """Windows CREDHIST file parser.
154
+
155
+ Windows XP: ``C:\\Documents and Settings\\username\\Application Data\\Microsoft\\Protect\\CREDHIST``
156
+ Windows 7 and up: ``C:\\Users\\username\\AppData\\Roaming\\Microsoft\\Protect\\CREDHIST``
157
+
158
+ Resources:
159
+ - https://www.passcape.com/index.php?section=docsys&cmd=details&id=28#41
160
+ """
161
+
162
+ def __init__(self, target: Target):
163
+ super().__init__(target)
164
+ self.files = list(self._find_files())
165
+
166
+ def _find_files(self) -> Iterator[tuple[UserDetails, Path]]:
167
+ hashes = set()
168
+ for user_details in self.target.user_details.all_with_home():
169
+ for path in ["AppData/Roaming/Microsoft/Protect/CREDHIST", "Application Data/Microsoft/Protect/CREDHIST"]:
170
+ credhist_path = user_details.home_path.joinpath(path)
171
+ if credhist_path.exists() and (hash := credhist_path.get().hash()) not in hashes:
172
+ hashes.add(hash)
173
+ yield user_details.user, credhist_path
174
+
175
+ def check_compatible(self) -> None:
176
+ if not self.files:
177
+ raise UnsupportedPluginError("No CREDHIST files found on target.")
178
+
179
+ @export(record=CredHistRecord)
180
+ def credhist(self) -> Iterator[CredHistRecord]:
181
+ """Yield and decrypt all Windows CREDHIST entries on the target."""
182
+ passwords = keychain_passwords()
183
+
184
+ if not passwords:
185
+ self.target.log.warning("No passwords provided in keychain, cannot decrypt CREDHIST hashes")
186
+
187
+ for user, path in self.files:
188
+ credhist = CredHistFile(path.open("rb"))
189
+
190
+ for password in passwords:
191
+ credhist.decrypt(hashlib.sha1(password.encode("utf-16-le")).digest())
192
+
193
+ for entry in credhist.entries:
194
+ yield CredHistRecord(
195
+ guid=entry.guid,
196
+ decrypted=entry.decrypted,
197
+ sha1=entry.sha1.hex() if entry.sha1 else None,
198
+ nt=entry.nt.hex() if entry.nt else None,
199
+ _user=user,
200
+ _target=self.target,
201
+ )
202
+
203
+
204
+ def keychain_passwords() -> set:
205
+ passphrases = set()
206
+ for key in keychain.get_keys_for_provider("user") + keychain.get_keys_without_provider():
207
+ if key.key_type == keychain.KeyType.PASSPHRASE:
208
+ passphrases.add(key.value)
209
+ passphrases.add("")
210
+ return passphrases
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.17.dev34
3
+ Version: 3.17.dev35
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
@@ -260,6 +260,7 @@ dissect/target/plugins/os/windows/amcache.py,sha256=ZZNOs3bILTf0AGkDkhoatndl0j39
260
260
  dissect/target/plugins/os/windows/catroot.py,sha256=eSfVqXvWWZpXoxKB1FT_evjXXNmlD7wHhA3lYpfQDeQ,11043
261
261
  dissect/target/plugins/os/windows/cim.py,sha256=jsrpu6TZpBUh7VWI9AV2Ib5bebTwsvqOwRfa5gjJd7c,3056
262
262
  dissect/target/plugins/os/windows/clfs.py,sha256=begVsZ-CY97Ksh6S1g03LjyBgu8ERY2hfNDWYPj0GXI,4872
263
+ dissect/target/plugins/os/windows/credhist.py,sha256=FX_pW-tU9esdvDTSx913kf_CpGE_1jbD6bkjDb-cxHk,7069
263
264
  dissect/target/plugins/os/windows/datetime.py,sha256=tuBOkewmbCW8sFXcYp5p82oM5RCsVwmtC79BDCTLz8k,9472
264
265
  dissect/target/plugins/os/windows/defender.py,sha256=Vp_IP6YKm4igR765WvXJrHQ3RMu7FJKM3VOoR8AybV8,23737
265
266
  dissect/target/plugins/os/windows/env.py,sha256=-u9F9xWy6PUbQmu5Tv_MDoVmy6YB-7CbHokIK_T3S44,13891
@@ -339,10 +340,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
339
340
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
340
341
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
341
342
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
342
- dissect.target-3.17.dev34.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
343
- dissect.target-3.17.dev34.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
344
- dissect.target-3.17.dev34.dist-info/METADATA,sha256=dCuOpFpGY7DjCc27MwZjfgtTnPx1iobAUR1GrzbpOZI,11300
345
- dissect.target-3.17.dev34.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
346
- dissect.target-3.17.dev34.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
347
- dissect.target-3.17.dev34.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
348
- dissect.target-3.17.dev34.dist-info/RECORD,,
343
+ dissect.target-3.17.dev35.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
344
+ dissect.target-3.17.dev35.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
345
+ dissect.target-3.17.dev35.dist-info/METADATA,sha256=Dd4TxlQKuWjqnUY6nMqiEvR-YBsmXIbhGN6CLjdsGhI,11300
346
+ dissect.target-3.17.dev35.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
347
+ dissect.target-3.17.dev35.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
348
+ dissect.target-3.17.dev35.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
349
+ dissect.target-3.17.dev35.dist-info/RECORD,,