dissect.target 3.19.dev20__py3-none-any.whl → 3.19.dev22__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import logging
5
+ from io import BytesIO
1
6
  from pathlib import Path
7
+ from typing import Iterator
8
+
9
+ from dissect.target.helpers import hashutil
2
10
 
3
11
  try:
4
12
  import yara
13
+
14
+ HAS_YARA = True
15
+
5
16
  except ImportError:
6
- raise ImportError("Please install 'yara-python' to use 'target-query -f yara'.")
17
+ HAS_YARA = False
7
18
 
8
- from dissect.target.exceptions import FileNotFoundError
19
+ from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
9
20
  from dissect.target.helpers.record import TargetRecordDescriptor
10
21
  from dissect.target.plugin import Plugin, arg, export
11
22
 
23
+ log = logging.getLogger(__name__)
24
+
12
25
  YaraMatchRecord = TargetRecordDescriptor(
13
26
  "filesystem/yara/match",
14
27
  [
@@ -16,48 +29,158 @@ YaraMatchRecord = TargetRecordDescriptor(
16
29
  ("digest", "digest"),
17
30
  ("string", "rule"),
18
31
  ("string[]", "tags"),
32
+ ("string", "namespace"),
19
33
  ],
20
34
  )
21
35
 
36
+ DEFAULT_MAX_SCAN_SIZE = 10 * 1024 * 1024
37
+
22
38
 
23
39
  class YaraPlugin(Plugin):
24
40
  """Plugin to scan files against a local YARA rules file."""
25
41
 
26
- DEFAULT_MAX_SIZE = 10 * 1024 * 1024
27
-
28
42
  def check_compatible(self) -> None:
29
- pass
43
+ if not HAS_YARA:
44
+ raise UnsupportedPluginError("Please install 'yara-python' to use the yara plugin.")
30
45
 
31
- @arg("--rule-files", "-r", type=Path, nargs="+", required=True, help="path to YARA rule file")
32
- @arg("--scan-path", default="/", help="path to recursively scan")
33
- @arg("--max-size", "-m", default=DEFAULT_MAX_SIZE, help="maximum file size in bytes to scan")
46
+ @arg("-r", "--rules", required=True, nargs="*", help="path(s) to YARA rule file(s) or folder(s)")
47
+ @arg("-p", "--path", default="/", help="path on target(s) to recursively scan")
48
+ @arg("-m", "--max-size", default=DEFAULT_MAX_SCAN_SIZE, help="maximum file size in bytes to scan")
49
+ @arg("-c", "--check", default=False, action="store_true", help="check if every YARA rule is valid")
34
50
  @export(record=YaraMatchRecord)
35
- def yara(self, rule_files, scan_path="/", max_size=DEFAULT_MAX_SIZE):
36
- """Scan files up to a given maximum size with a local YARA rule file.
51
+ def yara(
52
+ self,
53
+ rules: list[str | Path],
54
+ path: str = "/",
55
+ max_size: int = DEFAULT_MAX_SCAN_SIZE,
56
+ check: bool = False,
57
+ ) -> Iterator[YaraMatchRecord]:
58
+ """Scan files inside the target up to a given maximum size with YARA rule file(s).
59
+
60
+ Args:
61
+ rules: ``list`` of strings or ``Path`` objects pointing to rule files to use.
62
+ path: ``string`` of absolute target path to scan.
63
+ max_size: Files larger than this size will not be scanned.
64
+ check: Check if provided rules are valid, only compiles valid rules.
37
65
 
38
- Example:
39
- target-query <TARGET> -f yara --rule-file /path/to/yara_sigs.rule
66
+ Returns:
67
+ Iterator yields ``YaraMatchRecord``.
40
68
  """
41
69
 
42
- rule_data = "\n".join([rule_file.read_text() for rule_file in rule_files])
70
+ compiled_rules = process_rules(rules, check)
43
71
 
44
- rules = yara.compile(source=rule_data)
45
- for _, _, files in self.target.fs.walk_ext(scan_path):
46
- for file_entry in files:
47
- path = self.target.fs.path(file_entry.path)
72
+ if not rules:
73
+ self.target.log.error("No working rules found in '%s'", ",".join(rules))
74
+ return
75
+
76
+ if hasattr(compiled_rules, "warnings") and (num_warns := len(compiled_rules.warnings)) > 0:
77
+ self.target.log.warning("YARA generated %s warnings while compiling rules", num_warns)
78
+ for warning in compiled_rules.warnings:
79
+ self.target.log.debug(warning)
80
+
81
+ self.target.log.warning("Will not scan files larger than %s MB", max_size // 1024 // 1024)
82
+
83
+ for _, _, files in self.target.fs.walk_ext(path):
84
+ for file in files:
48
85
  try:
49
- if path.stat().st_size > max_size:
86
+ if file_size := file.stat().st_size > max_size:
87
+ self.target.log.debug(
88
+ "Skipping file '%s' as it is larger than %s bytes (size is %s)", file, file_size, max_size
89
+ )
50
90
  continue
51
91
 
52
- for match in rules.match(data=path.read_bytes()):
92
+ buf = file.open().read()
93
+ for match in compiled_rules.match(data=buf):
53
94
  yield YaraMatchRecord(
54
- path=path,
55
- digest=path.get().hash(),
95
+ path=self.target.fs.path(file.path),
96
+ digest=hashutil.common(BytesIO(buf)),
56
97
  rule=match.rule,
57
98
  tags=match.tags,
99
+ namespace=match.namespace,
58
100
  _target=self.target,
59
101
  )
102
+
60
103
  except FileNotFoundError:
61
104
  continue
62
- except Exception:
63
- self.target.log.exception("Error scanning file: %s", path)
105
+ except RuntimeWarning as e:
106
+ self.target.log.warning("Runtime warning while scanning file '%s': %s", file, e)
107
+ except Exception as e:
108
+ self.target.log.error("Exception scanning file '%s'", file)
109
+ self.target.log.debug("", exc_info=e)
110
+
111
+
112
+ def process_rules(paths: list[str | Path], check: bool = False) -> yara.Rules | None:
113
+ """Generate compiled YARA rules from the given path(s).
114
+
115
+ Provide path to one (compiled) YARA file or directory containing YARA files.
116
+
117
+ Args:
118
+ paths: Path to file(s) or folder(s) containing YARA files.
119
+ check: Attempt to compile every rule file before appending to rules.
120
+
121
+ Returns:
122
+ Compiled YARA rules or None.
123
+ """
124
+ files = set()
125
+ compiled_rules = None
126
+
127
+ for rules_path in paths:
128
+ if isinstance(rules_path, str):
129
+ rules_path = Path(rules_path)
130
+
131
+ if not rules_path.exists():
132
+ log.warning("File %s does not exist!", rules_path)
133
+ continue
134
+
135
+ if rules_path.is_dir():
136
+ for file in rules_path.rglob("*"):
137
+ if not file.is_file():
138
+ continue
139
+ files.add(file)
140
+ else:
141
+ files.add(rules_path)
142
+
143
+ for file in set(files):
144
+ with file.open("rb") as fh:
145
+ magic = fh.read(4)
146
+
147
+ if magic == b"YARA":
148
+ if len(files) > 1:
149
+ log.error("Providing multiple compiled YARA files is not supported. Did not add %s", file)
150
+ continue
151
+ else:
152
+ log.info("Adding single compiled YARA file %s", file)
153
+ compiled_rules = compile_yara(file, is_compiled=True)
154
+ break
155
+
156
+ elif check and not is_valid_yara({"check_namespace": file}):
157
+ log.warning("File %s contains invalid rule(s)!", file)
158
+ files.remove(file)
159
+ continue
160
+
161
+ if files and not compiled_rules:
162
+ try:
163
+ compiled_rules = compile_yara({hashlib.md5(file.as_posix().encode()).hexdigest(): file for file in files})
164
+ except yara.Error as e:
165
+ log.error("Failed to compile YARA file(s): %s", e)
166
+
167
+ return compiled_rules
168
+
169
+
170
+ def compile_yara(files: dict[str, Path] | Path, is_compiled: bool = False) -> yara.Rules | None:
171
+ """Compile or load the given YARA file(s) to rules."""
172
+ if is_compiled and isinstance(files, Path):
173
+ return yara.load(files.as_posix())
174
+ else:
175
+ return yara.compile(filepaths={ns: Path(path).as_posix() for ns, path in files.items()})
176
+
177
+
178
+ def is_valid_yara(files: dict[str, Path] | Path, is_compiled: bool = False) -> bool:
179
+ """Determine if the given YARA file(s) compile without errors or warnings."""
180
+ try:
181
+ compile_yara(files, is_compiled)
182
+ return True
183
+
184
+ except (yara.SyntaxError, yara.WarningError, yara.Error) as e:
185
+ log.debug("Rule file(s) '%s' invalid: %s", files, e)
186
+ return False
@@ -219,6 +219,7 @@ class UnixPlugin(OSPlugin):
219
219
 
220
220
  if fs.__type__ == "xfs":
221
221
  fs_id = fs.xfs.uuid
222
+ fs_volume_name = fs.xfs.name
222
223
  elif fs.__type__ == "ext":
223
224
  fs_id = fs.extfs.uuid
224
225
  fs_last_mount = fs.extfs.last_mount
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ import argparse
4
+ import logging
5
+
6
+ from dissect.target import Target
7
+ from dissect.target.exceptions import TargetError
8
+ from dissect.target.plugins.filesystem.yara import HAS_YARA, YaraPlugin
9
+ from dissect.target.tools.query import record_output
10
+ from dissect.target.tools.utils import (
11
+ catch_sigpipe,
12
+ configure_generic_arguments,
13
+ process_generic_arguments,
14
+ )
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+
19
+ @catch_sigpipe
20
+ def main():
21
+ help_formatter = argparse.ArgumentDefaultsHelpFormatter
22
+ parser = argparse.ArgumentParser(
23
+ description="target-yara",
24
+ fromfile_prefix_chars="@",
25
+ formatter_class=help_formatter,
26
+ )
27
+
28
+ parser.add_argument("targets", metavar="TARGETS", nargs="*", help="Targets to load")
29
+ parser.add_argument("-s", "--strings", default=False, action="store_true", help="print output as string")
30
+
31
+ for args, kwargs in getattr(YaraPlugin.yara, "__args__", []):
32
+ parser.add_argument(*args, **kwargs)
33
+
34
+ configure_generic_arguments(parser)
35
+
36
+ args = parser.parse_args()
37
+ process_generic_arguments(args)
38
+
39
+ if not HAS_YARA:
40
+ log.error("yara-python is not installed: pip install yara-python")
41
+ parser.exit(1)
42
+
43
+ if not args.targets:
44
+ log.error("No targets provided")
45
+ parser.exit(1)
46
+
47
+ try:
48
+ for target in Target.open_all(args.targets):
49
+ target.log.info("Scanning target")
50
+ rs = record_output(args.strings, False)
51
+ for record in target.yara(args.rules, args.path, args.max_size, args.check):
52
+ rs.write(record)
53
+
54
+ except TargetError as e:
55
+ log.error(e)
56
+ log.debug("", exc_info=e)
57
+ parser.exit(1)
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.19.dev20
3
+ Version: 3.19.dev22
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
@@ -164,7 +164,7 @@ dissect/target/plugins/filesystem/acquire_hash.py,sha256=OVxI19-Bl1tdqCiFMscFMLm
164
164
  dissect/target/plugins/filesystem/icat.py,sha256=bOMi04IlljnKwxTWTZJKtK7RxKnabFu3WcXyUwzkE-4,4090
165
165
  dissect/target/plugins/filesystem/resolver.py,sha256=HfyASUFV4F9uD-yFXilFpPTORAsRDvdmTvuYHgOaOWg,4776
166
166
  dissect/target/plugins/filesystem/walkfs.py,sha256=e8HEZcV5Wiua26FGWL3xgiQ_PIhcNvGI5KCdsAx2Nmo,2298
167
- dissect/target/plugins/filesystem/yara.py,sha256=q_pbrQArNaWP4ILRzK7VQhukIw16LhUvntoviHmZ38Q,2241
167
+ dissect/target/plugins/filesystem/yara.py,sha256=JdWqbqDBhKrht3fTroqX7NpBU9khEQUWyMcDgLv2l2g,6686
168
168
  dissect/target/plugins/filesystem/ntfs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
169
169
  dissect/target/plugins/filesystem/ntfs/mft.py,sha256=AD3w2FIjDAf8x2KEbBhz2NeOA_lxIAmw353w6J3ObYU,9565
170
170
  dissect/target/plugins/filesystem/ntfs/mft_timeline.py,sha256=vvNFAZbr7s3X2OTYf4ES_L6-XsouTXcTymfxnHfZ1Rw,6791
@@ -184,7 +184,7 @@ dissect/target/plugins/general/scrape.py,sha256=Fz7BNXflvuxlnVulyyDhLpyU8D_hJdH6
184
184
  dissect/target/plugins/general/users.py,sha256=cQXPQ2XbkPjckCPHYTUW4JEhYN0_CT8JI8hJPZn3qSs,3030
185
185
  dissect/target/plugins/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
186
186
  dissect/target/plugins/os/unix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
187
- dissect/target/plugins/os/unix/_os.py,sha256=GcbP8HbK1XtwYFGbl8x0BdfoLAC2ROv9xieeFGI5dWM,14557
187
+ dissect/target/plugins/os/unix/_os.py,sha256=u7m97qASdm_l90k4i7dzMtu2kcF6fVPKutx4_ISQTSg,14606
188
188
  dissect/target/plugins/os/unix/cronjobs.py,sha256=2ssj97UVJueyATVl7NMJmqd9uHflQ2tXUqdOCFIEje8,3182
189
189
  dissect/target/plugins/os/unix/datetime.py,sha256=gKfBdPyUirt3qmVYfOJ1oZXRPn8wRzssbZxR_ARrtk8,1518
190
190
  dissect/target/plugins/os/unix/etc.py,sha256=HoPEC1hxqurSnAXQAK-jf_HxdBIDe-1z_qSw_n-ViI4,258
@@ -333,6 +333,7 @@ dissect/target/tools/query.py,sha256=ONHu2FVomLccikb84qBrlhNmEfRoHYFQMcahk_y2c9A
333
333
  dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
334
334
  dissect/target/tools/shell.py,sha256=_widEuIRqZhYzcFR52NYI8O2aPFm6tG5Uiv-AIrC32U,45155
335
335
  dissect/target/tools/utils.py,sha256=sQizexY3ui5vmWw4KOBLg5ecK3TPFjD-uxDqRn56ZTY,11304
336
+ dissect/target/tools/yara.py,sha256=xIom_n78oBiDg6VEBMVk8qmvhYMOPzY5yv9Vl1rDbB4,1754
336
337
  dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
337
338
  dissect/target/tools/dump/run.py,sha256=aD84peRS4zHqC78fH7Vd4ni3m1ZmVP70LyMwBRvoDGY,9463
338
339
  dissect/target/tools/dump/state.py,sha256=YYgCff0kZZ-tx27lJlc9LQ7AfoGnLK5Gyi796OnktA8,9205
@@ -345,10 +346,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
345
346
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
346
347
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
347
348
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
348
- dissect.target-3.19.dev20.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
349
- dissect.target-3.19.dev20.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
350
- dissect.target-3.19.dev20.dist-info/METADATA,sha256=FJBn0EwCkYX3CRSKLZH9KpOLOR56e6JSApgvTpYVB_I,12719
351
- dissect.target-3.19.dev20.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
352
- dissect.target-3.19.dev20.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
353
- dissect.target-3.19.dev20.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
354
- dissect.target-3.19.dev20.dist-info/RECORD,,
349
+ dissect.target-3.19.dev22.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
350
+ dissect.target-3.19.dev22.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
351
+ dissect.target-3.19.dev22.dist-info/METADATA,sha256=f_1UaUvsl2v75UvJhA2SVyEH2FaQ21oR5byPzLlH6mU,12719
352
+ dissect.target-3.19.dev22.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
353
+ dissect.target-3.19.dev22.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
354
+ dissect.target-3.19.dev22.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
355
+ dissect.target-3.19.dev22.dist-info/RECORD,,
@@ -8,3 +8,4 @@ target-mount = dissect.target.tools.mount:main
8
8
  target-query = dissect.target.tools.query:main
9
9
  target-reg = dissect.target.tools.reg:main
10
10
  target-shell = dissect.target.tools.shell:main
11
+ target-yara = dissect.target.tools.yara:main