dissect.target 3.20.dev4__py3-none-any.whl → 3.20.dev6__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.
@@ -144,6 +144,7 @@ EmptyRecord = RecordDescriptor(
144
144
  )
145
145
 
146
146
  COMMON_INTERFACE_ELEMENTS = [
147
+ ("string", "source"),
147
148
  ("string", "name"),
148
149
  ("string", "type"),
149
150
  ("boolean", "enabled"),
@@ -151,7 +152,6 @@ COMMON_INTERFACE_ELEMENTS = [
151
152
  ("net.ipaddress[]", "dns"),
152
153
  ("net.ipaddress[]", "ip"),
153
154
  ("net.ipaddress[]", "gateway"),
154
- ("string", "source"),
155
155
  ]
156
156
 
157
157
 
@@ -165,8 +165,13 @@ WindowsInterfaceRecord = TargetRecordDescriptor(
165
165
  [
166
166
  *COMMON_INTERFACE_ELEMENTS,
167
167
  ("varint", "vlan"),
168
- ("string", "metric"),
168
+ ("net.ipnetwork[]", "network"),
169
+ ("varint", "metric"),
170
+ ("stringlist", "search_domain"),
171
+ ("datetime", "first_connected"),
169
172
  ("datetime", "last_connected"),
173
+ ("net.ipaddress[]", "subnetmask"),
174
+ ("boolean", "dhcp"),
170
175
  ],
171
176
  )
172
177
 
dissect/target/loader.py CHANGED
@@ -177,7 +177,6 @@ def open(item: Union[str, Path], *args, **kwargs) -> Loader:
177
177
  register("local", "LocalLoader")
178
178
  register("remote", "RemoteLoader")
179
179
  register("mqtt", "MQTTLoader")
180
- register("targetd", "TargetdLoader")
181
180
  register("asdf", "AsdfLoader")
182
181
  register("tar", "TarLoader")
183
182
  register("vmx", "VmxLoader")
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import argparse
3
4
  import atexit
4
5
  import logging
5
6
  import math
6
7
  import os
8
+ import re
7
9
  import ssl
8
10
  import sys
9
11
  import time
@@ -280,7 +282,7 @@ class Broker:
280
282
  factor = 1
281
283
 
282
284
  def __init__(
283
- self, broker: Broker, port: str, key: str, crt: str, ca: str, case: str, username: str, password: str, **kwargs
285
+ self, broker: str, port: str, key: str, crt: str, ca: str, case: str, username: str, password: str, **kwargs
284
286
  ):
285
287
  self.broker_host = broker
286
288
  self.broker_port = int(port)
@@ -352,8 +354,7 @@ class Broker:
352
354
  log.error(f"Failed to decode payload for hostname {hostname}: {e}")
353
355
  return
354
356
 
355
- # The payload with the username and password is comma separated
356
- print(f'"{hostname}",{decoded_payload}')
357
+ print(decoded_payload)
357
358
 
358
359
  def _on_log(self, client: mqtt.Client, userdata: Any, log_level: int, message: str) -> None:
359
360
  log.debug(message)
@@ -423,16 +424,97 @@ class Broker:
423
424
  self.mqtt_client.loop_start()
424
425
 
425
426
 
426
- @arg("--mqtt-peers", type=int, dest="peers", help="minimum number of peers to await for first alias")
427
- @arg("--mqtt-case", dest="case", help="case name (broker will determine if you are allowed to access this data)")
428
- @arg("--mqtt-port", type=int, dest="port", help="broker connection port")
429
- @arg("--mqtt-broker", dest="broker", help="broker ip-address")
430
- @arg("--mqtt-key", dest="key", help="private key file")
431
- @arg("--mqtt-crt", dest="crt", help="client certificate file")
432
- @arg("--mqtt-ca", dest="ca", help="certificate authority file")
427
+ def strictly_positive(value: str) -> int:
428
+ """
429
+ Validates that the provided value is a strictly positive integer.
430
+
431
+ This function is intended to be used as a type for argparse arguments.
432
+
433
+ Args:
434
+ value (str): The value to validate.
435
+
436
+ Returns:
437
+ int: The validated integer value.
438
+
439
+ Raises:
440
+ argparse.ArgumentTypeError: If the value is not a strictly positive integer.
441
+ """
442
+ try:
443
+ strictly_positive_value = int(value)
444
+ if strictly_positive_value < 1:
445
+ raise argparse.ArgumentTypeError("Value must be larger than or equal to 1.")
446
+ return strictly_positive_value
447
+ except ValueError:
448
+ raise argparse.ArgumentTypeError(f"Invalid integer value specified: '{value}'")
449
+
450
+
451
+ def port(value: str) -> int:
452
+ """
453
+ Convert a string value to an integer representing a valid port number.
454
+
455
+ This function is intended to be used as a type for argparse arguments.
456
+
457
+ Args:
458
+ value (str): The string representation of the port number.
459
+ Returns:
460
+ int: The port number as an integer.
461
+ Raises:
462
+ argparse.ArgumentTypeError: If the port number is not an integer or out of the valid range (1-65535).
463
+ """
464
+
465
+ try:
466
+ port = int(value)
467
+ if port < 1 or port > 65535:
468
+ raise argparse.ArgumentTypeError("Port number must be between 1 and 65535.")
469
+ return port
470
+ except ValueError:
471
+ raise argparse.ArgumentTypeError(f"Invalid port number specified: '{value}'")
472
+
473
+
474
+ def case(value: str) -> str:
475
+ """
476
+ Validates that the given value is a valid case name consisting of
477
+ alphanumeric characters and underscores only.
478
+
479
+ This function is intended to be used as a type for argparse arguments.
480
+
481
+ Args:
482
+ value (str): The case name to validate.
483
+
484
+ Returns:
485
+ str: The validated case name if it matches the required pattern.
486
+
487
+ Raises:
488
+ argparse.ArgumentTypeError: If the case name does not match the required pattern.
489
+ """
490
+
491
+ if re.match(r"^[a-zA-Z0-9_]+$", value):
492
+ return value
493
+
494
+ raise argparse.ArgumentTypeError(f"Invalid case name specified: '{value}'")
495
+
496
+
497
+ @arg(
498
+ "--mqtt-peers",
499
+ type=strictly_positive,
500
+ dest="peers",
501
+ default=1,
502
+ help="minimum number of peers to await for first alias",
503
+ )
504
+ @arg(
505
+ "--mqtt-case",
506
+ type=case,
507
+ dest="case",
508
+ help="case name (broker will determine if you are allowed to access this data)",
509
+ )
510
+ @arg("--mqtt-port", type=port, dest="port", default=443, help="broker connection port")
511
+ @arg("--mqtt-broker", default="localhost", dest="broker", help="broker ip-address")
512
+ @arg("--mqtt-key", type=Path, dest="key", required=True, help="private key file")
513
+ @arg("--mqtt-crt", type=Path, dest="crt", required=True, help="client certificate file")
514
+ @arg("--mqtt-ca", type=Path, dest="ca", required=True, help="certificate authority file")
433
515
  @arg("--mqtt-command", dest="command", help="direct command to client(s)")
434
516
  @arg("--mqtt-diag", action="store_true", dest="diag", help="show MQTT diagnostic information")
435
- @arg("--mqtt-username", dest="username", help="Username for connection")
517
+ @arg("--mqtt-username", dest="username", default="mqtt-loader", help="Username for connection")
436
518
  @arg("--mqtt-password", action="store_true", dest="password", help="Ask for password before connecting")
437
519
  class MQTTLoader(Loader):
438
520
  """Load remote targets through a broker."""
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from typing import Any, Iterator, Union
4
4
 
5
5
  from flow.record.fieldtypes.net import IPAddress, IPNetwork
6
+ from flow.record.fieldtypes.net.ipv4 import Address, addr_long, addr_str, mask_to_bits
6
7
 
7
8
  from dissect.target.helpers.record import (
8
9
  MacInterfaceRecord,
@@ -80,3 +81,10 @@ class NetworkPlugin(Plugin):
80
81
  for interface in self.interfaces():
81
82
  if any(ip_addr in cidr for ip_addr in interface.ip):
82
83
  yield interface
84
+
85
+ def calculate_network(self, ips: int | Address, subnets: int | Address) -> Iterator[str]:
86
+ for ip, subnet_mask in zip(ips, subnets):
87
+ subnet_mask_int = addr_long(subnet_mask)
88
+ cidr = mask_to_bits(subnet_mask_int)
89
+ network_address = addr_str(addr_long(ip) & subnet_mask_int)
90
+ yield f"{network_address}/{cidr}"
@@ -99,28 +99,7 @@ class WindowsPlugin(OSPlugin):
99
99
 
100
100
  @export(property=True)
101
101
  def ips(self) -> list[str]:
102
- key = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"
103
- fields = ["IPAddress", "DhcpIPAddress"]
104
- ips = set()
105
-
106
- for r in self.target.registry.keys(key):
107
- for s in r.subkeys():
108
- for field in fields:
109
- try:
110
- ip = s.value(field).value
111
- except RegistryValueNotFoundError:
112
- continue
113
-
114
- if isinstance(ip, str):
115
- ip = [ip]
116
-
117
- for i in ip:
118
- if i == "0.0.0.0":
119
- continue
120
-
121
- ips.add(i)
122
-
123
- return list(ips)
102
+ return self.target.network.ips()
124
103
 
125
104
  def _get_version_reg_value(self, value_name: str) -> Any:
126
105
  try:
@@ -0,0 +1,363 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import IntEnum
4
+ from typing import Iterator
5
+
6
+ from dissect.util.ts import wintimestamp
7
+
8
+ from dissect.target.exceptions import (
9
+ RegistryKeyNotFoundError,
10
+ RegistryValueNotFoundError,
11
+ )
12
+ from dissect.target.helpers.record import WindowsInterfaceRecord
13
+ from dissect.target.helpers.regutil import RegistryKey
14
+ from dissect.target.plugins.general.network import NetworkPlugin
15
+
16
+
17
+ class IfTypes(IntEnum):
18
+ OTHER = 1
19
+ REGULAR_1822 = 2
20
+ HDH_1822 = 3
21
+ DDN_X25 = 4
22
+ RFC877_X25 = 5
23
+ ETHERNET_CSMACD = 6
24
+ IS088023_CSMACD = 7
25
+ ISO88024_TOKENBUS = 8
26
+ ISO88025_TOKENRING = 9
27
+ ISO88026_MAN = 10
28
+ STARLAN = 11
29
+ PROTEON_10MBIT = 12
30
+ PROTEON_80MBIT = 13
31
+ HYPERCHANNEL = 14
32
+ FDDI = 15
33
+ LAP_B = 16
34
+ SDLC = 17
35
+ DS1 = 18
36
+ E1 = 19
37
+ BASIC_ISDN = 20
38
+ PRIMARY_ISDN = 21
39
+ PROP_POINT2POINT_SERIAL = 22
40
+ PPP = 23
41
+ SOFTWARE_LOOPBACK = 24
42
+ EON = 25
43
+ ETHERNET_3MBIT = 26
44
+ NSIP = 27
45
+ SLIP = 28
46
+ ULTRA = 29
47
+ DS3 = 30
48
+ SIP = 31
49
+ FRAMERELAY = 32
50
+ RS232 = 33
51
+ PARA = 34
52
+ ARCNET = 35
53
+ ARCNET_PLUS = 36
54
+ ATM = 37
55
+ MIO_X25 = 38
56
+ SONET = 39
57
+ X25_PLE = 40
58
+ ISO88022_LLC = 41
59
+ LOCALTALK = 42
60
+ SMDS_DXI = 43
61
+ FRAMERELAY_SERVICE = 44
62
+ V35 = 45
63
+ HSSI = 46
64
+ HIPPI = 47
65
+ MODEM = 48
66
+ AAL5 = 49
67
+ SONET_PATH = 50
68
+ SONET_VT = 51
69
+ SMDS_ICIP = 52
70
+ PROP_VIRTUAL = 53
71
+ PROP_MULTIPLEXOR = 54
72
+ IEEE80212 = 55
73
+ FIBRECHANNEL = 56
74
+ HIPPIINTERFACE = 57
75
+ FRAMERELAY_INTERCONNECT = 58
76
+ AFLANE_8023 = 59
77
+ AFLANE_8025 = 60
78
+ CCTEMUL = 61
79
+ FASTETHER = 62
80
+ ISDN = 63
81
+ V11 = 64
82
+ V36 = 65
83
+ G703_64K = 66
84
+ G703_2MB = 67
85
+ QLLC = 68
86
+ FASTETHER_FX = 69
87
+ CHANNEL = 70
88
+ IEEE80211 = 71
89
+ IBM370PARCHAN = 72
90
+ ESCON = 73
91
+ DLSW = 74
92
+ ISDN_S = 75
93
+ ISDN_U = 76
94
+ LAP_D = 77
95
+ IPSWITCH = 78
96
+ RSRB = 79
97
+ ATM_LOGICAL = 80
98
+ DS0 = 81
99
+ DS0_BUNDLE = 82
100
+ BSC = 83
101
+ ASYNC = 84
102
+ CNR = 85
103
+ ISO88025R_DTR = 86
104
+ EPLRS = 87
105
+ ARAP = 88
106
+ PROP_CNLS = 89
107
+ HOSTPAD = 90
108
+ TERMPAD = 91
109
+ FRAMERELAY_MPI = 92
110
+ X213 = 93
111
+ ADSL = 94
112
+ RADSL = 95
113
+ SDSL = 96
114
+ VDSL = 97
115
+ ISO88025_CRFPRINT = 98
116
+ MYRINET = 99
117
+ VOICE_EM = 100
118
+ VOICE_FXO = 101
119
+ VOICE_FXS = 102
120
+ VOICE_ENCAP = 103
121
+ VOICE_OVERIP = 104
122
+ ATM_DXI = 105
123
+ ATM_FUNI = 106
124
+ ATM_IMA = 107
125
+ PPPMULTILINKBUNDLE = 108
126
+ IPOVER_CDLC = 109
127
+ IPOVER_CLAW = 110
128
+ STACKTOSTACK = 111
129
+ VIRTUALIPADDRESS = 112
130
+ MPC = 113
131
+ IPOVER_ATM = 114
132
+ ISO88025_FIBER = 115
133
+ TDLC = 116
134
+ GIGABITETHERNET = 117
135
+ HDLC = 118
136
+ LAP_F = 119
137
+ V37 = 120
138
+ X25_MLP = 121
139
+ X25_HUNTGROUP = 122
140
+ TRANSPHDLC = 123
141
+ INTERLEAVE = 124
142
+ FAST = 125
143
+ IP = 126
144
+ DOCSCABLE_MACLAYER = 127
145
+ DOCSCABLE_DOWNSTREAM = 128
146
+ DOCSCABLE_UPSTREAM = 129
147
+ A12MPPSWITCH = 130
148
+ TUNNEL = 131
149
+ COFFEE = 132
150
+ CES = 133
151
+ ATM_SUBINTERFACE = 134
152
+ L2_VLAN = 135
153
+ L3_IPVLAN = 136
154
+ L3_IPXVLAN = 137
155
+ DIGITALPOWERLINE = 138
156
+ MEDIAMAILOVERIP = 139
157
+ DTM = 140
158
+ DCN = 141
159
+ IPFORWARD = 142
160
+ MSDSL = 143
161
+ IEEE1394 = 144
162
+ IF_GSN = 145
163
+ DVBRCC_MACLAYER = 146
164
+ DVBRCC_DOWNSTREAM = 147
165
+ DVBRCC_UPSTREAM = 148
166
+ ATM_VIRTUAL = 149
167
+ MPLS_TUNNEL = 150
168
+ SRP = 151
169
+ VOICEOVERATM = 152
170
+ VOICEOVERFRAMERELAY = 153
171
+ IDSL = 154
172
+ COMPOSITELINK = 155
173
+ SS7_SIGLINK = 156
174
+ PROP_WIRELESS_P2P = 157
175
+ FR_FORWARD = 158
176
+ RFC1483 = 159
177
+ USB = 160
178
+ IEEE8023AD_LAG = 161
179
+ BGP_POLICY_ACCOUNTING = 162
180
+ FRF16_MFR_BUNDLE = 163
181
+ H323_GATEKEEPER = 164
182
+ H323_PROXY = 165
183
+ MPLS = 166
184
+ MF_SIGLINK = 167
185
+ HDSL2 = 168
186
+ SHDSL = 169
187
+ DS1_FDL = 170
188
+ POS = 171
189
+ DVB_ASI_IN = 172
190
+ DVB_ASI_OUT = 173
191
+ PLC = 174
192
+ NFAS = 175
193
+ TR008 = 176
194
+ GR303_RDT = 177
195
+ GR303_IDT = 178
196
+ ISUP = 179
197
+ PROP_DOCS_WIRELESS_MACLAYER = 180
198
+ PROP_DOCS_WIRELESS_DOWNSTREAM = 181
199
+ PROP_DOCS_WIRELESS_UPSTREAM = 182
200
+ HIPERLAN2 = 183
201
+ PROP_BWA_P2MP = 184
202
+ SONET_OVERHEAD_CHANNEL = 185
203
+ DIGITAL_WRAPPER_OVERHEAD_CHANNEL = 186
204
+ AAL2 = 187
205
+ RADIO_MAC = 188
206
+ ATM_RADIO = 189
207
+ IMT = 190
208
+ MVL = 191
209
+ REACH_DSL = 192
210
+ FR_DLCI_ENDPT = 193
211
+ ATM_VCI_ENDPT = 194
212
+ OPTICAL_CHANNEL = 195
213
+ OPTICAL_TRANSPORT = 196
214
+ WWANPP = 243
215
+ WWANPP2 = 244
216
+
217
+
218
+ def _try_value(subkey: RegistryKey, value: str) -> str | list | None:
219
+ try:
220
+ return subkey.value(value).value
221
+ except RegistryValueNotFoundError:
222
+ return None
223
+
224
+
225
+ class WindowsNetworkPlugin(NetworkPlugin):
226
+ def _interfaces(self) -> Iterator[WindowsInterfaceRecord]:
227
+ # Get all the network interfaces
228
+ for keys in self.target.registry.keys(
229
+ "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e972-e325-11ce-bfc1-08002be10318}"
230
+ ):
231
+ for subkey in keys.subkeys():
232
+ device_info = {}
233
+
234
+ if (net_cfg_instance_id := _try_value(subkey, "NetCfgInstanceId")) is None:
235
+ # if no NetCfgInstanceId is found, skip this network interface
236
+ continue
237
+
238
+ # Extract the network device configuration for given interface id
239
+ config = self._extract_network_device_config(net_cfg_instance_id)
240
+ if config is None or all(not conf for conf in config):
241
+ # if no configuration is found or all configurations are empty, skip this network interface
242
+ continue
243
+
244
+ # Extract the network device name for given interface id
245
+ name_key = self.target.registry.key(
246
+ f"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\"
247
+ f"{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{net_cfg_instance_id}\\Connection"
248
+ )
249
+ if value_name := _try_value(name_key, "Name"):
250
+ device_info["name"] = value_name
251
+
252
+ # Extract the metric value from the REGISTRY_KEY_INTERFACE key
253
+ interface_key = self.target.registry.key(
254
+ f"HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{net_cfg_instance_id}"
255
+ )
256
+ if value_metric := _try_value(interface_key, "InterfaceMetric"):
257
+ device_info["metric"] = value_metric
258
+
259
+ # Extract the rest of the device information
260
+ device_info["mac"] = _try_value(subkey, "NetworkAddress")
261
+ device_info["vlan"] = _try_value(subkey, "VlanID")
262
+
263
+ if timestamp := _try_value(subkey, "NetworkInterfaceInstallTimestamp"):
264
+ device_info["first_connected"] = wintimestamp(timestamp)
265
+
266
+ if type_device := _try_value(subkey, "*IfType"):
267
+ device_info["type"] = IfTypes(int(type_device)).name
268
+
269
+ # Yield a record for each non-empty configuration
270
+ for conf in config:
271
+ if conf:
272
+ # Create a copy of device_info to avoid overwriting
273
+ record_info = device_info.copy()
274
+ record_info.update(conf)
275
+ yield WindowsInterfaceRecord(
276
+ **record_info,
277
+ source=f"HKLM\\SYSTEM\\{subkey.path}",
278
+ _target=self.target,
279
+ )
280
+
281
+ def _extract_network_device_config(
282
+ self, interface_id: str
283
+ ) -> list[dict[str, str | list], dict[str, str | list]] | None:
284
+ dhcp_config = {}
285
+ static_config = {}
286
+
287
+ # Get the registry keys for the given interface id
288
+ try:
289
+ keys = self.target.registry.key(
290
+ f"HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{interface_id}"
291
+ )
292
+ except RegistryKeyNotFoundError:
293
+ return None
294
+
295
+ if not len(keys):
296
+ return None
297
+
298
+ # Extract DHCP configuration from the registry
299
+ dhcp_gateway = _try_value(keys, "DhcpDefaultGateway")
300
+ if dhcp_gateway not in ["", "0.0.0.0", None, []]:
301
+ dhcp_config["gateway"] = dhcp_gateway
302
+
303
+ dhcp_ip = _try_value(keys, "DhcpIPAddress")
304
+ if dhcp_ip not in ["", "0.0.0.0", None]:
305
+ dhcp_config["ip"] = [dhcp_ip]
306
+
307
+ dhcp_dns = _try_value(keys, "DhcpNameServer")
308
+ if dhcp_dns not in ["", "0.0.0.0", None]:
309
+ dhcp_config["dns"] = dhcp_dns.split(" ")
310
+
311
+ dhcp_subnetmask = _try_value(keys, "DhcpSubnetMask")
312
+ if dhcp_subnetmask not in ["", "0.0.0.0", None]:
313
+ dhcp_config["subnetmask"] = [dhcp_subnetmask]
314
+
315
+ dhcp_domain = _try_value(keys, "DhcpDomain")
316
+ if dhcp_domain not in ["", None]:
317
+ dhcp_config["search_domain"] = [dhcp_domain]
318
+
319
+ if len(dhcp_config) > 0:
320
+ dhcp_enable = _try_value(keys, "EnableDHCP")
321
+ dhcp_config["enabled"] = dhcp_enable == 1
322
+ dhcp_config["dhcp"] = True
323
+
324
+ # Extract static configuration from the registry
325
+ static_gateway = _try_value(keys, "DefaultGateway")
326
+ if static_gateway not in ["", None, []]:
327
+ static_config["gateway"] = static_gateway
328
+
329
+ static_ip = _try_value(keys, "IPAddress")
330
+ if static_ip not in ["", "0.0.0.0", ["0.0.0.0"], None, []]:
331
+ static_config["ip"] = static_ip if isinstance(static_ip, list) else [static_ip]
332
+
333
+ static_dns = _try_value(keys, "NameServer")
334
+ if static_dns not in ["", "0.0.0.0", None]:
335
+ static_config["dns"] = static_dns.split(",")
336
+
337
+ static_subnetmask = _try_value(keys, "SubnetMask")
338
+ if static_subnetmask not in ["", "0.0.0.0", ["0.0.0.0"], None, []]:
339
+ static_config["subnetmask"] = (
340
+ static_subnetmask if isinstance(static_subnetmask, list) else [static_subnetmask]
341
+ )
342
+
343
+ static_domain = _try_value(keys, "Domain")
344
+ if static_domain not in ["", None]:
345
+ static_config["search_domain"] = [static_domain]
346
+
347
+ if len(static_config) > 0:
348
+ static_config["enabled"] = None
349
+ static_config["dhcp"] = False
350
+
351
+ # Combine ip and subnetmask for extraction
352
+ combined_configs = [
353
+ (dhcp_config, dhcp_config.get("ip", []), dhcp_config.get("subnetmask", [])),
354
+ (static_config, static_config.get("ip", []), static_config.get("subnetmask", [])),
355
+ ]
356
+
357
+ # Iterate over combined ip/subnet lists
358
+ for config, ips, subnet_masks in combined_configs:
359
+ for network_address in self.calculate_network(ips, subnet_masks):
360
+ config.setdefault("network", []).append(network_address)
361
+
362
+ # Return both configurations
363
+ return [dhcp_config, static_config]
@@ -18,7 +18,6 @@ from dissect.target.exceptions import (
18
18
  UnsupportedPluginError,
19
19
  )
20
20
  from dissect.target.helpers import cache, record_modifier
21
- from dissect.target.loaders.targetd import ProxyLoader
22
21
  from dissect.target.plugin import PLUGINS, OSPlugin, Plugin, find_plugin_functions
23
22
  from dissect.target.report import ExecutionReport
24
23
  from dissect.target.tools.utils import (
@@ -174,8 +173,6 @@ def main():
174
173
 
175
174
  if targets:
176
175
  for plugin_target in Target.open_all(targets, args.children):
177
- if isinstance(plugin_target._loader, ProxyLoader):
178
- parser.error("can't list compatible plugins for remote targets.")
179
176
  funcs, _ = find_plugin_functions(plugin_target, args.list, compatibility=True, show_hidden=True)
180
177
  for func in funcs:
181
178
  collected_plugins[func.path] = func.plugin_desc
@@ -16,7 +16,6 @@ from dissect.target import Target
16
16
  from dissect.target.exceptions import UnsupportedPluginError
17
17
  from dissect.target.helpers import docs, keychain
18
18
  from dissect.target.helpers.docs import get_docstring
19
- from dissect.target.helpers.targetd import CommandProxy
20
19
  from dissect.target.loader import LOADERS_BY_SCHEME
21
20
  from dissect.target.plugin import (
22
21
  OSPlugin,
@@ -250,9 +249,6 @@ def plugin_function_with_argparser(
250
249
 
251
250
  plugin_method = plugin_obj.get_all_records
252
251
  parser = generate_argparse_for_plugin(plugin_obj)
253
- elif isinstance(target_attr, CommandProxy):
254
- plugin_method = target_attr.command()
255
- parser = generate_argparse_for_bound_method(plugin_method)
256
252
  elif callable(target_attr):
257
253
  plugin_method = target_attr
258
254
  parser = generate_argparse_for_bound_method(target_attr)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.20.dev4
3
+ Version: 3.20.dev6
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
@@ -2,7 +2,7 @@ dissect/target/__init__.py,sha256=Oc7ounTgq2hE4nR6YcNabetc7SQA40ldSa35VEdZcQU,63
2
2
  dissect/target/container.py,sha256=0YcwcGmfJjhPXUB6DEcjWEoSuAtTDxMDpoTviMrLsxM,9353
3
3
  dissect/target/exceptions.py,sha256=ULi7NXlqju_d8KENEL3aimmfKTFfbNssfeWhAnOB654,2972
4
4
  dissect/target/filesystem.py,sha256=__p2p72B6mKgIiAOj85EC3ESdZ8gjgkm2pt1KKym3h0,60743
5
- dissect/target/loader.py,sha256=I8WNzDA0SMy42F7zfyBcSKj_VKNv64213WUvtGZ77qE,7374
5
+ dissect/target/loader.py,sha256=11B3W9IpfdTU9GJiKONGffuGeICog7J9ypXZ6mffsBE,7337
6
6
  dissect/target/plugin.py,sha256=xbvmjZMFNwJXZYMBE4KI93-nWL9Zhum8x39ydcO6s2o,50578
7
7
  dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
8
8
  dissect/target/target.py,sha256=m4bAKgPLUJERKgxRZFevKvEBNaz77wIC5mVrDe6eI8o,32438
@@ -61,12 +61,11 @@ dissect/target/helpers/mui.py,sha256=i-7XoHbu4WO2fYapK9yGAMW04rFlgRispknc1KQIS5Q
61
61
  dissect/target/helpers/network_managers.py,sha256=ByBSe2K3c8hgQC6dokcf-hHdmPcD8PmrOj0xs1C3yhs,25743
62
62
  dissect/target/helpers/polypath.py,sha256=h8p7m_OCNiQljGwoZh5Aflr9H2ot6CZr6WKq1OSw58o,2175
63
63
  dissect/target/helpers/protobuf.py,sha256=b4DsnqrRLrefcDjx7rQno-_LBcwtJXxuKf5RdOegzfE,1537
64
- dissect/target/helpers/record.py,sha256=zwqEnFSgxgX6JdhhF4zycMMZK09crCTWWEFzRxZSuC8,5658
64
+ dissect/target/helpers/record.py,sha256=euNDDZi29fo8ENN1gsPycB38OMn35clLM9_K-srZ5E0,5852
65
65
  dissect/target/helpers/record_modifier.py,sha256=O_Jj7zOi891HIyAYjxxe6LFPYETHdMa5lNjo4NA_T_w,3969
66
66
  dissect/target/helpers/regutil.py,sha256=kX-sSZbW8Qkg29Dn_9zYbaQrwLumrr4Y8zJ1EhHXIAM,27337
67
67
  dissect/target/helpers/shell_application_ids.py,sha256=hYxrP-YtHK7ZM0ectJFHfoMB8QUXLbYNKmKXMWLZRlA,38132
68
68
  dissect/target/helpers/shell_folder_ids.py,sha256=Behhb8oh0kMxrEk6YYKYigCDZe8Hw5QS6iK_d2hTs2Y,24978
69
- dissect/target/helpers/targetd.py,sha256=ELhUulzQ4OgXgHsWhsLgM14vut8Wm6btr7qTynlwKaE,1812
70
69
  dissect/target/helpers/utils.py,sha256=K3xVq9D0FwIhTBAuiWN8ph7Pq2GABgG3hOz-3AmKuEA,4244
71
70
  dissect/target/helpers/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
71
  dissect/target/helpers/compat/path_310.py,sha256=PsLDIodlp3Hv5u-w7GDl6_LnTtchBYcRjz2MicX1egg,16982
@@ -88,7 +87,7 @@ dissect/target/loaders/kape.py,sha256=t5TfrGLqPeIpUUpXzIl6aHsqXMEGDqJ5YwDCs07DiB
88
87
  dissect/target/loaders/libvirt.py,sha256=_3EFIytMGbiLMISHx4QXVrDebsRO6J6sMkE3TH68qsg,1374
89
88
  dissect/target/loaders/local.py,sha256=Ul-LCd_fY7SyWOVR6nH-NqbkuNpxoZVmffwrkvQElU8,16453
90
89
  dissect/target/loaders/log.py,sha256=cCkDIRS4aPlX3U-n_jUKaI2FPSV3BDpfqKceaU7rBbo,1507
91
- dissect/target/loaders/mqtt.py,sha256=28gmDFZ-9ikR2NXJ2mClUhXqH_YiAk1JpK-5yChmBjY,17095
90
+ dissect/target/loaders/mqtt.py,sha256=WVNXDCzEluyyUidXr25-H9Ic-c75dZIRdDdL5sa4yVQ,19375
92
91
  dissect/target/loaders/multiraw.py,sha256=4a3ZST0NwjnfPDxHkcEfAcX2ddUlT_C-rcrMHNg1wp4,1046
93
92
  dissect/target/loaders/ova.py,sha256=6h4O-7i87J394C6KgLsPkdXRAKNwtPubzLNS3vBGs7U,744
94
93
  dissect/target/loaders/overlay.py,sha256=tj99HKvNG5_JbGfb1WCv4KNSbXXSnEcPQY5XT-JUxn8,992
@@ -104,7 +103,6 @@ dissect/target/loaders/smb.py,sha256=qP8m4Jq7hvAvUCF9jB4yr2Zut7p_R02_vxziNN3R1to
104
103
  dissect/target/loaders/tanium.py,sha256=P9euiQzvVaQQtMQlEmNe0V25w1BkQFRZBuS-0-ksHpY,1585
105
104
  dissect/target/loaders/tar.py,sha256=2uF9-mmFbSdLsCkySfT9AkzagQXsTQvDjrdL1UyjFuI,4170
106
105
  dissect/target/loaders/target.py,sha256=MU_HUtg58YdhdZu6ga1sYG7fK61Dn7N0TBkWXDCWwyc,798
107
- dissect/target/loaders/targetd.py,sha256=sfbn2_j3il2G-rPywAoNT5YPtD5KmKkmBv1zrPDRs6I,8250
108
106
  dissect/target/loaders/utm.py,sha256=7oHYP_jmr5gcjoyOP1pnh9Rz-IqQirBI6bjSvGwiKao,1053
109
107
  dissect/target/loaders/vb.py,sha256=CdimOMeoJEDq8xYDgtldGSiwhR-dY5uxac1L0sYwAEU,2078
110
108
  dissect/target/loaders/vbox.py,sha256=8JD7D8iAY9JRvTHsrosp5ZMsZezuLhZ10Zt8sEL7KBI,732
@@ -184,7 +182,7 @@ dissect/target/plugins/general/config.py,sha256=Mdy9uhWn4OJ96zfXpLgjVifV5SrViqHn
184
182
  dissect/target/plugins/general/default.py,sha256=8W_9JV3jKEeETlyTrB25sACoIIFmmO8wlVU5Zoi51W0,1425
185
183
  dissect/target/plugins/general/example.py,sha256=6B_YOqajRBLNWBEOfIL_HnLaEANBF8KKoc0mweihiug,6034
186
184
  dissect/target/plugins/general/loaders.py,sha256=6iUxhlSAgo7qSE8_XFxgiihK8sdMiP-s4k0W5Iv8m9k,879
187
- dissect/target/plugins/general/network.py,sha256=Ol4Ls1w78-7zpmVaQQOZG27rvYOhJLFVhomZj5kwibs,2430
185
+ dissect/target/plugins/general/network.py,sha256=1dCWiVIaVPySquRs3YEsP7PxXXU5voa8CsxyIa7Vh54,2882
188
186
  dissect/target/plugins/general/osinfo.py,sha256=RdK5mw3-H9H3sGXz8yP8U_p3wUG1Ww7_HBKZpFdsbTE,1358
189
187
  dissect/target/plugins/general/plugins.py,sha256=4URjS6DN1Ey6Cqlbyx6NfFGgQZpWDrqxl8KLcZFODGE,4479
190
188
  dissect/target/plugins/general/scrape.py,sha256=Fz7BNXflvuxlnVulyyDhLpyU8D_hJdH6vWVtER9vjTg,6651
@@ -262,7 +260,7 @@ dissect/target/plugins/os/unix/log/lastlog.py,sha256=Wq89wRSFZSBsoKVCxjDofnC4yw9
262
260
  dissect/target/plugins/os/unix/log/messages.py,sha256=CXA-SkMPLaCgnTQg9nzII-7tO8Il_ENQmuYvDxo33rI,4698
263
261
  dissect/target/plugins/os/unix/log/utmp.py,sha256=1nPHIaBUHt_9z6PDrvyqg4huKLihUaWLrMmgMsbaeIo,7755
264
262
  dissect/target/plugins/os/windows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
265
- dissect/target/plugins/os/windows/_os.py,sha256=uBa0dVkFxDsxHAU3T23UEIOCgAx5R6cIpCgbGq3fflY,13131
263
+ dissect/target/plugins/os/windows/_os.py,sha256=-x5TD5BvFw-7zEfqT6WG7n04YSeyr7wVLO07y6xkBP8,12476
266
264
  dissect/target/plugins/os/windows/activitiescache.py,sha256=Q2aILnhJ2rp2AwEbWwyBuSLjMbGqaYJTsavSbfkcFKE,6741
267
265
  dissect/target/plugins/os/windows/adpolicy.py,sha256=qjv0s-gAIGKCznWdVOARJbLXnCKYgvzoFNWoXnq3m1M,7102
268
266
  dissect/target/plugins/os/windows/amcache.py,sha256=ZZNOs3bILTf0AGkDkhoatndl0j39DXkstN7oOyxJECU,27188
@@ -276,6 +274,7 @@ dissect/target/plugins/os/windows/generic.py,sha256=BSvDPfB9faU0uquMj0guw5tnR_97
276
274
  dissect/target/plugins/os/windows/jumplist.py,sha256=3gZk6O1B3lKK2Jxe0B-HapOCEehk94CYNvCVDpQC9nQ,11773
277
275
  dissect/target/plugins/os/windows/lnk.py,sha256=toEZV00CESLUsF7UmN65-ivWk0Ijg-ZPST0qyD-antY,7860
278
276
  dissect/target/plugins/os/windows/locale.py,sha256=yXVdclpUqss9h8Nq7N4kg3OHwWGDfjdfiLiUZR3wqv8,2324
277
+ dissect/target/plugins/os/windows/network.py,sha256=nKNgCqVjzjPwkwyXIIgIIECO2UEYnzvM0PzqRVtCGls,10788
279
278
  dissect/target/plugins/os/windows/notifications.py,sha256=T1CIvQgpW__qDR0Rq5zpeWmRWwjNDpvdMnvJJ_6tZXs,17378
280
279
  dissect/target/plugins/os/windows/prefetch.py,sha256=v4OgSKMwcihz0SOuA0o0Ec8wsAKuiuEmJolqZmHFgJA,10491
281
280
  dissect/target/plugins/os/windows/recyclebin.py,sha256=zx58hDCvcrD_eJl9nJmr_i80krSN03ya8nQzWFr2Tw0,4917
@@ -347,10 +346,10 @@ dissect/target/tools/fsutils.py,sha256=dyAdp2fzydcozaIZ1mFTpdUeVcibYNJCHN8AFw5Fo
347
346
  dissect/target/tools/info.py,sha256=8nnbqFUYeo4NLPE7ORcTBcDL-TioGB2Nqc1TKcu5qdY,5715
348
347
  dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
349
348
  dissect/target/tools/mount.py,sha256=8GRYnu4xEmFBHxuIZAYhOMyyTGX8fat1Ou07DNiUnW4,3945
350
- dissect/target/tools/query.py,sha256=XgMDSfaN4SivJmIIEntYJOXcOEwWrUp_tYt5AjEtB4k,15602
349
+ dissect/target/tools/query.py,sha256=e-yAN9zdQjuOiTuoOQoo17mVEQGGcOgaA9YkF4GYpkM,15394
351
350
  dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
352
351
  dissect/target/tools/shell.py,sha256=dmshIriwdd_UwrdUcTfWkcYD8Z0mjzbDqwyZG-snDdM,50482
353
- dissect/target/tools/utils.py,sha256=nnhjNW8v99eVZQ-CgxTbsi8Wa6Z2XKDFr1aWakgq9jc,12191
352
+ dissect/target/tools/utils.py,sha256=cGk_DxEqVL0ofxlIC15GD4w3PV5RSE_IaKvVkAxEhR8,11974
354
353
  dissect/target/tools/yara.py,sha256=70k-2VMulf1EdkX03nCACzejaOEcsFHOyX-4E40MdQU,2044
355
354
  dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
356
355
  dissect/target/tools/dump/run.py,sha256=aD84peRS4zHqC78fH7Vd4ni3m1ZmVP70LyMwBRvoDGY,9463
@@ -364,10 +363,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
364
363
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
365
364
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
366
365
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
367
- dissect.target-3.20.dev4.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
368
- dissect.target-3.20.dev4.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
369
- dissect.target-3.20.dev4.dist-info/METADATA,sha256=-UKtuhQiUY4OhJbAtGRU1GcyyCoHAHqkHJs51thHJOg,12896
370
- dissect.target-3.20.dev4.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
371
- dissect.target-3.20.dev4.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
372
- dissect.target-3.20.dev4.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
373
- dissect.target-3.20.dev4.dist-info/RECORD,,
366
+ dissect.target-3.20.dev6.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
367
+ dissect.target-3.20.dev6.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
368
+ dissect.target-3.20.dev6.dist-info/METADATA,sha256=fpn-GHvIaUQ8kWFx0kTIwF9A0-q43fdFymwUWjX-AXQ,12896
369
+ dissect.target-3.20.dev6.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
370
+ dissect.target-3.20.dev6.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
371
+ dissect.target-3.20.dev6.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
372
+ dissect.target-3.20.dev6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.2)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,58 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import functools
4
- from typing import Any, Callable, Optional, Union
5
-
6
- from dissect.util.stream import AlignedStream
7
-
8
- from dissect.target.exceptions import FatalError
9
- from dissect.target.loader import Loader
10
-
11
-
12
- class TargetdStream(AlignedStream):
13
- def _read(self, offset: int, length: int) -> bytes:
14
- return b""
15
-
16
-
17
- # Marker interface to indicate this loader loads targets from remote machines
18
- class ProxyLoader(Loader):
19
- pass
20
-
21
-
22
- class TargetdInvalidStateError(FatalError):
23
- pass
24
-
25
-
26
- class CommandProxy:
27
- def __init__(self, loader: Loader, func: Callable, namespace: Optional[str] = None):
28
- self._loader = loader
29
- self._func = func
30
- self._namespace = namespace or func
31
-
32
- def __getattr__(self, func: Union[Callable, str]) -> CommandProxy:
33
- if func == "func":
34
- return self._func.func
35
- self._func = func
36
- return self
37
-
38
- def command(self) -> Callable:
39
- namespace = None if self._func == self._namespace else self._namespace
40
- return self._get(namespace, self._func)
41
-
42
- def _get(self, namespace: Optional[str], plugin_func: Callable) -> Callable:
43
- if namespace:
44
- func = functools.update_wrapper(
45
- functools.partial(self._loader.plugin_bridge, plugin_func=self._func, namespace=self._namespace),
46
- self._loader.plugin_bridge,
47
- )
48
- else:
49
- func = functools.update_wrapper(
50
- functools.partial(self._loader.plugin_bridge, plugin_func=plugin_func), self._loader.plugin_bridge
51
- )
52
- return func
53
-
54
- def __call__(self, *args, **kwargs) -> Any:
55
- return self.command()(*args, **kwargs)
56
-
57
- def __repr__(self) -> str:
58
- return str(self._get(None, "get")(**{"property_name": self._func}))
@@ -1,223 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import functools
4
- import ssl
5
- import time
6
- import urllib
7
- from pathlib import Path
8
- from platform import os
9
- from typing import Any, Callable, Optional, Union
10
-
11
- from dissect.target.containers.raw import RawContainer
12
- from dissect.target.exceptions import LoaderError
13
-
14
- # to avoid loading issues during automatic loader detection
15
- from dissect.target.helpers.targetd import (
16
- CommandProxy,
17
- ProxyLoader,
18
- TargetdInvalidStateError,
19
- TargetdStream,
20
- )
21
- from dissect.target.loader import Loader
22
- from dissect.target.plugin import Plugin, export
23
- from dissect.target.target import Target
24
-
25
- TARGETD_AVAILABLE = False
26
- try:
27
- from flow import remoting
28
- from targetd.clients import Client
29
-
30
- TARGETD_AVAILABLE = True
31
- except Exception:
32
- pass
33
-
34
-
35
- class TargetdLoader(ProxyLoader):
36
- """Load remote targets through a broker."""
37
-
38
- instance = None
39
-
40
- def __init__(self, path: Union[Path, str], **kwargs):
41
- super().__init__(path)
42
- self._plugin_func = None
43
- self.client = None
44
- self.output = None
45
- self.peers = 1
46
- self.cacert = None
47
- self.adapter = "Flow.Remoting"
48
- self.host = "localhost"
49
- self.port = 1883
50
- self.chunking = True
51
- self.local_link = "unix:///tmp/targetd" if os.name == "posix" else "pipe:///tmp/targetd"
52
- # @todo Add these options to loader help (when available)
53
- self.configurables = [
54
- ["cacert", Path, "SSL: cacert file"],
55
- ["host", str, "IP-address of targetd broker"],
56
- ["port", int, "Port for connecting to targetd broker"],
57
- ["local_link", str, "Domain socket or named pipe"],
58
- ["adapter", str, "Adapter to use"],
59
- ["peers", int, "Minimum number of hosts to wait for before executing query"],
60
- ["chunking", int, "Chunk mode"],
61
- ]
62
- uri = kwargs.get("parsed_path")
63
- if uri is None:
64
- raise LoaderError("No URI connection details have been passed.")
65
- self.uri = uri.path
66
- self.options = dict(urllib.parse.parse_qsl(uri.query, keep_blank_values=True))
67
-
68
- def _process_options(self, target: Target) -> None:
69
- for configurable_details in self.configurables:
70
- configurable, value_type, description = configurable_details
71
- configuration = self.options.get(configurable)
72
- if not configuration:
73
- default_value = getattr(self, configurable)
74
- target.log.warning("%s not configured, using: %s=%s", description, configurable, default_value)
75
- else:
76
- setattr(self, configurable, value_type(configuration))
77
-
78
- @export(output="record", cache=False)
79
- def plugin_bridge(self, *args, **kwargs) -> list[Any]:
80
- """Command Execution Bridge Plugin for Targetd.
81
-
82
- This is a generic plugin interceptor that becomes active only if using
83
- the targetd loader. This plugin acts as a bridge to connect to the Targetd broker
84
- and will translate the requested plugin-operation into Targetd-commands using the selected
85
- adapter (i.e. Flow.Remoting).
86
- """
87
-
88
- if not TARGETD_AVAILABLE:
89
- raise ImportError("This loader requires the targetd package to be installed.")
90
-
91
- self.output = None
92
- self.has_output = False
93
-
94
- plugin_func = kwargs.pop("plugin_func", None)
95
-
96
- if self.client is None:
97
- ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
98
- ssl_context.check_hostname = False
99
- if self.cacert and not self.cacert.exists():
100
- raise LoaderError(f"file not found: {self.cacert}")
101
- if self.cacert:
102
- ssl_context.load_verify_locations(self.cacert)
103
- else:
104
- ssl_context.load_default_certs(purpose=ssl.Purpose.SERVER_AUTH)
105
- self.client = Client(self.host, self.port, ssl_context, [self.uri], self.local_link, "targetd")
106
- self.client.module_fullname = "dissect.target.loaders"
107
- self.client.module_fromlist = ["command_runner"]
108
- self.client.command = plugin_func
109
- try:
110
- self.client.start(*args, **kwargs)
111
- except Exception:
112
- # If something happens that prevents targetd from properly closing/resetting the
113
- # connection, this exception is thrown during the next connection and the connection
114
- # is closed properly after all so that the next time the loader will be able to
115
- # use a new connection with a new session.
116
- self.client.close()
117
- raise TargetdInvalidStateError("Targetd connection is in invalid state, retry.")
118
- else:
119
- self.client.command = plugin_func
120
- self.client.exec_command(*args, **kwargs)
121
-
122
- while not self.has_output:
123
- time.sleep(1)
124
- return self.output
125
-
126
- def _get_command(self, func: str, namespace: str = None) -> tuple[Loader, functools.partial]:
127
- return (self, CommandProxy(self, func, namespace=namespace))
128
-
129
- def each(self, func: Callable, target: Optional[Target] = None) -> Any:
130
- """Allows you to attach a scriptlet (function) to each of the remote hosts.
131
- The results of the function are accumulated using +=. Exceptions are
132
- catched and logged as warnings.
133
-
134
- Usage:
135
-
136
- def my_function(remote_target):
137
- ...do something interesting...
138
-
139
- targets = Target.open( targetd://... )
140
- targets.each(my_function)
141
-
142
- """
143
- result = None
144
- if not self.client:
145
- target.init()
146
- for peer in self.client.peers:
147
- target.select(peer)
148
- try:
149
- if result is None:
150
- result = func(target)
151
- else:
152
- result += func(target)
153
- except Exception as failure:
154
- target.log.warning("Exception while applying function to target: %s: %s", peer, failure)
155
- return result
156
-
157
- def _add_plugin(self, plugin: Plugin):
158
- plugin.check_compatibe = lambda: True
159
-
160
- def map(self, target: Target) -> None:
161
- if TargetdLoader.instance:
162
- raise Exception("You can only initiated 1 targetd control connection per session.")
163
- self._process_options(target)
164
- target.disks.add(RawContainer(TargetdStream()))
165
- target.get_function = self._get_command
166
- target.add_plugin = self._add_plugin
167
- target.each = functools.update_wrapper(functools.partial(self.each, target=target), self.each)
168
- TargetdLoader.instance = self
169
-
170
- @staticmethod
171
- def detect(path: Path) -> bool:
172
- # You can only activate this loader by URI-scheme "targetd://"
173
- return False
174
-
175
- def __del__(self) -> None:
176
- if self.client:
177
- self.client.close()
178
-
179
-
180
- if TARGETD_AVAILABLE:
181
- # Loader has to provide the control script for targetd in this case
182
- def command_runner(link: str, targetd: Client, *args, **kwargs) -> None:
183
- caller = TargetdLoader.instance
184
- if not targetd.rpcs:
185
- targetd.easy_connect_remoting(remoting, link, caller.peers)
186
-
187
- obj = targetd.rpcs
188
- if namespace := kwargs.get("namespace", None):
189
- obj = getattr(obj, namespace)
190
-
191
- caller.has_output = True
192
- if targetd.command == "init":
193
- caller.output = True
194
- return
195
-
196
- if targetd.command == "select":
197
- targetd.rpcs.select(*args)
198
- caller.output = True
199
- return
200
-
201
- if targetd.command == "deselect":
202
- targetd.rpcs.deselect()
203
- caller.output = True
204
- return
205
-
206
- func = getattr(obj, targetd.command)
207
-
208
- result = func(*args, **kwargs)
209
- if result is not None:
210
- if targetd.command == "get":
211
- result = list(result)[0]
212
- elif caller.chunking:
213
- data = []
214
- gen = func()
215
- targetd.reset()
216
- while True:
217
- try:
218
- data.append(next(gen))
219
- except Exception:
220
- break
221
- result = data
222
- caller.output = result
223
- targetd.reset()