From 943da71ca1b70ccbb0a9823a073f9838ebd0ccee Mon Sep 17 00:00:00 2001 From: Carl Flottmann Date: Mon, 9 Mar 2026 09:51:54 +1000 Subject: [PATCH 1/2] chore: update os inline import to just look for process spawning Signed-off-by: Carl Flottmann --- .../pypi_malware_rules/obfuscation.yaml | 23 +++++++++++++++++-- .../obfuscation/excessive_spacing.py | 4 ++-- .../obfuscation/inline_imports.py | 4 ++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/macaron/resources/pypi_malware_rules/obfuscation.yaml b/src/macaron/resources/pypi_malware_rules/obfuscation.yaml index 81b2f08f8..263dccbbe 100644 --- a/src/macaron/resources/pypi_malware_rules/obfuscation.yaml +++ b/src/macaron/resources/pypi_malware_rules/obfuscation.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. rules: @@ -14,7 +14,26 @@ rules: - pattern: __import__('builtins') - pattern: __import__('subprocess') - pattern: __import__('sys') - - pattern: __import__('os') + - pattern: __import__('os').execl(...) + - pattern: __import__('os').execle(...) + - pattern: __import__('os').execlp(...) + - pattern: __import__('os').execlpe(...) + - pattern: __import__('os').execv(...) + - pattern: __import__('os').execve(...) + - pattern: __import__('os').execvp(...) + - pattern: __import__('os').execvpe(...) + - pattern: __import__('os').popen(...) + - pattern: __import__('os').posix_spawn(...) + - pattern: __import__('os').posix_spawnp(...) + - pattern: __import__('os').spawnl(...) + - pattern: __import__('os').spawnle(...) + - pattern: __import__('os').spawnlp(...) + - pattern: __import__('os').spawnlpe(...) + - pattern: __import__('os').spawnv(...) + - pattern: __import__('os').spawnve(...) + - pattern: __import__('os').spawnvp(...) + - pattern: __import__('os').spawnvpe(...) + - pattern: __import__('os').system(...) - pattern: __import__('zlib') - pattern: __import__('marshal') # python will evaluate a hex/oct string diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/excessive_spacing.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/excessive_spacing.py index 4f9a77616..d1eadfa1b 100644 --- a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/excessive_spacing.py +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/excessive_spacing.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """ @@ -21,6 +21,6 @@ def test_function(): sys.exit() # excessive spacing obfuscation. The second line here will trigger two detections, which is expected since it matches both patterns. - print("hello"); __import__('os') + print("hello"); __import__('sys') print("hi") ; __import__('base64') print("things") ;__import__('zlib') diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py index 4e37c7c02..38d695b48 100644 --- a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """ @@ -24,7 +24,7 @@ def test_function(): __import__('builtins') __import__('subprocess') __import__('sys') - print("Hello world!") ;__import__('os') + print("Hello world!") ;__import__('sys') __import__('zlib') __import__('marshal') # these both just import builtins From bf7eb61333f4efb815667c760f55ba8f6f99a1f2 Mon Sep 17 00:00:00 2001 From: Carl Flottmann Date: Mon, 9 Mar 2026 13:10:47 +1000 Subject: [PATCH 2/2] chore: updated inline imports to be more specific for os, sys, and builtins Signed-off-by: Carl Flottmann --- .../pypi_malware_rules/obfuscation.yaml | 57 +++++++++++++++- .../obfuscation/expected_results.json | 65 +++++++++++++------ .../obfuscation/inline_imports.py | 15 ++++- 3 files changed, 114 insertions(+), 23 deletions(-) diff --git a/src/macaron/resources/pypi_malware_rules/obfuscation.yaml b/src/macaron/resources/pypi_malware_rules/obfuscation.yaml index 263dccbbe..c8283fafa 100644 --- a/src/macaron/resources/pypi_malware_rules/obfuscation.yaml +++ b/src/macaron/resources/pypi_malware_rules/obfuscation.yaml @@ -11,9 +11,28 @@ rules: severity: ERROR pattern-either: - pattern: __import__('base64') - - pattern: __import__('builtins') - pattern: __import__('subprocess') - - pattern: __import__('sys') + # process execution obfuscation using inline builtins import + - pattern: __import__('builtins').eval(...) + - pattern: __import__('builtins').exec(...) + # writing to a file obfuscation using inline builtins import + - patterns: + - pattern: __import__('builtins').open(..., $MODE, ...) + - pattern-not: __import__('builtins').open(..., 'r', ...) + - pattern-not: __import__('builtins').open(..., 'rb', ...) + - pattern-not: __import__('builtins').open(..., 'rt', ...) + - pattern-not: __import__('builtins').open(..., 'br', ...) + - pattern-not: __import__('builtins').open(..., 'tr', ...) + - patterns: + - pattern: __import__('builtins').open(..., mode=$MODE, ...) + - pattern-not: __import__('builtins').open(..., mode='r', ...) + - pattern-not: __import__('builtins').open(..., mode='rb', ...) + - pattern-not: __import__('builtins').open(..., mode='rt', ...) + - pattern-not: __import__('builtins').open(..., mode='br', ...) + - pattern-not: __import__('builtins').open(..., mode='tr', ...) + - pattern: __import__('sys').setrecursionlimit(...) + - pattern: __import__('sys').remote_exec(...) + # process execution obfuscation using inline os import - pattern: __import__('os').execl(...) - pattern: __import__('os').execle(...) - pattern: __import__('os').execlp(...) @@ -34,6 +53,40 @@ rules: - pattern: __import__('os').spawnvp(...) - pattern: __import__('os').spawnvpe(...) - pattern: __import__('os').system(...) + # environmen modification obfuscation using inline import + - pattern: __import__('os').putenv(...) + - pattern: __import__('os').unsetenv(...) + - pattern: __import__('os').environ[...] = ... + - pattern: __import__('os').environb[...] = ... + - pattern: del __import__('os').environ[...] + - pattern: del __import__('os').environb[...] + - pattern: __import__('os').environ.update(...) + - pattern: __import__('os').environb.update(...) + - pattern: __import__('os').environ.pop(...) + - pattern: __import__('os').environb.pop(...) + - pattern: __import__('os').environ.clear() + - pattern: __import__('os').environb.clear() + # writing to a file obfuscation using inline os import + - pattern: __import__('os').write(...) + - patterns: + - pattern: __import__('os').fdopen(..., $MODE, ...) + - pattern-not: __import__('os').fdopen(..., 'r', ...) + - pattern-not: __import__('os').fdopen(..., 'rb', ...) + - pattern-not: __import__('os').fdopen(..., 'rt', ...) + - pattern-not: __import__('os').fdopen(..., 'br', ...) + - pattern-not: __import__('os').fdopen(..., 'tr', ...) + - patterns: + - pattern: __import__('os').fdopen(..., mode=$MODE, ...) + - pattern-not: __import__('os').fdopen(..., mode='r', ...) + - pattern-not: __import__('os').fdopen(..., mode='rb', ...) + - pattern-not: __import__('os').fdopen(..., mode='rt', ...) + - pattern-not: __import__('os').fdopen(..., mode='br', ...) + - pattern-not: __import__('os').fdopen(..., mode='tr', ...) + - patterns: + - pattern: __import__('os').open(..., $FLAGS, ...) + - metavariable-regex: + metavariable: $FLAGS + regex: .*O_(WRONLY|RDWR|APPEND|CREAT|TRUNC).* - pattern: __import__('zlib') - pattern: __import__('marshal') # python will evaluate a hex/oct string 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 index 5fb7c3965..4d49c84b2 100644 --- a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/expected_results.json +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/expected_results.json @@ -43,11 +43,6 @@ "start": 53, "end": 53 }, - { - "file": "obfuscation/excessive_spacing.py", - "start": 24, - "end": 24 - }, { "file": "obfuscation/excessive_spacing.py", "start": 25, @@ -63,11 +58,6 @@ "start": 23, "end": 23 }, - { - "file": "obfuscation/inline_imports.py", - "start": 24, - "end": 24 - }, { "file": "obfuscation/inline_imports.py", "start": 25, @@ -85,23 +75,58 @@ }, { "file": "obfuscation/inline_imports.py", - "start": 28, - "end": 28 + "start": 30, + "end": 30 }, { "file": "obfuscation/inline_imports.py", - "start": 29, - "end": 29 + "start": 35, + "end": 35 }, { "file": "obfuscation/inline_imports.py", - "start": 31, - "end": 31 + "start": 36, + "end": 36 }, { "file": "obfuscation/inline_imports.py", - "start": 32, - "end": 32 + "start": 37, + "end": 37 + }, + { + "file": "obfuscation/inline_imports.py", + "start": 38, + "end": 38 + }, + { + "file": "obfuscation/inline_imports.py", + "start": 39, + "end": 39 + }, + { + "file": "obfuscation/inline_imports.py", + "start": 40, + "end": 40 + }, + { + "file": "obfuscation/inline_imports.py", + "start": 41, + "end": 41 + }, + { + "file": "obfuscation/inline_imports.py", + "start": 42, + "end": 42 + }, + { + "file": "obfuscation/inline_imports.py", + "start": 44, + "end": 44 + }, + { + "file": "obfuscation/inline_imports.py", + "start": 45, + "end": 45 }, { "file": "obfuscation/obfuscation_tools.py", @@ -135,8 +160,8 @@ }, { "file": "obfuscation/inline_imports.py", - "start": 27, - "end": 27 + "start": 32, + "end": 32 } ] }, diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py index 38d695b48..73e8ac30b 100644 --- a/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/obfuscation/inline_imports.py @@ -21,10 +21,23 @@ def test_function(): sys.exit() __import__('base64') - __import__('builtins') + __import__('builtins') # should not be detected + __import__('builtins').eval("print('hello')") + __import__('builtins').exec("print('hello')") + __import__('builtins').open("written.txt", "w") + __import__('builtins').open("README.md", "r") # should not be detected + _ = open("README.md").read() if __import__("os").path.exists("README.md") else "" # should not be detected __import__('subprocess') __import__('sys') print("Hello world!") ;__import__('sys') + __import__('os').getcwd() # should not be detected + __import__('os').path.join("docs", "README.md") # should not be detected + __import__('os').putenv("CRITICAL_ENV", "1") + __import__('os').environ["CRITICAL_ENV"] = "1" + del __import__('os').environ["CRITICAL_ENV"] + __import__('os').open("written.txt", __import__('os').O_WRONLY | __import__('os').O_CREAT) + __import__('os').write(1, b"hello") + __import__('os').fdopen(1, "w") __import__('zlib') __import__('marshal') # these both just import builtins