dissect.target 3.17.dev34__py3-none-any.whl → 3.17.dev35__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,,