From 1d8039bb135ed5f71f716f6f67c565f336ed8d78 Mon Sep 17 00:00:00 2001 From: Carl Flottmann Date: Mon, 3 Feb 2025 11:05:56 +1000 Subject: [PATCH] test: setup test environment for source code analyzer --- .pre-commit-config.yaml | 4 +- .semgrepignore | 1 + pyproject.toml | 11 +- .../sourcecode/pypi_sourcecode_analyzer.py | 18 +- .../pypi_malware_rules/exfiltration.yaml | 52 ++- .../pypi_malware_rules/obfuscation.yaml | 34 +- .../checks/detect_malicious_metadata_check.py | 2 +- .../obfuscation/decode_and_execute.py | 26 ++ .../obfuscation/default_assigning.py | 61 +++ .../obfuscation/expected_results.json | 405 ++++++++++++++++++ .../sourcecode_samples/obfuscation/tools.py | 69 +++ .../pypi/test_pypi_sourcecode_analyzer.py | 54 +++ 12 files changed, 704 insertions(+), 33 deletions(-) create mode 100644 .semgrepignore create mode 100644 tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/decode_and_execute.py create mode 100644 tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py create mode 100644 tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/expected_results.json create mode 100644 tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py create mode 100644 tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5e4f5029..d1afa79f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. # See https://pre-commit.com for more information @@ -64,6 +64,7 @@ repos: name: Check flake8 issues files: ^src/macaron/|^tests/ types: [text, python] + exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* additional_dependencies: [flake8-bugbear==22.10.27, flake8-builtins==2.0.1, flake8-comprehensions==3.10.1, flake8-docstrings==1.6.0, flake8-mutable==1.2.0, flake8-noqa==1.3.0, flake8-pytest-style==1.6.0, flake8-rst-docstrings==0.3.0, pep8-naming==0.13.2] args: [--config, .flake8] @@ -94,6 +95,7 @@ repos: language: python files: ^src/macaron/|^tests/ types: [text, python] + exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* args: [--show-traceback, --config-file, pyproject.toml] # Check for potential security issues. diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 000000000..3d53fd964 --- /dev/null +++ b/.semgrepignore @@ -0,0 +1 @@ +# Items added to this file will be ignored by Semgrep. diff --git a/pyproject.toml b/pyproject.toml index 2e6b813a3..53db35ac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -121,12 +121,14 @@ Issues = "https://github.com/oracle/macaron/issues" [tool.bandit] tests = [] skips = ["B101"] - +exclude_dirs = ['tests/malware_analyzer/pypi/resources/sourcecode_samples'] # https://github.com/psf/black#configuration [tool.black] line-length = 120 - +force-exclude = ''' +tests/malware_analyzer/pypi/resources/sourcecode_samples/ +''' # https://github.com/commitizen-tools/commitizen # https://commitizen-tools.github.io/commitizen/bump/ @@ -172,7 +174,6 @@ exclude = [ "SECURITY.md", ] - # https://pycqa.github.io/isort/ [tool.isort] profile = "black" @@ -183,7 +184,6 @@ skip_gitignore = true # https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml [tool.mypy] -# exclude= show_error_codes = true show_column_numbers = true check_untyped_defs = true @@ -210,7 +210,6 @@ module = [ ] ignore_missing_imports = true - # https://pylint.pycqa.org/en/latest/user_guide/configuration/index.html [tool.pylint.MASTER] fail-under = 10.0 @@ -241,6 +240,7 @@ disable = [ "too-many-statements", "duplicate-code", ] +ignore-paths = "tests/malware_analyzer/pypi/resources/sourcecode_samples" [tool.pylint.MISCELLANEOUS] notes = [ @@ -262,6 +262,7 @@ addopts = """-vv -ra --tb native \ --doctest-modules --doctest-continue-on-failure --doctest-glob '*.rst' \ --cov macaron \ --ignore tests/integration \ + --ignore tests/malware_analyzer/pypi/resources/sourcecode_samples \ """ # Consider adding --pdb # https://docs.python.org/3/library/doctest.html#option-flags doctest_optionflags = "IGNORE_EXCEPTION_DETAIL" diff --git a/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py b/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py index beb5e553b..e3c325690 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py @@ -54,11 +54,11 @@ class PyPISourcecodeAnalyzer: EXPECTED_PATTERN_CATEGORIES = [IMPORTS, CONSTANTS, CALLS] - def __init__(self) -> None: + def __init__(self, resources_path: str = global_config.resources_path) -> None: """Collect required data for analysing the source code.""" - self.default_rule_path, self.custom_rule_path = self._load_defaults() + self.default_rule_path, self.custom_rule_path = self._load_defaults(resources_path) - def _load_defaults(self) -> tuple[str, str | None]: + def _load_defaults(self, resources_path: str) -> tuple[str, str | None]: """ Load the default semgrep rules and, if present, the custom semgrep rules provided by the user. @@ -72,9 +72,15 @@ def _load_defaults(self) -> tuple[str, str | None]: Raises ------ ConfigurationError - If the heuristic.pypi entry is not present, or if the semgrep validation of the custom rule path failed. + If the default rule path is invalid, the heuristic.pypi entry is not present, or if the semgrep + validation of the custom rule path failed. """ - default_rule_path = os.path.join(global_config.resources_path, "pypi_malware_rules") + default_rule_path = os.path.join(resources_path, "pypi_malware_rules") + if not os.path.exists(default_rule_path): + error_msg = f"Error with locating default rule path {default_rule_path}" + logger.debug(error_msg) + raise ConfigurationError(error_msg) + section_name = "heuristic.pypi" if defaults.has_section(section_name): @@ -112,7 +118,7 @@ def _load_defaults(self) -> tuple[str, str | None]: logger.debug("Including custom ruleset from %s.", custom_rule_path) return default_rule_path, custom_rule_path - def analyze_patterns(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicResult, dict[str, JsonType]]: + def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicResult, dict[str, JsonType]]: """Analyze the source code of the package for malicious patterns. This is the first phase of the source code analyzer. diff --git a/src/macaron/resources/pypi_malware_rules/exfiltration.yaml b/src/macaron/resources/pypi_malware_rules/exfiltration.yaml index b0c8b078a..fa96f43d3 100644 --- a/src/macaron/resources/pypi_malware_rules/exfiltration.yaml +++ b/src/macaron/resources/pypi_malware_rules/exfiltration.yaml @@ -4,7 +4,7 @@ rules: - id: remote-exfiltration metadata: - description: Detected the exfiltration of data to a remote endpoint + description: Identifies the flow of sensitive information to a remote endpoint. message: Detected exfiltration of sensitive data to a remote endpoint. languages: - python @@ -23,6 +23,43 @@ rules: - pattern: __import__('builtins').exec(...) - pattern: __import__('builtins').eval(...) + # process spawning + # using subprocess module + - pattern: subprocess.check_output(...) + - pattern: subprocess.check_call(...) + - pattern: subprocess.run(...) + - pattern: subprocess.call(...) + - pattern: subprocess.Popen(...) + - pattern: subprocess.getoutput(...) + - pattern: subprocess.getstatusoutput(...) + # using os module + - pattern: os.execl(...) + - pattern: os.execle(...) + - pattern: os.execlp(...) + - pattern: os.execlpe(...) + - pattern: os.execv(...) + - pattern: os.execve(...) + - pattern: os.execvp(...) + - pattern: os.execvpe(...) + - pattern: os.popen(...) + - pattern: os.posix_spawn(...) + - pattern: os.posix_spawnp(...) + - pattern: os.spawnl(...) + - pattern: os.spawnle(...) + - pattern: os.spawnlp(...) + - pattern: os.spawnlpe(...) + - pattern: os.spawnv(...) + - pattern: os.spawnve(...) + - pattern: os.spawnvp(...) + - pattern: os.spawnvpe(...) + - pattern: os.system(...) + # using commands module + - pattern: commands.getstatusoutput(...) + - pattern: commands.getoutput(...) + # using runpy module + - pattern: runpy.run_module(...) + - pattern: runpy.run_path(...) + # environment variables - pattern: os.environ - pattern: os.environ[...] @@ -84,6 +121,19 @@ rules: - pattern: winreg.QueryInfoKey(...) - pattern: winreg.QueryValue(...) - pattern: winreg.QueryValueEx(...) + - pattern: sqlite3.connect(...) + + # file exfiltration + - patterns: + - pattern: open($FILE, $MODE) + - metavariable-regex: + metavariable: $MODE + regex: r|rt|r+|w+|rb|r+b|w+b|a+|a+b + - patterns: + - pattern: os.open($FILE, $MODE) + - metavariable-regex: + metavariable: $MODE + regex: os\.O_RDONLY|os\.O_RDWR pattern-sinks: - pattern-either: diff --git a/src/macaron/resources/pypi_malware_rules/obfuscation.yaml b/src/macaron/resources/pypi_malware_rules/obfuscation.yaml index 5f3bf329c..76b327578 100644 --- a/src/macaron/resources/pypi_malware_rules/obfuscation.yaml +++ b/src/macaron/resources/pypi_malware_rules/obfuscation.yaml @@ -67,31 +67,26 @@ rules: - pattern: __import__('__pyarmor__') # pyarmor RTF mode: pyarmor.readthedocs.io/en/latest/tutorial/advanced.html - pattern: __assert_armored__($PAYLOAD) - - patterns: - - pattern: | - def $FUNC_NAME(...): - ... - - metavariable-regex: - metavariable: $FUNC_NAME - regex: ^pyarmor__\d+$ # inline pyarmor marker: pyarmor.readthedocs.io/en/latest/tutorial/advanced.html - - pattern-regex: ^# pyarmor:.? + - pattern-regex: ^\s*#\s*pyarmor:.* # obfuscated names using pyob.oxyry.com with O, o, 0 or github.com/QQuick/Opy and pyobfuscate using l, I, 1 - patterns: - - pattern: | - def $OBF(...): - ... - - pattern: | - class $OBF(...): - ... - - pattern: $OBF = ... + - pattern-either: + - pattern: | + def $OBF(...): + ... + - pattern: | + class $OBF(...): + ... + - pattern: $OBF = ... - metavariable-regex: metavariable: $OBF - regex: (^_?[Oo0]|[1Il]+$) + regex: (^_*([lI1_]{5,}|[Oo0_]{5,})_*$)|(^pyarmor_*\d+$) # obfuscated using pyobfuscate.com - pattern: pyobfuscate=... # obfuscated using liftoff.github.io/pyminifier - pattern: import mystificate + - pattern: import demiurgic - id: inline-imports metadata: @@ -134,9 +129,10 @@ rules: - pattern: bytes.fromhex(...) # unicode construction - patterns: - - pattern: $STRING.join(map($FOO, [...])) - - pattern: $STRING.join($FOO($VAL) for $VAL in [...]) - - pattern: $STRING.join($FOO($VAL) for $VAL in $GEN(...)) + - pattern-either: + - pattern: $STRING.join(map($FOO, [...])) + - pattern: $STRING.join($FOO($VAL) for $VAL in [...]) + - pattern: $STRING.join($FOO($VAL) for $VAL in $GEN(...)) - metavariable-regex: metavariable: $FOO regex: unicode|unichr|chr|ord diff --git a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py index 42228df68..68b926d51 100644 --- a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py +++ b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py @@ -298,7 +298,7 @@ def analyze_source(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[Heuri logger.debug("Instantiating %s", PyPISourcecodeAnalyzer.__name__) try: sourcecode_analyzer = PyPISourcecodeAnalyzer() - return sourcecode_analyzer.analyze_patterns(pypi_package_json) + return sourcecode_analyzer.analyze(pypi_package_json) except (ConfigurationError, HeuristicAnalyzerValueError) as source_code_error: logger.debug("Unable to perform source code analysis: %s", source_code_error) return HeuristicResult.SKIP, {} diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/decode_and_execute.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/decode_and_execute.py new file mode 100644 index 000000000..74ce85c19 --- /dev/null +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/decode_and_execute.py @@ -0,0 +1,26 @@ +# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. + +""" +Running this code will not produce any malicious behavior, but code isolation measures are +in place for safety. +""" + +import sys + +# ensure no symbols are exported so this code cannot accidentally be used +__all__ = [] +sys.exit() + +def test_function(): + """ + All code to be tested will be defined inside this function, so it is all local to it. This is + to isolate the code to be tested, as it exists to replicate the patterns present in malware + samples. + """ + sys.exit() + # marshal encryption from pyobfuscate.com/marshal-encrypt, script is just print("Hello world!") + + from marshal import loads + bytecode = loads(b'\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00@\x00\x00\x00s\x0c\x00\x00\x00e\x00d\x00\x83\x01\x01\x00d\x01S\x00)\x02z\x0cHello world!N)\x01\xda\x05print\xa9\x00r\x02\x00\x00\x00r\x02\x00\x00\x00\xfa\x08\xda\x08\x01\x00\x00\x00\xf3\x00\x00\x00\x00') + exec(bytecode) diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py new file mode 100644 index 000000000..ed2c9dda9 --- /dev/null +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py @@ -0,0 +1,61 @@ +# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. + +""" +Running this code will not produce any malicious behavior, but code isolation measures are +in place for safety. +""" + +import sys + +# ensure no symbols are exported so this code cannot accidentally be used +__all__ = [] +sys.exit() + +def test_function(): + """ + All code to be tested will be defined inside this function, so it is all local to it. This is + to isolate the code to be tested, as it exists to replicate the patterns present in malware + samples. + """ + sys.exit() + import builtins + _ = __import__ + _ = getattr + _ = bytes + _ = bytearray + _ = exec + _ = eval + _ = setattr + _ = compile + _ = map + _ = open + _ = zip + _ = vars + _ = dir + _ = builtins.__import__ + _ = builtins.getattr + _ = builtins.bytes + _ = builtins.bytearray + _ = builtins.exec + _ = builtins.eval + _ = builtins.setattr + _ = builtins.compile + _ = builtins.map + _ = builtins.open + _ = builtins.zip + _ = builtins.vars + _ = builtins.dir + _ = __import__('builtins').__import__ + _ = __import__('builtins').getattr + _ = __import__('builtins').bytes + _ = __import__('builtins').bytearray + _ = __import__('builtins').exec + _ = __import__('builtins').eval + _ = __import__('builtins').setattr + _ = __import__('builtins').compile + _ = __import__('builtins').builtins.map + _ = __import__('builtins').open + _ = __import__('builtins').zip + _ = __import__('builtins').vars + _ = __import__('builtins').dir diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/expected_results.json b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/expected_results.json new file mode 100644 index 000000000..3376aa3ed --- /dev/null +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/expected_results.json @@ -0,0 +1,405 @@ +{ + "src.macaron.resources.pypi_malware_rules.decode-and-execute": [ + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/decode_and_execute.py", + "start": 26, + "end": 26 + } + ], + "src.macaron.resources.pypi_malware_rules.default-assigning": [ + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 23, + "end": 23 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 24, + "end": 24 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 25, + "end": 25 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 26, + "end": 26 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 27, + "end": 27 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 28, + "end": 28 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 29, + "end": 29 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 30, + "end": 30 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 31, + "end": 31 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 32, + "end": 32 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 33, + "end": 33 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 34, + "end": 34 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 35, + "end": 35 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 36, + "end": 36 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 37, + "end": 37 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 38, + "end": 38 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 39, + "end": 39 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 40, + "end": 40 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 41, + "end": 41 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 42, + "end": 42 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 43, + "end": 43 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 44, + "end": 44 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 45, + "end": 45 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 46, + "end": 46 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 47, + "end": 47 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 48, + "end": 48 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 49, + "end": 49 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 50, + "end": 50 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 51, + "end": 51 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 52, + "end": 52 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 53, + "end": 53 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 54, + "end": 54 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 55, + "end": 55 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 56, + "end": 56 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 57, + "end": 57 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 58, + "end": 58 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 59, + "end": 59 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 60, + "end": 60 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 61, + "end": 61 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 68, + "end": 68 + } + ], + "src.macaron.resources.pypi_malware_rules.inline-imports": [ + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 49, + "end": 49 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 50, + "end": 50 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 51, + "end": 51 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 52, + "end": 52 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 53, + "end": 53 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 54, + "end": 54 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 55, + "end": 55 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 56, + "end": 56 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 57, + "end": 57 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 58, + "end": 58 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 59, + "end": 59 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 60, + "end": 60 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/default_assigning.py", + "start": 61, + "end": 61 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 69, + "end": 69 + } + ], + "src.macaron.resources.pypi_malware_rules.obfuscation-tools": [ + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 23, + "end": 23 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 25, + "end": 31 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 26, + "end": 26 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 27, + "end": 27 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 28, + "end": 28 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 30, + "end": 31 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 33, + "end": 33 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 37, + "end": 37 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 39, + "end": 45 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 40, + "end": 40 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 41, + "end": 41 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 42, + "end": 42 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 44, + "end": 45 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 47, + "end": 47 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 51, + "end": 51 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 53, + "end": 59 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 54, + "end": 54 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 55, + "end": 55 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 56, + "end": 56 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 58, + "end": 59 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 61, + "end": 61 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 65, + "end": 65 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 68, + "end": 68 + }, + { + "file": "/home/carl_flottmann/macaron/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py", + "start": 68, + "end": 68 + } + ] +} diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py new file mode 100644 index 000000000..270f88600 --- /dev/null +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/tools.py @@ -0,0 +1,69 @@ +# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. + +""" +Running this code will not produce any malicious behavior, but code isolation measures are +in place for safety. +""" + +import sys + +# ensure no symbols are exported so this code cannot accidentally be used +__all__ = [] +sys.exit() + +def test_function(): + """ + All code to be tested will be defined inside this function, so it is all local to it. This is + to isolate the code to be tested, as it exists to replicate the patterns present in malware + samples. + """ + sys.exit() + # using pyobfuscate.com/rename-obf to rename items, code is a class that has one method that prints Hello world! + lllllllllllllll, llllllllllllllI = __name__, print + + class lIIlIIIIIIIlIlllIl: + IIlIllIIlllIlIlIll = 'Hello' + IlIIlIIIlIllIIlIIl = 'world' + IIlIlIlIIIIlIIlIlI = '!' + + def IIlIlIIIIlIlIlIIll(IIIlIlIIllllIlIlll): + llllllllllllllI(f'{IIIlIlIIllllIlIlll.IIlIllIIlllIlIlIll} {IIIlIlIIllllIlIlll.IlIIlIIIlIllIIlIIl}{IIIlIlIIllllIlIlll.IIlIlIlIIIIlIIlIlI}') + if lllllllllllllll == '__main__': + llIlIIIllIIIIlIlll = lIIlIIIIIIIlIlllIl() + llIlIIIllIIIIlIlll.IIlIlIIIIlIlIlIIll() + + # using using pyob.oxyry.com's naming convention + __O0O00O00O0OOOOO0O, __OO00000OOOO000OO0 = __name__, print + + class OO0OO0OOO0OOOO000: + OO000OOOOO00O0OOO = 'Hello' + OOO0O00O00000O0O0 = 'world' + OOOOO0O000O0O000O = '!' + + def OOOOOO000OOO0O0O0(O00O00O0O00O000O0): + __OO00000OOOO000OO0(f'{O00O00O0O00O000O0.OO000OOOOO00O0OOO} {O00O00O0O00O000O0.OOO0O00O00000O0O0}{O00O00O0O00O000O0.OOOOO0O000O0O000O}') + if __O0O00O00O0OOOOO0O == '__main__': + __OO00000O00OOOO0OO = OO0OO0OOO0OOOO000() + __OO00000O00OOOO0OO.OOOOOO000OOO0O0O0() + + # using pyarmor's RTF mode naming convention + pyarmor__12, pyarmor__14 = __name__, print + + class pyarmor__16: + pyarmor__18 = 'Hello' + pyarmor__0 = 'world' + pyarmor__8 = '!' + + def pyarmor__24(pyarmor__60): + pyarmor__14(f'{pyarmor__60.pyarmor__18} {pyarmor__60.pyarmor__0}{pyarmor__60.pyarmor__8}') + if pyarmor__12 == '__main__': + pyarmor__2 = pyarmor__16() + pyarmor__2.pyarmor__24() + + # inline pyarmor marker + # pyarmor: print('this script is obfuscated') + + # obfuscated using pyobfuscate.com/pyd's AES 256-bit encryption + pyobfuscate=(lambda getattr:[((lambda IIlII,IlIIl:setattr(__builtins__,IIlII,IlIIl))(IIlII,IlIIl)) for IIlII,IlIIl in getattr.items()]);Il=chr(114)+chr(101);lI=r'[^a-zA-Z0-9]';lIl=chr(115)+chr(117)+chr(98);lllllllllllllll, llllllllllllllI, lllllllllllllIl,lllllllllIIllIIlI = __import__, getattr, bytes,exec + __import__("sys").setrecursionlimit(100000000);lllllllllIIllIIlI(llllllllllllllI(lllllllllllllll(lllllllllllllIl.fromhex('7a6c6962').decode()), lllllllllllllIl.fromhex('6465636f6d7072657373').decode())(lllllllllllllIl.fromhex('789ced1ded6edb38f2557cbf22b559c1f737455e615f20300437717b069cb8485cec2e0ef7ee2759964491f3c90f597224140b9543cef70c6748b95b96f5f378d8be7e7fd9aeca87d33fbf76d99732ff56aecbc78fd3fbb7f2f23cbeec9f4fd5683775fd50ae8bb27c3ebeeccab2783e96dbf79fcfc7df6fa7f392c73f8f6fbbea6dfd58ae1b1c8f0d9eeca9c29ce5f7d59f167556c1d65983f7a15a5243fa918a52f5f79ada79c500500fe69b8ad9f2f169736195a2033fa648e7a7306617c822674276561db928cbeba752efba67ca62c2e400a67759e960e86d58affeb9eb665a04feb3fbfb1e18ae6c7d6f68a25179cf8b561bcc70d928e3a9056ebe7ae2ef703d652dfccb97f6ed6bb7240f2090197caa191d7006b15671e6ab5b9bb50e79ee25ec10599e354ee07a6af300feaa8e1867fec0271eda974eae40233276c89b5411413015d9388ec9a2e9c32383228566323767d253c1e8cbfd3c522fe5a89660fc772ccb926c991c6db0582eeddde36dfbdaef1f6d40fc381cb72710433be3e7e1f87d7bf870e7b413f6a7dd3bca439c6dd8021ad2fb99231c438b854d3fc364dbeaec690369ccdea9d78f55ddb38675db627a3ebefe3af4bbffd0e4cf87edc7c7d9e67d02fcb13f746ed08efef77ff736de5fc7bfee873c3dfcdc9db6a7d33b659baccab74f04bc99044ec80671e66d9750349874839853236dcbbcd8bc4517364c4c185df20cdd533bd3eb6a8ca924ff44ae5de510a7921bc6ffcbfeddee18cc59565fd2da6988b39d7b7c7fb9a71004158a4d13756efabc115ac054dd43a4d225298f00aff556a443953d6d545e9b653e55a4b85cedc73c0acfe00ddf4010905ece1a0d4c87bdc3085eaea0a98858b498e279a0bc89d23be320aed2679036a7b6676818a58caedbcc53b7c218cd2e141261778be27eceb241c4de20ec72823fbf6cb08c76fa103d18977c776bf94e7166e6229abb6e3a708a236d1feee225c31ad31f1d78035c7798874c59776e32c9d41b642b1c57b08dae6d645141c3edfbf076e40ea5e93de43919cc210af472e1f4fb2b91dfa0dd0fe0ba2ade18b2c16503c12c5660286f37e4b21a2b914b5c23374108dcf2d7a6dcc910a837777158dd14eb56246ceb362b1c7d5994e7c0317dbda1640643598e74d836c717dfb79224e638eb0e47a58cfd0f22bd3199af6eb2d61bc291ea63b6dde163677077666b9d886c470f02d5c01fc7f7b302f66f75f3919b370e5cd3c1160b0f46b510ff50c5f00977b139c3f856a5bffe299b3c70ae51065aa995d168a5b9a8af15d3b17ef6963cdf40df74148d104543d272caccf0315349608aab8611a7a7531f84ed2c7cd7554eae9dece619e12f6612e0b1c7276d2e908dd6b98ee3b8e968d0740d813d3e56b248b92fcc8e0cdf55f8b943fba22808e07b0d963a8094f345bd079a7b31f2c10acd1d5662912a91577ec3fb628eab0e5782161bff1081e38ad395108c38577d0c9aec1034ddf9eae3652caf37f084fcdb4f5a79c86c12ebb4519979049c091923f3879d490484bba521d9449d88635a0a103e4641e0a1b9880d89f169d7f5124bd8698a3eb12b542ededb0066f9649762bf10947d91e48f9f9c1869838c91bb5f31a6bb4fe424ed188c5d3c71843df21dad58a7a8948b0e08c416bdfa3531a350dc86c99aafa4bbf5c5e327e0e3ce8b321b246d8a74fe116fe7153a008d589c30f515c1d56ae7160cb5fec12d6fc43a35042cda84476ebbc58a715fc2ba5acf5d83c87983275d599b62b3c53a8564dd8cead39708a9d20547a8e9e36da481d93376334c6aab5bdbbafd355d9de4cfa78232a40bed92c33dcc233b46389c8f2e7460ef40a25166e694c78063ec65fefb7a4f2aec0aca5dece9a9aeec4adff1898ea8d54bac33b4de49af7a18d6c1639d09191286e5b274d5accb3551080579b9e025fd711576206c97af57dcd23bee8917d63126d7e13a1de10855751093ba3dc57d11745f89cbbba1575ff152ae9b203e4c135e4b8ce74a148fe4a5479ae336774d408f1fa7750cb8f6080c54f89f4998d0415ae86d162fcdf0f7bdd1bba69e57a53dbd0aec906fbcd2b520ad9fcda142f5edf6253c744812ddce881928553575ea8226ecaa5d73a5dbe7bb6bba62c7a4e645549886e849b427a708338f1d3bced6a966355483e0763bc5db4e9fb649204bacab2aed098a4b3566de1dedf4cc7f73d7ab0af4d5ebb96afab396511d324eea897cbc19a28098a72a486c26687426518f462d0113da3d6e4e0c3d115688ea187fb2f7386a170fb6b977b773c5eec1f74220fcdc3530a84d47bcfe114bb22633c6cdcb68ff6496f6a346da4d94aa8cd9faf67ab0f2dde4be919717bceee2d0ebe20e4fd4afeac38af44437d2f8575971afd1340df0668493c0321dfa712e85da9fed151740f3dfafdddfcda74021672001c2715ea0b70074408cbe4020ae460782a337392194afa0c54150b5c0f361d695540124d61033a3b0a0f6df07b3796a281663ba0b221d4aeb3d2acd721251e62078ee40851b0dc600171c61b1414ac9d181514ecdb7009935c9a9a4b332cc00c2a8d0818924818f1b1a45782d7a8773c881fe86e862305799300937c391396cd0080b4b1a96c72105c239006a85b354ca2a91e630230bb91aaa9f94873437c60b665e3a91810b7bf258ead56c8ba486344e8213004d66a37627b94b6da2b83b78953300c8e552538ab48b2016c9fc2ee1159509cf78ea3d51babd312a77025e610b63d0cda45205b8590411570b506a8ddcf8a9bc8b8258a589799339ea27f03ab555b59afbb47e0c5b5cd3c675624853b6af401c046242ca326137dec524ce230e2c5554201cd8eb901211a218da0542a0ae301304180a7415684de3538f5d1be1b3157641b55798a491ac07d45ceaadc72e93f5540dcc1c75649ccd17d59ff4dfc7253fdd64c2ec4680449e411049f72f18a2c878f82252589c840630857ce362e1e5472d042f91548710b5a1f023d509ca3cc5381f69239cb2b30ccd3bb8290004c272545e6b24109d630ede16780713217297f031e9d57e58831aebb876651792de73b56681095d4a5f62b640fb83c171e30eb7f4bbacc45efd2e44455cbbceb56ca5d04daef8bcb5b29550fe721f3f84ba296c18843203c019bdf4dad6fcea51a16659ed68c4a0b9e280d73f6a83add64fb5087ae150734a7160af1b8847535c8ed84a4d40077a168d786e85a13528090b1850481682becc00d29771da28c1e6cb8d898d7d02875b3a1156629f4e04a3ae3b48ef9e59b52644fcde101088a409b42376e4f99454444de8557ff1992b415501a7db525a6811e37245d2a27060dfaf1387bc8afd0b654864735c75586af6def5681cd24053188291c672cff4bfd31be5275ae3fdc3d2cbad6e14e054f723922c9b0974e53704b8b1cd080b8ee55657eb9bce3234efe0a6001078ef5db0932cb7ba43c6956967686fb175a8e24619981440a9b5e553668ee004bc6e39506125f6f3e3a0bbdd1997af1dcee9d49a4b093be471296135de8e05c852c2e2e0b0682a4897e5141016648848323a30ca19c60369a758d9c3f3330e944d6c5eb4b46cc700a8b814499bdaa597df95a6288bad41690a820d37cb2256b53561beaccea1ced827f0b5a5056325f6fd35a9b67ae3b4aeae5d18edc0c30002a1f524f9be194d51de79fdd88ffb91df7014bf33f1206d52e0bc091d66dca8703f9018ed0a6c6c62e3d01a557db33e87f1eac3e60574403e197c39839942cf49c98805c77206a3148a64de60c4c18ea627dc62132f6308aaea109c5fd3610d6aac73332d0213e194bec46c81f60783e3c61d6ee9725989f52e0cdadaf3a3edf333bf729708e21b0202e134ed3a57518088139635c427aa8914b5a8360125915cca4912496c50e530b3200aaa9c4f2132d6d13c595cc7081f10f9f273c51baf41ac410bae5040215948c6d7d58a5e93073fb589b903ddc019fb047eb7d4beacc4be9e0ce3d494c0a9cf93675452d306ba1120109d4b294dcfb0d1ba6486a1255301cc6c495b56b129905c4a4802835c02c518027911266cb1ab899853e8a09f2a65034c41038a4bf9ae8a11b9bd4817ba8d32ca1ab4e00a05149285a02f3380f4458f364ab0f9726362639fc0e196ba9d95d8bb030dfb7dcc8c8a6a1584c03827201049d3aeb199a914090dc01ae2d356829202ceb5b4fccafcb27c96a1148a64de6004dc4a347b278c40b80309b6b6cba8bf8628aaea109c5fc5610d6aac7363f50105e4d426e60e740330466edcef964a979538c493434a5de39955d50b7b7a8ffe1680401c4dbece35ed02da19331c44c2c53df98296d4945ff9280c6f563b4b11040d5a7085020ac94234d71180a508e251cfcfef962288957802455039b33ac85934a1fae55395458431fd0e6bc55512311f665d4995c987fc0c3b67b9796d18944b61c632ac77789c3f0a0231111a0fd07a30e804bb9f85d05a0572cae150ca497360afeb20a07a0614976f195a3712da456a2f654ccfb1d6b406256101030ac94234d40940faca501b25d87cb931b1b14fe0704b73c34a1cf02d030813b637c9bf142eddc68667d3e6550240357e4f7120303f1a53f7946c986788afa79befc329dd610c77e4318705c7d37edbd23c293f774fdd975fd65776697da55e5034aff70eb05ebc19edff0cd1139aa01aa0516615bc081c554ddda4fd178eaff5cbe63846011d763eec47f4a9012a582957d589cbd20c4d04e9758662a4c95ea0d325d70e12ffa9d3e5a7b33a8fcab2c448fbd56d1b62a8d311549a94c46ddb8a098a5b11de4eb8e93ca6f9f3ad22f194d5c0a29dd6b2d80b5a61e966d870688ef10ca7131311d4007e741a8e9f5802ad32a7a32c3b3869e1382686cb077349c418566a11cec979d5e65b76f1fa5576795b3d1f5f760fabdddffb5356bfe679f6ef7cb5ffb17a3b9e56cfdbc361fbfdb0cb7eee4edbd3e9bd6aeebfffde1f4efbb78fcabb5777cfc7d75ffbc3eeee7ef5e7f1ad5aba3abeafa0a9c56562b5a42c5f8f2fbf0fbbb2ac56dddde5ab7f3daeeeda997722046fdbd7e1f2968dd5eef0b1bbb092d50130088d6fad22eabf19efe0434ea834f994b5f02f5fdab7afdd927cf3d58b408db81df644919c4780d74d6ece0cd7ed43c777fb12c0abc9a7164b5695c9feab5d3e1883346aecb33a90e16393fc647e9aff1f99b5fed2'.replace("\n" , ""))).decode()) diff --git a/tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py b/tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py new file mode 100644 index 000000000..2e30b1e33 --- /dev/null +++ b/tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py @@ -0,0 +1,54 @@ +# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. + +"""Tests for experimental feature detecting malicious patterns in PyPI package sourcecode.""" +import json +import os +from unittest.mock import MagicMock + +import pytest + +import macaron +from macaron.errors import ConfigurationError, HeuristicAnalyzerValueError +from macaron.malware_analyzer.pypi_heuristics.heuristics import HeuristicResult +from macaron.malware_analyzer.pypi_heuristics.sourcecode.pypi_sourcecode_analyzer import PyPISourcecodeAnalyzer + + +@pytest.mark.skip(reason="experimental feature") +def test_no_resources() -> None: + """Test for when the semgrep rules can't be found, so error.""" + with pytest.raises(ConfigurationError): + _ = PyPISourcecodeAnalyzer(resources_path="") + + +@pytest.mark.skip(reason="experimental feature") +def test_no_sourcecode(pypi_package_json: MagicMock) -> None: + """Test for when there is no source code available, so error.""" + analyzer = PyPISourcecodeAnalyzer(resources_path=os.path.join(os.path.dirname(macaron.__file__), "resources")) + + pypi_package_json.package_sourcecode_path = "" + + with pytest.raises(HeuristicAnalyzerValueError): + analyzer.analyze(pypi_package_json) + + +@pytest.mark.skip(reason="experimental feature") +def test_obfuscation_rules(pypi_package_json: MagicMock) -> None: + """Test the semgrep rules for obfuscation on code samples.""" + sample_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "resources", "sourcecode_samples", "obfuscation" + ) + + with open(os.path.join(sample_path, "expected_results.json"), encoding="utf-8") as file: + expected_results = json.loads(file.read()) + __import__("pprint").pprint(expected_results) + + analyzer = PyPISourcecodeAnalyzer(resources_path=os.path.join(os.path.dirname(macaron.__file__), "resources")) + + pypi_package_json.package_sourcecode_path = sample_path + analyzer.default_rule_path = os.path.join(analyzer.default_rule_path, "obfuscation.yaml") + + result, analysis = analyzer.analyze(pypi_package_json) + + assert result == HeuristicResult.FAIL + assert expected_results == analysis