From 16a77c6607ef112bc7f591af3d56c5956ae75563 Mon Sep 17 00:00:00 2001 From: cccs-mog <117194682+cccs-mog@users.noreply.github.com> Date: Wed, 26 Jun 2024 20:59:13 +0000 Subject: [PATCH] HH module --- analyzer/windows/modules/hollowshunter.py | 152 ++++++++++++++++++++++ modules/processing/hollowshunter.py | 41 ++++++ 2 files changed, 193 insertions(+) create mode 100644 analyzer/windows/modules/hollowshunter.py create mode 100644 modules/processing/hollowshunter.py diff --git a/analyzer/windows/modules/hollowshunter.py b/analyzer/windows/modules/hollowshunter.py new file mode 100644 index 00000000..764a42b0 --- /dev/null +++ b/analyzer/windows/modules/hollowshunter.py @@ -0,0 +1,152 @@ +import os +import json +import logging +import subprocess +import platform +import shlex + +from lib.common.abstracts import Auxiliary +from lib.core.config import Config +from lib.common.exceptions import CuckooPackageError +from lib.common.results import upload_to_host + +log = logging.getLogger(__name__) + + +__author__ = "[Canadian Centre for Cyber Security] @CybercentreCanada" + + +class HollowsHunter(Auxiliary): + """Hunting the Hollows""" + + def __init__(self, options, config): + Auxiliary.__init__(self, options, config) + self.config = Config(cfg="analysis.conf") + self.enabled = self.config.hollowshunter + self.do_run = self.enabled + self.output_dir = "C:\\\\hollowshunter" + self.startupinfo = subprocess.STARTUPINFO() + self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + def start(self): + # First figure out what architecture the system in running (64 or 86) + bin_path = os.path.join(os.getcwd(), "bin") + + if "AMD64" in platform.uname(): + hollowshunter = os.path.join(bin_path, "hh_etw.exe") + else: + hollowshunter = os.path.join(bin_path, "hh_etw_x86.exe") + + if not os.path.exists(hollowshunter): + raise CuckooPackageError( + "In order to use the HollowsHunter functionality, it " + "is required to have HollowsHunter setup with Cuckoo." + ) + hollowshunter = hollowshunter.replace("\\","\\\\") + hh_args = self.options.get("hh_args") + if not hh_args: + hh_args = "/loop /data 0" # Re-add /shellc + + hh_cmd = f"{hollowshunter} {hh_args} /dir {self.output_dir} /mignore capemon.dll;capemon_x64.dll" + hh_cmd = shlex.split(hh_cmd) + log.debug(hh_cmd) + # Start HollowsHunter in the background +# subprocess.Popen([hollowshunter, "/loop", "/imp", "/shellc", "/dir", self.output_dir], startupinfo=self.startupinfo) + subprocess.Popen(hh_cmd, startupinfo=self.startupinfo) + + def stop(self): + get_all_files = False + + if "hollowshunter" in self.options and self.options["hollowshunter"] == "all": + get_all_files = True + + # VirtualQuery and VirtualProtect may be FPs + strings_of_interest = [b"This program cannot be run in DOS mode.", b"VirtualFree",b"VirtualAlloc", b"LoadLibrary", + b"LocalFree", b"GetProcAddress", b"GetModuleHandle", b"AdjustTokenPrivileges", + b"CheckRemoteDebuggerPresent", b"CreateMutex", b"EnumProcesses", b"EnumProcessModules", + b"gethostname", b"IsNTAdmin", b"OpenMutex", b"RtlWriteRegistryValue", b"VirtualAllocEx", + b"VirtualProtectEx", b"WinExec"] + files_to_upload = set() + max_upload = 25 if not get_all_files else 100 + upload_count = 0 + scan_report_json = "scan_report.json" + + for d in os.listdir(self.output_dir): + if get_all_files: + path = os.path.join(self.output_dir, d) + if os.path.isfile(path): + files_to_upload.add(path) + continue + for f in os.listdir(path): + file_path = os.path.join(path, f) + files_to_upload.add(file_path) + continue + + # Find all files in folders that start with process_* + if "process_" not in d: + continue + dirpath = os.path.join(self.output_dir, d) + only_files = [f for f in os.listdir(dirpath)] + + modules_of_interest = set() + + # We first care about modules that contain PEs + if scan_report_json in only_files: + scan_report_path = os.path.join(dirpath, scan_report_json) + report_json = json.loads(open(scan_report_path, "rb").read()) + scans = report_json["scans"] + for scan in scans: + if "workingset_scan" in scan: + workingset_scan = scan["workingset_scan"] + has_pe = workingset_scan["has_pe"] + if has_pe: + module = workingset_scan["module"] + modules_of_interest.add(module) + # We definitely want this + files_to_upload.add(scan_report_path) + + # Here we will perform a string search for certain key terms in shc files + for f in only_files: + filename_suffix = f.split(".")[-1] + file_path = os.path.join(dirpath, f) + + # 100000% we want this + if filename_suffix in ["exe", "dll"]: + files_to_upload.add(file_path) + + # At this point we only care about shc + if filename_suffix != "shc": + continue + + # No dups! + if file_path in files_to_upload: + continue + + if any(module_of_interest in f for module_of_interest in modules_of_interest): + files_to_upload.add(file_path) + + log.debug(file_path) + try: + file_contents = open(file_path, "rb").read() + if any(item in file_contents for item in strings_of_interest): + # We got a hit! + files_to_upload.add(file_path) + except Exception as e: + log.debug(f"Could not read and look for strings of interest in {file_path} due to '{e}'") + continue + + # Upload the HollowsHunter files to the host. + log.debug(files_to_upload) + for f in files_to_upload: + if upload_count >= max_upload: + log.debug("HollowsHunter has uploaded the maximum number of files (%d)" % max_upload) + return + + # Prepend file name with hh to indicate HollowsHunter + file_path_list = f.split("\\") + file_name = file_path_list[-1] + process = file_path_list[-2] + dumppath = os.path.join("hollowshunter", "hh_" + process + "_" + file_name) + log.debug("HollowsHunter Aux Module is uploading %s" % f) + upload_count += 1 + upload_to_host(f, dumppath) diff --git a/modules/processing/hollowshunter.py b/modules/processing/hollowshunter.py new file mode 100644 index 00000000..30f5f4d4 --- /dev/null +++ b/modules/processing/hollowshunter.py @@ -0,0 +1,41 @@ +import logging +import os +import re +import json +from lib.cuckoo.common.abstracts import Processing +from lib.cuckoo.common.exceptions import CuckooProcessingError + +log = logging.getLogger(__name__) + + +class HollowsHunter(Processing): + def parse_report(self, data): + pid = data["pid"] + if not self.hh_response.get(pid): + self.hh_response[pid] = {} + for key in data.keys(): + if key == "pid": + continue + self.hh_response[pid][key] = data[key] + return self.hh_response + + def run(self): + self.key = "hollowshunter" + hh_report_regex = "hh_process_[0-9]{3,}_(dump|scan)_report\.json$" + report_pattern = re.compile(hh_report_regex) + hh_path = "%s/hollowshunter/" % self.analysis_path + if not os.path.exists(hh_path): + return {} + hh_items = os.listdir(hh_path) + self.hh_response = {} + report_list = list(filter(report_pattern.match, hh_items)) + for report in report_list: + report_path = os.path.join(hh_path, report) + try: + report_contents = open(report_path).read() + report_json = json.loads(report_contents) + except Exception as e: + raise CuckooProcessingError("Failed parsing report %s due to %s" % (report_path, str(e))) + self.parse_report(report_json) + return self.hh_response +