dissect.target 3.17.dev33__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.
- 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
|