maco 1.2.5__tar.gz → 1.2.6__tar.gz

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.
Files changed (69) hide show
  1. {maco-1.2.5/maco.egg-info → maco-1.2.6}/PKG-INFO +14 -2
  2. {maco-1.2.5 → maco-1.2.6}/README.md +13 -1
  3. {maco-1.2.5 → maco-1.2.6}/demo_extractors/elfy.py +1 -1
  4. {maco-1.2.5 → maco-1.2.6}/demo_extractors/limit_other.py +5 -1
  5. {maco-1.2.5 → maco-1.2.6}/demo_extractors/nothing.py +1 -1
  6. {maco-1.2.5/model_setup → maco-1.2.6}/maco/base_test.py +1 -1
  7. {maco-1.2.5/model_setup → maco-1.2.6}/maco/cli.py +8 -3
  8. {maco-1.2.5 → maco-1.2.6}/maco/collector.py +11 -1
  9. maco-1.2.6/maco/model/__init__.py +1 -0
  10. {maco-1.2.5/model_setup → maco-1.2.6}/maco/model/model.py +53 -19
  11. {maco-1.2.5 → maco-1.2.6}/maco/utils.py +19 -10
  12. {maco-1.2.5/model_setup → maco-1.2.6}/maco/yara.py +0 -1
  13. {maco-1.2.5 → maco-1.2.6/maco.egg-info}/PKG-INFO +14 -2
  14. {maco-1.2.5 → maco-1.2.6}/model_setup/README.md +13 -1
  15. {maco-1.2.5 → maco-1.2.6/model_setup}/maco/base_test.py +1 -1
  16. {maco-1.2.5 → maco-1.2.6/model_setup}/maco/cli.py +8 -3
  17. {maco-1.2.5 → maco-1.2.6}/model_setup/maco/collector.py +11 -1
  18. maco-1.2.6/model_setup/maco/model/__init__.py +1 -0
  19. {maco-1.2.5 → maco-1.2.6/model_setup}/maco/model/model.py +53 -19
  20. {maco-1.2.5 → maco-1.2.6}/model_setup/maco/utils.py +19 -10
  21. {maco-1.2.5 → maco-1.2.6/model_setup}/maco/yara.py +0 -1
  22. {maco-1.2.5 → maco-1.2.6}/pipelines/test.yaml +21 -0
  23. {maco-1.2.5 → maco-1.2.6}/pyproject.toml +9 -0
  24. {maco-1.2.5 → maco-1.2.6}/tests/extractors/bob/bob.py +1 -4
  25. {maco-1.2.5 → maco-1.2.6}/tests/test_base_test.py +3 -0
  26. {maco-1.2.5 → maco-1.2.6}/tests/test_demo_extractors.py +1 -3
  27. {maco-1.2.5 → maco-1.2.6}/tests/test_detection.py +3 -0
  28. {maco-1.2.5 → maco-1.2.6}/tox.ini +10 -1
  29. maco-1.2.5/maco/model/__init__.py +0 -1
  30. maco-1.2.5/model_setup/maco/model/__init__.py +0 -1
  31. {maco-1.2.5 → maco-1.2.6}/.gitignore +0 -0
  32. {maco-1.2.5 → maco-1.2.6}/.vscode/settings.json +0 -0
  33. {maco-1.2.5 → maco-1.2.6}/LICENSE.md +0 -0
  34. {maco-1.2.5 → maco-1.2.6}/demo_extractors/__init__.py +0 -0
  35. {maco-1.2.5 → maco-1.2.6}/demo_extractors/complex/__init__.py +0 -0
  36. {maco-1.2.5 → maco-1.2.6}/demo_extractors/complex/complex.py +0 -0
  37. {maco-1.2.5 → maco-1.2.6}/demo_extractors/complex/complex_utils.py +0 -0
  38. {maco-1.2.5 → maco-1.2.6}/demo_extractors/requirements.txt +0 -0
  39. {maco-1.2.5 → maco-1.2.6}/demo_extractors/shared.py +0 -0
  40. {maco-1.2.5 → maco-1.2.6}/maco/__init__.py +0 -0
  41. {maco-1.2.5 → maco-1.2.6}/maco/extractor.py +0 -0
  42. {maco-1.2.5 → maco-1.2.6}/maco.egg-info/SOURCES.txt +0 -0
  43. {maco-1.2.5 → maco-1.2.6}/maco.egg-info/dependency_links.txt +0 -0
  44. {maco-1.2.5 → maco-1.2.6}/maco.egg-info/entry_points.txt +0 -0
  45. {maco-1.2.5 → maco-1.2.6}/maco.egg-info/requires.txt +0 -0
  46. {maco-1.2.5 → maco-1.2.6}/maco.egg-info/top_level.txt +0 -0
  47. {maco-1.2.5 → maco-1.2.6}/model_setup/LICENSE.md +0 -0
  48. {maco-1.2.5 → maco-1.2.6}/model_setup/maco/__init__.py +0 -0
  49. {maco-1.2.5 → maco-1.2.6}/model_setup/maco/extractor.py +0 -0
  50. {maco-1.2.5 → maco-1.2.6}/model_setup/pyproject.toml +0 -0
  51. {maco-1.2.5 → maco-1.2.6}/model_setup/setup.py +0 -0
  52. {maco-1.2.5 → maco-1.2.6}/pipelines/publish.yaml +0 -0
  53. {maco-1.2.5 → maco-1.2.6}/requirements.txt +0 -0
  54. {maco-1.2.5 → maco-1.2.6}/setup.cfg +0 -0
  55. {maco-1.2.5 → maco-1.2.6}/tests/benchmark.py +0 -0
  56. {maco-1.2.5 → maco-1.2.6}/tests/data/example.txt.cart +0 -0
  57. {maco-1.2.5 → maco-1.2.6}/tests/data/trigger_complex.txt +0 -0
  58. {maco-1.2.5 → maco-1.2.6}/tests/data/trigger_complex.txt.cart +0 -0
  59. {maco-1.2.5 → maco-1.2.6}/tests/extractors/__init__.py +0 -0
  60. {maco-1.2.5 → maco-1.2.6}/tests/extractors/basic.py +0 -0
  61. {maco-1.2.5 → maco-1.2.6}/tests/extractors/basic_longer.py +0 -0
  62. {maco-1.2.5 → maco-1.2.6}/tests/extractors/bob/__init__.py +0 -0
  63. {maco-1.2.5 → maco-1.2.6}/tests/extractors/test_basic.py +0 -0
  64. {maco-1.2.5 → maco-1.2.6}/tests/pytest.ini +0 -0
  65. {maco-1.2.5 → maco-1.2.6}/tests/requirements.txt +0 -0
  66. {maco-1.2.5 → maco-1.2.6}/tests/test_cli.py +0 -0
  67. {maco-1.2.5 → maco-1.2.6}/tests/test_extractor.py +0 -0
  68. {maco-1.2.5 → maco-1.2.6}/tests/test_helpers.py +0 -0
  69. {maco-1.2.5 → maco-1.2.6}/tests/test_model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maco
3
- Version: 1.2.5
3
+ Version: 1.2.6
4
4
  Author: sl-govau
5
5
  Maintainer: cccs-rs
6
6
  License: MIT License
@@ -38,7 +38,7 @@ Requires-Dist: yara-x==0.11.0
38
38
 
39
39
  # Maco - Malware config extractor framework
40
40
 
41
- ## Maco is a framework for ***ma***lware ***co***nfig extractors.
41
+ ## Maco is a framework for <ins>ma</ins>lware <ins>co</ins>nfig extractors.
42
42
 
43
43
  It aims to solve two problems:
44
44
 
@@ -272,3 +272,15 @@ run Complex extractor from rules ['ComplexAlt']
272
272
  The demo extractors are designed to trigger when run over the '`demo_extractors`' folder.
273
273
 
274
274
  e.g. `maco demo_extractors demo_extractors`
275
+
276
+ # Contributions
277
+
278
+ Please use ruff to format and lint PRs. This may be the cause of PR test failures.
279
+
280
+ Ruff will attempt to fix most issues, but some may require manual resolution.
281
+
282
+ ```
283
+ pip install ruff
284
+ ruff format
285
+ ruff check --fix
286
+ ```
@@ -1,6 +1,6 @@
1
1
  # Maco - Malware config extractor framework
2
2
 
3
- ## Maco is a framework for ***ma***lware ***co***nfig extractors.
3
+ ## Maco is a framework for <ins>ma</ins>lware <ins>co</ins>nfig extractors.
4
4
 
5
5
  It aims to solve two problems:
6
6
 
@@ -234,3 +234,15 @@ run Complex extractor from rules ['ComplexAlt']
234
234
  The demo extractors are designed to trigger when run over the '`demo_extractors`' folder.
235
235
 
236
236
  e.g. `maco demo_extractors demo_extractors`
237
+
238
+ # Contributions
239
+
240
+ Please use ruff to format and lint PRs. This may be the cause of PR test failures.
241
+
242
+ Ruff will attempt to fix most issues, but some may require manual resolution.
243
+
244
+ ```
245
+ pip install ruff
246
+ ruff format
247
+ ruff check --fix
248
+ ```
@@ -1,5 +1,5 @@
1
1
  from io import BytesIO
2
- from typing import Dict, List, Optional
2
+ from typing import List, Optional
3
3
 
4
4
  from maco import extractor, model, yara
5
5
 
@@ -1,5 +1,5 @@
1
1
  from io import BytesIO
2
- from typing import Dict, List, Optional
2
+ from typing import List, Optional
3
3
 
4
4
  from demo_extractors import shared
5
5
  from maco import extractor, model, yara
@@ -27,6 +27,10 @@ class LimitOther(extractor.Extractor):
27
27
  # the tests that do direct importing
28
28
  import httpx
29
29
 
30
+ # use httpx so it doesn't get deleted by auto linter
31
+ if not httpx.__name__:
32
+ raise Exception("wow I really want to use this library in a useful way")
33
+
30
34
  # use a custom model that inherits from ExtractorModel
31
35
  # this model defines what can go in the 'other' dict
32
36
  tmp = shared.MyCustomModel(family="specify_other")
@@ -1,5 +1,5 @@
1
1
  from io import BytesIO
2
- from typing import Dict, List, Optional
2
+ from typing import List, Optional
3
3
 
4
4
  from maco import extractor, model, yara
5
5
 
@@ -32,7 +32,7 @@ class BaseTest(unittest.TestCase):
32
32
  # I recommend something like os.path.join(__file__, "../../extractors")
33
33
  # if your extractors are in a folder 'extractors' next to a folder of tests
34
34
  path: str = None
35
- create_venv: bool=False
35
+ create_venv: bool = False
36
36
 
37
37
  @classmethod
38
38
  def setUpClass(cls) -> None:
@@ -1,4 +1,5 @@
1
1
  """CLI example of how extractors can be executed."""
2
+
2
3
  import argparse
3
4
  import base64
4
5
  import binascii
@@ -150,6 +151,7 @@ def process_filesystem(
150
151
  logger.info(f"{num_analysed} analysed, {num_hits} hits, {num_extracted} extracted")
151
152
  return num_analysed, num_hits, num_extracted
152
153
 
154
+
153
155
  def main():
154
156
  parser = argparse.ArgumentParser(description="Run extractors over samples.")
155
157
  parser.add_argument("extractors", type=str, help="path to extractors")
@@ -165,7 +167,8 @@ def main():
165
167
  parser.add_argument(
166
168
  "--base64",
167
169
  action="store_true",
168
- help="Include base64 encoded binary data in output (can be large, consider printing to file rather than console)",
170
+ help="Include base64 encoded binary data in output "
171
+ "(can be large, consider printing to file rather than console)",
169
172
  )
170
173
  parser.add_argument("--logfile", type=str, help="file to log output")
171
174
  parser.add_argument("--include", type=str, help="comma separated extractors to run")
@@ -179,7 +182,9 @@ def main():
179
182
  parser.add_argument(
180
183
  "--create_venv",
181
184
  action="store_true",
182
- help="Creates venvs for every requirements.txt found (only applies when extractor path is a directory). This runs much slower than the alternative but may be necessary when there are many extractors with conflicting dependencies.",
185
+ help="Creates venvs for every requirements.txt found (only applies when extractor path is a directory). "
186
+ "This runs much slower than the alternative but may be necessary "
187
+ "when there are many extractors with conflicting dependencies.",
183
188
  )
184
189
  args = parser.parse_args()
185
190
  inc = args.include.split(",") if args.include else []
@@ -225,7 +230,7 @@ def main():
225
230
  pretty=args.pretty,
226
231
  force=args.force,
227
232
  include_base64=args.base64,
228
- create_venv=args.create_venv
233
+ create_venv=args.create_venv,
229
234
  )
230
235
 
231
236
 
@@ -4,6 +4,7 @@ import inspect
4
4
  import logging
5
5
  import logging.handlers
6
6
  import os
7
+ import sys
7
8
  from multiprocessing import Manager, Process, Queue
8
9
  from tempfile import NamedTemporaryFile
9
10
  from types import ModuleType
@@ -48,6 +49,15 @@ class Collector:
48
49
  create_venv: bool = False,
49
50
  ):
50
51
  """Discover and load extractors from file system."""
52
+ # maco requires the extractor to be imported directly, so ensure they are available on the path
53
+ full_path_extractors = os.path.abspath(path_extractors)
54
+ full_path_above_extractors = os.path.dirname(full_path_extractors)
55
+ # Modify the PATH so we can recognize this new package on import
56
+ if full_path_extractors not in sys.path:
57
+ sys.path.insert(1, full_path_extractors)
58
+ if full_path_above_extractors not in sys.path:
59
+ sys.path.insert(1, full_path_above_extractors)
60
+
51
61
  path_extractors = os.path.realpath(path_extractors)
52
62
  self.path: str = path_extractors
53
63
  self.extractors: Dict[str, Dict[str, str]] = {}
@@ -89,7 +99,7 @@ class Collector:
89
99
 
90
100
  # multiprocess logging is awkward - set up a queue to ensure we can log
91
101
  logging_queue = Queue()
92
- queue_handler = logging.handlers.QueueListener(logging_queue,*logging.getLogger().handlers)
102
+ queue_handler = logging.handlers.QueueListener(logging_queue, *logging.getLogger().handlers)
93
103
  queue_handler.start()
94
104
 
95
105
  # Find the extractors within the given directory
@@ -0,0 +1 @@
1
+ from maco.model.model import * # noqa: F403
@@ -59,31 +59,48 @@ class CategoryEnum(str, Enum):
59
59
  # Malware related to an Advanced Persistent Threat (APT) group.
60
60
  apt = "apt"
61
61
 
62
- # A backdoor Trojan gives malicious users remote control over the infected computer. They enable the author to do anything they wish on the infected computer including sending, receiving, launching and deleting files, displaying data and rebooting the computer. Backdoor Trojans are often used to unite a group of victim computers to form a botnet or zombie network that can be used for criminal purposes.
62
+ # A backdoor Trojan gives malicious users remote control over the infected computer.
63
+ # They enable the author to do anything they wish on the infected computer including
64
+ # sending, receiving, launching and deleting files, displaying data and rebooting the computer.
65
+ # Backdoor Trojans are often used to unite a group of victim computers to form a botnet or
66
+ # zombie network that can be used for criminal purposes.
63
67
  backdoor = "backdoor"
64
68
 
65
- # Trojan Banker programs are designed to steal your account data for online banking systems, e-payment systems and credit or debit cards.
69
+ # Trojan Banker programs are designed to steal your account data for online banking systems,
70
+ # e-payment systems and credit or debit cards.
66
71
  banker = "banker"
67
72
 
68
- # A malware variant that modifies the boot sectors of a hard drive, including the Master Boot Record (MBR) and Volume Boot Record (VBR).
73
+ # A malware variant that modifies the boot sectors of a hard drive, including the Master Boot Record (MBR)
74
+ # and Volume Boot Record (VBR).
69
75
  bootkit = "bootkit"
70
76
 
71
- # A malicious bot is self-propagating malware designed to infect a host and connect back to a central server or servers that act as a command and control (C&C) center for an entire network of compromised devices, or botnet.
77
+ # A malicious bot is self-propagating malware designed to infect a host and connect back to a central server
78
+ # or servers that act as a command and control (C&C) center for an entire network of compromised devices,
79
+ # or botnet.
72
80
  bot = "bot"
73
81
 
74
- # A browser hijacker is defined as a form of unwanted software that modifies a web browser's settings without the user's permission. The result is the placement of unwanted advertising into the browser, and possibly the replacement of an existing home page or search page with the hijacker page.
82
+ # A browser hijacker is defined as a form of unwanted software that modifies a web browser's settings without
83
+ # the user's permission. The result is the placement of unwanted advertising into the browser,
84
+ # and possibly the replacement of an existing home page or search page with the hijacker page.
75
85
  browser_hijacker = "browser_hijacker"
76
86
 
77
- # Trojan bruteforcer are trying to brute force website in order to achieve something else (EX: Finding WordPress websites with default credentials).
87
+ # Trojan bruteforcer are trying to brute force website in order to achieve something else
88
+ # (EX: Finding WordPress websites with default credentials).
78
89
  bruteforcer = "bruteforcer"
79
90
 
80
- # A type of trojan that can use your PC to 'click' on websites or applications. They are usually used to make money for a malicious hacker by clicking on online advertisements and making it look like the website gets more traffic than it does. They can also be used to skew online polls, install programs on your PC, or make unwanted software appear more popular than it is.
91
+ # A type of trojan that can use your PC to 'click' on websites or applications.
92
+ # They are usually used to make money for a malicious hacker by clicking on online advertisements
93
+ # and making it look like the website gets more traffic than it does.
94
+ # They can also be used to skew online polls, install programs on your PC, or make unwanted software
95
+ # appear more popular than it is.
81
96
  clickfraud = "clickfraud"
82
97
 
83
98
  # Cryptocurrency mining malware.
84
99
  cryptominer = "cryptominer"
85
100
 
86
- # These programs conduct DoS (Denial of Service) attacks against a targeted web address. By sending multiple requests from your computer and several other infected computers, the attack can overwhelm the target address leading to a denial of service.
101
+ # These programs conduct DoS (Denial of Service) attacks against a targeted web address.
102
+ # By sending multiple requests from your computer and several other infected computers,
103
+ # the attack can overwhelm the target address leading to a denial of service.
87
104
  ddos = "ddos"
88
105
 
89
106
  # Trojan Downloaders can download and install new versions of malicious programs in the target system.
@@ -92,49 +109,66 @@ class CategoryEnum(str, Enum):
92
109
  # These programs are used by hackers in order to install malware or to prevent the detection of malicious programs.
93
110
  dropper = "dropper"
94
111
 
95
- # Exploit kits are programs that contain data or code that takes advantage of a vulnerability within an application that is running in the target system.
112
+ # Exploit kits are programs that contain data or code that takes advantage of a vulnerability
113
+ # within an application that is running in the target system.
96
114
  exploitkit = "exploitkit"
97
115
 
98
- # Trojan FakeAV programs simulate the activity of antivirus software. They are designed to extort money in return for the detection and removal of threat, even though the threats that they report are actually non-existent.
116
+ # Trojan FakeAV programs simulate the activity of antivirus software.
117
+ # They are designed to extort money in return for the detection and removal of threat, even though the
118
+ # threats that they report are actually non-existent.
99
119
  fakeav = "fakeav"
100
120
 
101
121
  # A type of tool that can be used to allow and maintain unauthorized access to your PC.
102
122
  hacktool = "hacktool"
103
123
 
104
- # A program that collects your personal information, such as your browsing history, and uses it without adequate consent.
124
+ # A program that collects your personal information, such as your browsing history,
125
+ # and uses it without adequate consent.
105
126
  infostealer = "infostealer"
106
127
 
107
- # A keylogger monitors and logs every keystroke it can identify. Once installed, the virus either keeps track of all the keys and stores the information locally, after which the hacker needs physical access to the computer to retrieve the information, or the logs are sent over the internet back to the hacker.
128
+ # A keylogger monitors and logs every keystroke it can identify.
129
+ # Once installed, the virus either keeps track of all the keys and stores the information locally,
130
+ # after which the hacker needs physical access to the computer to retrieve the information,
131
+ # or the logs are sent over the internet back to the hacker.
108
132
  keylogger = "keylogger"
109
133
 
110
134
  # A program that loads another application / memory space.
111
135
  loader = "loader"
112
136
 
113
- # A type of malware that hides its code and purpose to make it more difficult for security software to detect or remove it.
137
+ # A type of malware that hides its code and purpose to make it more difficult for
138
+ # security software to detect or remove it.
114
139
  obfuscator = "obfuscator"
115
140
 
116
- # Point-of-sale malware is usually a type of malware that is used by cybercriminals to target point of sale (POS) and payment terminals with the intent to obtain credit card and debit card information.
141
+ # Point-of-sale malware is usually a type of malware that is used by cybercriminals to target point of sale (POS)
142
+ # and payment terminals with the intent to obtain credit card and debit card information.
117
143
  pos = "pos"
118
144
 
119
- # This type of trojan allows unauthorized parties to use the infected computer as a proxy server to access the Internet anonymously.
145
+ # This type of trojan allows unauthorized parties to use the infected computer as a proxy server
146
+ # to access the Internet anonymously.
120
147
  proxy = "proxy"
121
148
 
122
149
  # A program that can be used by a remote hacker to gain access and control of an infected machine.
123
150
  rat = "rat"
124
151
 
125
- # This type of malware can modify data in the target computer so the operating system will stop running correctly or the data is no longer accessible. The criminal will only restore the computer state or data after a ransom is paid to them (mostly using cryptocurrency).
152
+ # This type of malware can modify data in the target computer so the operating system
153
+ # will stop running correctly or the data is no longer accessible.
154
+ # The criminal will only restore the computer state or data after a ransom is paid to them
155
+ # (mostly using cryptocurrency).
126
156
  ransomware = "ransomware"
127
157
 
128
158
  # A reverse proxy is a server that receives requests from the internet and forwards them to a small set of servers.
129
159
  reverse_proxy = "reverse_proxy"
130
160
 
131
- # Rootkits are designed to conceal certain objects or activities in the system. Often their main purpose is to prevent malicious programs being detected in order to extend the period in which programs can run on an infected computer.
161
+ # Rootkits are designed to conceal certain objects or activities in the system.
162
+ # Often their main purpose is to prevent malicious programs being detected
163
+ # in order to extend the period in which programs can run on an infected computer.
132
164
  rootkit = "rootkit"
133
165
 
134
- # This type of malware scan the internet / network(s) / system(s) / service(s) to collect information. That information could be used later to perpetuate an cyber attack.
166
+ # This type of malware scan the internet / network(s) / system(s) / service(s) to collect information.
167
+ # That information could be used later to perpetuate an cyber attack.
135
168
  scanner = "scanner"
136
169
 
137
- # Scareware is a form of malware which uses social engineering to cause shock, anxiety, or the perception of a threat in order to manipulate users into buying unwanted software.
170
+ # Scareware is a form of malware which uses social engineering to cause shock, anxiety,
171
+ # or the perception of a threat in order to manipulate users into buying unwanted software.
138
172
  scareware = "scareware"
139
173
 
140
174
  # Malware that is sending spam.
@@ -46,6 +46,7 @@ UV_BIN = find_uv_bin()
46
46
  PIP_CMD = f"{UV_BIN} pip"
47
47
  VENV_CREATE_CMD = f"{UV_BIN} venv"
48
48
 
49
+
49
50
  class Base64Decoder(json.JSONDecoder):
50
51
  def __init__(self, *args, **kwargs):
51
52
  json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
@@ -179,11 +180,9 @@ def scan_for_extractors(root_directory: str, scanner: yara.Rules, logger: Logger
179
180
  for pattern in [RELATIVE_FROM_IMPORT_RE, RELATIVE_FROM_RE]:
180
181
  for match in pattern.findall(data):
181
182
  depth = match.count(".")
182
- data = data.replace(
183
- f"from {match}",
184
- f"from {'.'.join(split[depth - 1 : split.index(package) + 1][::-1])}{'.' if pattern == RELATIVE_FROM_RE else ''}",
185
- 1,
186
- )
183
+ abspath = ".".join(split[depth - 1 : split.index(package) + 1][::-1])
184
+ abspath += "." if pattern == RELATIVE_FROM_RE else ""
185
+ data = data.replace(f"from {match}", f"from {abspath}", 1)
187
186
  f.write(data)
188
187
 
189
188
  if scanner.match(path):
@@ -222,7 +221,12 @@ def _install_required_packages(create_venv: bool, directories: List[str], python
222
221
  subprocess.run(cmd.split(" ") + [venv_path], capture_output=True, env=env)
223
222
 
224
223
  # Install/Update the packages in the environment
225
- install_command = PIP_CMD.split(" ") + ["install", "-U"]
224
+ install_command = PIP_CMD.split(" ") + ["install"]
225
+ # When running locally, only install packages to required spec.
226
+ # This prevents issues during maco development and building extractors against local libraries.
227
+ if create_venv:
228
+ # when running in custom virtual environment, always upgrade packages.
229
+ install_command.append("-U")
226
230
 
227
231
  # Update the pip install command depending on where the dependencies are coming from
228
232
  if "requirements.txt" in req_files:
@@ -307,8 +311,10 @@ def register_extractors(
307
311
  parent_directory = os.path.dirname(current_directory)
308
312
  if venvs and package_name in sys.modules:
309
313
  # this may happen as part of testing if some part of the extractor code was directly imported
310
- logger.warning(f"Looks like {package_name} is already loaded. "
311
- "If your maco extractor overlaps an existing package name this could cause problems.")
314
+ logger.warning(
315
+ f"Looks like {package_name} is already loaded. "
316
+ "If your maco extractor overlaps an existing package name this could cause problems."
317
+ )
312
318
 
313
319
  try:
314
320
  # Modify the PATH so we can recognize this new package on import
@@ -383,6 +389,7 @@ def register_extractors(
383
389
  # We were able to find all the extractor files
384
390
  break
385
391
 
392
+
386
393
  def proxy_logging(queue: multiprocessing.Queue, callback: Callable[[ModuleType, str], None], *args, **kwargs):
387
394
  """Ensures logging is set up correctly for a child process and then executes the callback."""
388
395
  logger = logging.getLogger()
@@ -391,6 +398,7 @@ def proxy_logging(queue: multiprocessing.Queue, callback: Callable[[ModuleType,
391
398
  logger.addHandler(qh)
392
399
  callback(*args, **kwargs, logger=logger)
393
400
 
401
+
394
402
  def import_extractors(
395
403
  extractor_module_callback: Callable[[ModuleType, str], bool],
396
404
  *,
@@ -416,6 +424,7 @@ def import_extractors(
416
424
  # holds cached extractors when not running in venv mode
417
425
  _loaded_extractors: Dict[str, Extractor] = {}
418
426
 
427
+
419
428
  def run_extractor(
420
429
  sample_path,
421
430
  module_name,
@@ -438,7 +447,7 @@ def run_extractor(
438
447
  extractor = _loaded_extractors[key]
439
448
  if extractor.yara_compiled:
440
449
  matches = extractor.yara_compiled.match(sample_path)
441
- loaded = extractor.run(open(sample_path, 'rb'), matches=matches)
450
+ loaded = extractor.run(open(sample_path, "rb"), matches=matches)
442
451
  else:
443
452
  # execute extractor in child process with separate virtual environment
444
453
  # Write temporary script in the same directory as extractor to resolve relative imports
@@ -477,7 +486,7 @@ def run_extractor(
477
486
  try:
478
487
  # Load results and return them
479
488
  output.seek(0)
480
- loaded = json.load(output, cls=json_decoder)
489
+ loaded = json.load(output, cls=json_decoder)
481
490
  except Exception as e:
482
491
  # If there was an error raised during runtime, then propagate
483
492
  delim = f'File "{module_path}"'
@@ -3,7 +3,6 @@ from collections import namedtuple
3
3
  from itertools import cycle
4
4
  from typing import Dict
5
5
 
6
- import yara
7
6
  import yara_x
8
7
 
9
8
  RULE_ID_RE = re.compile("(\w+)? ?rule (\w+)")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: maco
3
- Version: 1.2.5
3
+ Version: 1.2.6
4
4
  Author: sl-govau
5
5
  Maintainer: cccs-rs
6
6
  License: MIT License
@@ -38,7 +38,7 @@ Requires-Dist: yara-x==0.11.0
38
38
 
39
39
  # Maco - Malware config extractor framework
40
40
 
41
- ## Maco is a framework for ***ma***lware ***co***nfig extractors.
41
+ ## Maco is a framework for <ins>ma</ins>lware <ins>co</ins>nfig extractors.
42
42
 
43
43
  It aims to solve two problems:
44
44
 
@@ -272,3 +272,15 @@ run Complex extractor from rules ['ComplexAlt']
272
272
  The demo extractors are designed to trigger when run over the '`demo_extractors`' folder.
273
273
 
274
274
  e.g. `maco demo_extractors demo_extractors`
275
+
276
+ # Contributions
277
+
278
+ Please use ruff to format and lint PRs. This may be the cause of PR test failures.
279
+
280
+ Ruff will attempt to fix most issues, but some may require manual resolution.
281
+
282
+ ```
283
+ pip install ruff
284
+ ruff format
285
+ ruff check --fix
286
+ ```
@@ -1,6 +1,6 @@
1
1
  # Maco - Malware config extractor framework
2
2
 
3
- ## Maco is a framework for ***ma***lware ***co***nfig extractors.
3
+ ## Maco is a framework for <ins>ma</ins>lware <ins>co</ins>nfig extractors.
4
4
 
5
5
  It aims to solve two problems:
6
6
 
@@ -234,3 +234,15 @@ run Complex extractor from rules ['ComplexAlt']
234
234
  The demo extractors are designed to trigger when run over the '`demo_extractors`' folder.
235
235
 
236
236
  e.g. `maco demo_extractors demo_extractors`
237
+
238
+ # Contributions
239
+
240
+ Please use ruff to format and lint PRs. This may be the cause of PR test failures.
241
+
242
+ Ruff will attempt to fix most issues, but some may require manual resolution.
243
+
244
+ ```
245
+ pip install ruff
246
+ ruff format
247
+ ruff check --fix
248
+ ```
@@ -32,7 +32,7 @@ class BaseTest(unittest.TestCase):
32
32
  # I recommend something like os.path.join(__file__, "../../extractors")
33
33
  # if your extractors are in a folder 'extractors' next to a folder of tests
34
34
  path: str = None
35
- create_venv: bool=False
35
+ create_venv: bool = False
36
36
 
37
37
  @classmethod
38
38
  def setUpClass(cls) -> None:
@@ -1,4 +1,5 @@
1
1
  """CLI example of how extractors can be executed."""
2
+
2
3
  import argparse
3
4
  import base64
4
5
  import binascii
@@ -150,6 +151,7 @@ def process_filesystem(
150
151
  logger.info(f"{num_analysed} analysed, {num_hits} hits, {num_extracted} extracted")
151
152
  return num_analysed, num_hits, num_extracted
152
153
 
154
+
153
155
  def main():
154
156
  parser = argparse.ArgumentParser(description="Run extractors over samples.")
155
157
  parser.add_argument("extractors", type=str, help="path to extractors")
@@ -165,7 +167,8 @@ def main():
165
167
  parser.add_argument(
166
168
  "--base64",
167
169
  action="store_true",
168
- help="Include base64 encoded binary data in output (can be large, consider printing to file rather than console)",
170
+ help="Include base64 encoded binary data in output "
171
+ "(can be large, consider printing to file rather than console)",
169
172
  )
170
173
  parser.add_argument("--logfile", type=str, help="file to log output")
171
174
  parser.add_argument("--include", type=str, help="comma separated extractors to run")
@@ -179,7 +182,9 @@ def main():
179
182
  parser.add_argument(
180
183
  "--create_venv",
181
184
  action="store_true",
182
- help="Creates venvs for every requirements.txt found (only applies when extractor path is a directory). This runs much slower than the alternative but may be necessary when there are many extractors with conflicting dependencies.",
185
+ help="Creates venvs for every requirements.txt found (only applies when extractor path is a directory). "
186
+ "This runs much slower than the alternative but may be necessary "
187
+ "when there are many extractors with conflicting dependencies.",
183
188
  )
184
189
  args = parser.parse_args()
185
190
  inc = args.include.split(",") if args.include else []
@@ -225,7 +230,7 @@ def main():
225
230
  pretty=args.pretty,
226
231
  force=args.force,
227
232
  include_base64=args.base64,
228
- create_venv=args.create_venv
233
+ create_venv=args.create_venv,
229
234
  )
230
235
 
231
236
 
@@ -4,6 +4,7 @@ import inspect
4
4
  import logging
5
5
  import logging.handlers
6
6
  import os
7
+ import sys
7
8
  from multiprocessing import Manager, Process, Queue
8
9
  from tempfile import NamedTemporaryFile
9
10
  from types import ModuleType
@@ -48,6 +49,15 @@ class Collector:
48
49
  create_venv: bool = False,
49
50
  ):
50
51
  """Discover and load extractors from file system."""
52
+ # maco requires the extractor to be imported directly, so ensure they are available on the path
53
+ full_path_extractors = os.path.abspath(path_extractors)
54
+ full_path_above_extractors = os.path.dirname(full_path_extractors)
55
+ # Modify the PATH so we can recognize this new package on import
56
+ if full_path_extractors not in sys.path:
57
+ sys.path.insert(1, full_path_extractors)
58
+ if full_path_above_extractors not in sys.path:
59
+ sys.path.insert(1, full_path_above_extractors)
60
+
51
61
  path_extractors = os.path.realpath(path_extractors)
52
62
  self.path: str = path_extractors
53
63
  self.extractors: Dict[str, Dict[str, str]] = {}
@@ -89,7 +99,7 @@ class Collector:
89
99
 
90
100
  # multiprocess logging is awkward - set up a queue to ensure we can log
91
101
  logging_queue = Queue()
92
- queue_handler = logging.handlers.QueueListener(logging_queue,*logging.getLogger().handlers)
102
+ queue_handler = logging.handlers.QueueListener(logging_queue, *logging.getLogger().handlers)
93
103
  queue_handler.start()
94
104
 
95
105
  # Find the extractors within the given directory
@@ -0,0 +1 @@
1
+ from maco.model.model import * # noqa: F403
@@ -59,31 +59,48 @@ class CategoryEnum(str, Enum):
59
59
  # Malware related to an Advanced Persistent Threat (APT) group.
60
60
  apt = "apt"
61
61
 
62
- # A backdoor Trojan gives malicious users remote control over the infected computer. They enable the author to do anything they wish on the infected computer including sending, receiving, launching and deleting files, displaying data and rebooting the computer. Backdoor Trojans are often used to unite a group of victim computers to form a botnet or zombie network that can be used for criminal purposes.
62
+ # A backdoor Trojan gives malicious users remote control over the infected computer.
63
+ # They enable the author to do anything they wish on the infected computer including
64
+ # sending, receiving, launching and deleting files, displaying data and rebooting the computer.
65
+ # Backdoor Trojans are often used to unite a group of victim computers to form a botnet or
66
+ # zombie network that can be used for criminal purposes.
63
67
  backdoor = "backdoor"
64
68
 
65
- # Trojan Banker programs are designed to steal your account data for online banking systems, e-payment systems and credit or debit cards.
69
+ # Trojan Banker programs are designed to steal your account data for online banking systems,
70
+ # e-payment systems and credit or debit cards.
66
71
  banker = "banker"
67
72
 
68
- # A malware variant that modifies the boot sectors of a hard drive, including the Master Boot Record (MBR) and Volume Boot Record (VBR).
73
+ # A malware variant that modifies the boot sectors of a hard drive, including the Master Boot Record (MBR)
74
+ # and Volume Boot Record (VBR).
69
75
  bootkit = "bootkit"
70
76
 
71
- # A malicious bot is self-propagating malware designed to infect a host and connect back to a central server or servers that act as a command and control (C&C) center for an entire network of compromised devices, or botnet.
77
+ # A malicious bot is self-propagating malware designed to infect a host and connect back to a central server
78
+ # or servers that act as a command and control (C&C) center for an entire network of compromised devices,
79
+ # or botnet.
72
80
  bot = "bot"
73
81
 
74
- # A browser hijacker is defined as a form of unwanted software that modifies a web browser's settings without the user's permission. The result is the placement of unwanted advertising into the browser, and possibly the replacement of an existing home page or search page with the hijacker page.
82
+ # A browser hijacker is defined as a form of unwanted software that modifies a web browser's settings without
83
+ # the user's permission. The result is the placement of unwanted advertising into the browser,
84
+ # and possibly the replacement of an existing home page or search page with the hijacker page.
75
85
  browser_hijacker = "browser_hijacker"
76
86
 
77
- # Trojan bruteforcer are trying to brute force website in order to achieve something else (EX: Finding WordPress websites with default credentials).
87
+ # Trojan bruteforcer are trying to brute force website in order to achieve something else
88
+ # (EX: Finding WordPress websites with default credentials).
78
89
  bruteforcer = "bruteforcer"
79
90
 
80
- # A type of trojan that can use your PC to 'click' on websites or applications. They are usually used to make money for a malicious hacker by clicking on online advertisements and making it look like the website gets more traffic than it does. They can also be used to skew online polls, install programs on your PC, or make unwanted software appear more popular than it is.
91
+ # A type of trojan that can use your PC to 'click' on websites or applications.
92
+ # They are usually used to make money for a malicious hacker by clicking on online advertisements
93
+ # and making it look like the website gets more traffic than it does.
94
+ # They can also be used to skew online polls, install programs on your PC, or make unwanted software
95
+ # appear more popular than it is.
81
96
  clickfraud = "clickfraud"
82
97
 
83
98
  # Cryptocurrency mining malware.
84
99
  cryptominer = "cryptominer"
85
100
 
86
- # These programs conduct DoS (Denial of Service) attacks against a targeted web address. By sending multiple requests from your computer and several other infected computers, the attack can overwhelm the target address leading to a denial of service.
101
+ # These programs conduct DoS (Denial of Service) attacks against a targeted web address.
102
+ # By sending multiple requests from your computer and several other infected computers,
103
+ # the attack can overwhelm the target address leading to a denial of service.
87
104
  ddos = "ddos"
88
105
 
89
106
  # Trojan Downloaders can download and install new versions of malicious programs in the target system.
@@ -92,49 +109,66 @@ class CategoryEnum(str, Enum):
92
109
  # These programs are used by hackers in order to install malware or to prevent the detection of malicious programs.
93
110
  dropper = "dropper"
94
111
 
95
- # Exploit kits are programs that contain data or code that takes advantage of a vulnerability within an application that is running in the target system.
112
+ # Exploit kits are programs that contain data or code that takes advantage of a vulnerability
113
+ # within an application that is running in the target system.
96
114
  exploitkit = "exploitkit"
97
115
 
98
- # Trojan FakeAV programs simulate the activity of antivirus software. They are designed to extort money in return for the detection and removal of threat, even though the threats that they report are actually non-existent.
116
+ # Trojan FakeAV programs simulate the activity of antivirus software.
117
+ # They are designed to extort money in return for the detection and removal of threat, even though the
118
+ # threats that they report are actually non-existent.
99
119
  fakeav = "fakeav"
100
120
 
101
121
  # A type of tool that can be used to allow and maintain unauthorized access to your PC.
102
122
  hacktool = "hacktool"
103
123
 
104
- # A program that collects your personal information, such as your browsing history, and uses it without adequate consent.
124
+ # A program that collects your personal information, such as your browsing history,
125
+ # and uses it without adequate consent.
105
126
  infostealer = "infostealer"
106
127
 
107
- # A keylogger monitors and logs every keystroke it can identify. Once installed, the virus either keeps track of all the keys and stores the information locally, after which the hacker needs physical access to the computer to retrieve the information, or the logs are sent over the internet back to the hacker.
128
+ # A keylogger monitors and logs every keystroke it can identify.
129
+ # Once installed, the virus either keeps track of all the keys and stores the information locally,
130
+ # after which the hacker needs physical access to the computer to retrieve the information,
131
+ # or the logs are sent over the internet back to the hacker.
108
132
  keylogger = "keylogger"
109
133
 
110
134
  # A program that loads another application / memory space.
111
135
  loader = "loader"
112
136
 
113
- # A type of malware that hides its code and purpose to make it more difficult for security software to detect or remove it.
137
+ # A type of malware that hides its code and purpose to make it more difficult for
138
+ # security software to detect or remove it.
114
139
  obfuscator = "obfuscator"
115
140
 
116
- # Point-of-sale malware is usually a type of malware that is used by cybercriminals to target point of sale (POS) and payment terminals with the intent to obtain credit card and debit card information.
141
+ # Point-of-sale malware is usually a type of malware that is used by cybercriminals to target point of sale (POS)
142
+ # and payment terminals with the intent to obtain credit card and debit card information.
117
143
  pos = "pos"
118
144
 
119
- # This type of trojan allows unauthorized parties to use the infected computer as a proxy server to access the Internet anonymously.
145
+ # This type of trojan allows unauthorized parties to use the infected computer as a proxy server
146
+ # to access the Internet anonymously.
120
147
  proxy = "proxy"
121
148
 
122
149
  # A program that can be used by a remote hacker to gain access and control of an infected machine.
123
150
  rat = "rat"
124
151
 
125
- # This type of malware can modify data in the target computer so the operating system will stop running correctly or the data is no longer accessible. The criminal will only restore the computer state or data after a ransom is paid to them (mostly using cryptocurrency).
152
+ # This type of malware can modify data in the target computer so the operating system
153
+ # will stop running correctly or the data is no longer accessible.
154
+ # The criminal will only restore the computer state or data after a ransom is paid to them
155
+ # (mostly using cryptocurrency).
126
156
  ransomware = "ransomware"
127
157
 
128
158
  # A reverse proxy is a server that receives requests from the internet and forwards them to a small set of servers.
129
159
  reverse_proxy = "reverse_proxy"
130
160
 
131
- # Rootkits are designed to conceal certain objects or activities in the system. Often their main purpose is to prevent malicious programs being detected in order to extend the period in which programs can run on an infected computer.
161
+ # Rootkits are designed to conceal certain objects or activities in the system.
162
+ # Often their main purpose is to prevent malicious programs being detected
163
+ # in order to extend the period in which programs can run on an infected computer.
132
164
  rootkit = "rootkit"
133
165
 
134
- # This type of malware scan the internet / network(s) / system(s) / service(s) to collect information. That information could be used later to perpetuate an cyber attack.
166
+ # This type of malware scan the internet / network(s) / system(s) / service(s) to collect information.
167
+ # That information could be used later to perpetuate an cyber attack.
135
168
  scanner = "scanner"
136
169
 
137
- # Scareware is a form of malware which uses social engineering to cause shock, anxiety, or the perception of a threat in order to manipulate users into buying unwanted software.
170
+ # Scareware is a form of malware which uses social engineering to cause shock, anxiety,
171
+ # or the perception of a threat in order to manipulate users into buying unwanted software.
138
172
  scareware = "scareware"
139
173
 
140
174
  # Malware that is sending spam.
@@ -46,6 +46,7 @@ UV_BIN = find_uv_bin()
46
46
  PIP_CMD = f"{UV_BIN} pip"
47
47
  VENV_CREATE_CMD = f"{UV_BIN} venv"
48
48
 
49
+
49
50
  class Base64Decoder(json.JSONDecoder):
50
51
  def __init__(self, *args, **kwargs):
51
52
  json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
@@ -179,11 +180,9 @@ def scan_for_extractors(root_directory: str, scanner: yara.Rules, logger: Logger
179
180
  for pattern in [RELATIVE_FROM_IMPORT_RE, RELATIVE_FROM_RE]:
180
181
  for match in pattern.findall(data):
181
182
  depth = match.count(".")
182
- data = data.replace(
183
- f"from {match}",
184
- f"from {'.'.join(split[depth - 1 : split.index(package) + 1][::-1])}{'.' if pattern == RELATIVE_FROM_RE else ''}",
185
- 1,
186
- )
183
+ abspath = ".".join(split[depth - 1 : split.index(package) + 1][::-1])
184
+ abspath += "." if pattern == RELATIVE_FROM_RE else ""
185
+ data = data.replace(f"from {match}", f"from {abspath}", 1)
187
186
  f.write(data)
188
187
 
189
188
  if scanner.match(path):
@@ -222,7 +221,12 @@ def _install_required_packages(create_venv: bool, directories: List[str], python
222
221
  subprocess.run(cmd.split(" ") + [venv_path], capture_output=True, env=env)
223
222
 
224
223
  # Install/Update the packages in the environment
225
- install_command = PIP_CMD.split(" ") + ["install", "-U"]
224
+ install_command = PIP_CMD.split(" ") + ["install"]
225
+ # When running locally, only install packages to required spec.
226
+ # This prevents issues during maco development and building extractors against local libraries.
227
+ if create_venv:
228
+ # when running in custom virtual environment, always upgrade packages.
229
+ install_command.append("-U")
226
230
 
227
231
  # Update the pip install command depending on where the dependencies are coming from
228
232
  if "requirements.txt" in req_files:
@@ -307,8 +311,10 @@ def register_extractors(
307
311
  parent_directory = os.path.dirname(current_directory)
308
312
  if venvs and package_name in sys.modules:
309
313
  # this may happen as part of testing if some part of the extractor code was directly imported
310
- logger.warning(f"Looks like {package_name} is already loaded. "
311
- "If your maco extractor overlaps an existing package name this could cause problems.")
314
+ logger.warning(
315
+ f"Looks like {package_name} is already loaded. "
316
+ "If your maco extractor overlaps an existing package name this could cause problems."
317
+ )
312
318
 
313
319
  try:
314
320
  # Modify the PATH so we can recognize this new package on import
@@ -383,6 +389,7 @@ def register_extractors(
383
389
  # We were able to find all the extractor files
384
390
  break
385
391
 
392
+
386
393
  def proxy_logging(queue: multiprocessing.Queue, callback: Callable[[ModuleType, str], None], *args, **kwargs):
387
394
  """Ensures logging is set up correctly for a child process and then executes the callback."""
388
395
  logger = logging.getLogger()
@@ -391,6 +398,7 @@ def proxy_logging(queue: multiprocessing.Queue, callback: Callable[[ModuleType,
391
398
  logger.addHandler(qh)
392
399
  callback(*args, **kwargs, logger=logger)
393
400
 
401
+
394
402
  def import_extractors(
395
403
  extractor_module_callback: Callable[[ModuleType, str], bool],
396
404
  *,
@@ -416,6 +424,7 @@ def import_extractors(
416
424
  # holds cached extractors when not running in venv mode
417
425
  _loaded_extractors: Dict[str, Extractor] = {}
418
426
 
427
+
419
428
  def run_extractor(
420
429
  sample_path,
421
430
  module_name,
@@ -438,7 +447,7 @@ def run_extractor(
438
447
  extractor = _loaded_extractors[key]
439
448
  if extractor.yara_compiled:
440
449
  matches = extractor.yara_compiled.match(sample_path)
441
- loaded = extractor.run(open(sample_path, 'rb'), matches=matches)
450
+ loaded = extractor.run(open(sample_path, "rb"), matches=matches)
442
451
  else:
443
452
  # execute extractor in child process with separate virtual environment
444
453
  # Write temporary script in the same directory as extractor to resolve relative imports
@@ -477,7 +486,7 @@ def run_extractor(
477
486
  try:
478
487
  # Load results and return them
479
488
  output.seek(0)
480
- loaded = json.load(output, cls=json_decoder)
489
+ loaded = json.load(output, cls=json_decoder)
481
490
  except Exception as e:
482
491
  # If there was an error raised during runtime, then propagate
483
492
  delim = f'File "{module_path}"'
@@ -3,7 +3,6 @@ from collections import namedtuple
3
3
  from itertools import cycle
4
4
  from typing import Dict
5
5
 
6
- import yara
7
6
  import yara_x
8
7
 
9
8
  RULE_ID_RE = re.compile("(\w+)? ?rule (\w+)")
@@ -7,6 +7,27 @@ pool:
7
7
  vmImage: "ubuntu-22.04"
8
8
 
9
9
  jobs:
10
+ - job: style_test
11
+ strategy:
12
+ matrix:
13
+ Python3_12:
14
+ python.version: "3.12"
15
+ timeoutInMinutes: 10
16
+
17
+ steps:
18
+ - task: UsePythonVersion@0
19
+ displayName: Set python version
20
+ inputs:
21
+ versionSpec: "$(python.version)"
22
+
23
+ - script: |
24
+ python -m pip install -U tox
25
+ displayName: Install tox
26
+
27
+ - script: |
28
+ python -m tox -e style
29
+ displayName: "Run style tests"
30
+
10
31
  - job: run_test
11
32
  strategy:
12
33
  matrix:
@@ -41,3 +41,12 @@ dependencies = { file = ["requirements.txt"] }
41
41
  [tool.setuptools.packages.find]
42
42
  where = ["."]
43
43
  exclude = ["test", "tests", "extractors", "model_setup"]
44
+
45
+ [tool.ruff]
46
+ line-length = 119
47
+
48
+ [tool.ruff.lint]
49
+ # Add the `line-too-long` rule to the enforced rule set. By default, Ruff omits rules that
50
+ # overlap with the use of a formatter, like Black, but we can override this behavior by
51
+ # explicitly adding the rule.
52
+ extend-select = ["E501"]
@@ -1,7 +1,4 @@
1
- from io import BytesIO
2
- from typing import List, Optional
3
-
4
- from maco import extractor, model, yara
1
+ from maco import extractor
5
2
 
6
3
 
7
4
  class Bob(extractor.Extractor):
@@ -22,6 +22,7 @@ class TestLimitOther(base_test.BaseTest):
22
22
 
23
23
  class TestComplex(base_test.BaseTest):
24
24
  """Test that complex extractor can be used in base environment."""
25
+
25
26
  name = "Complex"
26
27
  path = os.path.join(__file__, "../../demo_extractors")
27
28
  create_venv = False
@@ -47,8 +48,10 @@ class TestComplex(base_test.BaseTest):
47
48
  result = instance.run(data, [])
48
49
  self.assertEqual(result.family, "complex")
49
50
 
51
+
50
52
  class TestComplexVenv(base_test.BaseTest):
51
53
  """Test that complex extractor can be used in full venv isolation."""
54
+
52
55
  name = "Complex"
53
56
  path = os.path.join(__file__, "../../demo_extractors")
54
57
  create_venv = True
@@ -7,9 +7,7 @@ from maco.collector import Collector
7
7
 
8
8
  class TestDemoExtractors(unittest.TestCase):
9
9
  def test_complex(self):
10
- path_file = os.path.normpath(
11
- os.path.join(__file__, "../data/trigger_complex.txt")
12
- )
10
+ path_file = os.path.normpath(os.path.join(__file__, "../data/trigger_complex.txt"))
13
11
  collector = Collector(os.path.join(__file__, "../../demo_extractors"))
14
12
  self.assertEqual(
15
13
  set(collector.extractors.keys()),
@@ -129,6 +129,9 @@ def test_module_confusion():
129
129
 
130
130
  import git
131
131
 
132
+ # ensure that the git import is kept
133
+ assert git.__name__
134
+
132
135
  # Directories that have the same name as the Python module, shouldn't cause confusion on loading the right module
133
136
  collector = Collector(os.path.join(__file__, "../extractors/bob"))
134
137
  assert collector.extractors["Bob"]
@@ -1,5 +1,5 @@
1
1
  [tox]
2
- envlist = py38,py39,py310,py311,py312
2
+ envlist = py38,py39,py310,py311,py312,style
3
3
  [testenv]
4
4
  # install testing framework
5
5
  deps =
@@ -8,3 +8,12 @@ deps =
8
8
  -r tests/requirements.txt
9
9
  # run the tests
10
10
  commands = python -m pytest tests/ -p no:cacheprovider --durations=10 -ra -q -k "not git and not extractors" -vv -W ignore::DeprecationWarning
11
+
12
+ [testenv:style]
13
+ # install testing framework
14
+ deps =
15
+ ruff
16
+ # run the tests
17
+ commands =
18
+ ruff format --check
19
+ ruff check
@@ -1 +0,0 @@
1
- from maco.model.model import *
@@ -1 +0,0 @@
1
- from maco.model.model import *
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes