diff --git a/mvt/android/cli.py b/mvt/android/cli.py index a65dfcb..7556d55 100644 --- a/mvt/android/cli.py +++ b/mvt/android/cli.py @@ -10,7 +10,7 @@ import click from rich.logging import RichHandler from mvt.common.help import HELP_MSG_MODULE, HELP_MSG_IOC -from mvt.common.help import HELP_MSG_OUTPUT, HELP_MSG_LIST_MODULES +from mvt.common.help import HELP_MSG_FAST, HELP_MSG_OUTPUT, HELP_MSG_LIST_MODULES from mvt.common.help import HELP_MSG_SERIAL from mvt.common.indicators import Indicators, IndicatorsFileBadFormat from mvt.common.logo import logo @@ -107,10 +107,11 @@ def download_apks(ctx, all_apks, virustotal, koodous, all_checks, output, from_f default=[], help=HELP_MSG_IOC) @click.option("--output", "-o", type=click.Path(exists=False), help=HELP_MSG_OUTPUT) +@click.option("--fast", "-f", is_flag=True, help=HELP_MSG_FAST) @click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES) @click.option("--module", "-m", help=HELP_MSG_MODULE) @click.pass_context -def check_adb(ctx, iocs, output, list_modules, module, serial): +def check_adb(ctx, iocs, output, fast, list_modules, module, serial): if list_modules: log.info("Following is the list of available check-adb modules:") for adb_module in ADB_MODULES: @@ -142,7 +143,8 @@ def check_adb(ctx, iocs, output, list_modules, module, serial): if module and adb_module.__name__ != module: continue - m = adb_module(output_folder=output, log=logging.getLogger(adb_module.__module__)) + m = adb_module(output_folder=output, fast_mode=fast, + log=logging.getLogger(adb_module.__module__)) if serial: m.serial = serial diff --git a/mvt/android/modules/adb/files.py b/mvt/android/modules/adb/files.py index 521f349..926a0a0 100644 --- a/mvt/android/modules/adb/files.py +++ b/mvt/android/modules/adb/files.py @@ -5,6 +5,10 @@ import logging import os +import stat +import datetime + +from mvt.common.utils import check_for_links, convert_timestamp_to_iso from .base import AndroidExtraction @@ -12,23 +16,109 @@ log = logging.getLogger(__name__) class Files(AndroidExtraction): - """This module extracts the list of installed packages.""" + """This module extracts the list of files on the device.""" def __init__(self, file_path=None, base_folder=None, output_folder=None, serial=None, fast_mode=False, log=None, results=[]): super().__init__(file_path=file_path, base_folder=base_folder, output_folder=output_folder, fast_mode=fast_mode, log=log, results=results) + self.full_find = None + + def find_path(self, file_path): + """Checks if Android system supports full find command output""" + # Check find command params on first run + # Run find command with correct args and parse results. + + # Check that full file printf options are suppported on first run. + if self.full_find == None: + output = self._adb_command(f"find '/' -maxdepth 1 -printf '%T@ %m %s %u %g %p\n' 2> /dev/null") + if not (output or output.strip().splitlines()): + # Full find command failed to generate output, fallback to basic file arguments + self.full_find = False + else: + self.full_find = True + + found_files = [] + if self.full_find == True: + # Run full file command and collect additonal file information. + output = self._adb_command(f"find '{file_path}' -printf '%T@ %m %s %u %g %p\n' 2> /dev/null") + for file_line in output.splitlines(): + [unix_timestamp, mode, size, owner, group, full_path] = file_line.rstrip().split(" ", 5) + mod_time = convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(int(float(unix_timestamp)))) + found_files.append({ + "path": full_path, + "modified_time": mod_time, + "mode": mode, + "is_suid": (int(mode, 8) & stat.S_ISUID) == 2048, + "is_sgid": (int(mode, 8) & stat.S_ISGID) == 1024, + "size": size, + "owner": owner, + "group": group, + }) + else: + # Run a basic listing of file paths. + output = self._adb_command(f"find '{file_path}' 2> /dev/null") + for file_line in output.splitlines(): + found_files.append({ + "path": file_line.rstrip() + }) + + return found_files + + def serialize(self, record): + if "modified_time" in record: + return { + "timestamp": record["modified_time"], + "module": self.__class__.__name__, + "event": "file_modified", + "data": record["path"], + } + + def check_suspicious(self): + """Check for files with suspicious permissions""" + for result in sorted(self.results, key=lambda item: item["path"]): + if result.get("is_suid"): + self.log.warning("Found an SUID file in a non-standard directory \"%s\".", + result["path"]) + self.detected.append(result) + + def check_indicators(self): + """Check file list for known suspicious files or suspicious properties""" + self.check_suspicious() + if not self.indicators: + return + + for result in self.results: + if self.indicators.check_filename(result["path"]): + self.log.warning("Found a known suspicous filename at path: \"%s\"", result["path"]) + self.detected.append(result) + + if self.indicators.check_file_path(result["path"]): + self.log.warning("Found a known suspicous file at path: \"%s\"", result["path"]) + self.detected.append(result) def run(self): self._adb_connect() + found_file_paths = [] - output = self._adb_command("find / -type f 2> /dev/null") - if output and self.output_folder: - files_txt_path = os.path.join(self.output_folder, "files.txt") - with open(files_txt_path, "w") as handle: - handle.write(output) + DATA_PATHS = ["/data/local/tmp/", "/sdcard/", "/tmp/"] + for path in DATA_PATHS: + file_info = self.find_path(path) + found_file_paths.extend(file_info) - log.info("List of visible files stored at %s", files_txt_path) + # Store results + self.results.extend(found_file_paths) + self.log.info("Found %s files in primary Android data directories.", len(found_file_paths)) + + if self.fast_mode: + self.log.info("Flag --fast was enabled: skipping full file listing") + else: + self.log.info("Flag --fast was not enabled: processing full file listing. " + "This may take a while...") + output = self.find_path("/") + if output and self.output_folder: + self.results.extend(output) + log.info("List of visible files stored in files.json") self._adb_disconnect() diff --git a/mvt/common/indicators.py b/mvt/common/indicators.py index fd2a2f1..061056f 100644 --- a/mvt/common/indicators.py +++ b/mvt/common/indicators.py @@ -249,7 +249,7 @@ class Indicators: return False - def check_file(self, file_path) -> bool: + def check_filename(self, file_path) -> bool: """Check the provided file path against the list of file indicators. :param file_path: File path or file name to check against file @@ -264,11 +264,29 @@ class Indicators: file_name = os.path.basename(file_path) if file_name in self.ioc_files: - self.log.warning("Found a known suspicious file: \"%s\"", file_path) return True return False + def check_file_path(self, file_path) -> bool: + """Check the provided file path against the list of file indicators. + + :param file_path: File path or file name to check against file + indicators + :type file_path: str + :returns: True if the file path matched an indicator, otherwise False + :rtype: bool + + """ + if not file_path: + return False + + for ioc_file in self.ioc_files: + # Strip any trailing slash from indicator paths to match directories. + if file_path.startswith(ioc_file.rstrip("/")): + return True + return False + def check_profile(self, profile_uuid) -> bool: """Check the provided configuration profile UUID against the list of indicators. diff --git a/mvt/ios/modules/backup/manifest.py b/mvt/ios/modules/backup/manifest.py index 4865a66..2cce637 100644 --- a/mvt/ios/modules/backup/manifest.py +++ b/mvt/ios/modules/backup/manifest.py @@ -83,7 +83,7 @@ class Manifest(IOSExtraction): self.detected.append(result) continue - if self.indicators.check_file(result["relative_path"]): + if self.indicators.check_filename(result["relative_path"]): self.log.warning("Found a known malicious file at path: %s", result["relative_path"]) self.detected.append(result) continue diff --git a/mvt/ios/modules/fs/filesystem.py b/mvt/ios/modules/fs/filesystem.py index acfe89c..be89191 100644 --- a/mvt/ios/modules/fs/filesystem.py +++ b/mvt/ios/modules/fs/filesystem.py @@ -38,7 +38,11 @@ class Filesystem(IOSExtraction): for result in self.results: if self.indicators.check_file(result["path"]): - self.log.warning("Found a known malicious file at path: %s", result["path"]) + self.log.warning("Found a known malicious file name at path: %s", result["path"]) + self.detected.append(result) + + if self.indicators.check_file_path(result["path"]): + self.log.warning("Found a known malicious file path at path: %s", result["path"]) self.detected.append(result) # If we are instructed to run fast, we skip this.