From 1d740ad80209c350230023f0a1d087ac223c064f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 10:21:08 +0200 Subject: [PATCH 1/2] Add new iOS versions and build numbers (#373) Co-authored-by: DonnchaC --- mvt/ios/data/ios_versions.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mvt/ios/data/ios_versions.json b/mvt/ios/data/ios_versions.json index a4a3423..747be6b 100644 --- a/mvt/ios/data/ios_versions.json +++ b/mvt/ios/data/ios_versions.json @@ -871,6 +871,10 @@ "version": "15.7.7", "build": "19H357" }, + { + "version": "15.7.8", + "build": "19H364" + }, { "build": "20A362", "version": "16.0" @@ -927,5 +931,9 @@ { "version": "16.5.1", "build": "20F75" + }, + { + "version": "16.6", + "build": "20G75" } ] \ No newline at end of file From 57d4aca72e0930f0ea8e9b49f3186eafba5f45a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donncha=20=C3=93=20Cearbhaill?= Date: Wed, 26 Jul 2023 13:42:17 +0200 Subject: [PATCH 2/2] Refactor Android modules to remove duplication (#368) * Remove duplicated detection logic from GetProp modules * Deduplicate settings and processes * Refactor detection in artifacts * Improves Artifact class --------- Co-authored-by: tek --- mvt/android/artifacts/__init__.py | 0 mvt/android/artifacts/artifact.py | 9 +++ mvt/android/artifacts/getprop.py | 59 ++++++++++++++++++ mvt/android/artifacts/processes.py | 69 +++++++++++++++++++++ mvt/android/artifacts/settings.py | 71 ++++++++++++++++++++++ mvt/android/modules/adb/getprop.py | 30 +-------- mvt/android/modules/adb/processes.py | 56 ++--------------- mvt/android/modules/adb/settings.py | 67 +------------------- mvt/android/modules/androidqf/getprop.py | 44 +------------- mvt/android/modules/androidqf/processes.py | 64 ++----------------- mvt/android/modules/androidqf/settings.py | 14 +---- mvt/android/modules/bugreport/getprop.py | 22 ++----- mvt/android/parsers/__init__.py | 1 - mvt/android/parsers/getprop.py | 26 -------- mvt/android/utils.py | 19 ++++++ mvt/common/artifact.py | 28 +++++++++ mvt/ios/modules/mixed/calls.py | 2 +- tests/android/test_artifact_getprop.py | 41 +++++++++++++ tests/android/test_artifact_processes.py | 38 ++++++++++++ tests/artifacts/android_data/getprop.txt | 14 +++++ tests/artifacts/android_data/ps.txt | 18 ++++++ 21 files changed, 390 insertions(+), 302 deletions(-) create mode 100644 mvt/android/artifacts/__init__.py create mode 100644 mvt/android/artifacts/artifact.py create mode 100644 mvt/android/artifacts/getprop.py create mode 100644 mvt/android/artifacts/processes.py create mode 100644 mvt/android/artifacts/settings.py delete mode 100644 mvt/android/parsers/getprop.py create mode 100644 mvt/android/utils.py create mode 100644 mvt/common/artifact.py create mode 100644 tests/android/test_artifact_getprop.py create mode 100644 tests/android/test_artifact_processes.py create mode 100644 tests/artifacts/android_data/getprop.txt create mode 100644 tests/artifacts/android_data/ps.txt diff --git a/mvt/android/artifacts/__init__.py b/mvt/android/artifacts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mvt/android/artifacts/artifact.py b/mvt/android/artifacts/artifact.py new file mode 100644 index 0000000..d757569 --- /dev/null +++ b/mvt/android/artifacts/artifact.py @@ -0,0 +1,9 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +from mvt.common.artifact import Artifact + + +class AndroidArtifact(Artifact): + pass diff --git a/mvt/android/artifacts/getprop.py b/mvt/android/artifacts/getprop.py new file mode 100644 index 0000000..54db0e4 --- /dev/null +++ b/mvt/android/artifacts/getprop.py @@ -0,0 +1,59 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# 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 +from typing import Dict, List + +from mvt.android.utils import warn_android_patch_level + +from .artifact import AndroidArtifact + +INTERESTING_PROPERTIES = [ + "gsm.sim.operator.alpha", + "gsm.sim.operator.iso-country", + "persist.sys.timezone", + "ro.boot.serialno", + "ro.build.version.sdk", + "ro.build.version.security_patch", + "ro.product.cpu.abi", + "ro.product.locale", + "ro.product.vendor.manufacturer", + "ro.product.vendor.model", + "ro.product.vendor.name", +] + + +class GetProp(AndroidArtifact): + def parse(self, entry: str) -> None: + self.results: List[Dict[str, str]] = [] + rxp = re.compile(r"\[(.+?)\]: \[(.+?)\]") + + for line in entry.splitlines(): + line = line.strip() + if line == "": + continue + + matches = re.findall(rxp, line) + if not matches or len(matches[0]) != 2: + continue + + entry = {"name": matches[0][0], "value": matches[0][1]} + self.results.append(entry) + + def check_indicators(self) -> None: + for entry in self.results: + if entry["name"] in INTERESTING_PROPERTIES: + self.log.info("%s: %s", entry["name"], entry["value"]) + + if entry["name"] == "ro.build.version.security_patch": + warn_android_patch_level(entry["value"], self.log) + + if not self.indicators: + return + + for result in self.results: + ioc = self.indicators.check_android_property_name(result.get("name", "")) + if ioc: + result["matched_indicator"] = ioc + self.detected.append(result) diff --git a/mvt/android/artifacts/processes.py b/mvt/android/artifacts/processes.py new file mode 100644 index 0000000..9ce53bc --- /dev/null +++ b/mvt/android/artifacts/processes.py @@ -0,0 +1,69 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +from .artifact import AndroidArtifact + + +class Processes(AndroidArtifact): + def parse(self, entry: str) -> None: + for line in entry.split("\n")[1:]: + proc = line.split() + + # Skip empty lines + if len(proc) == 0: + continue + + # Sometimes WCHAN is empty. + if len(proc) == 8: + proc = proc[:5] + [""] + proc[5:] + + # Sometimes there is the security label. + if proc[0].startswith("u:r"): + label = proc[0] + proc = proc[1:] + else: + label = "" + + # Sometimes there is no WCHAN. + if len(proc) < 9: + proc = proc[:5] + [""] + proc[5:] + + self.results.append( + { + "user": proc[0], + "pid": int(proc[1]), + "ppid": int(proc[2]), + "virtual_memory_size": int(proc[3]), + "resident_set_size": int(proc[4]), + "wchan": proc[5], + "aprocress": proc[6], + "stat": proc[7], + "proc_name": proc[8].strip("[]"), + "label": label, + } + ) + + def check_indicators(self) -> None: + if not self.indicators: + return + + for result in self.results: + proc_name = result.get("proc_name", "") + if not proc_name: + continue + + # Skipping this process because of false positives. + if result["proc_name"] == "gatekeeperd": + continue + + ioc = self.indicators.check_app_id(proc_name) + if ioc: + result["matched_indicator"] = ioc + self.detected.append(result) + continue + + ioc = self.indicators.check_process(proc_name) + if ioc: + result["matched_indicator"] = ioc + self.detected.append(result) diff --git a/mvt/android/artifacts/settings.py b/mvt/android/artifacts/settings.py new file mode 100644 index 0000000..e53371e --- /dev/null +++ b/mvt/android/artifacts/settings.py @@ -0,0 +1,71 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +from .artifact import AndroidArtifact + +ANDROID_DANGEROUS_SETTINGS = [ + { + "description": "disabled Google Play Services apps verification", + "key": "verifier_verify_adb_installs", + "safe_value": "1", + }, + { + "description": "disabled Google Play Protect", + "key": "package_verifier_enable", + "safe_value": "1", + }, + { + "description": "disabled Google Play Protect", + "key": "package_verifier_user_consent", + "safe_value": "1", + }, + { + "description": "disabled Google Play Protect", + "key": "upload_apk_enable", + "safe_value": "1", + }, + { + "description": "disabled confirmation of adb apps installation", + "key": "adb_install_need_confirm", + "safe_value": "1", + }, + { + "description": "disabled sharing of security reports", + "key": "send_security_reports", + "safe_value": "1", + }, + { + "description": "disabled sharing of crash logs with manufacturer", + "key": "samsung_errorlog_agree", + "safe_value": "1", + }, + { + "description": "disabled applications errors reports", + "key": "send_action_app_error", + "safe_value": "1", + }, + { + "description": "enabled installation of non Google Play apps", + "key": "install_non_market_apps", + "safe_value": "0", + }, +] + + +class Settings(AndroidArtifact): + def check_indicators(self) -> None: + for namespace, settings in self.results.items(): + for key, value in settings.items(): + for danger in ANDROID_DANGEROUS_SETTINGS: + # Check if one of the dangerous settings is using an unsafe + # value (different than the one specified). + if danger["key"] == key and danger["safe_value"] != value: + self.log.warning( + 'Found suspicious "%s" setting "%s = %s" (%s)', + namespace, + key, + value, + danger["description"], + ) + break diff --git a/mvt/android/modules/adb/getprop.py b/mvt/android/modules/adb/getprop.py index 12ae343..7caedbc 100644 --- a/mvt/android/modules/adb/getprop.py +++ b/mvt/android/modules/adb/getprop.py @@ -4,15 +4,14 @@ # https://license.mvt.re/1.1/ import logging -from datetime import datetime, timedelta from typing import Optional -from mvt.android.parsers import parse_getprop +from mvt.android.artifacts.getprop import GetProp as GetPropArtifact from .base import AndroidExtraction -class Getprop(AndroidExtraction): +class Getprop(GetPropArtifact, AndroidExtraction): """This module extracts device properties from getprop command.""" def __init__( @@ -35,33 +34,10 @@ class Getprop(AndroidExtraction): self.results = {} if not results else results - def check_indicators(self) -> None: - if not self.indicators: - return - - for result in self.results: - ioc = self.indicators.check_android_property_name(result.get("name", "")) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - def run(self) -> None: self._adb_connect() output = self._adb_command("getprop") self._adb_disconnect() - self.results = parse_getprop(output) - - # Alert if phone is outdated. - for entry in self.results: - if entry.get("name", "") != "ro.build.version.security_patch": - continue - patch_date = datetime.strptime(entry["value"], "%Y-%m-%d") - if (datetime.now() - patch_date) > timedelta(days=6 * 30): - self.log.warning( - "This phone has not received security updates " - "for more than six months (last update: %s)", - entry["value"], - ) - + self.parse(output) self.log.info("Extracted %d Android system properties", len(self.results)) diff --git a/mvt/android/modules/adb/processes.py b/mvt/android/modules/adb/processes.py index 0f0758c..73ca04b 100644 --- a/mvt/android/modules/adb/processes.py +++ b/mvt/android/modules/adb/processes.py @@ -6,10 +6,12 @@ import logging from typing import Optional +from mvt.android.artifacts.processes import Processes as ProcessesArtifact + from .base import AndroidExtraction -class Processes(AndroidExtraction): +class Processes(ProcessesArtifact, AndroidExtraction): """This module extracts details on running processes.""" def __init__( @@ -30,61 +32,11 @@ class Processes(AndroidExtraction): results=results, ) - def check_indicators(self) -> None: - if not self.indicators: - return - - for result in self.results: - proc_name = result.get("proc_name", "") - if not proc_name: - continue - - # Skipping this process because of false positives. - if result["proc_name"] == "gatekeeperd": - continue - - ioc = self.indicators.check_app_id(proc_name) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - continue - - ioc = self.indicators.check_process(proc_name) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - def run(self) -> None: self._adb_connect() output = self._adb_command("ps -A") - - for line in output.splitlines()[1:]: - line = line.strip() - if line == "": - continue - - fields = line.split() - proc = { - "user": fields[0], - "pid": fields[1], - "parent_pid": fields[2], - "vsize": fields[3], - "rss": fields[4], - } - - # Sometimes WCHAN is empty, so we need to re-align output fields. - if len(fields) == 8: - proc["wchan"] = "" - proc["pc"] = fields[5] - proc["name"] = fields[7] - elif len(fields) == 9: - proc["wchan"] = fields[5] - proc["pc"] = fields[6] - proc["name"] = fields[8] - - self.results.append(proc) - + self.parse(output) self._adb_disconnect() self.log.info("Extracted records on a total of %d processes", len(self.results)) diff --git a/mvt/android/modules/adb/settings.py b/mvt/android/modules/adb/settings.py index 8dc2643..5f98371 100644 --- a/mvt/android/modules/adb/settings.py +++ b/mvt/android/modules/adb/settings.py @@ -6,58 +6,12 @@ import logging from typing import Optional +from mvt.android.artifacts.settings import Settings as SettingsArtifact + from .base import AndroidExtraction -ANDROID_DANGEROUS_SETTINGS = [ - { - "description": "disabled Google Play Services apps verification", - "key": "verifier_verify_adb_installs", - "safe_value": "1", - }, - { - "description": "disabled Google Play Protect", - "key": "package_verifier_enable", - "safe_value": "1", - }, - { - "description": "disabled Google Play Protect", - "key": "package_verifier_user_consent", - "safe_value": "1", - }, - { - "description": "disabled Google Play Protect", - "key": "upload_apk_enable", - "safe_value": "1", - }, - { - "description": "disabled confirmation of adb apps installation", - "key": "adb_install_need_confirm", - "safe_value": "1", - }, - { - "description": "disabled sharing of security reports", - "key": "send_security_reports", - "safe_value": "1", - }, - { - "description": "disabled sharing of crash logs with manufacturer", - "key": "samsung_errorlog_agree", - "safe_value": "1", - }, - { - "description": "disabled applications errors reports", - "key": "send_action_app_error", - "safe_value": "1", - }, - { - "description": "enabled installation of non Google Play apps", - "key": "install_non_market_apps", - "safe_value": "0", - }, -] - -class Settings(AndroidExtraction): +class Settings(SettingsArtifact, AndroidExtraction): """This module extracts Android system settings.""" def __init__( @@ -80,21 +34,6 @@ class Settings(AndroidExtraction): self.results = {} if not results else results - def check_indicators(self) -> None: - for _, settings in self.results.items(): - for key, value in settings.items(): - for danger in ANDROID_DANGEROUS_SETTINGS: - # Check if one of the dangerous settings is using an unsafe - # value (different than the one specified). - if danger["key"] == key and danger["safe_value"] != value: - self.log.warning( - 'Found suspicious setting "%s = %s" (%s)', - key, - value, - danger["description"], - ) - break - def run(self) -> None: self._adb_connect() diff --git a/mvt/android/modules/androidqf/getprop.py b/mvt/android/modules/androidqf/getprop.py index ab3f49d..b048001 100644 --- a/mvt/android/modules/androidqf/getprop.py +++ b/mvt/android/modules/androidqf/getprop.py @@ -4,29 +4,14 @@ # https://license.mvt.re/1.1/ import logging -from datetime import datetime, timedelta from typing import Optional -from mvt.android.parsers.getprop import parse_getprop +from mvt.android.artifacts.getprop import GetProp as GetPropArtifact from .base import AndroidQFModule -INTERESTING_PROPERTIES = [ - "gsm.sim.operator.alpha", - "gsm.sim.operator.iso-country", - "persist.sys.timezone", - "ro.boot.serialno", - "ro.build.version.sdk", - "ro.build.version.security_patch", - "ro.product.cpu.abi", - "ro.product.locale", - "ro.product.vendor.manufacturer", - "ro.product.vendor.model", - "ro.product.vendor.name", -] - -class Getprop(AndroidQFModule): +class Getprop(GetPropArtifact, AndroidQFModule): """This module extracts data from get properties.""" def __init__( @@ -48,16 +33,6 @@ class Getprop(AndroidQFModule): ) self.results = [] - def check_indicators(self) -> None: - if not self.indicators: - return - - for result in self.results: - ioc = self.indicators.check_android_property_name(result.get("name", "")) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - def run(self) -> None: getprop_files = self._get_files_by_pattern("*/getprop.txt") if not getprop_files: @@ -67,18 +42,5 @@ class Getprop(AndroidQFModule): with open(getprop_files[0]) as f: data = f.read() - self.results = parse_getprop(data) - for entry in self.results: - if entry["name"] in INTERESTING_PROPERTIES: - self.log.info("%s: %s", entry["name"], entry["value"]) - if entry["name"] == "ro.build.version.security_patch": - last_patch = datetime.strptime(entry["value"], "%Y-%m-%d") - if (datetime.now() - last_patch) > timedelta(days=6 * 31): - self.log.warning( - "This phone has not received security " - "updates for more than six months " - "(last update: %s)", - entry["value"], - ) - + self.parse(data) self.log.info("Extracted a total of %d properties", len(self.results)) diff --git a/mvt/android/modules/androidqf/processes.py b/mvt/android/modules/androidqf/processes.py index 4b52426..b743250 100644 --- a/mvt/android/modules/androidqf/processes.py +++ b/mvt/android/modules/androidqf/processes.py @@ -6,10 +6,12 @@ import logging from typing import Optional +from mvt.android.artifacts.processes import Processes as ProcessesArtifact + from .base import AndroidQFModule -class Processes(AndroidQFModule): +class Processes(ProcessesArtifact, AndroidQFModule): """This module analyse running processes""" def __init__( @@ -30,70 +32,12 @@ class Processes(AndroidQFModule): results=results, ) - def check_indicators(self) -> None: - if not self.indicators: - return - - for result in self.results: - proc_name = result.get("proc_name", "") - if not proc_name: - continue - - # Skipping this process because of false positives. - if result["proc_name"] == "gatekeeperd": - continue - - ioc = self.indicators.check_app_id(proc_name) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - continue - - ioc = self.indicators.check_process(proc_name) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - - def _parse_ps(self, data): - for line in data.split("\n")[1:]: - proc = line.split() - - # Sometimes WCHAN is empty. - if len(proc) == 8: - proc = proc[:5] + [""] + proc[5:] - - # Sometimes there is the security label. - if proc[0].startswith("u:r"): - label = proc[0] - proc = proc[1:] - else: - label = "" - - # Sometimes there is no WCHAN. - if len(proc) < 9: - proc = proc[:5] + [""] + proc[5:] - - self.results.append( - { - "user": proc[0], - "pid": int(proc[1]), - "ppid": int(proc[2]), - "virtual_memory_size": int(proc[3]), - "resident_set_size": int(proc[4]), - "wchan": proc[5], - "aprocress": proc[6], - "stat": proc[7], - "proc_name": proc[8].strip("[]"), - "label": label, - } - ) - def run(self) -> None: ps_files = self._get_files_by_pattern("*/ps.txt") if not ps_files: return with open(ps_files[0]) as handle: - self._parse_ps(handle.read()) + self.parse(handle.read()) self.log.info("Identified %d running processes", len(self.results)) diff --git a/mvt/android/modules/androidqf/settings.py b/mvt/android/modules/androidqf/settings.py index 7f2ac1e..f398dc3 100644 --- a/mvt/android/modules/androidqf/settings.py +++ b/mvt/android/modules/androidqf/settings.py @@ -6,12 +6,12 @@ import logging from typing import Optional -from mvt.android.modules.adb.settings import ANDROID_DANGEROUS_SETTINGS +from mvt.android.artifacts.settings import Settings as SettingsArtifact from .base import AndroidQFModule -class Settings(AndroidQFModule): +class Settings(SettingsArtifact, AndroidQFModule): """This module analyse setting files""" def __init__( @@ -52,16 +52,6 @@ class Settings(AndroidQFModule): except IndexError: continue - for danger in ANDROID_DANGEROUS_SETTINGS: - if danger["key"] == key and danger["safe_value"] != value: - self.log.warning( - 'Found suspicious setting "%s = %s" (%s)', - key, - value, - danger["description"], - ) - break - self.log.info( "Identified %d settings", sum([len(val) for val in self.results.values()]) ) diff --git a/mvt/android/modules/bugreport/getprop.py b/mvt/android/modules/bugreport/getprop.py index f272bf7..6a3d607 100644 --- a/mvt/android/modules/bugreport/getprop.py +++ b/mvt/android/modules/bugreport/getprop.py @@ -4,15 +4,14 @@ # https://license.mvt.re/1.1/ import logging -from datetime import datetime, timedelta from typing import Optional -from mvt.android.parsers import parse_getprop +from mvt.android.artifacts.getprop import GetProp as GetPropArtifact from .base import BugReportModule -class Getprop(BugReportModule): +class Getprop(GetPropArtifact, BugReportModule): """This module extracts device properties from getprop command.""" def __init__( @@ -33,7 +32,7 @@ class Getprop(BugReportModule): results=results, ) - self.results = {} if not results else results + self.results = [] if not results else results def run(self) -> None: content = self._get_dumpstate_file() @@ -60,18 +59,5 @@ class Getprop(BugReportModule): lines.append(line) - self.results = parse_getprop("\n".join(lines)) - - # Alert if phone is outdated. - for entry in self.results: - if entry["name"] == "ro.build.version.security_patch": - security_patch = entry["value"] - patch_date = datetime.strptime(security_patch, "%Y-%m-%d") - if (datetime.now() - patch_date) > timedelta(days=6 * 30): - self.log.warning( - "This phone has not received security updates " - "for more than six months (last update: %s)", - security_patch, - ) - + self.parse("\n".join(lines)) self.log.info("Extracted %d Android system properties", len(self.results)) diff --git a/mvt/android/parsers/__init__.py b/mvt/android/parsers/__init__.py index f86d5b3..dcdc4ff 100644 --- a/mvt/android/parsers/__init__.py +++ b/mvt/android/parsers/__init__.py @@ -12,4 +12,3 @@ from .dumpsys import ( parse_dumpsys_dbinfo, parse_dumpsys_receiver_resolver_table, ) -from .getprop import parse_getprop diff --git a/mvt/android/parsers/getprop.py b/mvt/android/parsers/getprop.py deleted file mode 100644 index 70dd19d..0000000 --- a/mvt/android/parsers/getprop.py +++ /dev/null @@ -1,26 +0,0 @@ -# Mobile Verification Toolkit (MVT) -# Copyright (c) 2021-2023 Claudio Guarnieri. -# 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 -from typing import Dict, List - - -def parse_getprop(output: str) -> List[Dict[str, str]]: - results = [] - rxp = re.compile(r"\[(.+?)\]: \[(.+?)\]") - - for line in output.splitlines(): - line = line.strip() - if line == "": - continue - - matches = re.findall(rxp, line) - if not matches or len(matches[0]) != 2: - continue - - entry = {"name": matches[0][0], "value": matches[0][1]} - results.append(entry) - - return results diff --git a/mvt/android/utils.py b/mvt/android/utils.py new file mode 100644 index 0000000..73908a8 --- /dev/null +++ b/mvt/android/utils.py @@ -0,0 +1,19 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +from datetime import datetime, timedelta + + +def warn_android_patch_level(patch_level: str, log) -> bool: + """Alert if Android patch level out-of-date""" + patch_date = datetime.strptime(patch_level, "%Y-%m-%d") + if (datetime.now() - patch_date) > timedelta(days=6 * 31): + log.warning( + "This phone has not received security updates " + "for more than six months (last update: %s)", + patch_level, + ) + return True + + return False diff --git a/mvt/common/artifact.py b/mvt/common/artifact.py new file mode 100644 index 0000000..c8cf55a --- /dev/null +++ b/mvt/common/artifact.py @@ -0,0 +1,28 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ + + +class Artifact: + """ + Main artifact class + """ + + def __init__(self, *args, **kwargs): + self.results = [] + self.detected = [] + self.indicators = None + super().__init__(*args, **kwargs) + + def parse(self, entry: str): + """ + Parse the artifact, adds the parsed information to self.results + """ + raise NotImplementedError + + def check_indicators(self) -> None: + """Check the results of this module against a provided list of + indicators coming from self.indicators + """ + raise NotImplementedError diff --git a/mvt/ios/modules/mixed/calls.py b/mvt/ios/modules/mixed/calls.py index 7d9b4db..7657015 100644 --- a/mvt/ios/modules/mixed/calls.py +++ b/mvt/ios/modules/mixed/calls.py @@ -5,7 +5,7 @@ import logging import sqlite3 -from typing import Union, Optional +from typing import Optional, Union from mvt.common.utils import convert_mactime_to_iso diff --git a/tests/android/test_artifact_getprop.py b/tests/android/test_artifact_getprop.py new file mode 100644 index 0000000..3c04a2a --- /dev/null +++ b/tests/android/test_artifact_getprop.py @@ -0,0 +1,41 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +import logging + +from mvt.android.artifacts.getprop import GetProp +from mvt.common.indicators import Indicators + +from ..utils import get_artifact + + +class TestGetPropArtifact: + def test_parsing(self): + gp = GetProp() + file = get_artifact("android_data/getprop.txt") + with open(file) as f: + data = f.read() + + assert len(gp.results) == 0 + gp.parse(data) + assert len(gp.results) == 13 + assert gp.results[0]["name"] == "af.fast_track_multiplier" + assert gp.results[0]["value"] == "1" + + def test_ioc_check(self, indicator_file): + gp = GetProp() + file = get_artifact("android_data/getprop.txt") + with open(file) as f: + data = f.read() + gp.parse(data) + + ind = Indicators(log=logging.getLogger()) + ind.parse_stix2(indicator_file) + ind.ioc_collections[0]["android_property_names"].append( + "dalvik.vm.appimageformat" + ) + gp.indicators = ind + assert len(gp.detected) == 0 + gp.check_indicators() + assert len(gp.detected) == 1 diff --git a/tests/android/test_artifact_processes.py b/tests/android/test_artifact_processes.py new file mode 100644 index 0000000..047c4b7 --- /dev/null +++ b/tests/android/test_artifact_processes.py @@ -0,0 +1,38 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +import logging + +from mvt.android.artifacts.processes import Processes +from mvt.common.indicators import Indicators + +from ..utils import get_artifact + + +class TestProcessesArtifact: + def test_parsing(self): + p = Processes() + file = get_artifact("android_data/ps.txt") + with open(file) as f: + data = f.read() + + assert len(p.results) == 0 + p.parse(data) + assert len(p.results) == 17 + assert p.results[0]["proc_name"] == "init" + + def test_ioc_check(self, indicator_file): + p = Processes() + file = get_artifact("android_data/ps.txt") + with open(file) as f: + data = f.read() + p.parse(data) + + ind = Indicators(log=logging.getLogger()) + ind.parse_stix2(indicator_file) + ind.ioc_collections[0]["processes"].append("lru-add-drain") + p.indicators = ind + assert len(p.detected) == 0 + p.check_indicators() + assert len(p.detected) == 1 diff --git a/tests/artifacts/android_data/getprop.txt b/tests/artifacts/android_data/getprop.txt new file mode 100644 index 0000000..8a00977 --- /dev/null +++ b/tests/artifacts/android_data/getprop.txt @@ -0,0 +1,14 @@ +[af.fast_track_multiplier]: [1] +[audio.deep_buffer.media]: [true] +[audio.offload.min.duration.secs]: [30] +[audio.offload.video]: [true] +[av.debug.disable.pers.cache]: [1] +[dalvik.vm.appimageformat]: [lz4] +[dalvik.vm.dex2oat-Xms]: [64m] +[dalvik.vm.dex2oat-Xmx]: [512m] + +[dalvik.vm.dex2oat-max-image-block-size]: [524288] +[dalvik.vm.dex2oat-minidebuginfo]: [true] +[dalvik.vm.dex2oat-resolve-startup-strings]: [true] +[dalvik.vm.dexopt.secondary]: [true] +[dalvik.vm.heapgrowthlimit]: [192m] diff --git a/tests/artifacts/android_data/ps.txt b/tests/artifacts/android_data/ps.txt new file mode 100644 index 0000000..f9f6c20 --- /dev/null +++ b/tests/artifacts/android_data/ps.txt @@ -0,0 +1,18 @@ +USER PID PPID VSZ RSS WCHAN ADDR S NAME +root 1 0 68696 2864 0 0 S init +root 2 0 0 0 0 0 S [kthreadd] +root 4 2 0 0 0 0 S [kworker/0:0H] +root 5 2 0 0 0 0 S [kworker/u16:0] +root 6 2 0 0 0 0 S [ksoftirqd/0] +root 7 2 0 0 0 0 S [rcu_preempt] +root 8 2 0 0 0 0 S [rcu_sched] +root 9 2 0 0 0 0 S [rcu_bh] +root 10 2 0 0 0 0 S [rcuop/0] +root 11 2 0 0 0 0 S [rcuos/0] +root 12 2 0 0 0 0 S [rcuob/0] +root 13 2 0 0 0 0 S [migration/0] +root 14 2 0 0 0 0 S [lru-add-drain] +root 15 2 0 0 0 0 S [cpuhp/0] +root 16 2 0 0 0 0 S [cpuhp/1] +root 17 2 0 0 0 0 S [migration/1] +root 18 2 0 0 0 0 S [ksoftirqd/1]