maco 1.2.10__py3-none-any.whl → 1.2.12__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.
@@ -1 +1,4 @@
1
1
  httpx
2
+
3
+ # Install maco from source for testing
4
+ ../
@@ -0,0 +1,17 @@
1
+ from io import BytesIO
2
+ from typing import List, Optional
3
+
4
+ from maco import extractor, model, yara
5
+ from maco.exceptions import AnalysisAbortedException
6
+
7
+
8
+ class Terminator(extractor.Extractor):
9
+ """Terminates early during extraction"""
10
+
11
+ family = "terminator"
12
+ author = "skynet"
13
+ last_modified = "1997-08-29"
14
+
15
+ def run(self, stream: BytesIO, matches: List[yara.Match]) -> Optional[model.ExtractorModel]:
16
+ # Terminate early and indicate I can't run on this sample
17
+ raise AnalysisAbortedException("I can't run on this sample")
maco/cli.py CHANGED
@@ -3,18 +3,20 @@
3
3
  import argparse
4
4
  import base64
5
5
  import binascii
6
+ import cart
6
7
  import hashlib
7
8
  import io
8
9
  import json
9
10
  import logging
10
11
  import os
11
12
  import sys
12
- from typing import BinaryIO, List, Tuple
13
13
 
14
- import cart
14
+ from importlib.metadata import version
15
+ from typing import BinaryIO, List, Tuple
15
16
 
16
17
  from maco import collector
17
18
 
19
+
18
20
  logger = logging.getLogger("maco.lib.cli")
19
21
 
20
22
 
@@ -92,6 +94,7 @@ def process_filesystem(
92
94
  force: bool,
93
95
  include_base64: bool,
94
96
  create_venv: bool = False,
97
+ skip_install: bool = False,
95
98
  ) -> Tuple[int, int, int]:
96
99
  """Process filesystem with extractors and print results of extraction.
97
100
 
@@ -99,7 +102,9 @@ def process_filesystem(
99
102
  """
100
103
  if force:
101
104
  logger.warning("force execute will cause errors if an extractor requires a yara rule hit during execution")
102
- collected = collector.Collector(path_extractors, include=include, exclude=exclude, create_venv=create_venv)
105
+ collected = collector.Collector(
106
+ path_extractors, include=include, exclude=exclude, create_venv=create_venv, skip_install=skip_install
107
+ )
103
108
 
104
109
  logger.info(f"extractors loaded: {[x for x in collected.extractors.keys()]}\n")
105
110
  for _, extractor in collected.extractors.items():
@@ -191,6 +196,18 @@ def main():
191
196
  "This runs much slower than the alternative but may be necessary "
192
197
  "when there are many extractors with conflicting dependencies.",
193
198
  )
199
+ parser.add_argument(
200
+ "--force_install",
201
+ action="store_true",
202
+ help="Force installation of Python dependencies for extractors (in both host and virtual environments).",
203
+ )
204
+ parser.add_argument(
205
+ "--version",
206
+ action="version",
207
+ version=f"version: {version('maco')}",
208
+ help="Show version of MACO",
209
+ )
210
+
194
211
  args = parser.parse_args()
195
212
  inc = args.include.split(",") if args.include else []
196
213
  exc = args.exclude.split(",") if args.exclude else []
@@ -236,6 +253,7 @@ def main():
236
253
  force=args.force,
237
254
  include_base64=args.base64,
238
255
  create_venv=args.create_venv,
256
+ skip_install=not args.force_install,
239
257
  )
240
258
 
241
259
 
maco/collector.py CHANGED
@@ -13,6 +13,7 @@ from multiprocess import Manager, Process, Queue
13
13
  from pydantic import BaseModel
14
14
 
15
15
  from maco import extractor, model, utils, yara
16
+ from maco.exceptions import AnalysisAbortedException
16
17
 
17
18
 
18
19
  class ExtractorLoadError(Exception):
@@ -67,6 +68,7 @@ class Collector:
67
68
  include: List[str] = None,
68
69
  exclude: List[str] = None,
69
70
  create_venv: bool = False,
71
+ skip_install: bool = False,
70
72
  ):
71
73
  """Discover and load extractors from file system."""
72
74
  # maco requires the extractor to be imported directly, so ensure they are available on the path
@@ -135,6 +137,7 @@ class Collector:
135
137
  root_directory=path_extractors,
136
138
  scanner=yara.compile(source=utils.MACO_YARA_RULE),
137
139
  create_venv=create_venv and os.path.isdir(path_extractors),
140
+ skip_install=skip_install,
138
141
  ),
139
142
  )
140
143
  p.start()
@@ -189,6 +192,9 @@ class Collector:
189
192
  venv=extractor["venv"],
190
193
  )
191
194
  )
195
+ except AnalysisAbortedException:
196
+ # Extractor voluntarily aborted analysis of sample
197
+ return
192
198
  except Exception:
193
199
  # caller can deal with the exception
194
200
  raise
maco/exceptions.py ADDED
@@ -0,0 +1,3 @@
1
+ # Can be raised by extractors to abort analysis of a sample
2
+ # ie. Can abort if preliminary checks at start of run indicate the file shouldn't be analyzed by extractor
3
+ class AnalysisAbortedException(Exception): ...
maco/utils.py CHANGED
@@ -34,6 +34,7 @@ from uv import find_uv_bin
34
34
 
35
35
  from maco import model
36
36
  from maco.extractor import Extractor
37
+ from maco.exceptions import AnalysisAbortedException
37
38
 
38
39
  logger = logging.getLogger("maco.lib.utils")
39
40
 
@@ -450,9 +451,19 @@ def run_extractor(
450
451
  key = f"{module_name}_{extractor_class}"
451
452
  if key not in _loaded_extractors:
452
453
  # dynamic import of extractor
453
- mod = importlib.import_module(module_name)
454
- extractor_cls = mod.__getattribute__(extractor_class)
455
- extractor = extractor_cls()
454
+ try:
455
+ # Add the correct directory to the PATH before attempting to load the extractor
456
+ import_path = module_path[: -4 - len(module_name)]
457
+ sys.path.insert(1, import_path)
458
+ mod = importlib.import_module(module_name)
459
+ extractor_cls = mod.__getattribute__(extractor_class)
460
+ extractor = extractor_cls()
461
+
462
+ # Add to cache
463
+ _loaded_extractors[key] = extractor
464
+ finally:
465
+ sys.path.pop(1)
466
+
456
467
  else:
457
468
  # retrieve cached extractor
458
469
  extractor = _loaded_extractors[key]
@@ -504,9 +515,15 @@ def run_extractor(
504
515
  exception = stderr
505
516
  if delim in exception:
506
517
  exception = f"{delim}{exception.split(delim, 1)[1]}"
507
- # print extractor logging at error level
508
- logger.error(f"maco extractor raised exception, stderr:\n{stderr}")
509
- raise Exception(exception) from e
518
+ if "maco.exceptions.AnalysisAbortedException" in exception:
519
+ # Extractor voluntarily terminated, re-raise exception to be handled by collector
520
+ raise AnalysisAbortedException(
521
+ exception.split("maco.exceptions.AnalysisAbortedException: ")[-1]
522
+ )
523
+ else:
524
+ # print extractor logging at error level
525
+ logger.error(f"maco extractor raised exception, stderr:\n{stderr}")
526
+ raise Exception(exception) from e
510
527
  # ensure that extractor logging is available
511
528
  logger.info(f"maco extractor stderr:\n{stderr}")
512
529
  return loaded
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: maco
3
- Version: 1.2.10
3
+ Version: 1.2.12
4
4
  Author: sl-govau
5
5
  Maintainer: cccs-rs
6
6
  License: MIT License
@@ -2,26 +2,29 @@ demo_extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  demo_extractors/elfy.py,sha256=Jo_GKExCeFOKGENJnNM_9ONfJO7LQFucCNz0ryTAo9U,765
3
3
  demo_extractors/limit_other.py,sha256=BWjeyOxB75kw4eRla5zvSzdcXtELOS8R6hc71rLPh1s,1295
4
4
  demo_extractors/nothing.py,sha256=MNPlb0IsBjrlU5e438JlJ4DIKoBpBRAaYY3JhD3yHqk,601
5
- demo_extractors/requirements.txt,sha256=E0tD6xBZldq6sQGTHng6k88lBeASOhmLJcdcjpcqBNE,6
5
+ demo_extractors/requirements.txt,sha256=nD7BPNv7YEPUr9MDcaKYNs2UfHtxvN8FOKKesgC_L5g,50
6
6
  demo_extractors/shared.py,sha256=2P1cyuRbHDvM9IRt3UZnwdyhxx7OWqNC83xLyV8Y190,305
7
+ demo_extractors/terminator.py,sha256=G1AQHL1JGI8iMa4-H55nWwLzOlPicWH-2HiADw4aE9M,552
7
8
  demo_extractors/complex/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
9
  demo_extractors/complex/complex.py,sha256=tXrzj_zWIXbTOwj7Lezapk-qkrM-lfwcyjd5m-BYzdg,2322
9
10
  demo_extractors/complex/complex_utils.py,sha256=aec8kJsYUrMPo-waihkVLt-0QpiOPkw7dDqfT9MNuHk,123
10
11
  maco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
12
  maco/base_test.py,sha256=cjGLEy2c69wl9sjn74QFz7X-VxWOfdin4W8MvYsXc4Q,2718
12
- maco/cli.py,sha256=pPS8euWaLV-6csBCCzT1Mtc7GwP7a_RikDjfUYxoxU8,8415
13
- maco/collector.py,sha256=cBZEHx5qjFwf-EfAgEmXlu4kT4rWZkcDE926gBrOoN8,7493
13
+ maco/cli.py,sha256=K9gjsyGXcmSqBSkRkertVTH-Z4mwGFVk7TQxx8Y2mXo,8937
14
+ maco/collector.py,sha256=T1tGibakx-yfgHzrHC4mqY-G2Y-VhDz8Qx1KoXa7l0g,7752
15
+ maco/exceptions.py,sha256=aUbkX9CZfgp1aZFTEa98Uwge6MeQcQ3LbYkgNOwWaCg,214
14
16
  maco/extractor.py,sha256=uGSGiCQ4jd8jFmfw2T99BGcY5iQJzXHcG_RoTIxClTE,2802
15
- maco/utils.py,sha256=Tjop6lfnb5LtkS0QruUzIO1NVnNkYi0i1I94CyjHL1Q,20895
17
+ maco/utils.py,sha256=bD2z6xZeR-MNhM834w2vQi4IP-kpQSXL-l71vmGweAU,21726
16
18
  maco/yara.py,sha256=8RVaGyeUWY5f8_wfQ25lDX1bcXsb_VoSja85ZC2SqGw,2913
17
19
  maco/model/__init__.py,sha256=ULdyHx8R5D2ICHZo3VoCk1YTlewTok36TYIpwx__pNY,45
18
20
  maco/model/model.py,sha256=4uY88WphbP3iu-L2WjuYwtgZCS_wNul_hr0bAVuTpvc,23740
19
21
  model_setup/maco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
22
  model_setup/maco/base_test.py,sha256=cjGLEy2c69wl9sjn74QFz7X-VxWOfdin4W8MvYsXc4Q,2718
21
- model_setup/maco/cli.py,sha256=pPS8euWaLV-6csBCCzT1Mtc7GwP7a_RikDjfUYxoxU8,8415
22
- model_setup/maco/collector.py,sha256=cBZEHx5qjFwf-EfAgEmXlu4kT4rWZkcDE926gBrOoN8,7493
23
+ model_setup/maco/cli.py,sha256=K9gjsyGXcmSqBSkRkertVTH-Z4mwGFVk7TQxx8Y2mXo,8937
24
+ model_setup/maco/collector.py,sha256=T1tGibakx-yfgHzrHC4mqY-G2Y-VhDz8Qx1KoXa7l0g,7752
25
+ model_setup/maco/exceptions.py,sha256=aUbkX9CZfgp1aZFTEa98Uwge6MeQcQ3LbYkgNOwWaCg,214
23
26
  model_setup/maco/extractor.py,sha256=uGSGiCQ4jd8jFmfw2T99BGcY5iQJzXHcG_RoTIxClTE,2802
24
- model_setup/maco/utils.py,sha256=Tjop6lfnb5LtkS0QruUzIO1NVnNkYi0i1I94CyjHL1Q,20895
27
+ model_setup/maco/utils.py,sha256=bD2z6xZeR-MNhM834w2vQi4IP-kpQSXL-l71vmGweAU,21726
25
28
  model_setup/maco/yara.py,sha256=8RVaGyeUWY5f8_wfQ25lDX1bcXsb_VoSja85ZC2SqGw,2913
26
29
  model_setup/maco/model/__init__.py,sha256=ULdyHx8R5D2ICHZo3VoCk1YTlewTok36TYIpwx__pNY,45
27
30
  model_setup/maco/model/model.py,sha256=4uY88WphbP3iu-L2WjuYwtgZCS_wNul_hr0bAVuTpvc,23740
@@ -36,9 +39,9 @@ tests/extractors/basic_longer.py,sha256=1ClU2QD-Y0TOl_loNFvEqIEpTR5TSVJ6zg9ZmC-E
36
39
  tests/extractors/test_basic.py,sha256=FLKekfSGM69HaiF7Vu_7D7KDXHZko-9hZkMO8_DoyYA,697
37
40
  tests/extractors/bob/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
41
  tests/extractors/bob/bob.py,sha256=G5aOoz58J0ZQK2_lA7HRxAzeLzBxssWxBTZcv1pSbi8,176
39
- maco-1.2.10.dist-info/LICENSE.md,sha256=gMSjshPhXvV_F1qxmeNkKdBqGWkd__fEJf4glS504bM,1478
40
- maco-1.2.10.dist-info/METADATA,sha256=FtHe8aMGg8ij5Z9RQ1alHpGYUA2hkCnPetwhKnVh8cE,15893
41
- maco-1.2.10.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
42
- maco-1.2.10.dist-info/entry_points.txt,sha256=TpcwG1gedIg8Y7a9ZOv8aQpuwEUftCefDrAjzeP-o6U,39
43
- maco-1.2.10.dist-info/top_level.txt,sha256=iMRwuzmrHA3zSwiSeMIl6FWhzRpn_st-I4fAv-kw5_o,49
44
- maco-1.2.10.dist-info/RECORD,,
42
+ maco-1.2.12.dist-info/LICENSE.md,sha256=gMSjshPhXvV_F1qxmeNkKdBqGWkd__fEJf4glS504bM,1478
43
+ maco-1.2.12.dist-info/METADATA,sha256=fQa29RHfFtLo3isR5emR5qEtpeTTTHwBk4EN1CWzyRs,15893
44
+ maco-1.2.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
45
+ maco-1.2.12.dist-info/entry_points.txt,sha256=TpcwG1gedIg8Y7a9ZOv8aQpuwEUftCefDrAjzeP-o6U,39
46
+ maco-1.2.12.dist-info/top_level.txt,sha256=iMRwuzmrHA3zSwiSeMIl6FWhzRpn_st-I4fAv-kw5_o,49
47
+ maco-1.2.12.dist-info/RECORD,,
model_setup/maco/cli.py CHANGED
@@ -3,18 +3,20 @@
3
3
  import argparse
4
4
  import base64
5
5
  import binascii
6
+ import cart
6
7
  import hashlib
7
8
  import io
8
9
  import json
9
10
  import logging
10
11
  import os
11
12
  import sys
12
- from typing import BinaryIO, List, Tuple
13
13
 
14
- import cart
14
+ from importlib.metadata import version
15
+ from typing import BinaryIO, List, Tuple
15
16
 
16
17
  from maco import collector
17
18
 
19
+
18
20
  logger = logging.getLogger("maco.lib.cli")
19
21
 
20
22
 
@@ -92,6 +94,7 @@ def process_filesystem(
92
94
  force: bool,
93
95
  include_base64: bool,
94
96
  create_venv: bool = False,
97
+ skip_install: bool = False,
95
98
  ) -> Tuple[int, int, int]:
96
99
  """Process filesystem with extractors and print results of extraction.
97
100
 
@@ -99,7 +102,9 @@ def process_filesystem(
99
102
  """
100
103
  if force:
101
104
  logger.warning("force execute will cause errors if an extractor requires a yara rule hit during execution")
102
- collected = collector.Collector(path_extractors, include=include, exclude=exclude, create_venv=create_venv)
105
+ collected = collector.Collector(
106
+ path_extractors, include=include, exclude=exclude, create_venv=create_venv, skip_install=skip_install
107
+ )
103
108
 
104
109
  logger.info(f"extractors loaded: {[x for x in collected.extractors.keys()]}\n")
105
110
  for _, extractor in collected.extractors.items():
@@ -191,6 +196,18 @@ def main():
191
196
  "This runs much slower than the alternative but may be necessary "
192
197
  "when there are many extractors with conflicting dependencies.",
193
198
  )
199
+ parser.add_argument(
200
+ "--force_install",
201
+ action="store_true",
202
+ help="Force installation of Python dependencies for extractors (in both host and virtual environments).",
203
+ )
204
+ parser.add_argument(
205
+ "--version",
206
+ action="version",
207
+ version=f"version: {version('maco')}",
208
+ help="Show version of MACO",
209
+ )
210
+
194
211
  args = parser.parse_args()
195
212
  inc = args.include.split(",") if args.include else []
196
213
  exc = args.exclude.split(",") if args.exclude else []
@@ -236,6 +253,7 @@ def main():
236
253
  force=args.force,
237
254
  include_base64=args.base64,
238
255
  create_venv=args.create_venv,
256
+ skip_install=not args.force_install,
239
257
  )
240
258
 
241
259
 
@@ -13,6 +13,7 @@ from multiprocess import Manager, Process, Queue
13
13
  from pydantic import BaseModel
14
14
 
15
15
  from maco import extractor, model, utils, yara
16
+ from maco.exceptions import AnalysisAbortedException
16
17
 
17
18
 
18
19
  class ExtractorLoadError(Exception):
@@ -67,6 +68,7 @@ class Collector:
67
68
  include: List[str] = None,
68
69
  exclude: List[str] = None,
69
70
  create_venv: bool = False,
71
+ skip_install: bool = False,
70
72
  ):
71
73
  """Discover and load extractors from file system."""
72
74
  # maco requires the extractor to be imported directly, so ensure they are available on the path
@@ -135,6 +137,7 @@ class Collector:
135
137
  root_directory=path_extractors,
136
138
  scanner=yara.compile(source=utils.MACO_YARA_RULE),
137
139
  create_venv=create_venv and os.path.isdir(path_extractors),
140
+ skip_install=skip_install,
138
141
  ),
139
142
  )
140
143
  p.start()
@@ -189,6 +192,9 @@ class Collector:
189
192
  venv=extractor["venv"],
190
193
  )
191
194
  )
195
+ except AnalysisAbortedException:
196
+ # Extractor voluntarily aborted analysis of sample
197
+ return
192
198
  except Exception:
193
199
  # caller can deal with the exception
194
200
  raise
@@ -0,0 +1,3 @@
1
+ # Can be raised by extractors to abort analysis of a sample
2
+ # ie. Can abort if preliminary checks at start of run indicate the file shouldn't be analyzed by extractor
3
+ class AnalysisAbortedException(Exception): ...
model_setup/maco/utils.py CHANGED
@@ -34,6 +34,7 @@ from uv import find_uv_bin
34
34
 
35
35
  from maco import model
36
36
  from maco.extractor import Extractor
37
+ from maco.exceptions import AnalysisAbortedException
37
38
 
38
39
  logger = logging.getLogger("maco.lib.utils")
39
40
 
@@ -450,9 +451,19 @@ def run_extractor(
450
451
  key = f"{module_name}_{extractor_class}"
451
452
  if key not in _loaded_extractors:
452
453
  # dynamic import of extractor
453
- mod = importlib.import_module(module_name)
454
- extractor_cls = mod.__getattribute__(extractor_class)
455
- extractor = extractor_cls()
454
+ try:
455
+ # Add the correct directory to the PATH before attempting to load the extractor
456
+ import_path = module_path[: -4 - len(module_name)]
457
+ sys.path.insert(1, import_path)
458
+ mod = importlib.import_module(module_name)
459
+ extractor_cls = mod.__getattribute__(extractor_class)
460
+ extractor = extractor_cls()
461
+
462
+ # Add to cache
463
+ _loaded_extractors[key] = extractor
464
+ finally:
465
+ sys.path.pop(1)
466
+
456
467
  else:
457
468
  # retrieve cached extractor
458
469
  extractor = _loaded_extractors[key]
@@ -504,9 +515,15 @@ def run_extractor(
504
515
  exception = stderr
505
516
  if delim in exception:
506
517
  exception = f"{delim}{exception.split(delim, 1)[1]}"
507
- # print extractor logging at error level
508
- logger.error(f"maco extractor raised exception, stderr:\n{stderr}")
509
- raise Exception(exception) from e
518
+ if "maco.exceptions.AnalysisAbortedException" in exception:
519
+ # Extractor voluntarily terminated, re-raise exception to be handled by collector
520
+ raise AnalysisAbortedException(
521
+ exception.split("maco.exceptions.AnalysisAbortedException: ")[-1]
522
+ )
523
+ else:
524
+ # print extractor logging at error level
525
+ logger.error(f"maco extractor raised exception, stderr:\n{stderr}")
526
+ raise Exception(exception) from e
510
527
  # ensure that extractor logging is available
511
528
  logger.info(f"maco extractor stderr:\n{stderr}")
512
529
  return loaded
File without changes