maco 1.2.3__py3-none-any.whl → 1.2.4__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.
- demo_extractors/__init__.py +0 -0
- demo_extractors/complex/complex.py +1 -1
- demo_extractors/limit_other.py +1 -1
- maco/collector.py +19 -6
- maco/utils.py +46 -22
- {maco-1.2.3.dist-info → maco-1.2.4.dist-info}/METADATA +1 -1
- {maco-1.2.3.dist-info → maco-1.2.4.dist-info}/RECORD +14 -12
- model_setup/maco/collector.py +19 -6
- model_setup/maco/utils.py +46 -22
- tests/data/trigger_complex.txt.cart +0 -0
- {maco-1.2.3.dist-info → maco-1.2.4.dist-info}/LICENSE.md +0 -0
- {maco-1.2.3.dist-info → maco-1.2.4.dist-info}/WHEEL +0 -0
- {maco-1.2.3.dist-info → maco-1.2.4.dist-info}/entry_points.txt +0 -0
- {maco-1.2.3.dist-info → maco-1.2.4.dist-info}/top_level.txt +0 -0
|
File without changes
|
demo_extractors/limit_other.py
CHANGED
maco/collector.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import logging
|
|
5
|
+
import logging.handlers
|
|
5
6
|
import os
|
|
6
|
-
from multiprocessing import Manager, Process
|
|
7
|
+
from multiprocessing import Manager, Process, Queue
|
|
7
8
|
from tempfile import NamedTemporaryFile
|
|
8
9
|
from types import ModuleType
|
|
9
10
|
from typing import Any, BinaryIO, Dict, List, Union
|
|
@@ -86,21 +87,33 @@ class Collector:
|
|
|
86
87
|
)
|
|
87
88
|
namespaced_rules[name] = member.yara_rule or extractor.DEFAULT_YARA_RULE.format(name=name)
|
|
88
89
|
|
|
90
|
+
# multiprocess logging is awkward - set up a queue to ensure we can log
|
|
91
|
+
logging_queue = Queue()
|
|
92
|
+
queue_handler = logging.handlers.QueueListener(logging_queue,*logging.getLogger().handlers)
|
|
93
|
+
queue_handler.start()
|
|
94
|
+
|
|
89
95
|
# Find the extractors within the given directory
|
|
90
96
|
# Execute within a child process to ensure main process interpreter is kept clean
|
|
91
97
|
p = Process(
|
|
92
|
-
target=utils.
|
|
98
|
+
target=utils.proxy_logging,
|
|
93
99
|
args=(
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
logging_queue,
|
|
101
|
+
utils.import_extractors,
|
|
96
102
|
extractor_module_callback,
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
),
|
|
104
|
+
kwargs=dict(
|
|
105
|
+
root_directory=path_extractors,
|
|
106
|
+
scanner=yara.compile(source=utils.MACO_YARA_RULE),
|
|
107
|
+
create_venv=create_venv and os.path.isdir(path_extractors),
|
|
99
108
|
),
|
|
100
109
|
)
|
|
101
110
|
p.start()
|
|
102
111
|
p.join()
|
|
103
112
|
|
|
113
|
+
# stop multiprocess logging
|
|
114
|
+
queue_handler.stop()
|
|
115
|
+
logging_queue.close()
|
|
116
|
+
|
|
104
117
|
self.extractors = dict(extractors)
|
|
105
118
|
if not self.extractors:
|
|
106
119
|
raise ExtractorLoadError("no extractors were loaded")
|
maco/utils.py
CHANGED
|
@@ -4,11 +4,14 @@ import importlib.machinery
|
|
|
4
4
|
import importlib.util
|
|
5
5
|
import inspect
|
|
6
6
|
import json
|
|
7
|
+
import logging.handlers
|
|
7
8
|
import os
|
|
8
9
|
import re
|
|
9
10
|
import shutil
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
13
|
+
import multiprocessing
|
|
14
|
+
import logging
|
|
12
15
|
import tempfile
|
|
13
16
|
|
|
14
17
|
from maco import yara
|
|
@@ -28,10 +31,12 @@ from typing import Callable, Dict, List, Set, Tuple
|
|
|
28
31
|
|
|
29
32
|
from maco.extractor import Extractor
|
|
30
33
|
|
|
34
|
+
logger = logging.getLogger("maco.lib.utils")
|
|
35
|
+
|
|
31
36
|
VENV_DIRECTORY_NAME = ".venv"
|
|
32
37
|
|
|
33
|
-
RELATIVE_FROM_RE = re.compile("from (\.+)")
|
|
34
|
-
RELATIVE_FROM_IMPORT_RE = re.compile("from (\.+) import")
|
|
38
|
+
RELATIVE_FROM_RE = re.compile(r"from (\.+)")
|
|
39
|
+
RELATIVE_FROM_IMPORT_RE = re.compile(r"from (\.+) import")
|
|
35
40
|
|
|
36
41
|
try:
|
|
37
42
|
# Attempt to use the uv package manager (Recommended)
|
|
@@ -69,6 +74,7 @@ import importlib
|
|
|
69
74
|
import json
|
|
70
75
|
import os
|
|
71
76
|
import sys
|
|
77
|
+
import logging
|
|
72
78
|
|
|
73
79
|
try:
|
|
74
80
|
from maco import yara
|
|
@@ -76,6 +82,19 @@ except:
|
|
|
76
82
|
import yara
|
|
77
83
|
|
|
78
84
|
from base64 import b64encode
|
|
85
|
+
|
|
86
|
+
# ensure we have a logger to stderr
|
|
87
|
+
import logging
|
|
88
|
+
logger = logging.getLogger()
|
|
89
|
+
logger.setLevel(logging.DEBUG)
|
|
90
|
+
sh = logging.StreamHandler()
|
|
91
|
+
logger.addHandler(sh)
|
|
92
|
+
sh.setLevel(logging.DEBUG)
|
|
93
|
+
formatter = logging.Formatter(
|
|
94
|
+
fmt="%(asctime)s, [%(levelname)s] %(module)s.%(funcName)s: %(message)s", datefmt="%Y-%m-%d (%H:%M:%S)"
|
|
95
|
+
)
|
|
96
|
+
sh.setFormatter(formatter)
|
|
97
|
+
|
|
79
98
|
parent_package_path = "{parent_package_path}"
|
|
80
99
|
sys.path.insert(1, parent_package_path)
|
|
81
100
|
mod = importlib.import_module("{module_name}")
|
|
@@ -101,7 +120,7 @@ with open("{output_path}", 'w') as fp:
|
|
|
101
120
|
json.dump(result.dict(exclude_defaults=True, exclude_none=True), fp, cls=Base64Encoder)
|
|
102
121
|
"""
|
|
103
122
|
|
|
104
|
-
MACO_YARA_RULE = """
|
|
123
|
+
MACO_YARA_RULE = r"""
|
|
105
124
|
rule MACO {
|
|
106
125
|
meta:
|
|
107
126
|
desc = "Used to match on Python files that contain MACO extractors"
|
|
@@ -292,15 +311,10 @@ def register_extractors(
|
|
|
292
311
|
):
|
|
293
312
|
package_name = os.path.basename(current_directory)
|
|
294
313
|
parent_directory = os.path.dirname(current_directory)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# We'll need to create a link back to the original
|
|
301
|
-
if package_name not in sys.modules:
|
|
302
|
-
symlink = os.path.join(parent_directory, package_name)
|
|
303
|
-
os.symlink(current_directory, symlink)
|
|
314
|
+
if package_name in sys.modules:
|
|
315
|
+
# this may happen as part of testing if some part of the extractor code was directly imported
|
|
316
|
+
logger.warning(f"Looks like {package_name} is already loaded. "
|
|
317
|
+
"If your maco extractor overlaps an existing package name this could cause problems.")
|
|
304
318
|
|
|
305
319
|
try:
|
|
306
320
|
# Modify the PATH so we can recognize this new package on import
|
|
@@ -351,10 +365,6 @@ def register_extractors(
|
|
|
351
365
|
# Remove any modules that were loaded to deconflict with later modules loads
|
|
352
366
|
[sys.modules.pop(k) for k in set(sys.modules.keys()) - default_loaded_modules]
|
|
353
367
|
|
|
354
|
-
# Cleanup any symlinks
|
|
355
|
-
if symlink:
|
|
356
|
-
os.remove(symlink)
|
|
357
|
-
|
|
358
368
|
# If there still exists extractor files we haven't found yet, try searching in the available subdirectories
|
|
359
369
|
if extractor_files:
|
|
360
370
|
for dir in os.listdir(current_directory):
|
|
@@ -379,14 +389,22 @@ def register_extractors(
|
|
|
379
389
|
# We were able to find all the extractor files
|
|
380
390
|
break
|
|
381
391
|
|
|
392
|
+
def proxy_logging(queue: multiprocessing.Queue, callback: Callable[[ModuleType, str], None], *args, **kwargs):
|
|
393
|
+
"""Ensures logging is set up correctly for a child process and then executes the callback."""
|
|
394
|
+
logger = logging.getLogger()
|
|
395
|
+
qh = logging.handlers.QueueHandler(queue)
|
|
396
|
+
qh.setLevel(logging.DEBUG)
|
|
397
|
+
logger.addHandler(qh)
|
|
398
|
+
callback(*args, **kwargs, logger=logger)
|
|
382
399
|
|
|
383
400
|
def import_extractors(
|
|
401
|
+
extractor_module_callback: Callable[[ModuleType, str], bool],
|
|
402
|
+
*,
|
|
384
403
|
root_directory: str,
|
|
385
404
|
scanner: yara.Rules,
|
|
386
|
-
extractor_module_callback: Callable[[ModuleType, str], bool],
|
|
387
|
-
logger: Logger,
|
|
388
405
|
create_venv: bool = False,
|
|
389
406
|
python_version: str = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
407
|
+
logger: Logger,
|
|
390
408
|
):
|
|
391
409
|
extractor_dirs, extractor_files = scan_for_extractors(root_directory, scanner, logger)
|
|
392
410
|
|
|
@@ -453,14 +471,20 @@ def run_extractor(
|
|
|
453
471
|
cwd=cwd,
|
|
454
472
|
capture_output=True,
|
|
455
473
|
)
|
|
474
|
+
stderr = proc.stderr.decode()
|
|
456
475
|
try:
|
|
457
476
|
# Load results and return them
|
|
458
477
|
output.seek(0)
|
|
459
|
-
|
|
460
|
-
except Exception:
|
|
478
|
+
loaded = json.load(output, cls=json_decoder)
|
|
479
|
+
except Exception as e:
|
|
461
480
|
# If there was an error raised during runtime, then propagate
|
|
462
481
|
delim = f'File "{module_path}"'
|
|
463
|
-
exception =
|
|
482
|
+
exception = stderr
|
|
464
483
|
if delim in exception:
|
|
465
484
|
exception = f"{delim}{exception.split(delim, 1)[1]}"
|
|
466
|
-
|
|
485
|
+
# print extractor logging at error level
|
|
486
|
+
logger.error(f"maco extractor raised exception, stderr:\n{stderr}")
|
|
487
|
+
raise Exception(exception) from e
|
|
488
|
+
# ensure that extractor logging is available
|
|
489
|
+
logger.info(f"maco extractor stderr:\n{stderr}")
|
|
490
|
+
return loaded
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
+
demo_extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1
2
|
demo_extractors/elfy.py,sha256=AAFr5i1aivPwO4nycyXJEud57EpVNA-5k_2GicWesbY,771
|
|
2
|
-
demo_extractors/limit_other.py,sha256=
|
|
3
|
+
demo_extractors/limit_other.py,sha256=RAFx_0K_WnhUURA5uwXGmjrWODrAuLZFBxqZcWaxf64,944
|
|
3
4
|
demo_extractors/nothing.py,sha256=3aeQJTY-dakmVXmyfmrRM8YCQVT7q3bq880DFH1Ol_Y,607
|
|
4
5
|
demo_extractors/shared.py,sha256=Wlvy77SCAR97gxi8uUhGYyjxGmDb-pOSvN8b1rXrVWs,304
|
|
5
6
|
demo_extractors/complex/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
demo_extractors/complex/complex.py,sha256=
|
|
7
|
+
demo_extractors/complex/complex.py,sha256=JFKqBGKwkuDSz4zZUJuqhCLUQv6dlCMhBqNj33grBsE,2323
|
|
7
8
|
demo_extractors/complex/complex_utils.py,sha256=aec8kJsYUrMPo-waihkVLt-0QpiOPkw7dDqfT9MNuHk,123
|
|
8
9
|
maco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
10
|
maco/base_test.py,sha256=pqet9ofMwFRTj3JHgPdh9WHwdyp8kxNMi1vJNUzkSNA,2518
|
|
10
11
|
maco/cli.py,sha256=1I3U54yPddTxqWclCtZ5Ma5hW6RoVTZMzLSFOjjfM1g,8008
|
|
11
|
-
maco/collector.py,sha256=
|
|
12
|
+
maco/collector.py,sha256=Vlo7KcJC7TKZFTElv8i_f_hvWEnlWCRzOP1xOc9x7vk,6532
|
|
12
13
|
maco/extractor.py,sha256=4ZQd8OfvEQYUIkUS3LzZ5tcioembuLhT9_uRVNKSsyM,2750
|
|
13
|
-
maco/utils.py,sha256=
|
|
14
|
+
maco/utils.py,sha256=vQeJKw4whWTXp1mTd2oEhfvL4nvvgAzWjBnCp2XxWLI,19275
|
|
14
15
|
maco/yara.py,sha256=vPzCqauVp52ivcTdt8zwrYqDdkLutGlesma9DhKPzHw,2925
|
|
15
16
|
maco/model/__init__.py,sha256=SJrwdn12wklUFm2KoIgWjX_KgvJxCM7Ca9ntXOneuzc,31
|
|
16
17
|
maco/model/model.py,sha256=ngen4ViyLdRo_z_TqZBjw2DN0NrRLpuxOy15-6QmtNw,23536
|
|
17
18
|
model_setup/maco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
19
|
model_setup/maco/base_test.py,sha256=pqet9ofMwFRTj3JHgPdh9WHwdyp8kxNMi1vJNUzkSNA,2518
|
|
19
20
|
model_setup/maco/cli.py,sha256=1I3U54yPddTxqWclCtZ5Ma5hW6RoVTZMzLSFOjjfM1g,8008
|
|
20
|
-
model_setup/maco/collector.py,sha256=
|
|
21
|
+
model_setup/maco/collector.py,sha256=Vlo7KcJC7TKZFTElv8i_f_hvWEnlWCRzOP1xOc9x7vk,6532
|
|
21
22
|
model_setup/maco/extractor.py,sha256=4ZQd8OfvEQYUIkUS3LzZ5tcioembuLhT9_uRVNKSsyM,2750
|
|
22
|
-
model_setup/maco/utils.py,sha256=
|
|
23
|
+
model_setup/maco/utils.py,sha256=vQeJKw4whWTXp1mTd2oEhfvL4nvvgAzWjBnCp2XxWLI,19275
|
|
23
24
|
model_setup/maco/yara.py,sha256=vPzCqauVp52ivcTdt8zwrYqDdkLutGlesma9DhKPzHw,2925
|
|
24
25
|
model_setup/maco/model/__init__.py,sha256=SJrwdn12wklUFm2KoIgWjX_KgvJxCM7Ca9ntXOneuzc,31
|
|
25
26
|
model_setup/maco/model/model.py,sha256=ngen4ViyLdRo_z_TqZBjw2DN0NrRLpuxOy15-6QmtNw,23536
|
|
@@ -27,15 +28,16 @@ pipelines/publish.yaml,sha256=xt3WNU-5kIICJgKIiiE94M3dWjS3uEiun-n4OmIssK8,1471
|
|
|
27
28
|
pipelines/test.yaml,sha256=3KOoo-8SqP_bTAscsz5V3xxnuL91J-62mTjnQD1Btag,1019
|
|
28
29
|
tests/data/example.txt.cart,sha256=j4ZdDnFNVq7lb-Qi4pY4evOXKQPKG-GSg-n-uEqPhV0,289
|
|
29
30
|
tests/data/trigger_complex.txt,sha256=uqnLSrnyDGCmXwuPmZ2s8vdhH0hJs8DxvyaW_tuYY24,64
|
|
31
|
+
tests/data/trigger_complex.txt.cart,sha256=Z7qF1Zi640O45Znkl9ooP2RhSLAEqY0NRf51d-q7utU,345
|
|
30
32
|
tests/extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
33
|
tests/extractors/basic.py,sha256=r5eLCL6Ynr14nCBgtbLvUbm0NdrXizyc9c-4xBCNShU,828
|
|
32
34
|
tests/extractors/basic_longer.py,sha256=1ClU2QD-Y0TOl_loNFvEqIEpTR5TSVJ6zg9ZmC-ESJo,860
|
|
33
35
|
tests/extractors/test_basic.py,sha256=FLKekfSGM69HaiF7Vu_7D7KDXHZko-9hZkMO8_DoyYA,697
|
|
34
36
|
tests/extractors/bob/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
37
|
tests/extractors/bob/bob.py,sha256=Gy5p8KssJX87cwa9vVv8UBODF_ulbUteZXh15frW2hs,247
|
|
36
|
-
maco-1.2.
|
|
37
|
-
maco-1.2.
|
|
38
|
-
maco-1.2.
|
|
39
|
-
maco-1.2.
|
|
40
|
-
maco-1.2.
|
|
41
|
-
maco-1.2.
|
|
38
|
+
maco-1.2.4.dist-info/LICENSE.md,sha256=gMSjshPhXvV_F1qxmeNkKdBqGWkd__fEJf4glS504bM,1478
|
|
39
|
+
maco-1.2.4.dist-info/METADATA,sha256=EoAEnCfbaXe8eUFAMHj3-C9lQIDR9Ss31NcY4CbEXjI,15610
|
|
40
|
+
maco-1.2.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
41
|
+
maco-1.2.4.dist-info/entry_points.txt,sha256=TpcwG1gedIg8Y7a9ZOv8aQpuwEUftCefDrAjzeP-o6U,39
|
|
42
|
+
maco-1.2.4.dist-info/top_level.txt,sha256=iMRwuzmrHA3zSwiSeMIl6FWhzRpn_st-I4fAv-kw5_o,49
|
|
43
|
+
maco-1.2.4.dist-info/RECORD,,
|
model_setup/maco/collector.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import logging
|
|
5
|
+
import logging.handlers
|
|
5
6
|
import os
|
|
6
|
-
from multiprocessing import Manager, Process
|
|
7
|
+
from multiprocessing import Manager, Process, Queue
|
|
7
8
|
from tempfile import NamedTemporaryFile
|
|
8
9
|
from types import ModuleType
|
|
9
10
|
from typing import Any, BinaryIO, Dict, List, Union
|
|
@@ -86,21 +87,33 @@ class Collector:
|
|
|
86
87
|
)
|
|
87
88
|
namespaced_rules[name] = member.yara_rule or extractor.DEFAULT_YARA_RULE.format(name=name)
|
|
88
89
|
|
|
90
|
+
# multiprocess logging is awkward - set up a queue to ensure we can log
|
|
91
|
+
logging_queue = Queue()
|
|
92
|
+
queue_handler = logging.handlers.QueueListener(logging_queue,*logging.getLogger().handlers)
|
|
93
|
+
queue_handler.start()
|
|
94
|
+
|
|
89
95
|
# Find the extractors within the given directory
|
|
90
96
|
# Execute within a child process to ensure main process interpreter is kept clean
|
|
91
97
|
p = Process(
|
|
92
|
-
target=utils.
|
|
98
|
+
target=utils.proxy_logging,
|
|
93
99
|
args=(
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
logging_queue,
|
|
101
|
+
utils.import_extractors,
|
|
96
102
|
extractor_module_callback,
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
),
|
|
104
|
+
kwargs=dict(
|
|
105
|
+
root_directory=path_extractors,
|
|
106
|
+
scanner=yara.compile(source=utils.MACO_YARA_RULE),
|
|
107
|
+
create_venv=create_venv and os.path.isdir(path_extractors),
|
|
99
108
|
),
|
|
100
109
|
)
|
|
101
110
|
p.start()
|
|
102
111
|
p.join()
|
|
103
112
|
|
|
113
|
+
# stop multiprocess logging
|
|
114
|
+
queue_handler.stop()
|
|
115
|
+
logging_queue.close()
|
|
116
|
+
|
|
104
117
|
self.extractors = dict(extractors)
|
|
105
118
|
if not self.extractors:
|
|
106
119
|
raise ExtractorLoadError("no extractors were loaded")
|
model_setup/maco/utils.py
CHANGED
|
@@ -4,11 +4,14 @@ import importlib.machinery
|
|
|
4
4
|
import importlib.util
|
|
5
5
|
import inspect
|
|
6
6
|
import json
|
|
7
|
+
import logging.handlers
|
|
7
8
|
import os
|
|
8
9
|
import re
|
|
9
10
|
import shutil
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
13
|
+
import multiprocessing
|
|
14
|
+
import logging
|
|
12
15
|
import tempfile
|
|
13
16
|
|
|
14
17
|
from maco import yara
|
|
@@ -28,10 +31,12 @@ from typing import Callable, Dict, List, Set, Tuple
|
|
|
28
31
|
|
|
29
32
|
from maco.extractor import Extractor
|
|
30
33
|
|
|
34
|
+
logger = logging.getLogger("maco.lib.utils")
|
|
35
|
+
|
|
31
36
|
VENV_DIRECTORY_NAME = ".venv"
|
|
32
37
|
|
|
33
|
-
RELATIVE_FROM_RE = re.compile("from (\.+)")
|
|
34
|
-
RELATIVE_FROM_IMPORT_RE = re.compile("from (\.+) import")
|
|
38
|
+
RELATIVE_FROM_RE = re.compile(r"from (\.+)")
|
|
39
|
+
RELATIVE_FROM_IMPORT_RE = re.compile(r"from (\.+) import")
|
|
35
40
|
|
|
36
41
|
try:
|
|
37
42
|
# Attempt to use the uv package manager (Recommended)
|
|
@@ -69,6 +74,7 @@ import importlib
|
|
|
69
74
|
import json
|
|
70
75
|
import os
|
|
71
76
|
import sys
|
|
77
|
+
import logging
|
|
72
78
|
|
|
73
79
|
try:
|
|
74
80
|
from maco import yara
|
|
@@ -76,6 +82,19 @@ except:
|
|
|
76
82
|
import yara
|
|
77
83
|
|
|
78
84
|
from base64 import b64encode
|
|
85
|
+
|
|
86
|
+
# ensure we have a logger to stderr
|
|
87
|
+
import logging
|
|
88
|
+
logger = logging.getLogger()
|
|
89
|
+
logger.setLevel(logging.DEBUG)
|
|
90
|
+
sh = logging.StreamHandler()
|
|
91
|
+
logger.addHandler(sh)
|
|
92
|
+
sh.setLevel(logging.DEBUG)
|
|
93
|
+
formatter = logging.Formatter(
|
|
94
|
+
fmt="%(asctime)s, [%(levelname)s] %(module)s.%(funcName)s: %(message)s", datefmt="%Y-%m-%d (%H:%M:%S)"
|
|
95
|
+
)
|
|
96
|
+
sh.setFormatter(formatter)
|
|
97
|
+
|
|
79
98
|
parent_package_path = "{parent_package_path}"
|
|
80
99
|
sys.path.insert(1, parent_package_path)
|
|
81
100
|
mod = importlib.import_module("{module_name}")
|
|
@@ -101,7 +120,7 @@ with open("{output_path}", 'w') as fp:
|
|
|
101
120
|
json.dump(result.dict(exclude_defaults=True, exclude_none=True), fp, cls=Base64Encoder)
|
|
102
121
|
"""
|
|
103
122
|
|
|
104
|
-
MACO_YARA_RULE = """
|
|
123
|
+
MACO_YARA_RULE = r"""
|
|
105
124
|
rule MACO {
|
|
106
125
|
meta:
|
|
107
126
|
desc = "Used to match on Python files that contain MACO extractors"
|
|
@@ -292,15 +311,10 @@ def register_extractors(
|
|
|
292
311
|
):
|
|
293
312
|
package_name = os.path.basename(current_directory)
|
|
294
313
|
parent_directory = os.path.dirname(current_directory)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# We'll need to create a link back to the original
|
|
301
|
-
if package_name not in sys.modules:
|
|
302
|
-
symlink = os.path.join(parent_directory, package_name)
|
|
303
|
-
os.symlink(current_directory, symlink)
|
|
314
|
+
if package_name in sys.modules:
|
|
315
|
+
# this may happen as part of testing if some part of the extractor code was directly imported
|
|
316
|
+
logger.warning(f"Looks like {package_name} is already loaded. "
|
|
317
|
+
"If your maco extractor overlaps an existing package name this could cause problems.")
|
|
304
318
|
|
|
305
319
|
try:
|
|
306
320
|
# Modify the PATH so we can recognize this new package on import
|
|
@@ -351,10 +365,6 @@ def register_extractors(
|
|
|
351
365
|
# Remove any modules that were loaded to deconflict with later modules loads
|
|
352
366
|
[sys.modules.pop(k) for k in set(sys.modules.keys()) - default_loaded_modules]
|
|
353
367
|
|
|
354
|
-
# Cleanup any symlinks
|
|
355
|
-
if symlink:
|
|
356
|
-
os.remove(symlink)
|
|
357
|
-
|
|
358
368
|
# If there still exists extractor files we haven't found yet, try searching in the available subdirectories
|
|
359
369
|
if extractor_files:
|
|
360
370
|
for dir in os.listdir(current_directory):
|
|
@@ -379,14 +389,22 @@ def register_extractors(
|
|
|
379
389
|
# We were able to find all the extractor files
|
|
380
390
|
break
|
|
381
391
|
|
|
392
|
+
def proxy_logging(queue: multiprocessing.Queue, callback: Callable[[ModuleType, str], None], *args, **kwargs):
|
|
393
|
+
"""Ensures logging is set up correctly for a child process and then executes the callback."""
|
|
394
|
+
logger = logging.getLogger()
|
|
395
|
+
qh = logging.handlers.QueueHandler(queue)
|
|
396
|
+
qh.setLevel(logging.DEBUG)
|
|
397
|
+
logger.addHandler(qh)
|
|
398
|
+
callback(*args, **kwargs, logger=logger)
|
|
382
399
|
|
|
383
400
|
def import_extractors(
|
|
401
|
+
extractor_module_callback: Callable[[ModuleType, str], bool],
|
|
402
|
+
*,
|
|
384
403
|
root_directory: str,
|
|
385
404
|
scanner: yara.Rules,
|
|
386
|
-
extractor_module_callback: Callable[[ModuleType, str], bool],
|
|
387
|
-
logger: Logger,
|
|
388
405
|
create_venv: bool = False,
|
|
389
406
|
python_version: str = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
407
|
+
logger: Logger,
|
|
390
408
|
):
|
|
391
409
|
extractor_dirs, extractor_files = scan_for_extractors(root_directory, scanner, logger)
|
|
392
410
|
|
|
@@ -453,14 +471,20 @@ def run_extractor(
|
|
|
453
471
|
cwd=cwd,
|
|
454
472
|
capture_output=True,
|
|
455
473
|
)
|
|
474
|
+
stderr = proc.stderr.decode()
|
|
456
475
|
try:
|
|
457
476
|
# Load results and return them
|
|
458
477
|
output.seek(0)
|
|
459
|
-
|
|
460
|
-
except Exception:
|
|
478
|
+
loaded = json.load(output, cls=json_decoder)
|
|
479
|
+
except Exception as e:
|
|
461
480
|
# If there was an error raised during runtime, then propagate
|
|
462
481
|
delim = f'File "{module_path}"'
|
|
463
|
-
exception =
|
|
482
|
+
exception = stderr
|
|
464
483
|
if delim in exception:
|
|
465
484
|
exception = f"{delim}{exception.split(delim, 1)[1]}"
|
|
466
|
-
|
|
485
|
+
# print extractor logging at error level
|
|
486
|
+
logger.error(f"maco extractor raised exception, stderr:\n{stderr}")
|
|
487
|
+
raise Exception(exception) from e
|
|
488
|
+
# ensure that extractor logging is available
|
|
489
|
+
logger.info(f"maco extractor stderr:\n{stderr}")
|
|
490
|
+
return loaded
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|