dissect.target 3.17.dev33__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.
- dissect/target/loaders/dir.py +23 -5
- dissect/target/loaders/velociraptor.py +5 -4
- dissect/target/plugins/os/windows/credhist.py +210 -0
- {dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/METADATA +1 -1
- {dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/RECORD +10 -9
- {dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/LICENSE +0 -0
- {dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/WHEEL +0 -0
- {dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/top_level.txt +0 -0
dissect/target/loaders/dir.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import zipfile
|
4
|
+
from collections import defaultdict
|
4
5
|
from pathlib import Path
|
5
6
|
from typing import TYPE_CHECKING
|
6
7
|
|
8
|
+
from dissect.target.filesystem import LayerFilesystem
|
7
9
|
from dissect.target.filesystems.dir import DirectoryFilesystem
|
8
10
|
from dissect.target.filesystems.zip import ZipFilesystem
|
9
11
|
from dissect.target.helpers import loaderutil
|
@@ -48,6 +50,7 @@ def map_dirs(target: Target, dirs: list[Path | tuple[str, Path]], os_type: str,
|
|
48
50
|
alt_separator = "\\"
|
49
51
|
case_sensitive = False
|
50
52
|
|
53
|
+
drive_letter_map = defaultdict(list)
|
51
54
|
for path in dirs:
|
52
55
|
drive_letter = None
|
53
56
|
if isinstance(path, tuple):
|
@@ -59,13 +62,28 @@ def map_dirs(target: Target, dirs: list[Path | tuple[str, Path]], os_type: str,
|
|
59
62
|
dfs = ZipFilesystem(path.root.fp, path.at, alt_separator=alt_separator, case_sensitive=case_sensitive)
|
60
63
|
else:
|
61
64
|
dfs = DirectoryFilesystem(path, alt_separator=alt_separator, case_sensitive=case_sensitive)
|
62
|
-
target.filesystems.add(dfs)
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
+
drive_letter_map[drive_letter].append(dfs)
|
67
|
+
|
68
|
+
fs_to_add = []
|
69
|
+
for drive_letter, dfs in drive_letter_map.items():
|
70
|
+
if drive_letter is not None:
|
71
|
+
if len(dfs) > 1:
|
72
|
+
vfs = LayerFilesystem()
|
73
|
+
for fs in dfs:
|
74
|
+
vfs.append_fs_layer(fs)
|
75
|
+
else:
|
76
|
+
vfs = dfs[0]
|
66
77
|
|
67
|
-
|
68
|
-
|
78
|
+
fs_to_add.append(vfs)
|
79
|
+
target.fs.mount(drive_letter.lower() + ":", vfs)
|
80
|
+
else:
|
81
|
+
fs_to_add.extend(dfs)
|
82
|
+
|
83
|
+
for fs in fs_to_add:
|
84
|
+
target.filesystems.add(fs)
|
85
|
+
if os_type == OperatingSystem.WINDOWS:
|
86
|
+
loaderutil.add_virtual_ntfs_filesystem(target, fs, **kwargs)
|
69
87
|
|
70
88
|
|
71
89
|
def find_and_map_dirs(target: Target, path: Path, **kwargs) -> None:
|
@@ -61,6 +61,10 @@ def extract_drive_letter(name: str) -> Optional[str]:
|
|
61
61
|
if len(name) == 14 and name.startswith("%5C%5C.%5C") and name.endswith("%3A"):
|
62
62
|
return name[10].lower()
|
63
63
|
|
64
|
+
# X: in URL encoding
|
65
|
+
if len(name) == 4 and name.endswith("%3A"):
|
66
|
+
return name[0].lower()
|
67
|
+
|
64
68
|
|
65
69
|
class VelociraptorLoader(DirLoader):
|
66
70
|
"""Load Rapid7 Velociraptor forensic image files.
|
@@ -71,10 +75,7 @@ class VelociraptorLoader(DirLoader):
|
|
71
75
|
{"Generic.Collectors.File":{"Root":"/","collectionSpec":"Glob\\netc/**\\nvar/log/**"}}
|
72
76
|
|
73
77
|
Generic.Collectors.File (Windows) and Windows.KapeFiles.Targets (Windows) uses the accessors mft, ntfs, lazy_ntfs,
|
74
|
-
ntfs_vss and auto. The loader
|
75
|
-
using the following configuration::
|
76
|
-
|
77
|
-
{"Windows.KapeFiles.Targets":{"VSSAnalysisAge":"1000","_SANS_Triage":"Y"}}
|
78
|
+
ntfs_vss and auto. The loader supports a collection where multiple accessors were used.
|
78
79
|
|
79
80
|
References:
|
80
81
|
- https://www.rapid7.com/products/velociraptor/
|
@@ -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.
|
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
|
@@ -80,7 +80,7 @@ dissect/target/loaders/ad1.py,sha256=1_VmPZckDzXVvNF-HNtoUZqabnhCKBLUD3vVaitHQ00
|
|
80
80
|
dissect/target/loaders/asdf.py,sha256=dvPPDBrnz2JPXpCbqsu-NgQWIdVGMOit2KAdhIO1iiQ,972
|
81
81
|
dissect/target/loaders/cb.py,sha256=EGhdytBKBdofTd89juavDZZbmupEZmMBadeUXvVIK20,6612
|
82
82
|
dissect/target/loaders/cyber.py,sha256=Ip2hI7L98ZP7gUZuHQr0GxBdmbTzD-PntXmLJ5KpBuQ,1533
|
83
|
-
dissect/target/loaders/dir.py,sha256=
|
83
|
+
dissect/target/loaders/dir.py,sha256=F-PgvBw82XmL0rdKyBxznUkDc5Oct6-_Y9xM4fhvA6I,5791
|
84
84
|
dissect/target/loaders/hyperv.py,sha256=_IOUJEO0BXaCBZ6sjIX0DZTkG9UNW5Vs9VcNHYv073w,5928
|
85
85
|
dissect/target/loaders/itunes.py,sha256=rKOhlDRypQBGkuSZudMDS1Mlb9XV6BD5FRvM7tGq9jU,13128
|
86
86
|
dissect/target/loaders/kape.py,sha256=t5TfrGLqPeIpUUpXzIl6aHsqXMEGDqJ5YwDCs07DiBA,1237
|
@@ -106,7 +106,7 @@ dissect/target/loaders/targetd.py,sha256=sfbn2_j3il2G-rPywAoNT5YPtD5KmKkmBv1zrPD
|
|
106
106
|
dissect/target/loaders/utm.py,sha256=e5x5ZI3HeL0STh4S-CaQb68Rnug4SVZR9zlmHaGFj0M,978
|
107
107
|
dissect/target/loaders/vb.py,sha256=CdimOMeoJEDq8xYDgtldGSiwhR-dY5uxac1L0sYwAEU,2078
|
108
108
|
dissect/target/loaders/vbox.py,sha256=8JD7D8iAY9JRvTHsrosp5ZMsZezuLhZ10Zt8sEL7KBI,732
|
109
|
-
dissect/target/loaders/velociraptor.py,sha256=
|
109
|
+
dissect/target/loaders/velociraptor.py,sha256=bMrmJsyrYFVr5loRbIttpLgddtX94d65UH_BM-PuIXQ,4911
|
110
110
|
dissect/target/loaders/vma.py,sha256=AAY5-s-nz6wgvmcFkptJD7nNXhpkdf6SqEKVOrJaIKs,644
|
111
111
|
dissect/target/loaders/vmwarevm.py,sha256=1MlKoIuWSwpYmpuLxDuVacvaYHUhAGO1KgZxzrc4fyg,428
|
112
112
|
dissect/target/loaders/vmx.py,sha256=o1rYYKu6ReleqqHf2aeRcNrmoRcngWZNhz1h7GlmggQ,962
|
@@ -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.
|
343
|
-
dissect.target-3.17.
|
344
|
-
dissect.target-3.17.
|
345
|
-
dissect.target-3.17.
|
346
|
-
dissect.target-3.17.
|
347
|
-
dissect.target-3.17.
|
348
|
-
dissect.target-3.17.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.17.dev33.dist-info → dissect.target-3.17.dev35.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|