dissect.target 3.19.dev23__py3-none-any.whl → 3.19.dev24__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,29 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ import re
1
4
  import struct
5
+ from typing import Iterator
2
6
 
3
7
  from dissect.util.ts import wintimestamp
4
8
 
5
- from dissect.target.exceptions import RegistryValueNotFoundError, UnsupportedPluginError
9
+ from dissect.target.exceptions import (
10
+ RegistryKeyNotFoundError,
11
+ RegistryValueNotFoundError,
12
+ UnsupportedPluginError,
13
+ )
6
14
  from dissect.target.helpers.record import TargetRecordDescriptor
7
- from dissect.target.plugin import Plugin, export, internal
15
+ from dissect.target.helpers.regutil import VirtualKey
16
+ from dissect.target.plugin import Plugin, export
8
17
 
9
18
  UsbRegistryRecord = TargetRecordDescriptor(
10
19
  "windows/registry/usb",
11
20
  [
12
- ("string", "device_type"),
21
+ ("string", "type"),
13
22
  ("string", "serial"),
14
- ("string", "vid"),
15
- ("string", "pid"),
16
- ("string", "rev"),
17
- ("string", "containerid"),
23
+ ("string", "container_id"),
18
24
  ("string", "vendor"),
19
25
  ("string", "product"),
20
- ("string", "version"),
21
- ("string", "friendlyname"),
26
+ ("string", "revision"),
27
+ ("string", "friendly_name"),
22
28
  ("datetime", "first_insert"),
23
29
  ("datetime", "first_install"),
24
30
  ("datetime", "last_insert"),
25
31
  ("datetime", "last_removal"),
26
- ("string", "info_origin"),
32
+ ("string[]", "volumes"),
33
+ ("string[]", "mounts"),
34
+ ("string[]", "users"),
35
+ ("path", "source"),
27
36
  ],
28
37
  )
29
38
 
@@ -34,128 +43,184 @@ USB_DEVICE_PROPERTY_KEYS = {
34
43
  "last_removal": ("0067", "00000067"), # Windows 8 and higer. USB device last removal date.
35
44
  }
36
45
 
46
+ RE_DEVICE_NAME = re.compile(r"^(?P<type>.+?)&Ven_(?P<vendor>.+?)&Prod_(?P<product>.+?)(&Rev_(?P<revision>.+?))?$")
47
+
37
48
 
38
49
  class UsbPlugin(Plugin):
39
- """USB plugin."""
50
+ """Windows USB history plugin.
51
+
52
+ Parses Windows registry data about attached USB devices. Does not parse EVTX EventIDs
53
+ or ``C:\\Windows\\inf\\setupapi(.dev).log``.
40
54
 
41
- # USB device locations
55
+ To get a full picture of the USB history on a Windows machine, you should parse the
56
+ relevant EventIDs using the evtx plugin. For more research on event log USB forensics, see:
57
+ - https://www.researchgate.net/publication/318514858
58
+ - https://dfir.pubpub.org/pub/h78di10n/release/2
59
+ - https://www.senturean.com/posts/19_08_03_usb_storage_forensics_1/#1-system-events
60
+
61
+ Resources:
62
+ - https://hatsoffsecurity.com/2014/06/05/usb-forensics-pt-1-serial-number/
63
+ - http://www.swiftforensics.com/2013/11/windows-8-new-registry-artifacts-part-1.html
64
+ - https://www.sans.org/blog/the-truth-about-usb-device-serial-numbers/
65
+ """
66
+
67
+ # Stores history of mounted USB devices
42
68
  USB_STOR = "HKLM\\SYSTEM\\CurrentControlSet\\Enum\\USBSTOR"
43
- # DeviceContainers holds all USB information. Only present in windows 8 or higher
44
- DEVICE_CONTAINERS = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceContainers"
45
- USB = "HKLM\\SYSTEM\\CurrentControlSet\\Enum\\USB"
46
- HID = "HKLM\\SYSTEM\\CurrentControlSet\\Enum\\HID"
47
- SCSI = "HKLM\\SYSTEM\\CurrentControlSet\\Enum\\SCSI"
48
69
 
49
- def check_compatible(self) -> None:
50
- if not len(list(self.target.registry.keys(self.USB_STOR))) > 0:
51
- raise UnsupportedPluginError(f"Registry key not found: {self.USB_STOR}")
70
+ # Stores the relation between a USB container_id and the FriendlyName of mounted volume(s) (Windows 7 and up)
71
+ PORTABLE_DEVICES = "HKLM\\SOFTWARE\\Microsoft\\Windows Portable Devices\\Devices"
52
72
 
53
- @internal
54
- def unpack_timestamps(self, usb_reg_properties):
55
- """
56
- Params:
57
- usb_reg_properties (Regf): A registry object with USB properties
58
- Returns:
59
- timestamps (Dict): A dict containing parsed timestamps within passed registry object
60
- """
61
- usb_reg_properties = usb_reg_properties.subkey("{83da6326-97a6-4088-9453-a1923f573b29}")
62
- timestamps = {}
63
-
64
- for device_property, usbstor_values in USB_DEVICE_PROPERTY_KEYS.items():
65
- for usb_val in usbstor_values:
66
- if usb_val in [x.name for x in usb_reg_properties.subkeys()]:
67
- version_key = usb_reg_properties.subkey(usb_val)
68
- if "00000000" in version_key.subkeys():
69
- data_value = version_key.subkey("00000000").value("Data").value
70
- else:
71
- data_value = version_key.value("(Default)").value
72
- timestamps[device_property] = wintimestamp(struct.unpack("<Q", data_value)[0])
73
- break
74
- else:
75
- timestamps[device_property] = None
76
- return timestamps
73
+ # Stores the most recent mapping of a mount letter and a container_id
74
+ MOUNT_LETTER_MAP = "HKLM\\SYSTEM\\MountedDevices"
77
75
 
78
- @internal
79
- def parse_device_name(self, device_name):
80
- device_info = device_name.split("&")
81
- device_type = device_info[0]
82
- vendor = device_info[1].split("Ven_")[1]
83
- product = device_info[2].split("Prod_")[1]
84
- version = None if len(device_info) < 4 else device_info[3].split("Rev_")[1]
76
+ # User history of mount points accesses in explorer.exe
77
+ USER_MOUNTS = "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Mountpoints2"
85
78
 
86
- return dict(device_type=device_type, vendor=vendor, product=product, version=version)
79
+ # Other artifacts we currently do not parse:
80
+ # - "sysvol\Windows\inf\setupapi(.dev).log"
81
+ # - "HKLM\\SYSTEM\\CurrentControlSet\\Enum\\USB"
82
+ # - "HKLM\\SYSTEM\\CurrentControlSet\\Enum\\HID"
83
+ # - "HKLM\\SYSTEM\\CurrentControlSet\\Enum\\SCSI"
84
+ # - "HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceContainers"
85
+ # - "SOFTWARE\Microsoft\Windows NT\CurrentVersion\EMDMgmt"
86
+
87
+ def check_compatible(self) -> None:
88
+ if not list(self.target.registry.keys(self.USB_STOR)):
89
+ raise UnsupportedPluginError(f"Registry key not found: {self.USB_STOR}")
87
90
 
88
91
  @export(record=UsbRegistryRecord)
89
- def usb(self):
90
- """Return information about attached USB devices.
91
-
92
- Use the registry to find information about USB devices that have been attached to the system, for example the
93
- HKLM\\SYSTEM\\CurrentControlSet\\Enum\\USBSTOR registry key.
94
-
95
- Yields UsbRegistryRecord with fields:
96
-
97
- .. code-block:: text
98
-
99
- hostname (string): The target hostname
100
- domain (string): The target domain
101
- type (string): Type of USB device
102
- serial (string): Serial number of USB storage device
103
- vid (string): Vendor ID of USB storage device
104
- pid (string): Product ID of the USB storage device
105
- rev (string): Version of the USB storage device
106
- containerid (string):
107
- friendlyname (string): Display name of the USB storage device
108
- first_insert (datetime): First insertion date of USB storage device
109
- first_install (datetime): First instalation date of USB storage device
110
- last_insert (datetime): Most recent insertion (arrival) date of USB storage device
111
- last_removal (datetime): Most recent removal (unplug) date of USB storage device
112
- info_origin (string): Location of info present in output
92
+ def usb(self) -> Iterator[UsbRegistryRecord]:
93
+ """Yields information about (historically) attached USB storage devices on Windows.
94
+
95
+ Uses the registry to find information about USB storage devices that have been attached to the system.
96
+ Also tries to find the past volume name and mount letters of the USB device and what user(s) interacted
97
+ with them using ``explorer.exe``.
113
98
  """
114
99
 
115
- for k in self.target.registry.keys(self.USB_STOR):
116
- info_origin = "\\".join((k.path, k.name))
117
- usb_stor = k.subkeys()
100
+ for key in self.target.registry.keys(self.USB_STOR):
101
+ for usb_type in key.subkeys():
102
+ try:
103
+ device_info = parse_device_name(usb_type.name)
104
+ except ValueError:
105
+ self.target.log.warning("Unable to parse USB device name: %s", usb_type.name)
106
+ device_info = {"type": None, "vendor": None, "product": None, "revision": None}
118
107
 
119
- for usb_type in usb_stor:
120
- device_info = self.parse_device_name(usb_type.name)
121
- usb_devices = usb_type.subkeys()
122
- for usb_device in usb_devices:
123
- properties = list(usb_device.subkeys())
108
+ for usb_device in usb_type.subkeys():
124
109
  serial = usb_device.name
110
+ friendly_name = None
111
+ container_id = None
112
+ timestamps = {
113
+ "first_install": None,
114
+ "first_insert": None,
115
+ "last_insert": None,
116
+ "last_removal": None,
117
+ }
118
+
125
119
  try:
126
- friendlyname = usb_device.value("FriendlyName").value
127
- # NOTE: make this more gracefull, windows 10 does not have the LogConf subkey
128
- timestamps = (
129
- self.unpack_timestamps(properties[2])
130
- if len(properties) == 3
131
- else self.unpack_timestamps(properties[1])
132
- )
133
- # ContainerIDs can be found back in USB and WdpBusEnumRoot
134
- containerid = usb_device.value("ContainerID").value
120
+ friendly_name = usb_device.value("FriendlyName").value
135
121
  except RegistryValueNotFoundError:
136
- friendlyname = None
137
- timestamps = {
138
- "first_install": None,
139
- "first_insert": None,
140
- "last_insert": None,
141
- "last_removal": None,
142
- }
143
- containerid = None
122
+ self.target.log.warning("No FriendlyName for USB with serial: %s", serial)
123
+ pass
124
+
125
+ try:
126
+ container_id = usb_device.value("ContainerID").value
127
+ except RegistryValueNotFoundError:
128
+ self.target.log.warning("No ContainerID for USB with serial: %s", serial)
129
+
130
+ try:
131
+ timestamps = unpack_timestamps(usb_device.subkey("Properties"))
132
+ except RegistryValueNotFoundError as e:
133
+ self.target.log.warning("Unable to parse USBSTOR registry properties for serial: %s", serial)
134
+ self.target.log.debug("", exc_info=e)
135
+
136
+ # We can check if any HKCU hive(s) are populated with the Volume GUID of the USB storage device.
137
+ # If a user has interacted with the mounted volume using explorer.exe we will get a match.
138
+ volumes = list(self.find_volumes(serial))
139
+ mounts = list(self.find_mounts(serial))
140
+ users = [
141
+ u.user.name for u in self.find_users([m[10:] for m in mounts if m.startswith("\\??\\Volume{")])
142
+ ]
144
143
 
145
144
  yield UsbRegistryRecord(
146
- device_type=device_info["device_type"],
147
- friendlyname=friendlyname,
145
+ friendly_name=friendly_name,
148
146
  serial=serial,
149
- vid=None,
150
- pid=None,
151
- vendor=device_info["vendor"],
152
- product=device_info["product"],
153
- version=device_info["version"],
154
- containerid=containerid,
155
- first_install=timestamps["first_install"],
156
- first_insert=timestamps["first_insert"],
157
- last_insert=timestamps["last_insert"], # AKA first arrival
158
- last_removal=timestamps["last_removal"],
159
- info_origin=info_origin,
147
+ container_id=container_id,
148
+ **device_info,
149
+ **timestamps,
150
+ volumes=volumes,
151
+ mounts=mounts,
152
+ users=users,
153
+ source=self.USB_STOR,
160
154
  _target=self.target,
161
155
  )
156
+
157
+ def find_volumes(self, serial: str) -> Iterator[str]:
158
+ """Attempts to find mounted volume names for the given serial."""
159
+ serial = serial.lower()
160
+ try:
161
+ for device in self.target.registry.key(self.PORTABLE_DEVICES).subkeys():
162
+ if serial in device.name.lower():
163
+ yield device.value("FriendlyName").value
164
+ except RegistryKeyNotFoundError:
165
+ pass
166
+
167
+ def find_mounts(self, serial: str) -> Iterator[str]:
168
+ """Attempts to find drive letters the given serial has been mounted on."""
169
+ serial = serial.lower()
170
+ try:
171
+ for mount in self.target.registry.key(self.MOUNT_LETTER_MAP).values():
172
+ try:
173
+ if serial in mount.value.decode("utf-16-le").lower():
174
+ yield mount.name.replace("\\DosDevices\\", "")
175
+ except UnicodeDecodeError:
176
+ pass
177
+ except RegistryKeyNotFoundError:
178
+ pass
179
+
180
+ def find_users(self, volume_guids: list[str]) -> Iterator[str]:
181
+ """Attempt to find Windows users that have interacted with the given volume GUIDs."""
182
+
183
+ for volume_guid in volume_guids:
184
+ try:
185
+ for key in self.target.registry.key(self.USER_MOUNTS + "\\" + volume_guid):
186
+ yield self.target.registry.get_user_details(key)
187
+ except RegistryKeyNotFoundError:
188
+ pass
189
+
190
+
191
+ def unpack_timestamps(usb_reg_properties: VirtualKey) -> dict[str, int]:
192
+ """Unpack relevant Windows timestamps from the provided USB registry properties subkey.
193
+
194
+ Args:
195
+ usb_reg_properties: A registry object with USB properties.
196
+
197
+ Returns:
198
+ A dict containing parsed timestamps within passed registry object.
199
+ """
200
+
201
+ usb_reg_properties = usb_reg_properties.subkey("{83da6326-97a6-4088-9453-a1923f573b29}")
202
+ timestamps = {}
203
+
204
+ for device_property, usbstor_values in USB_DEVICE_PROPERTY_KEYS.items():
205
+ for usb_val in usbstor_values:
206
+ if usb_val in [x.name for x in usb_reg_properties.subkeys()]:
207
+ version_key = usb_reg_properties.subkey(usb_val)
208
+ if "00000000" in version_key.subkeys():
209
+ data_value = version_key.subkey("00000000").value("Data").value
210
+ else:
211
+ data_value = version_key.value("(Default)").value
212
+ timestamps[device_property] = wintimestamp(struct.unpack("<Q", data_value)[0])
213
+ break
214
+ else:
215
+ timestamps[device_property] = None
216
+ return timestamps
217
+
218
+
219
+ def parse_device_name(device_name: str) -> dict[str, str]:
220
+ """Parse a registry device name into components."""
221
+
222
+ match = RE_DEVICE_NAME.match(device_name)
223
+ if not match:
224
+ raise ValueError(f"Unable to parse USB device name: {device_name}")
225
+
226
+ return match.groupdict()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.19.dev23
3
+ Version: 3.19.dev24
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
@@ -316,7 +316,7 @@ dissect/target/plugins/os/windows/regf/runkeys.py,sha256=-2HcdnVytzCt1xwgAI8rHDn
316
316
  dissect/target/plugins/os/windows/regf/shellbags.py,sha256=hXAqThFkHmGPmhNRSXwMNzw25kAyIC6OOZivgpPEwTQ,25679
317
317
  dissect/target/plugins/os/windows/regf/shimcache.py,sha256=no78i0nxbnfgDJ5TpDZNAJggCigD_zLrXNYss7gdg2Q,9994
318
318
  dissect/target/plugins/os/windows/regf/trusteddocs.py,sha256=3yvpBDM-Asg0rvGN2TwALGRm9DYogG6TxRau9D6FBbw,3700
319
- dissect/target/plugins/os/windows/regf/usb.py,sha256=hR5fnqy_sint1YyWgm1-AMhGQ4MxJOH_Wz0vbYzr9p4,7213
319
+ dissect/target/plugins/os/windows/regf/usb.py,sha256=nSAHB4Cdd0wF2C1EK_XYOfWCyqOgTZCLfDhuSmr7rdM,9709
320
320
  dissect/target/plugins/os/windows/regf/userassist.py,sha256=bSioEQdqUxdGwkdgMUfDIY2_pzrl9PdxPjmzmMaIwHs,5490
321
321
  dissect/target/plugins/os/windows/task_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
322
322
  dissect/target/plugins/os/windows/task_helpers/tasks_job.py,sha256=7w3UGOiTAUQkP3xQ3sj4X3MTgHUJmmfdgiEadWmYquI,21197
@@ -346,10 +346,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
346
346
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
347
347
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
348
348
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
349
- dissect.target-3.19.dev23.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
350
- dissect.target-3.19.dev23.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
351
- dissect.target-3.19.dev23.dist-info/METADATA,sha256=c2q_-Jvv4xRoYAE0jWyTGNV-oOKAD73yjU9Rk5-mavY,12719
352
- dissect.target-3.19.dev23.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
353
- dissect.target-3.19.dev23.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
354
- dissect.target-3.19.dev23.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
355
- dissect.target-3.19.dev23.dist-info/RECORD,,
349
+ dissect.target-3.19.dev24.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
350
+ dissect.target-3.19.dev24.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
351
+ dissect.target-3.19.dev24.dist-info/METADATA,sha256=svE2PfocnTmkG0NldymfR4W2rf2I-Jg2pfXkN_O-cvw,12719
352
+ dissect.target-3.19.dev24.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
353
+ dissect.target-3.19.dev24.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
354
+ dissect.target-3.19.dev24.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
355
+ dissect.target-3.19.dev24.dist-info/RECORD,,