From fd9d2d062e1a7809e5d71b5fa5378cde6a2366de Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 8 Sep 2024 15:40:29 +0200 Subject: [PATCH 01/65] Update callback.py --- modules/reporting/callback.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/reporting/callback.py b/modules/reporting/callback.py index 97eb8a5a4ef..7dd1bf30ea4 100644 --- a/modules/reporting/callback.py +++ b/modules/reporting/callback.py @@ -20,9 +20,8 @@ def run(self, results): task_id = int(results.get("info", {}).get("id")) """Handles a possible race condition where the status is not updated before the callback is consumed.""" # set completed_on time - main_db.set_status(task_id, TASK_COMPLETED) - # set reported time - main_db.set_status(task_id, TASK_REPORTED) + with Database().session.begin(): + Database().set_status(task_id, TASK_REPORTED) for url in urls: try: for value in (task_id, str(task_id)): From 134469960c4ce9cb81543aa17606805b49131b25 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 8 Sep 2024 15:49:21 +0200 Subject: [PATCH 02/65] Update callback.py --- modules/reporting/callback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/reporting/callback.py b/modules/reporting/callback.py index 7dd1bf30ea4..40fcfaedca5 100644 --- a/modules/reporting/callback.py +++ b/modules/reporting/callback.py @@ -4,7 +4,7 @@ import requests from lib.cuckoo.common.abstracts import Report -from lib.cuckoo.core.database import TASK_COMPLETED, TASK_REPORTED, Database +from lib.cuckoo.core.database import TASK_REPORTED, Database log = logging.getLogger(__name__) main_db = Database() From 6cebe4f10d40c64ff496d540f3290168857ab887 Mon Sep 17 00:00:00 2001 From: "Robin Koumis (SecureWorks)" Date: Tue, 10 Sep 2024 04:09:32 -0400 Subject: [PATCH 03/65] Package summary/description in UI (#2313) - Following on from #2220 now we display the package summary and description in the UI - In views.py: - Added code to parse the python analysis package modules, pulling out summary and description - Updated `get_form_data()` to return list of dicts, not list of strings - When sorting the package names, no longer be case sensitive - Updated the `submission/index.html` template to expect a dict per package - Added some tests - Tweak to `web/settings.py` so it can find the templates, even during test execution --- analyzer/windows/lib/common/constants.py | 9 + .../windows/modules/packages/Unpacker_dll.py | 11 +- analyzer/windows/modules/packages/dll.py | 10 +- analyzer/windows/modules/packages/exe.py | 3 +- analyzer/windows/modules/packages/service.py | 4 +- .../windows/modules/packages/service_dll.py | 4 +- tests/web/test_submission_views.py | 245 ++++++++++++++++++ web/submission/views.py | 166 +++++++++++- web/templates/submission/index.html | 15 +- web/web/settings.py | 4 +- 10 files changed, 435 insertions(+), 36 deletions(-) create mode 100644 tests/web/test_submission_views.py diff --git a/analyzer/windows/lib/common/constants.py b/analyzer/windows/lib/common/constants.py index 3aee3c083d2..e18566c587b 100644 --- a/analyzer/windows/lib/common/constants.py +++ b/analyzer/windows/lib/common/constants.py @@ -52,6 +52,7 @@ ARCHIVE_OPTIONS = (OPT_FILE, OPT_PASSWORD) DLL_OPTIONS = (OPT_ARGUMENTS, OPT_DLLLOADER, OPT_FUNCTION) +SERVICE_OPTIONS = (OPT_SERVICENAME, OPT_SERVICEDESC, OPT_ARGUMENTS) """ Excel, Word, and Powerpoint won't have macros enabled without interaction for @@ -65,3 +66,11 @@ TRUSTED_PATH_TEXT = ( f"Use MS Office Trusted Path location {MSOFFICE_TRUSTED_PATH} unless the user has provided a '{OPT_CURDIR}' option." ) + +DLL_OPTION_TEXT = f"""\ +Use the '{OPT_DLLLOADER}' option to set the name of the process loading the DLL (defaults to rundll32.exe). +Use the '{OPT_ARGUMENTS}' option to set the arguments to be passed to the exported function(s). +Use the '{OPT_FUNCTION}' option to set the name of the exported function/ordinal to execute. +The default function is '#1'. +Can be multiple function/ordinals split by colon. Ex: function=#1:#3 or #2-4 +""" diff --git a/analyzer/windows/modules/packages/Unpacker_dll.py b/analyzer/windows/modules/packages/Unpacker_dll.py index eaa870b3a7b..09e5a5ed16d 100644 --- a/analyzer/windows/modules/packages/Unpacker_dll.py +++ b/analyzer/windows/modules/packages/Unpacker_dll.py @@ -7,8 +7,15 @@ from lib.common.abstracts import Package from lib.common.common import check_file_extension -from lib.common.constants import OPT_ARGUMENTS, OPT_DLLLOADER, OPT_FUNCTION, OPT_INJECTION, OPT_UNPACKER -from modules.packages.dll import DLL_OPTION_TEXT, DLL_OPTIONS +from lib.common.constants import ( + DLL_OPTION_TEXT, + DLL_OPTIONS, + OPT_ARGUMENTS, + OPT_DLLLOADER, + OPT_FUNCTION, + OPT_INJECTION, + OPT_UNPACKER, +) class Unpacker_dll(Package): diff --git a/analyzer/windows/modules/packages/dll.py b/analyzer/windows/modules/packages/dll.py index 1b7aa59deb5..1c047bf2154 100644 --- a/analyzer/windows/modules/packages/dll.py +++ b/analyzer/windows/modules/packages/dll.py @@ -9,7 +9,7 @@ from lib.common.abstracts import Package from lib.common.common import check_file_extension -from lib.common.constants import DLL_OPTIONS, OPT_ARGUMENTS, OPT_DLLLOADER, OPT_FUNCTION +from lib.common.constants import DLL_OPTION_TEXT, DLL_OPTIONS, OPT_ARGUMENTS, OPT_DLLLOADER, OPT_FUNCTION log = logging.getLogger(__name__) @@ -18,14 +18,6 @@ _OPT_USE_EXPORT_NAME = "use_export_name" MAX_DLL_EXPORTS_DEFAULT = 8 -DLL_OPTION_TEXT = f""" -Use the '{OPT_DLLLOADER}' option to set the name of the process loading the DLL (defaults to rundll32.exe). -Use the '{OPT_ARGUMENTS}' option to set the arguments to be passed to the exported function(s). -Use the '{OPT_FUNCTION}' option to set the name of the exported function/ordinal to execute. -The default function is '#1'. -Can be multiple function/ordinals split by colon. Ex: function=#1:#3 or #2-4 -""" - class Dll(Package): """DLL analysis package.""" diff --git a/analyzer/windows/modules/packages/exe.py b/analyzer/windows/modules/packages/exe.py index 4a1e4c7741e..7f2acb07e1c 100644 --- a/analyzer/windows/modules/packages/exe.py +++ b/analyzer/windows/modules/packages/exe.py @@ -15,7 +15,8 @@ class Exe(Package): """EXE analysis package.""" summary = "Runs the supplied executable." - description = f"""Executes the given sample, passing '{OPT_ARGUMENTS}' if specified. + description = f"""\ + Executes the given sample, passing '{OPT_ARGUMENTS}' if specified. Use the '{OPT_APPDATA}' option to run the executable from the APPDATA directory. Use the '{OPT_RUNASX86}' option to set the 32BITREQUIRED flag in the PE header, using 'CorFlags.exe /32bit+'. diff --git a/analyzer/windows/modules/packages/service.py b/analyzer/windows/modules/packages/service.py index f3268c2ac52..d66fc089ab3 100644 --- a/analyzer/windows/modules/packages/service.py +++ b/analyzer/windows/modules/packages/service.py @@ -9,7 +9,7 @@ from lib.api.process import Process from lib.common.abstracts import Package from lib.common.common import check_file_extension -from lib.common.constants import OPT_ARGUMENTS, OPT_SERVICEDESC, OPT_SERVICENAME +from lib.common.constants import OPT_ARGUMENTS, OPT_SERVICEDESC, OPT_SERVICENAME, SERVICE_OPTIONS from lib.common.defines import ADVAPI32, KERNEL32 INJECT_CREATEREMOTETHREAD = 0 @@ -54,8 +54,6 @@ SERVICE_ERROR_IGNORE = 0x0000 log = logging.getLogger(__name__) -SERVICE_OPTIONS = (OPT_SERVICENAME, OPT_SERVICEDESC, OPT_ARGUMENTS) - class Service(Package): """Service analysis package.""" diff --git a/analyzer/windows/modules/packages/service_dll.py b/analyzer/windows/modules/packages/service_dll.py index cb4ae9ab2ea..0fdc32ea253 100644 --- a/analyzer/windows/modules/packages/service_dll.py +++ b/analyzer/windows/modules/packages/service_dll.py @@ -16,11 +16,9 @@ from lib.api.process import Process from lib.common.abstracts import Package from lib.common.common import check_file_extension, disable_wow64_redirection -from lib.common.constants import OPT_ARGUMENTS, OPT_SERVICEDESC, OPT_SERVICENAME +from lib.common.constants import OPT_ARGUMENTS, OPT_SERVICEDESC, OPT_SERVICENAME, SERVICE_OPTIONS from lib.common.defines import ADVAPI32, KERNEL32 -from .service import SERVICE_OPTIONS - SC_MANAGER_CONNECT = 0x0001 SC_MANAGER_CREATE_SERVICE = 0x0002 SC_MANAGER_ENUMERATE_SERVICE = 0x0004 diff --git a/tests/web/test_submission_views.py b/tests/web/test_submission_views.py new file mode 100644 index 00000000000..802104c8c26 --- /dev/null +++ b/tests/web/test_submission_views.py @@ -0,0 +1,245 @@ +import ast +import os +import re +import textwrap + +import pytest +from django.test import SimpleTestCase +from submission.views import ( + correlate_platform_packages, + get_form_data, + get_lib_common_constants, + get_package_info, + parse_ast, + web_conf, +) + + +@pytest.mark.usefixtures("db") +class TestSubmissionViews(SimpleTestCase): + maxDiff = None + + def setUp(self): + self.linux_enabled = web_conf.linux.enabled + self.excluded_packages = web_conf.package_exclusion.packages + self.linux_package_dir = os.path.join( + os.path.abspath(os.path.dirname(__file__)), "..", "..", "analyzer", "linux", "modules", "packages" + ) + self.windows_package_dir = os.path.join( + os.path.abspath(os.path.dirname(__file__)), "..", "..", "analyzer", "windows", "modules", "packages" + ) + web_conf.linux.enabled = True + + def tearDown(self): + web_conf.linux.enabled = self.linux_enabled + web_conf.package_exclusion.packages = self.excluded_packages + + def find_in_list(self, regex, list_of_strings): + """Test if the regex matches any string in the list.""" + pattern = re.compile(regex, flags=re.DOTALL) + for string in list_of_strings: + if pattern.match(string): + return True + return False + + def one_should_match(self, regex, list_of_strings): + """One of the strings should match.""" + found = self.find_in_list(regex, list_of_strings) + self.assertTrue(found, f"No string matched <{regex}>") + + def none_should_match(self, regex, list_of_strings): + """None of the strings should match.""" + found = self.find_in_list(regex, list_of_strings) + self.assertFalse(found, f"One or more strings matched <{regex}>") + + def test_submission_page(self): + """The submission page should have a package selection form. + + The form should have a list of at least 10 options. + """ + submission_page = self.client.get("/submit/#file") + self.assertIsNotNone(submission_page.content) + self.assertIn("Analysis Package", submission_page.content.decode()) + pattern = re.compile(r'select class="form-control" id="form_package" name="package">(.*?)', flags=re.DOTALL) + matches = re.findall(pattern, submission_page.content.decode()) + self.assertEqual(len(matches), 1) + group0 = matches[0].strip() + self.assertTrue(group0.startswith("