From 25c6c0307508e91e8bca319c0eef15744b640a75 Mon Sep 17 00:00:00 2001 From: Nex Date: Thu, 27 Jan 2022 12:50:37 +0100 Subject: [PATCH] Added Getprop module and cleaned Files and Packages Android modules --- mvt/android/modules/adb/__init__.py | 5 +- mvt/android/modules/adb/files.py | 56 +++++------------ mvt/android/modules/adb/getprop.py | 46 ++++++++++++++ mvt/android/modules/adb/packages.py | 24 +++---- .../adb/{rootbinaries.py => root_binaries.py} | 0 mvt/common/indicators.py | 62 ++++++++++++++----- 6 files changed, 124 insertions(+), 69 deletions(-) create mode 100644 mvt/android/modules/adb/getprop.py rename mvt/android/modules/adb/{rootbinaries.py => root_binaries.py} (100%) diff --git a/mvt/android/modules/adb/__init__.py b/mvt/android/modules/adb/__init__.py index eb583d7..e6f975b 100644 --- a/mvt/android/modules/adb/__init__.py +++ b/mvt/android/modules/adb/__init__.py @@ -13,12 +13,13 @@ from .dumpsys_receivers import DumpsysReceivers from .files import Files from .logcat import Logcat from .packages import Packages +from .getprop import Getprop from .processes import Processes -from .rootbinaries import RootBinaries +from .root_binaries import RootBinaries from .sms import SMS from .whatsapp import Whatsapp -ADB_MODULES = [ChromeHistory, SMS, Whatsapp, Processes, +ADB_MODULES = [ChromeHistory, SMS, Whatsapp, Processes, Getprop, DumpsysAccessibility, DumpsysBatterystats, DumpsysProcstats, DumpsysPackages, DumpsysReceivers, DumpsysFull, Packages, RootBinaries, Logcat, Files] diff --git a/mvt/android/modules/adb/files.py b/mvt/android/modules/adb/files.py index 63a5420..9891ee6 100644 --- a/mvt/android/modules/adb/files.py +++ b/mvt/android/modules/adb/files.py @@ -22,30 +22,16 @@ class Files(AndroidExtraction): 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 + self.full_find = False - 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 is None: - output = self._adb_command("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 is True: - # Run full file command and collect additonal file information. + def find_files(self, file_path): + if self.full_find: 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({ + self.results.append({ "path": full_path, "modified_time": mod_time, "mode": mode, @@ -56,14 +42,9 @@ class Files(AndroidExtraction): "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 + self.results.append({"path": file_line.rstrip()}) def serialize(self, record): if "modified_time" in record: @@ -85,6 +66,7 @@ class Files(AndroidExtraction): def check_indicators(self): """Check file list for known suspicious files or suspicious properties""" self.check_suspicious() + if not self.indicators: return @@ -95,25 +77,21 @@ class Files(AndroidExtraction): def run(self): self._adb_connect() - found_file_paths = [] - DATA_PATHS = ["/data/local/tmp/", "/sdcard/", "/tmp/"] - for path in DATA_PATHS: - file_info = self.find_path(path) - found_file_paths.extend(file_info) + output = self._adb_command("find '/' -maxdepth 1 -printf '%T@ %m %s %u %g %p\n' 2> /dev/null") + if output or output.strip().splitlines(): + self.full_find = True - # Store results - self.results.extend(found_file_paths) - self.log.info("Found %s files in primary Android data directories.", len(found_file_paths)) + for data_path in ["/data/local/tmp/", "/sdcard/", "/tmp/"]: + self.find_files(data_path) + + self.log.info("Found %s files in primary Android data directories", len(self.results)) 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.log.info("Processing full file listing. This may take a while...") + self.find_files("/") + self.log.info("Found %s total files", len(self.results)) self._adb_disconnect() diff --git a/mvt/android/modules/adb/getprop.py b/mvt/android/modules/adb/getprop.py new file mode 100644 index 0000000..449dbf7 --- /dev/null +++ b/mvt/android/modules/adb/getprop.py @@ -0,0 +1,46 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021 The MVT Project Authors. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ + +import re +import logging +import os + +from .base import AndroidExtraction + +log = logging.getLogger(__name__) + + +class Getprop(AndroidExtraction): + """This module extracts device properties from getprop command.""" + + 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.results = {} + + def run(self): + self._adb_connect() + + rxp = re.compile("\\[(.+?)\\]: \\[(.+?)\\]") + out = self._adb_command("getprop") + for line in out.split("\n"): + line = line.strip() + if line == "": + continue + + matches = re.findall(rxp, line) + if not matches or len(matches[0]) != 2: + continue + + key = matches[0][0] + value = matches[0][1] + self.results[key] = value + + self._adb_disconnect() + + self.log.info("Extracted %d Android system properties", len(self.results)) diff --git a/mvt/android/modules/adb/packages.py b/mvt/android/modules/adb/packages.py index 027000b..34e12f4 100644 --- a/mvt/android/modules/adb/packages.py +++ b/mvt/android/modules/adb/packages.py @@ -42,9 +42,6 @@ class Packages(AndroidExtraction): return records def check_indicators(self): - if not self.indicators: - return - root_packages_path = os.path.join("..", "..", "data", "root_packages.txt") root_packages_string = pkg_resources.resource_string(__name__, root_packages_path) root_packages = root_packages_string.decode("utf-8").split("\n") @@ -55,16 +52,19 @@ class Packages(AndroidExtraction): self.log.warning("Found an installed package related to rooting/jailbreaking: \"%s\"", result["package_name"]) self.detected.append(result) - if result["package_name"] in self.indicators.ioc_app_ids: - self.log.warning("Found a malicious package name: \"%s\"", - result["package_name"]) + continue + + ioc = self.indicators.check_app_id(result.get("package_name")) + if ioc: + result["matched_indicators"] = ioc self.detected.append(result) - for file in result["files"]: - if file["sha256"] in self.indicators.ioc_files_sha256: - self.log.warning("Found a malicious APK: \"%s\" %s", - result["package_name"], - file["sha256"]) - self.detected.append(result) + continue + + for package_file in result["files"]: + ioc = self.indicators.check_file_hash(package_file["sha256"]) + if ioc: + result["matched_indicators"] = ioc + self.detected.append(result) def _get_files_for_package(self, package_name): output = self._adb_command(f"pm path {package_name}") diff --git a/mvt/android/modules/adb/rootbinaries.py b/mvt/android/modules/adb/root_binaries.py similarity index 100% rename from mvt/android/modules/adb/rootbinaries.py rename to mvt/android/modules/adb/root_binaries.py diff --git a/mvt/common/indicators.py b/mvt/common/indicators.py index 4f0a79c..68f8db6 100644 --- a/mvt/common/indicators.py +++ b/mvt/common/indicators.py @@ -173,8 +173,7 @@ class Indicators: :param url: URL to match against domain indicators :type url: str - :returns: True if the URL matched an indicator, otherwise False - :rtype: bool + :returns: Indicator details if matched, otherwise None """ # TODO: If the IOC domain contains a subdomain, it is not currently @@ -246,8 +245,7 @@ class Indicators: :param urls: List of URLs to check against domain indicators :type urls: list - :returns: True if any URL matched an indicator, otherwise False - :rtype: bool + :returns: Indicator details if matched, otherwise None """ if not urls: @@ -264,8 +262,7 @@ class Indicators: :param process: Process name to check against process indicators :type process: str - :returns: True if process matched an indicator, otherwise False - :rtype: bool + :returns: Indicator details if matched, otherwise None """ if not process: @@ -290,8 +287,7 @@ class Indicators: :param processes: List of processes to check against process indicators :type processes: list - :returns: True if process matched an indicator, otherwise False - :rtype: bool + :returns: Indicator details if matched, otherwise None """ if not processes: @@ -307,8 +303,7 @@ class Indicators: :param email: Email address to check against email indicators :type email: str - :returns: True if email address matched an indicator, otherwise False - :rtype: bool + :returns: Indicator details if matched, otherwise None """ if not email: @@ -326,8 +321,7 @@ class Indicators: :param file_name: File name to check against file indicators :type file_name: str - :returns: True if the file name matched an indicator, otherwise False - :rtype: bool + :returns: Indicator details if matched, otherwise None """ if not file_name: @@ -345,8 +339,7 @@ class 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 + :returns: Indicator details if matched, otherwise None """ if not file_path: @@ -368,16 +361,53 @@ class Indicators: :param profile_uuid: Profile UUID to check against configuration profile indicators :type profile_uuid: str - :returns: True if the UUID in indicator list, otherwise False - :rtype: bool + :returns: Indicator details if matched, otherwise None """ + if not profile_uuid: + return None + for ioc in self.get_iocs("ios_profile_ids"): if profile_uuid in ioc["value"]: self.log.warning("Found a known suspicious profile ID \"%s\" matching indicators from \"%s\"", profile_uuid, ioc["name"]) return ioc + def check_file_hash(self, file_hash): + """Check the provided SHA256 file hash against the list of indicators. + + :param file_hash: SHA256 hash to check + :type file_hash: str + :returns: Indicator details if matched, otherwise None + + """ + if not file_hash: + return None + + for ioc in self.get_iocs("files_sha256"): + if file_hash.lower() == ioc["value"].lower(): + self.log.warning("Found a known suspicious file with hash \"%s\" matching indicators from \"%s\"", + file_hash, ioc["name"]) + return ioc + + def check_app_id(self, app_id): + """Check the provided app identifier (typically an Android package name) + against the list of indicators. + + :param app_id: App ID to check against the list of indicators + :type app_id: str + :returns: Indicator details if matched, otherwise None + + """ + if not app_id: + return None + + for ioc in self.get_iocs("app_ids"): + if app_id.lower() == ioc["value"].lower(): + self.log.warning("Found a known suspicious app with ID \"%s\" matching indicators from \"%s\"", + app_id, ioc["name"]) + return ioc + def download_indicators_files(log): """