dissect.target 3.20.dev48__py3-none-any.whl → 3.20.dev50__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -53,22 +53,22 @@ class NetworkPlugin(Plugin):
53
53
  @export
54
54
  def ips(self) -> list[IPAddress]:
55
55
  """Return IP addresses as list of :class:`IPAddress`."""
56
- return list(self._get_record_type("ip"))
56
+ return list(set(self._get_record_type("ip")))
57
57
 
58
58
  @export
59
59
  def gateways(self) -> list[IPAddress]:
60
60
  """Return gateways as list of :class:`IPAddress`."""
61
- return list(self._get_record_type("gateway"))
61
+ return list(set(self._get_record_type("gateway")))
62
62
 
63
63
  @export
64
64
  def macs(self) -> list[str]:
65
65
  """Return MAC addresses as list of :class:`str`."""
66
- return list(self._get_record_type("mac"))
66
+ return list(set(self._get_record_type("mac")))
67
67
 
68
68
  @export
69
- def dns(self) -> list[str]:
69
+ def dns(self) -> list[str | IPAddress]:
70
70
  """Return DNS addresses as list of :class:`str`."""
71
- return list(self._get_record_type("dns"))
71
+ return list(set(self._get_record_type("dns")))
72
72
 
73
73
  @internal
74
74
  def with_ip(self, ip_addr: str) -> Iterator[InterfaceRecord]:
@@ -4,7 +4,6 @@ import logging
4
4
  import re
5
5
  import uuid
6
6
  from pathlib import Path
7
- from struct import unpack
8
7
  from typing import Iterator
9
8
 
10
9
  from flow.record.fieldtypes import posix_path
@@ -19,6 +18,25 @@ from dissect.target.target import Target
19
18
  log = logging.getLogger(__name__)
20
19
 
21
20
 
21
+ # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#ISA
22
+ ARCH_MAP = {
23
+ 0x00: "unknown",
24
+ 0x02: "sparc",
25
+ 0x03: "x86",
26
+ 0x08: "mips",
27
+ 0x14: "powerpc32",
28
+ 0x15: "powerpc64",
29
+ 0x16: "s390", # and s390x
30
+ 0x28: "aarch32", # armv7
31
+ 0x2A: "superh",
32
+ 0x32: "ia-64",
33
+ 0x3E: "x86_64",
34
+ 0xB7: "aarch64", # armv8
35
+ 0xF3: "riscv64",
36
+ 0xF7: "bpf",
37
+ }
38
+
39
+
22
40
  class UnixPlugin(OSPlugin):
23
41
  def __init__(self, target: Target):
24
42
  super().__init__(target)
@@ -301,37 +319,30 @@ class UnixPlugin(OSPlugin):
301
319
  continue
302
320
  return os_release
303
321
 
304
- def _get_architecture(self, os: str = "unix", path: str = "/bin/ls") -> str | None:
305
- arch_strings = {
306
- 0x00: "Unknown",
307
- 0x02: "SPARC",
308
- 0x03: "x86",
309
- 0x08: "MIPS",
310
- 0x14: "PowerPC",
311
- 0x16: "S390",
312
- 0x28: "ARM",
313
- 0x2A: "SuperH",
314
- 0x32: "IA-64",
315
- 0x3E: "x86_64",
316
- 0xB7: "AArch64",
317
- 0xF3: "RISC-V",
318
- }
319
-
320
- for fs in self.target.filesystems:
321
- if fs.exists(path):
322
- fh = fs.open(path)
323
- fh.seek(4)
324
- # ELF - e_ident[EI_CLASS]
325
- bits = unpack("B", fh.read(1))[0]
326
- fh.seek(18)
327
- # ELF - e_machine
328
- arch = unpack("H", fh.read(2))[0]
329
- arch = arch_strings.get(arch)
330
-
331
- if bits == 1: # 32 bit system
332
- return f"{arch}_32-{os}"
333
- else:
334
- return f"{arch}-{os}"
322
+ def _get_architecture(self, os: str = "unix", path: Path | str = "/bin/ls") -> str | None:
323
+ """Determine architecture by reading an ELF header of a binary on the target.
324
+
325
+ Resources:
326
+ - https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#ISA
327
+ """
328
+
329
+ if not isinstance(path, TargetPath):
330
+ for fs in [self.target.fs, *self.target.filesystems]:
331
+ if (path := fs.path(path)).exists():
332
+ break
333
+
334
+ if not path.exists():
335
+ return
336
+
337
+ fh = path.open("rb")
338
+ fh.seek(4) # ELF - e_ident[EI_CLASS]
339
+ bits = fh.read(1)[0]
340
+
341
+ fh.seek(18) # ELF - e_machine
342
+ e_machine = int.from_bytes(fh.read(2), "little")
343
+ arch = ARCH_MAP.get(e_machine, "unknown")
344
+
345
+ return f"{arch}_32-{os}" if bits == 1 and not arch[-2:] == "32" else f"{arch}-{os}"
335
346
 
336
347
 
337
348
  def parse_fstab(
@@ -41,10 +41,7 @@ class MacPlugin(BsdPlugin):
41
41
 
42
42
  @export(property=True)
43
43
  def ips(self) -> Optional[list[str]]:
44
- ips = set()
45
- for ip in self.target.network.ips():
46
- ips.add(str(ip))
47
- return list(ips)
44
+ return list(set(map(str, self.target.network.ips())))
48
45
 
49
46
  @export(property=True)
50
47
  def version(self) -> Optional[str]:
@@ -34,17 +34,15 @@ class LinuxPlugin(UnixPlugin, LinuxNetworkManager):
34
34
  @export(property=True)
35
35
  def ips(self) -> list[str]:
36
36
  """Returns a list of static IP addresses and DHCP lease IP addresses found on the host system."""
37
- ips = []
37
+ ips = set()
38
38
 
39
39
  for ip_set in self.network_manager.get_config_value("ips"):
40
- for ip in ip_set:
41
- ips.append(ip)
40
+ ips.update(ip_set)
42
41
 
43
42
  for ip in parse_unix_dhcp_log_messages(self.target, iter_all=False):
44
- if ip not in ips:
45
- ips.append(ip)
43
+ ips.add(ip)
46
44
 
47
- return ips
45
+ return list(ips)
48
46
 
49
47
  @export(property=True)
50
48
  def dns(self) -> list[str]:
@@ -6,7 +6,7 @@ from base64 import b64decode
6
6
  from datetime import datetime
7
7
  from io import BytesIO
8
8
  from tarfile import ReadError
9
- from typing import BinaryIO, Iterator, Optional, TextIO, Union
9
+ from typing import BinaryIO, Iterator, TextIO
10
10
 
11
11
  from dissect.util import cpio
12
12
  from dissect.util.compression import xz
@@ -73,10 +73,11 @@ class FortiOSPlugin(LinuxPlugin):
73
73
  return config
74
74
 
75
75
  @classmethod
76
- def detect(cls, target: Target) -> Optional[Filesystem]:
76
+ def detect(cls, target: Target) -> Filesystem | None:
77
77
  for fs in target.filesystems:
78
- # Tested on FortiGate and FortiAnalyzer, other Fortinet devices may look different.
79
- if fs.exists("/rootfs.gz") and (fs.exists("/.fgtsum") or fs.exists("/.fmg_sign") or fs.exists("/flatkc")):
78
+ # Tested on FortiGate, FortiAnalyzer and FortiManager.
79
+ # Other Fortinet devices may look different.
80
+ if fs.exists("/rootfs.gz") and (any(map(fs.exists, (".fgtsum", ".fmg_sign", "flatkc", "system.conf")))):
80
81
  return fs
81
82
 
82
83
  @classmethod
@@ -212,7 +213,7 @@ class FortiOSPlugin(LinuxPlugin):
212
213
  return "FortiOS Unknown"
213
214
 
214
215
  @export(record=FortiOSUserRecord)
215
- def users(self) -> Iterator[Union[FortiOSUserRecord, UnixUserRecord]]:
216
+ def users(self) -> Iterator[FortiOSUserRecord | UnixUserRecord]:
216
217
  """Return local users of the FortiOS system."""
217
218
 
218
219
  # Possible unix-like users
@@ -224,7 +225,7 @@ class FortiOSPlugin(LinuxPlugin):
224
225
  yield FortiOSUserRecord(
225
226
  name=username,
226
227
  password=":".join(entry.get("password", [])),
227
- groups=[entry["accprofile"][0]],
228
+ groups=list(entry.get("accprofile", [])),
228
229
  home="/root",
229
230
  _target=self.target,
230
231
  )
@@ -233,69 +234,72 @@ class FortiOSPlugin(LinuxPlugin):
233
234
  self.target.log.debug("", exc_info=e)
234
235
 
235
236
  # FortiManager administrative users
236
- try:
237
- for username, entry in self._config["global-config"]["system"]["admin"]["user"].items():
238
- yield FortiOSUserRecord(
239
- name=username,
240
- password=":".join(entry.get("password", [])),
241
- groups=[entry["profileid"][0]],
242
- home="/root",
243
- _target=self.target,
244
- )
245
- except KeyError as e:
246
- self.target.log.warning("Exception while parsing FortiManager admin users")
247
- self.target.log.debug("", exc_info=e)
248
-
249
- # Local users
250
- try:
251
- local_groups = local_groups_to_users(self._config["root-config"]["user"]["group"])
252
- for username, entry in self._config["root-config"]["user"].get("local", {}).items():
253
- try:
254
- password = decrypt_password(entry["passwd"][-1])
255
- except (ValueError, RuntimeError):
256
- password = ":".join(entry.get("passwd", []))
257
-
258
- yield FortiOSUserRecord(
259
- name=username,
260
- password=password,
261
- groups=local_groups.get(username, []),
262
- home=None,
263
- _target=self.target,
264
- )
265
- except KeyError as e:
266
- self.target.log.warning("Exception while parsing FortiOS local users")
267
- self.target.log.debug("", exc_info=e)
268
-
269
- # Temporary guest users
270
- try:
271
- for _, entry in self._config["root-config"]["user"]["group"].get("guestgroup", {}).get("guest", {}).items():
272
- try:
273
- password = decrypt_password(entry.get("password")[-1])
274
- except (ValueError, RuntimeError):
275
- password = ":".join(entry.get("password"))
276
-
277
- yield FortiOSUserRecord(
278
- name=entry["user-id"][0],
279
- password=password,
280
- groups=["guestgroup"],
281
- home=None,
282
- _target=self.target,
283
- )
284
- except KeyError as e:
285
- self.target.log.warning("Exception while parsing FortiOS temporary guest users")
286
- self.target.log.debug("", exc_info=e)
237
+ if self._config.get("global-config", {}).get("system", {}).get("admin", {}).get("user"):
238
+ try:
239
+ for username, entry in self._config["global-config"]["system"]["admin"]["user"].items():
240
+ yield FortiOSUserRecord(
241
+ name=username,
242
+ password=":".join(entry.get("password", [])),
243
+ groups=list(entry.get("profileid", [])),
244
+ home="/root",
245
+ _target=self.target,
246
+ )
247
+ except KeyError as e:
248
+ self.target.log.warning("Exception while parsing FortiManager admin users")
249
+ self.target.log.debug("", exc_info=e)
250
+
251
+ if self._config.get("root-config"):
252
+ # Local users
253
+ try:
254
+ local_groups = local_groups_to_users(self._config["root-config"]["user"]["group"])
255
+ for username, entry in self._config["root-config"]["user"].get("local", {}).items():
256
+ try:
257
+ password = decrypt_password(entry["passwd"][-1])
258
+ except (ValueError, RuntimeError):
259
+ password = ":".join(entry.get("passwd", []))
260
+
261
+ yield FortiOSUserRecord(
262
+ name=username,
263
+ password=password,
264
+ groups=local_groups.get(username, []),
265
+ home=None,
266
+ _target=self.target,
267
+ )
268
+ except KeyError as e:
269
+ self.target.log.warning("Exception while parsing FortiOS local users")
270
+ self.target.log.debug("", exc_info=e)
271
+
272
+ # Temporary guest users
273
+ try:
274
+ for _, entry in (
275
+ self._config["root-config"]["user"]["group"].get("guestgroup", {}).get("guest", {}).items()
276
+ ):
277
+ try:
278
+ password = decrypt_password(entry.get("password")[-1])
279
+ except (ValueError, RuntimeError):
280
+ password = ":".join(entry.get("password"))
281
+
282
+ yield FortiOSUserRecord(
283
+ name=entry["user-id"][0],
284
+ password=password,
285
+ groups=["guestgroup"],
286
+ home=None,
287
+ _target=self.target,
288
+ )
289
+ except KeyError as e:
290
+ self.target.log.warning("Exception while parsing FortiOS temporary guest users")
291
+ self.target.log.debug("", exc_info=e)
287
292
 
288
293
  @export(property=True)
289
294
  def os(self) -> str:
290
295
  return OperatingSystem.FORTIOS.value
291
296
 
292
297
  @export(property=True)
293
- def architecture(self) -> Optional[str]:
298
+ def architecture(self) -> str | None:
294
299
  """Return architecture FortiOS runs on."""
295
- paths = ["/lib/libav.so", "/bin/ctr"]
296
- for path in paths:
297
- if self.target.fs.path(path).exists():
298
- return self._get_architecture(path=path)
300
+ for path in ["/lib/libav.so", "/bin/ctr", "/bin/grep"]:
301
+ if (bin := self.target.fs.path(path)).exists():
302
+ return self._get_architecture(path=bin)
299
303
 
300
304
 
301
305
  class ConfigNode(dict):
@@ -528,7 +532,7 @@ def decrypt_rootfs(fh: BinaryIO, key: bytes, iv: bytes) -> BinaryIO:
528
532
  return BytesIO(result)
529
533
 
530
534
 
531
- def _kdf_7_4_x(key_data: Union[str, bytes]) -> tuple[bytes, bytes]:
535
+ def _kdf_7_4_x(key_data: str | bytes) -> tuple[bytes, bytes]:
532
536
  """Derive 32 byte key and 16 byte IV from 32 byte seed.
533
537
 
534
538
  As the IV needs to be 16 bytes, we return the first 16 bytes of the sha256 hash.
@@ -542,7 +546,7 @@ def _kdf_7_4_x(key_data: Union[str, bytes]) -> tuple[bytes, bytes]:
542
546
  return key, iv
543
547
 
544
548
 
545
- def get_kernel_hash(sysvol: Filesystem) -> Optional[str]:
549
+ def get_kernel_hash(sysvol: Filesystem) -> str | None:
546
550
  """Return the SHA256 hash of the (compressed) kernel."""
547
551
  kernel_files = ["flatkc", "vmlinuz", "vmlinux"]
548
552
  for k in kernel_files:
@@ -12,6 +12,14 @@ from dissect.target.helpers.record import WindowsUserRecord
12
12
  from dissect.target.plugin import OperatingSystem, OSPlugin, export
13
13
  from dissect.target.target import Target
14
14
 
15
+ ARCH_MAP = {
16
+ "x86": 32,
17
+ "IA64": 64,
18
+ "ARM64": 64,
19
+ "EM64T": 64,
20
+ "AMD64": 64,
21
+ }
22
+
15
23
 
16
24
  class WindowsPlugin(OSPlugin):
17
25
  CURRENT_VERSION_KEY = "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion"
@@ -101,7 +109,7 @@ class WindowsPlugin(OSPlugin):
101
109
 
102
110
  @export(property=True)
103
111
  def ips(self) -> list[str]:
104
- return self.target.network.ips()
112
+ return list(set(map(str, self.target.network.ips())))
105
113
 
106
114
  def _get_version_reg_value(self, value_name: str) -> Any:
107
115
  try:
@@ -265,19 +273,11 @@ class WindowsPlugin(OSPlugin):
265
273
  Dict: arch: architecture, bitness: bits
266
274
  """
267
275
 
268
- arch_strings = {
269
- "x86": 32,
270
- "IA64": 64,
271
- "ARM64": 64,
272
- "EM64T": 64,
273
- "AMD64": 64,
274
- }
275
-
276
276
  key = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
277
277
 
278
278
  try:
279
279
  arch = self.target.registry.key(key).value("PROCESSOR_ARCHITECTURE").value
280
- bits = arch_strings.get(arch)
280
+ bits = ARCH_MAP.get(arch)
281
281
 
282
282
  # return {"arch": arch, "bitness": bits}
283
283
  if bits == 64:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from enum import IntEnum
4
+ from functools import lru_cache
4
5
  from typing import Iterator
5
6
 
6
7
  from dissect.util.ts import wintimestamp
@@ -12,6 +13,7 @@ from dissect.target.exceptions import (
12
13
  from dissect.target.helpers.record import WindowsInterfaceRecord
13
14
  from dissect.target.helpers.regutil import RegistryKey
14
15
  from dissect.target.plugins.general.network import NetworkPlugin
16
+ from dissect.target.target import Target
15
17
 
16
18
 
17
19
  class IfTypes(IntEnum):
@@ -222,15 +224,32 @@ def _try_value(subkey: RegistryKey, value: str) -> str | list | None:
222
224
  return None
223
225
 
224
226
 
227
+ def _get_config_value(key: RegistryKey, name: str) -> set:
228
+ value = _try_value(key, name)
229
+ if not value or value in ("", "0.0.0.0", None, [], ["0.0.0.0"]):
230
+ return set()
231
+
232
+ if isinstance(value, list):
233
+ return set(value)
234
+
235
+ return {value}
236
+
237
+
225
238
  class WindowsNetworkPlugin(NetworkPlugin):
226
239
  """Windows network interface plugin."""
227
240
 
241
+ def __init__(self, target: Target):
242
+ super().__init__(target)
243
+ self._extract_network_device_config = lru_cache(128)(self._extract_network_device_config)
244
+
228
245
  def _interfaces(self) -> Iterator[WindowsInterfaceRecord]:
246
+ """Yields found Windows interfaces used by :meth:`NetworkPlugin.interfaces() <dissect.target.plugins.general.network.NetworkPlugin.interfaces>`.""" # noqa: E501
247
+
229
248
  # Get all the network interfaces
230
- for keys in self.target.registry.keys(
249
+ for key in self.target.registry.keys(
231
250
  "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e972-e325-11ce-bfc1-08002be10318}"
232
251
  ):
233
- for subkey in keys.subkeys():
252
+ for subkey in key.subkeys():
234
253
  device_info = {}
235
254
 
236
255
  if (net_cfg_instance_id := _try_value(subkey, "NetCfgInstanceId")) is None:
@@ -239,24 +258,26 @@ class WindowsNetworkPlugin(NetworkPlugin):
239
258
 
240
259
  # Extract the network device configuration for given interface id
241
260
  config = self._extract_network_device_config(net_cfg_instance_id)
242
- if config is None or all(not conf for conf in config):
243
- # if no configuration is found or all configurations are empty, skip this network interface
244
- continue
245
261
 
246
- # Extract the network device name for given interface id
247
- name_key = self.target.registry.key(
248
- f"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\"
249
- f"{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{net_cfg_instance_id}\\Connection"
250
- )
251
- if value_name := _try_value(name_key, "Name"):
252
- device_info["name"] = value_name
253
-
254
- # Extract the metric value from the REGISTRY_KEY_INTERFACE key
255
- interface_key = self.target.registry.key(
256
- f"HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{net_cfg_instance_id}"
257
- )
258
- if value_metric := _try_value(interface_key, "InterfaceMetric"):
259
- device_info["metric"] = value_metric
262
+ # Extract a network device name for given interface id
263
+ try:
264
+ name_key = self.target.registry.key(
265
+ f"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{net_cfg_instance_id}\\Connection" # noqa: E501
266
+ )
267
+ if value_name := _try_value(name_key, "Name"):
268
+ device_info["name"] = value_name
269
+ except RegistryKeyNotFoundError:
270
+ pass
271
+
272
+ # Extract the metric value from the interface registry key
273
+ try:
274
+ interface_key = self.target.registry.key(
275
+ f"HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{net_cfg_instance_id}" # noqa: E501
276
+ )
277
+ if value_metric := _try_value(interface_key, "InterfaceMetric"):
278
+ device_info["metric"] = value_metric
279
+ except RegistryKeyNotFoundError:
280
+ pass
260
281
 
261
282
  # Extract the rest of the device information
262
283
  device_info["mac"] = _try_value(subkey, "NetworkAddress")
@@ -270,26 +291,57 @@ class WindowsNetworkPlugin(NetworkPlugin):
270
291
 
271
292
  # Yield a record for each non-empty configuration
272
293
  for conf in config:
273
- if conf:
274
- # Create a copy of device_info to avoid overwriting
275
- record_info = device_info.copy()
276
- record_info.update(conf)
277
- yield WindowsInterfaceRecord(
278
- **record_info,
279
- source=f"HKLM\\SYSTEM\\{subkey.path}",
280
- _target=self.target,
281
- )
294
+ # If no configuration is found or all configurations are empty,
295
+ # skip this network interface.
296
+ if not conf or not any(
297
+ [
298
+ conf["dns"],
299
+ conf["ip"],
300
+ conf["gateway"],
301
+ conf["subnetmask"],
302
+ conf["search_domain"],
303
+ ]
304
+ ):
305
+ continue
306
+
307
+ # Create a copy of device_info to avoid overwriting
308
+ record_info = device_info.copy()
309
+ record_info.update(conf)
310
+ yield WindowsInterfaceRecord(
311
+ **record_info,
312
+ source=f"HKLM\\SYSTEM\\{subkey.path}",
313
+ _target=self.target,
314
+ )
282
315
 
283
316
  def _extract_network_device_config(
284
317
  self, interface_id: str
285
318
  ) -> list[dict[str, str | list], dict[str, str | list]] | None:
286
- dhcp_config = {}
287
- static_config = {}
319
+ """Extract network device configuration from the given interface_id for all ControlSets on the system."""
320
+
321
+ dhcp_config = {
322
+ "gateway": set(),
323
+ "ip": set(),
324
+ "dns": set(),
325
+ "subnetmask": set(),
326
+ "search_domain": set(),
327
+ "network": set(),
328
+ }
329
+
330
+ static_config = {
331
+ "ip": set(),
332
+ "dns": set(),
333
+ "subnetmask": set(),
334
+ "search_domain": set(),
335
+ "gateway": set(),
336
+ "network": set(),
337
+ }
288
338
 
289
339
  # Get the registry keys for the given interface id
290
340
  try:
291
- keys = self.target.registry.key(
292
- f"HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{interface_id}"
341
+ keys = list(
342
+ self.target.registry.keys(
343
+ f"HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{interface_id}"
344
+ )
293
345
  )
294
346
  except RegistryKeyNotFoundError:
295
347
  return None
@@ -297,69 +349,33 @@ class WindowsNetworkPlugin(NetworkPlugin):
297
349
  if not len(keys):
298
350
  return None
299
351
 
300
- # Extract DHCP configuration from the registry
301
- dhcp_gateway = _try_value(keys, "DhcpDefaultGateway")
302
- if dhcp_gateway not in ["", "0.0.0.0", None, []]:
303
- dhcp_config["gateway"] = dhcp_gateway
304
-
305
- dhcp_ip = _try_value(keys, "DhcpIPAddress")
306
- if dhcp_ip not in ["", "0.0.0.0", None]:
307
- dhcp_config["ip"] = [dhcp_ip]
308
-
309
- dhcp_dns = _try_value(keys, "DhcpNameServer")
310
- if dhcp_dns not in ["", "0.0.0.0", None]:
311
- dhcp_config["dns"] = dhcp_dns.split(" ")
312
-
313
- dhcp_subnetmask = _try_value(keys, "DhcpSubnetMask")
314
- if dhcp_subnetmask not in ["", "0.0.0.0", None]:
315
- dhcp_config["subnetmask"] = [dhcp_subnetmask]
316
-
317
- dhcp_domain = _try_value(keys, "DhcpDomain")
318
- if dhcp_domain not in ["", None]:
319
- dhcp_config["search_domain"] = [dhcp_domain]
352
+ for key in keys:
353
+ # Extract DHCP configuration from the registry
354
+ dhcp_config["gateway"].update(_get_config_value(key, "DhcpDefaultGateway"))
355
+ dhcp_config["ip"].update(_get_config_value(key, "DhcpIPAddress"))
356
+ dhcp_config["subnetmask"].update(_get_config_value(key, "DhcpSubnetMask"))
357
+ dhcp_config["search_domain"].update(_get_config_value(key, "DhcpDomain"))
358
+ dhcp_config["dns"].update(_get_config_value(key, "DhcpNameServer"))
359
+
360
+ # Extract static configuration from the registry
361
+ static_config["gateway"].update(_get_config_value(key, "DefaultGateway"))
362
+ static_config["dns"].update(_get_config_value(key, "NameServer"))
363
+ static_config["search_domain"].update(_get_config_value(key, "Domain"))
364
+ static_config["ip"].update(_get_config_value(key, "IPAddress"))
365
+ static_config["subnetmask"].update(_get_config_value(key, "SubnetMask"))
320
366
 
321
367
  if len(dhcp_config) > 0:
322
- dhcp_enable = _try_value(keys, "EnableDHCP")
323
- dhcp_config["enabled"] = dhcp_enable == 1
368
+ dhcp_config["enabled"] = _try_value(key, "EnableDHCP") == 1
324
369
  dhcp_config["dhcp"] = True
325
370
 
326
- # Extract static configuration from the registry
327
- static_gateway = _try_value(keys, "DefaultGateway")
328
- if static_gateway not in ["", None, []]:
329
- static_config["gateway"] = static_gateway
330
-
331
- static_ip = _try_value(keys, "IPAddress")
332
- if static_ip not in ["", "0.0.0.0", ["0.0.0.0"], None, []]:
333
- static_config["ip"] = static_ip if isinstance(static_ip, list) else [static_ip]
334
-
335
- static_dns = _try_value(keys, "NameServer")
336
- if static_dns not in ["", "0.0.0.0", None]:
337
- static_config["dns"] = static_dns.split(",")
338
-
339
- static_subnetmask = _try_value(keys, "SubnetMask")
340
- if static_subnetmask not in ["", "0.0.0.0", ["0.0.0.0"], None, []]:
341
- static_config["subnetmask"] = (
342
- static_subnetmask if isinstance(static_subnetmask, list) else [static_subnetmask]
343
- )
344
-
345
- static_domain = _try_value(keys, "Domain")
346
- if static_domain not in ["", None]:
347
- static_config["search_domain"] = [static_domain]
348
-
349
371
  if len(static_config) > 0:
350
372
  static_config["enabled"] = None
351
373
  static_config["dhcp"] = False
352
374
 
353
- # Combine ip and subnetmask for extraction
354
- combined_configs = [
355
- (dhcp_config, dhcp_config.get("ip", []), dhcp_config.get("subnetmask", [])),
356
- (static_config, static_config.get("ip", []), static_config.get("subnetmask", [])),
357
- ]
358
-
359
375
  # Iterate over combined ip/subnet lists
360
- for config, ips, subnet_masks in combined_configs:
361
- for network_address in self.calculate_network(ips, subnet_masks):
362
- config.setdefault("network", []).append(network_address)
376
+ for config in (dhcp_config, static_config):
377
+ if (ips := config.get("ip")) and (masks := config.get("subnetmask")):
378
+ config["network"].update(set(self.calculate_network(ips, masks)))
363
379
 
364
380
  # Return both configurations
365
381
  return [dhcp_config, static_config]
@@ -137,7 +137,7 @@ def print_target_info(target: Target) -> None:
137
137
  continue
138
138
 
139
139
  if isinstance(value, list):
140
- value = ", ".join(value)
140
+ value = ", ".join(map(str, value))
141
141
 
142
142
  if isinstance(value, datetime):
143
143
  value = value.isoformat(timespec="microseconds")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.20.dev48
3
+ Version: 3.20.dev50
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
@@ -31,7 +31,7 @@ Requires-Dist: dissect.ntfs <4,>=3.4
31
31
  Requires-Dist: dissect.regf <4,>=3.3
32
32
  Requires-Dist: dissect.util <4,>=3
33
33
  Requires-Dist: dissect.volume <4,>=2
34
- Requires-Dist: flow.record ~=3.16.0
34
+ Requires-Dist: flow.record ~=3.17.0
35
35
  Requires-Dist: structlog
36
36
  Provides-Extra: cb
37
37
  Requires-Dist: dissect.target[full] ; extra == 'cb'
@@ -189,14 +189,14 @@ dissect/target/plugins/general/config.py,sha256=Mdy9uhWn4OJ96zfXpLgjVifV5SrViqHn
189
189
  dissect/target/plugins/general/default.py,sha256=8W_9JV3jKEeETlyTrB25sACoIIFmmO8wlVU5Zoi51W0,1425
190
190
  dissect/target/plugins/general/example.py,sha256=mYAbhtfQmUBj2L2C1DFt9bWpI7rQLJwCIYUsNLcA_pc,6053
191
191
  dissect/target/plugins/general/loaders.py,sha256=z_t55Q1XNjmTOxq0E4tCwpZ-utFyxiLKyAJIFgJMlJs,1508
192
- dissect/target/plugins/general/network.py,sha256=J8aMfUJ7dgwqpaXzZpHHyOUYg-cPef2Qaa3krUj-A-Q,3225
192
+ dissect/target/plugins/general/network.py,sha256=TWfSdI5fTgwe1_nV7u_ldtvvRwgmkVFLd4XFzy4cEZU,3257
193
193
  dissect/target/plugins/general/osinfo.py,sha256=oU-vmMiA-oaSEQWTSyn6-yQiH2sLQT6aTQHRd0677wo,1415
194
194
  dissect/target/plugins/general/plugins.py,sha256=9KJ70YvYwBfxt19C9yISv8YE4mOdHNvP16fTCTHC68U,6033
195
195
  dissect/target/plugins/general/scrape.py,sha256=Fz7BNXflvuxlnVulyyDhLpyU8D_hJdH6vWVtER9vjTg,6651
196
196
  dissect/target/plugins/general/users.py,sha256=yy9gvRXfN9BT71v4Xqo5hpwfgN9he9Otu8TBPZ_Tegs,3009
197
197
  dissect/target/plugins/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
198
198
  dissect/target/plugins/os/unix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
199
- dissect/target/plugins/os/unix/_os.py,sha256=prvcuIkJ1jmbb_g1CtgHmmJUcGjFaZn3yU6VPNMJ27k,15061
199
+ dissect/target/plugins/os/unix/_os.py,sha256=mk2yxWGqdZMzdr8hyYT5nyjSIEN3F5JuoPaaiz0Rvf8,15311
200
200
  dissect/target/plugins/os/unix/applications.py,sha256=AUgZRP35FzswGyFyChj2o4dfGO34Amc6nqHgiMEaqdI,3129
201
201
  dissect/target/plugins/os/unix/cronjobs.py,sha256=tgWQ3BUZpfyvRzodMwGtwFUdPjZ17k7ZRbZ9Q8wmXPk,3393
202
202
  dissect/target/plugins/os/unix/datetime.py,sha256=gKfBdPyUirt3qmVYfOJ1oZXRPn8wRzssbZxR_ARrtk8,1518
@@ -218,7 +218,7 @@ dissect/target/plugins/os/unix/bsd/ios/_os.py,sha256=VlJXGxkQZ4RbGbSC-FlbR2YWOJp
218
218
  dissect/target/plugins/os/unix/bsd/openbsd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
219
219
  dissect/target/plugins/os/unix/bsd/openbsd/_os.py,sha256=9npz-osM-wHmjOACUqof5N5HJeps7J8KuyenUS5MZDs,923
220
220
  dissect/target/plugins/os/unix/bsd/osx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
221
- dissect/target/plugins/os/unix/bsd/osx/_os.py,sha256=_ceh3R9hHsQrYM-vX8c_9igT3luM3oQebTMem8vFb1E,3497
221
+ dissect/target/plugins/os/unix/bsd/osx/_os.py,sha256=hNFB1rwahLwgZD1kc3T4xalFusT88EoxM2Mh-8jOW_w,3440
222
222
  dissect/target/plugins/os/unix/bsd/osx/network.py,sha256=0Qf1jsCDNPmc_L-AmrvHjXaN_x-AtT1Ow3tdQOvFRsk,3734
223
223
  dissect/target/plugins/os/unix/bsd/osx/user.py,sha256=5rsGhsntBW9IXYIOrLpfYpSsJcBDL61QJkuZ456lXlE,2411
224
224
  dissect/target/plugins/os/unix/esxi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -226,7 +226,7 @@ dissect/target/plugins/os/unix/esxi/_os.py,sha256=s6pAgUyfHh3QcY6sgvk5uVMmLvqK1t
226
226
  dissect/target/plugins/os/unix/etc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
227
227
  dissect/target/plugins/os/unix/etc/etc.py,sha256=QngR0nA1azvbNTau4U9-jKOjSoGdyduDpyEna_6yxWY,2636
228
228
  dissect/target/plugins/os/unix/linux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
229
- dissect/target/plugins/os/unix/linux/_os.py,sha256=W5ok4TS0nZfR6x9vk12FAjGhZyS5cNrdxFeFpQkVOrQ,3037
229
+ dissect/target/plugins/os/unix/linux/_os.py,sha256=k1aHhWqocSHMVbF54VDw9wqwa0QSToOa69TMKAyQcxw,2979
230
230
  dissect/target/plugins/os/unix/linux/cmdline.py,sha256=n_Uetoplx33XpIY27oPtMaw1E2AbAEeGLCSkxHshWgY,1673
231
231
  dissect/target/plugins/os/unix/linux/environ.py,sha256=n7KttVzUtBHTIXQuS1DI5Azv6tM__d9gGqhPR_3ArIE,1932
232
232
  dissect/target/plugins/os/unix/linux/iptables.py,sha256=qTzY5PHHXA33WnPYb5NESgoSwI7ECZ8YPoEe_Fmln-8,6045
@@ -251,7 +251,7 @@ dissect/target/plugins/os/unix/linux/debian/vyos/__init__.py,sha256=47DEQpj8HBSa
251
251
  dissect/target/plugins/os/unix/linux/debian/vyos/_os.py,sha256=TPjcfv1n68RCe3Er4aCVQwQDCZwJT-NLvje3kPjDfhk,1744
252
252
  dissect/target/plugins/os/unix/linux/fortios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
253
253
  dissect/target/plugins/os/unix/linux/fortios/_keys.py,sha256=jDDHObfsUn9BGoIir9p4J_-rg9rI1rgoOfnL3R3lg4o,123358
254
- dissect/target/plugins/os/unix/linux/fortios/_os.py,sha256=Cyw6KyGNc-uZn2WDlD-7G9K7swe_ofxwykIZeQRGYKU,19416
254
+ dissect/target/plugins/os/unix/linux/fortios/_os.py,sha256=381VI9TDMR2-XPwLsvCU8hcRgTz1H5yJ-q_sCNQzSiM,19790
255
255
  dissect/target/plugins/os/unix/linux/fortios/generic.py,sha256=dc6YTDLV-VZq9k8IWmY_PE0sTGkkp3yamR-cYNUCtes,1265
256
256
  dissect/target/plugins/os/unix/linux/fortios/locale.py,sha256=Pe7Bdj8UemCiktLeQnQ50TpY_skARAzRJA0ewAB4710,5243
257
257
  dissect/target/plugins/os/unix/linux/redhat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -274,7 +274,7 @@ dissect/target/plugins/os/unix/log/lastlog.py,sha256=Wr3-2n1-GwckN9mSx-yM55N6_L0
274
274
  dissect/target/plugins/os/unix/log/messages.py,sha256=XtjZ0a2budgQm_K5JT3fMf7JcjuD0AelcD3zOFN2xpI,5732
275
275
  dissect/target/plugins/os/unix/log/utmp.py,sha256=k2A69s2qUT2JunJrH8GO6nQ0zMDotXMTaj8OzQ7ljj8,7336
276
276
  dissect/target/plugins/os/windows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
277
- dissect/target/plugins/os/windows/_os.py,sha256=-Bsp9696JqU7luh_AbqojzG9BxVdYIFl5Ma-LiFBQBo,12505
277
+ dissect/target/plugins/os/windows/_os.py,sha256=SUTfCPEVi2ADfjsQQJad6dEsnKUzRtsKJXOlEuiT9Xk,12462
278
278
  dissect/target/plugins/os/windows/activitiescache.py,sha256=BbGD-vETHm1IRMoazVer_vqSJIoQxxhWcJ_xlBeOMds,6899
279
279
  dissect/target/plugins/os/windows/adpolicy.py,sha256=ul8lKlG9ExABnd6yVLMPFFgVxN74CG4T3MvcRuBLHJc,7158
280
280
  dissect/target/plugins/os/windows/amcache.py,sha256=1jq-S80_FIzGegrqQ6HqrjmaAPTyxyn69HxnbRBlaUc,27608
@@ -288,7 +288,7 @@ dissect/target/plugins/os/windows/generic.py,sha256=Z4eb9SrVMiO871bi5GS8V-rGF6QJ
288
288
  dissect/target/plugins/os/windows/jumplist.py,sha256=3gZk6O1B3lKK2Jxe0B-HapOCEehk94CYNvCVDpQC9nQ,11773
289
289
  dissect/target/plugins/os/windows/lnk.py,sha256=KTqhw0JMW-KjAxe4xlRDNSRSx-th-_nPVgTGyBaKmW0,7891
290
290
  dissect/target/plugins/os/windows/locale.py,sha256=QiLWGgWrGBGHiXgep5iSOo6VNim4YC-xd4MdW0BUJPA,2486
291
- dissect/target/plugins/os/windows/network.py,sha256=SRBoWD2tFsSPpW6ZoFVANaPsbM0okpD8I76Rrf2q0QE,10833
291
+ dissect/target/plugins/os/windows/network.py,sha256=cffJmQwHJmTAGZkAEKKGxNi1ZYLiDomfOcPczZn85Fo,11284
292
292
  dissect/target/plugins/os/windows/notifications.py,sha256=xxfMEY_noDxMVqvT3QS1a3j-X3qAYikOtT6v2owxuCY,17480
293
293
  dissect/target/plugins/os/windows/prefetch.py,sha256=wbbYoy05gWbJfRsM2ci4wPG7kM58OocVwXD3hkQlbRw,10647
294
294
  dissect/target/plugins/os/windows/recyclebin.py,sha256=zx58hDCvcrD_eJl9nJmr_i80krSN03ya8nQzWFr2Tw0,4917
@@ -358,7 +358,7 @@ dissect/target/tools/build_pluginlist.py,sha256=5fomcuMwsVzcnYx5Htf5f9lSwsLeUUvo
358
358
  dissect/target/tools/dd.py,sha256=rTM-lgXxrYBpVAtJqFqAatDz45bLoD8-mFt_59Q3Lio,1928
359
359
  dissect/target/tools/fs.py,sha256=3Ny8zoooVeeF7OUkQ0nxZVdEaQeU7vPRjDOYhz6XfRA,5385
360
360
  dissect/target/tools/fsutils.py,sha256=q0t9gFwKHaPr2Ya-MN2o4LsYledde7kp2DZZTd8roIc,8314
361
- dissect/target/tools/info.py,sha256=8nnbqFUYeo4NLPE7ORcTBcDL-TioGB2Nqc1TKcu5qdY,5715
361
+ dissect/target/tools/info.py,sha256=t2bWENeyaEh87ayE_brdKvz9kHAWOLqkKJcGixl6hGo,5725
362
362
  dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
363
363
  dissect/target/tools/mount.py,sha256=8GRYnu4xEmFBHxuIZAYhOMyyTGX8fat1Ou07DNiUnW4,3945
364
364
  dissect/target/tools/query.py,sha256=OYWVmCx2nFx85x1r8Y6D17UdUIi8PJm304xBfT-H8vs,15605
@@ -378,10 +378,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
378
378
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
379
379
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
380
380
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
381
- dissect.target-3.20.dev48.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
382
- dissect.target-3.20.dev48.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
383
- dissect.target-3.20.dev48.dist-info/METADATA,sha256=coXjiskalhbUYyedrn0Fe49ZmJvIbeAaMYawTB3Q9QQ,12897
384
- dissect.target-3.20.dev48.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
385
- dissect.target-3.20.dev48.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
386
- dissect.target-3.20.dev48.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
387
- dissect.target-3.20.dev48.dist-info/RECORD,,
381
+ dissect.target-3.20.dev50.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
382
+ dissect.target-3.20.dev50.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
383
+ dissect.target-3.20.dev50.dist-info/METADATA,sha256=GXXc4LbwWhRuEYw8FhQSkRDdbCUApDrAwJQss7yANgY,12897
384
+ dissect.target-3.20.dev50.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
385
+ dissect.target-3.20.dev50.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
386
+ dissect.target-3.20.dev50.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
387
+ dissect.target-3.20.dev50.dist-info/RECORD,,