dissect.target 3.9.dev36__py3-none-any.whl → 3.9.dev38__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
dissect/target/plugin.py CHANGED
@@ -13,7 +13,7 @@ import logging
13
13
  import os
14
14
  import sys
15
15
  import traceback
16
- from dataclasses import dataclass
16
+ from dataclasses import dataclass, field
17
17
  from pathlib import Path
18
18
  from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Type
19
19
 
@@ -811,13 +811,13 @@ class InternalPlugin(Plugin):
811
811
  return cls
812
812
 
813
813
 
814
- @dataclass
814
+ @dataclass(frozen=True, eq=True)
815
815
  class PluginFunction:
816
816
  name: str
817
817
  output_type: str
818
818
  class_object: str
819
819
  method_name: str
820
- plugin_desc: dict
820
+ plugin_desc: dict = field(hash=False)
821
821
 
822
822
 
823
823
  def plugin_function_index(target: Target) -> tuple[dict[str, Any], set[str]]:
@@ -831,7 +831,9 @@ def plugin_function_index(target: Target) -> tuple[dict[str, Any], set[str]]:
831
831
  rootset = set()
832
832
 
833
833
  def all_plugins():
834
- yield from plugins()
834
+ # Filter out plugins based on the target os
835
+ os_type = type(target._os) if target._os else None
836
+ yield from plugins(os_type)
835
837
  yield from os_plugins()
836
838
  yield from child_plugins() # Doesn't export anything but added for completeness.
837
839
 
@@ -885,33 +887,34 @@ def find_plugin_functions(target: Target, patterns: str, compatibility: bool = F
885
887
  pattern += "*"
886
888
 
887
889
  if wildcard or treematch:
888
- for index_name, func in functions.items():
889
- if fnmatch.fnmatch(index_name, pattern):
890
- method_name = index_name.split(".")[-1]
891
- loaded_plugin_object = load(func)
890
+ for index_name in fnmatch.filter(functions.keys(), pattern):
891
+ func = functions[index_name]
892
892
 
893
- # Skip plugins that don't want to be found by wildcards
894
- if not loaded_plugin_object.__findable__:
895
- continue
893
+ method_name = index_name.split(".")[-1]
894
+ loaded_plugin_object = load(func)
895
+
896
+ # Skip plugins that don't want to be found by wildcards
897
+ if not loaded_plugin_object.__findable__:
898
+ continue
896
899
 
897
- fobject = inspect.getattr_static(loaded_plugin_object, method_name)
900
+ fobject = inspect.getattr_static(loaded_plugin_object, method_name)
898
901
 
899
- if compatibility:
900
- try:
901
- if not loaded_plugin_object(target).is_compatible():
902
- continue
903
- except Exception:
902
+ if compatibility:
903
+ try:
904
+ if not loaded_plugin_object(target).is_compatible():
904
905
  continue
906
+ except Exception:
907
+ continue
905
908
 
906
- result.append(
907
- PluginFunction(
908
- name=index_name,
909
- class_object=loaded_plugin_object,
910
- method_name=method_name,
911
- output_type=getattr(fobject, "__output__", "text"),
912
- plugin_desc=func,
913
- )
909
+ result.append(
910
+ PluginFunction(
911
+ name=index_name,
912
+ class_object=loaded_plugin_object,
913
+ method_name=method_name,
914
+ output_type=getattr(fobject, "__output__", "text"),
915
+ plugin_desc=func,
914
916
  )
917
+ )
915
918
  else:
916
919
  # otherwise match using ~ classic style
917
920
  if pattern.find(".") > -1:
@@ -920,15 +923,15 @@ def find_plugin_functions(target: Target, patterns: str, compatibility: bool = F
920
923
  funcname = pattern
921
924
  namespace = None
922
925
 
923
- plugindesc = None
924
- for index_name, func in functions.items():
926
+ plugin_descriptions = []
927
+ for _, func in functions.items():
925
928
  nsmatch = namespace and func["namespace"] == namespace and funcname in func["exports"]
926
929
  fmatch = not namespace and not func["namespace"] and funcname in func["exports"]
927
930
  if nsmatch or fmatch:
928
- plugindesc = func
931
+ plugin_descriptions.append(func)
929
932
 
930
- if plugindesc:
931
- loaded_plugin_object = load(plugindesc)
933
+ for description in plugin_descriptions:
934
+ loaded_plugin_object = load(description)
932
935
  fobject = inspect.getattr_static(loaded_plugin_object, funcname)
933
936
 
934
937
  if compatibility and not loaded_plugin_object(target).is_compatible():
@@ -936,11 +939,12 @@ def find_plugin_functions(target: Target, patterns: str, compatibility: bool = F
936
939
 
937
940
  result.append(
938
941
  PluginFunction(
939
- name=f"{plugindesc['module']}.{pattern}",
942
+ name=f"{description['module']}.{pattern}",
940
943
  class_object=loaded_plugin_object,
941
944
  method_name=funcname,
942
945
  output_type=getattr(fobject, "__output__", "text"),
943
- plugin_desc=plugindesc,
946
+ plugin_desc=description,
944
947
  )
945
948
  )
946
- return result
949
+
950
+ return list(set(result))
@@ -202,10 +202,16 @@ def main():
202
202
  basic_entries = []
203
203
  yield_entries = []
204
204
 
205
+ # Keep a set of plugins that were already executed on the target.
206
+ executed_plugins = set()
207
+
205
208
  first_seen_output_type = default_output_type
206
209
  cli_params_unparsed = rest
207
210
 
208
- for func_def in funcs:
211
+ for func_def in find_plugin_functions(target, args.function, False):
212
+ if func_def.method_name in executed_plugins:
213
+ continue
214
+
209
215
  try:
210
216
  output_type, result, cli_params_unparsed = execute_function_on_target(
211
217
  target, func_def, cli_params_unparsed
@@ -213,17 +219,17 @@ def main():
213
219
  except UnsupportedPluginError as e:
214
220
  target.log.error(
215
221
  "Unsupported plugin for `%s`: %s",
216
- func,
222
+ func_def,
217
223
  e.root_cause_str(),
218
224
  )
219
225
 
220
226
  target.log.debug("", exc_info=e)
221
227
  continue
222
228
  except PluginNotFoundError:
223
- target.log.error("Cannot find plugin `%s`", func)
229
+ target.log.error("Cannot find plugin `%s`", func_def)
224
230
  continue
225
231
  except Exception:
226
- target.log.error("Exception while executing function `%s`", func, exc_info=True)
232
+ target.log.error("Exception while executing function `%s`", func_def, exc_info=True)
227
233
  continue
228
234
 
229
235
  if first_seen_output_type and output_type != first_seen_output_type:
@@ -236,7 +242,7 @@ def main():
236
242
  "does not match first seen output `%s`."
237
243
  ),
238
244
  output_type,
239
- func,
245
+ func_def,
240
246
  first_seen_output_type,
241
247
  )
242
248
  parser.exit()
@@ -244,12 +250,14 @@ def main():
244
250
  if not first_seen_output_type:
245
251
  first_seen_output_type = output_type
246
252
 
253
+ executed_plugins.add(func_def.method_name)
254
+
247
255
  if output_type == "record":
248
256
  record_entries.append(result)
249
257
  elif output_type == "yield":
250
258
  yield_entries.append(result)
251
259
  elif output_type == "none":
252
- target.log.info("No result for function `%s` (output type is set to 'none')", func)
260
+ target.log.info("No result for function `%s` (output type is set to 'none')", func_def)
253
261
  continue
254
262
  else:
255
263
  basic_entries.append(result)
@@ -3,7 +3,7 @@ import inspect
3
3
  import json
4
4
  from datetime import datetime
5
5
  from pathlib import Path
6
- from typing import Any, Callable, Dict, List, Optional, Tuple, Type
6
+ from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Type, Union
7
7
 
8
8
  from dissect.target import Target
9
9
  from dissect.target.exceptions import UnsupportedPluginError
@@ -129,13 +129,16 @@ def generate_argparse_for_plugin(
129
129
  return generate_argparse_for_plugin_class(plugin_instance.__class__, usage_tmpl=usage_tmpl)
130
130
 
131
131
 
132
- def plugin_factory(target: Target, plugin_class: type, funcname: str) -> tuple[Plugin, str]:
132
+ def plugin_factory(target: Target, plugin: Union[type, object], funcname: str) -> tuple[Plugin, str]:
133
133
  if TargetdLoader.instance:
134
134
  return target.get_function(funcname)
135
+
136
+ if isinstance(plugin, type):
137
+ plugin_obj = plugin(target)
138
+ target_attr = getattr(plugin_obj, funcname)
139
+ return plugin_obj, target_attr
135
140
  else:
136
- plugin_obj = plugin_class(target)
137
- target_attr = getattr(plugin_obj, funcname)
138
- return plugin_obj, target_attr
141
+ return plugin, getattr(plugin, funcname)
139
142
 
140
143
 
141
144
  def execute_function_on_target(
@@ -149,9 +152,40 @@ def execute_function_on_target(
149
152
 
150
153
  cli_params = cli_params or []
151
154
 
152
- plugin_class = func.class_object
155
+ target_attr = get_target_attribute(target, func)
156
+ plugin_method, parser = plugin_function_with_argparser(target_attr)
157
+
158
+ if parser:
159
+ parsed_params, cli_params = parser.parse_known_args(cli_params)
160
+ method_kwargs = vars(parsed_params)
161
+ value = plugin_method(**method_kwargs)
162
+ else:
163
+ value = target_attr
164
+
165
+ output_type = getattr(plugin_method, "__output__", "default") if plugin_method else "default"
166
+ return (output_type, value, cli_params)
167
+
168
+
169
+ def get_target_attribute(target: Target, func: PluginFunction) -> Union[Plugin, Callable]:
170
+ """Retrieves the function attribute from the target.
171
+
172
+ If the function does not exist yet, it will attempt to load it into the target.
173
+
174
+ Args:
175
+ target: The target we wish to run the function on.
176
+ func: The function to run on the target.
153
177
 
154
- if issubclass(plugin_class, OSPlugin):
178
+ Returns:
179
+ The function, either plugin or a callable to execute.
180
+
181
+ Raises:
182
+ UnsupportedPluginError: When the function was incompatible with the target.
183
+ """
184
+ plugin_class = func.class_object
185
+ if target.has_function(func.method_name):
186
+ # If the function is already attached, use the one inside the target.
187
+ plugin_class, _ = target.get_function(func.method_name)
188
+ elif issubclass(plugin_class, OSPlugin):
155
189
  # OS plugin does not need to be added
156
190
  plugin_class = target._os_plugin
157
191
  else:
@@ -162,7 +196,14 @@ def execute_function_on_target(
162
196
  f"Unsupported function `{func.method_name}` for target with plugin {func.class_object}", cause=e
163
197
  )
164
198
 
165
- plugin_obj, target_attr = plugin_factory(target, plugin_class, func.method_name)
199
+ _, target_attr = plugin_factory(target, plugin_class, func.method_name)
200
+ return target_attr
201
+
202
+
203
+ def plugin_function_with_argparser(
204
+ target_attr: Union[Plugin, Callable]
205
+ ) -> tuple[Optional[Iterator], Optional[argparse.ArgumentParser]]:
206
+ """Resolves which plugin function to execute, and creates the argument parser for said plugin."""
166
207
  plugin_method = None
167
208
  parser = None
168
209
 
@@ -178,16 +219,7 @@ def execute_function_on_target(
178
219
  elif callable(target_attr):
179
220
  plugin_method = target_attr
180
221
  parser = generate_argparse_for_bound_method(target_attr)
181
-
182
- if parser:
183
- parsed_params, cli_params = parser.parse_known_args(cli_params)
184
- method_kwargs = vars(parsed_params)
185
- value = plugin_method(**method_kwargs)
186
- else:
187
- value = target_attr
188
-
189
- output_type = getattr(plugin_method, "__output__", "default") if plugin_method else "default"
190
- return (output_type, value, cli_params)
222
+ return plugin_method, parser
191
223
 
192
224
 
193
225
  def persist_execution_report(output_dir: Path, report_data: Dict, timestamp: datetime) -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.9.dev36
3
+ Version: 3.9.dev38
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
@@ -21,7 +21,7 @@ Requires-Dist: dissect.ntfs (<4.0.dev,>=3.4.dev)
21
21
  Requires-Dist: dissect.regf (<4.0.dev,>=3.3.dev)
22
22
  Requires-Dist: dissect.util (<4.0.dev,>=3.0.dev)
23
23
  Requires-Dist: dissect.volume (<4.0.dev,>=3.0.dev)
24
- Requires-Dist: flow.record (~=3.9)
24
+ Requires-Dist: flow.record (~=3.10)
25
25
  Requires-Dist: structlog
26
26
  Provides-Extra: full
27
27
  Requires-Dist: asn1crypto ; extra == 'full'
@@ -3,7 +3,7 @@ dissect/target/container.py,sha256=R8M9EE7DqKq8DeMuekcpR1nxtZ827zuqmTmO4s7PYkg,7
3
3
  dissect/target/exceptions.py,sha256=w3qNa4dtGpxUkOCfmAQtvseQrpQq9qmO9wIbztRlIDQ,2273
4
4
  dissect/target/filesystem.py,sha256=YJfuRzd37DIm91cVPlMZK-XjiJ1UCZG93htaYcSUrIk,46276
5
5
  dissect/target/loader.py,sha256=_gLb3nTAlyS0MS3JEFFD44Qk7X5MQMfhoRTFWaH7sA4,7072
6
- dissect/target/plugin.py,sha256=qF0okKg7TxYfiLVxpCWhb18gvDsNtr9nCYD7kSeaYIU,31955
6
+ dissect/target/plugin.py,sha256=GLoCrD1eKMy_tbRkwd8YzkSdPNdym5M0-e-aR8kTt10,32098
7
7
  dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
8
8
  dissect/target/target.py,sha256=TgDY-yAsReOQOG-Phz_m1vdNucdbk9fUI_RMZpMeYG8,28334
9
9
  dissect/target/volume.py,sha256=vHBXdDttpiu-Q_oWycNM7fdJ5N8Ob7-i_UBJK9DEs24,15027
@@ -248,10 +248,10 @@ dissect/target/tools/fs.py,sha256=dz6IEz1CKTVsSuMu6oAqdkc1qfIfqnMGABHSDDKKSDM,36
248
248
  dissect/target/tools/info.py,sha256=ZYRKLANt99uhpMcP5eHj6WegWAffqq8tS8MNj5txwqE,5141
249
249
  dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
250
250
  dissect/target/tools/mount.py,sha256=gPez-PLaeof7a6s7spJ23F_JcdQ7cUOPx5FX42rqF4A,2500
251
- dissect/target/tools/query.py,sha256=Btt_PMVAWIYHhPLg0b8u5e_TDdG8CG5xapxLu82dbSE,11466
251
+ dissect/target/tools/query.py,sha256=PHj-lNgcHSOQ401Hlc-HvpupAx4wnQNPeFOrxYyDJg0,11779
252
252
  dissect/target/tools/reg.py,sha256=37g_Xdb5ZbYAkMgQFmZNdKM_wWP9Bcw2Kk6quo1gwZ4,2147
253
253
  dissect/target/tools/shell.py,sha256=HICeIN5kCZYyGmAm_riWO9xrGnQmOzSp-Oici4QeO6Y,36003
254
- dissect/target/tools/utils.py,sha256=Et3Z67JKv-zzKLhJ6VWn6oiZ9GLoSt6em3sUxNJjyoc,7191
254
+ dissect/target/tools/utils.py,sha256=yDdQxAlWpwlOSnZlN9-ktreMxG_q-NXY_1Z8f7lnAQI,8446
255
255
  dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
256
256
  dissect/target/tools/dump/run.py,sha256=yHn9xl_VjasgiuLpjtZdnLW32QCbkwHfnnTPY6Ck_aw,9689
257
257
  dissect/target/tools/dump/state.py,sha256=ZBNz4ou2Xk20K1H8R83S1gq6qcqPvPPVAaPWzpKpX34,9123
@@ -261,10 +261,10 @@ dissect/target/volumes/bde.py,sha256=gYGg5yF9MNARwNzEkrEfZmKkxyZW4rhLkpdnPJCbhGk
261
261
  dissect/target/volumes/disk.py,sha256=95grSsPt1BLVpKwTclwQYzPFGKTkFFqapIk0RoGWf38,968
262
262
  dissect/target/volumes/lvm.py,sha256=_kIB1mdRs1OFhRgoT4VEP5Fv8imQnI7oQ_ie4x710tQ,1814
263
263
  dissect/target/volumes/vmfs.py,sha256=mlAJ8278tYaoRjk1u6tFFlCaDQUrVu5ZZE4ikiFvxi8,1707
264
- dissect.target-3.9.dev36.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
265
- dissect.target-3.9.dev36.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
266
- dissect.target-3.9.dev36.dist-info/METADATA,sha256=gvoKMDbMeKEx-jaq_u5oxUS4PW4FrDShGItVS8LVTKA,10233
267
- dissect.target-3.9.dev36.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
268
- dissect.target-3.9.dev36.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
269
- dissect.target-3.9.dev36.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
270
- dissect.target-3.9.dev36.dist-info/RECORD,,
264
+ dissect.target-3.9.dev38.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
265
+ dissect.target-3.9.dev38.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
266
+ dissect.target-3.9.dev38.dist-info/METADATA,sha256=F2OhtulG3gqAU4z12JPG5uc8kjBJmrZJ8Jcgl86OdjI,10234
267
+ dissect.target-3.9.dev38.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
268
+ dissect.target-3.9.dev38.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
269
+ dissect.target-3.9.dev38.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
270
+ dissect.target-3.9.dev38.dist-info/RECORD,,