maco 1.2.10__tar.gz → 1.2.12__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 (71) hide show
  1. {maco-1.2.10/maco.egg-info → maco-1.2.12}/PKG-INFO +1 -1
  2. maco-1.2.12/demo_extractors/requirements.txt +4 -0
  3. maco-1.2.12/demo_extractors/terminator.py +17 -0
  4. {maco-1.2.10/model_setup → maco-1.2.12}/maco/cli.py +21 -3
  5. {maco-1.2.10 → maco-1.2.12}/maco/collector.py +6 -0
  6. maco-1.2.12/maco/exceptions.py +3 -0
  7. {maco-1.2.10 → maco-1.2.12}/maco/utils.py +23 -6
  8. {maco-1.2.10 → maco-1.2.12/maco.egg-info}/PKG-INFO +1 -1
  9. {maco-1.2.10 → maco-1.2.12}/maco.egg-info/SOURCES.txt +3 -0
  10. {maco-1.2.10 → maco-1.2.12/model_setup}/maco/cli.py +21 -3
  11. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/collector.py +6 -0
  12. maco-1.2.12/model_setup/maco/exceptions.py +3 -0
  13. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/utils.py +23 -6
  14. {maco-1.2.10 → maco-1.2.12}/tests/test_base_test.py +26 -0
  15. {maco-1.2.10 → maco-1.2.12}/tests/test_demo_extractors.py +1 -6
  16. {maco-1.2.10 → maco-1.2.12}/tests/test_helpers.py +1 -8
  17. maco-1.2.10/demo_extractors/requirements.txt +0 -1
  18. {maco-1.2.10 → maco-1.2.12}/.gitignore +0 -0
  19. {maco-1.2.10 → maco-1.2.12}/.vscode/settings.json +0 -0
  20. {maco-1.2.10 → maco-1.2.12}/LICENSE.md +0 -0
  21. {maco-1.2.10 → maco-1.2.12}/README.md +0 -0
  22. {maco-1.2.10 → maco-1.2.12}/demo_extractors/__init__.py +0 -0
  23. {maco-1.2.10 → maco-1.2.12}/demo_extractors/complex/__init__.py +0 -0
  24. {maco-1.2.10 → maco-1.2.12}/demo_extractors/complex/complex.py +0 -0
  25. {maco-1.2.10 → maco-1.2.12}/demo_extractors/complex/complex_utils.py +0 -0
  26. {maco-1.2.10 → maco-1.2.12}/demo_extractors/elfy.py +0 -0
  27. {maco-1.2.10 → maco-1.2.12}/demo_extractors/limit_other.py +0 -0
  28. {maco-1.2.10 → maco-1.2.12}/demo_extractors/nothing.py +0 -0
  29. {maco-1.2.10 → maco-1.2.12}/demo_extractors/shared.py +0 -0
  30. {maco-1.2.10 → maco-1.2.12}/maco/__init__.py +0 -0
  31. {maco-1.2.10 → maco-1.2.12}/maco/base_test.py +0 -0
  32. {maco-1.2.10 → maco-1.2.12}/maco/extractor.py +0 -0
  33. {maco-1.2.10 → maco-1.2.12}/maco/model/__init__.py +0 -0
  34. {maco-1.2.10 → maco-1.2.12}/maco/model/model.py +0 -0
  35. {maco-1.2.10 → maco-1.2.12}/maco/yara.py +0 -0
  36. {maco-1.2.10 → maco-1.2.12}/maco.egg-info/dependency_links.txt +0 -0
  37. {maco-1.2.10 → maco-1.2.12}/maco.egg-info/entry_points.txt +0 -0
  38. {maco-1.2.10 → maco-1.2.12}/maco.egg-info/requires.txt +0 -0
  39. {maco-1.2.10 → maco-1.2.12}/maco.egg-info/top_level.txt +0 -0
  40. {maco-1.2.10 → maco-1.2.12}/model_setup/LICENSE.md +0 -0
  41. {maco-1.2.10 → maco-1.2.12}/model_setup/README.md +0 -0
  42. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/__init__.py +0 -0
  43. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/base_test.py +0 -0
  44. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/extractor.py +0 -0
  45. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/model/__init__.py +0 -0
  46. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/model/model.py +0 -0
  47. {maco-1.2.10 → maco-1.2.12}/model_setup/maco/yara.py +0 -0
  48. {maco-1.2.10 → maco-1.2.12}/model_setup/pyproject.toml +0 -0
  49. {maco-1.2.10 → maco-1.2.12}/model_setup/setup.py +0 -0
  50. {maco-1.2.10 → maco-1.2.12}/pipelines/publish.yaml +0 -0
  51. {maco-1.2.10 → maco-1.2.12}/pipelines/test.yaml +0 -0
  52. {maco-1.2.10 → maco-1.2.12}/pyproject.toml +0 -0
  53. {maco-1.2.10 → maco-1.2.12}/requirements.txt +0 -0
  54. {maco-1.2.10 → maco-1.2.12}/setup.cfg +0 -0
  55. {maco-1.2.10 → maco-1.2.12}/tests/benchmark.py +0 -0
  56. {maco-1.2.10 → maco-1.2.12}/tests/data/example.txt.cart +0 -0
  57. {maco-1.2.10 → maco-1.2.12}/tests/data/trigger_complex.txt +0 -0
  58. {maco-1.2.10 → maco-1.2.12}/tests/data/trigger_complex.txt.cart +0 -0
  59. {maco-1.2.10 → maco-1.2.12}/tests/extractors/__init__.py +0 -0
  60. {maco-1.2.10 → maco-1.2.12}/tests/extractors/basic.py +0 -0
  61. {maco-1.2.10 → maco-1.2.12}/tests/extractors/basic_longer.py +0 -0
  62. {maco-1.2.10 → maco-1.2.12}/tests/extractors/bob/__init__.py +0 -0
  63. {maco-1.2.10 → maco-1.2.12}/tests/extractors/bob/bob.py +0 -0
  64. {maco-1.2.10 → maco-1.2.12}/tests/extractors/test_basic.py +0 -0
  65. {maco-1.2.10 → maco-1.2.12}/tests/pytest.ini +0 -0
  66. {maco-1.2.10 → maco-1.2.12}/tests/requirements.txt +0 -0
  67. {maco-1.2.10 → maco-1.2.12}/tests/test_cli.py +0 -0
  68. {maco-1.2.10 → maco-1.2.12}/tests/test_detection.py +0 -0
  69. {maco-1.2.10 → maco-1.2.12}/tests/test_extractor.py +0 -0
  70. {maco-1.2.10 → maco-1.2.12}/tests/test_model.py +0 -0
  71. {maco-1.2.10 → maco-1.2.12}/tox.ini +0 -0
@@ -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
@@ -0,0 +1,4 @@
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")
@@ -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): ...
@@ -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
@@ -11,6 +11,7 @@ demo_extractors/limit_other.py
11
11
  demo_extractors/nothing.py
12
12
  demo_extractors/requirements.txt
13
13
  demo_extractors/shared.py
14
+ demo_extractors/terminator.py
14
15
  demo_extractors/complex/__init__.py
15
16
  demo_extractors/complex/complex.py
16
17
  demo_extractors/complex/complex_utils.py
@@ -18,6 +19,7 @@ maco/__init__.py
18
19
  maco/base_test.py
19
20
  maco/cli.py
20
21
  maco/collector.py
22
+ maco/exceptions.py
21
23
  maco/extractor.py
22
24
  maco/utils.py
23
25
  maco/yara.py
@@ -38,6 +40,7 @@ model_setup/maco/__init__.py
38
40
  model_setup/maco/base_test.py
39
41
  model_setup/maco/cli.py
40
42
  model_setup/maco/collector.py
43
+ model_setup/maco/exceptions.py
41
44
  model_setup/maco/extractor.py
42
45
  model_setup/maco/utils.py
43
46
  model_setup/maco/yara.py
@@ -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): ...
@@ -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
@@ -61,3 +61,29 @@ class TestComplexVenv(base_test.BaseTest):
61
61
  ret = self.extract(self.load_cart("data/trigger_complex.txt.cart"))
62
62
  self.assertEqual(ret["family"], "complex")
63
63
  self.assertEqual(ret["version"], "5")
64
+
65
+
66
+ class TestTerminator(base_test.BaseTest):
67
+ """Test that terminator extractor can be used in base environment."""
68
+
69
+ name = "Terminator"
70
+ path = os.path.join(__file__, "../../demo_extractors")
71
+ create_venv = False
72
+
73
+ def test_extract(self):
74
+ """Tests that we can run an extractor through maco."""
75
+ ret = self.extract(self.load_cart("data/trigger_complex.txt.cart"))
76
+ self.assertEqual(ret, None)
77
+
78
+
79
+ class TestTerminatorVenv(base_test.BaseTest):
80
+ """Test that terminator extractor can be used in base environment."""
81
+
82
+ name = "Terminator"
83
+ path = os.path.join(__file__, "../../demo_extractors")
84
+ create_venv = True
85
+
86
+ def test_extract(self):
87
+ """Tests that we can run an extractor through maco."""
88
+ ret = self.extract(self.load_cart("data/trigger_complex.txt.cart"))
89
+ self.assertEqual(ret, None)
@@ -11,12 +11,7 @@ class TestDemoExtractors(unittest.TestCase):
11
11
  collector = Collector(os.path.join(__file__, "../../demo_extractors"))
12
12
  self.assertEqual(
13
13
  set(collector.extractors.keys()),
14
- {
15
- "Elfy",
16
- "Nothing",
17
- "Complex",
18
- "LimitOther",
19
- },
14
+ {"Elfy", "Nothing", "Complex", "LimitOther", "Terminator"},
20
15
  )
21
16
 
22
17
  with open(path_file, "rb") as stream:
@@ -25,14 +25,7 @@ class TestHelpersCompileYara(unittest.TestCase):
25
25
  m = collector.Collector(target)
26
26
  self.assertEqual(
27
27
  {x.identifier for x in m.rules},
28
- {
29
- "Elfy",
30
- "Complex",
31
- "ComplexSubtext",
32
- "Nothing",
33
- "ComplexAlt",
34
- "LimitOther",
35
- },
28
+ {"Elfy", "Complex", "ComplexSubtext", "Nothing", "ComplexAlt", "LimitOther", "Terminator"},
36
29
  )
37
30
 
38
31
 
@@ -1 +0,0 @@
1
- httpx
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
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