diff --git a/Makefile b/Makefile index 1d5c384..16bde09 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ install: python3 -m pip install --upgrade -e . test-requirements: - python3 -m pip install --upgrade -r test-requirements.txt + python3 -m pip install --upgrade --group dev generate-proto-parsers: # Generate python parsers for protobuf files diff --git a/docs/requirements.txt b/docs/requirements.txt index e3190d7..e6c51bb 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ mkdocs==1.6.1 -mkdocs-autorefs==1.4.2 -mkdocs-material==9.6.14 +mkdocs-autorefs==1.4.3 +mkdocs-material==9.6.20 mkdocs-material-extensions==1.3.1 -mkdocstrings==0.29.1 \ No newline at end of file +mkdocstrings==0.30.1 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index fac7311..c23f83e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,11 @@ [project] name = "mvt" dynamic = ["version"] -authors = [ - {name = "Claudio Guarnieri", email = "nex@nex.sx"} -] +authors = [{ name = "Claudio Guarnieri", email = "nex@nex.sx" }] maintainers = [ - {name = "Etienne Maynier", email = "tek@randhome.io"}, - {name = "Donncha Ó Cearbhaill", email = "donncha.ocearbhaill@amnesty.org"}, - {name = "Rory Flynn", email = "rory.flynn@amnesty.org"} + { name = "Etienne Maynier", email = "tek@randhome.io" }, + { name = "Donncha Ó Cearbhaill", email = "donncha.ocearbhaill@amnesty.org" }, + { name = "Rory Flynn", email = "rory.flynn@amnesty.org" }, ] description = "Mobile Verification Toolkit" readme = "README.md" @@ -16,11 +14,11 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Information Technology", "Operating System :: OS Independent", - "Programming Language :: Python" + "Programming Language :: Python", ] dependencies = [ "click==8.2.1", - "rich==14.0.0", + "rich==14.1.0", "tld==0.13.1", "requests==2.32.4", "simplejson==3.20.1", @@ -29,14 +27,15 @@ dependencies = [ "iOSbackup==0.9.925", "adb-shell[usb]==0.4.4", "libusb1==3.3.1", - "cryptography==45.0.4", + "cryptography==45.0.6", "PyYAML>=6.0.2", "pyahocorasick==2.2.0", "betterproto==1.2.5", - "pydantic==2.11.5", - "pydantic-settings==2.9.1", + "pydantic==2.11.7", + "pydantic-settings==2.10.1", "NSKeyedUnArchiver==1.5.2", "python-dateutil==2.9.0.post0", + "tzdata==2025.2", ] requires-python = ">= 3.10" @@ -45,20 +44,31 @@ homepage = "https://docs.mvt.re/en/latest/" repository = "https://github.com/mvt-project/mvt" [project.scripts] - mvt-ios = "mvt.ios:cli" - mvt-android = "mvt.android:cli" +mvt-ios = "mvt.ios:cli" +mvt-android = "mvt.android:cli" + +[dependency-groups] +dev = [ + "requests>=2.31.0", + "pytest>=7.4.3", + "pytest-cov>=4.1.0", + "pytest-github-actions-annotate-failures>=0.2.0", + "pytest-mock>=3.14.0", + "stix2>=3.0.1", + "ruff>=0.1.6", + "mypy>=1.7.1", + "betterproto[compiler]", +] [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [tool.coverage.run] -omit = [ - "tests/*", -] +omit = ["tests/*"] [tool.coverage.html] -directory= "htmlcov" +directory = "htmlcov" [tool.mypy] install_types = true @@ -68,15 +78,13 @@ packages = "src" [tool.pytest.ini_options] addopts = "-ra -q --cov=mvt --cov-report html --junitxml=pytest.xml --cov-report=term-missing:skip-covered" -testpaths = [ - "tests" -] +testpaths = ["tests"] [tool.ruff.lint] -select = ["C90", "E", "F", "W"] # flake8 default set +select = ["C90", "E", "F", "W"] # flake8 default set ignore = [ - "E501", # don't enforce line length violations - "C901", # complex-structure + "E501", # don't enforce line length violations + "C901", # complex-structure # These were previously ignored but don't seem to be required: # "E265", # no-space-after-block-comment @@ -88,14 +96,14 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] -"__init__.py" = ["F401"] # unused-import +"__init__.py" = ["F401"] # unused-import [tool.ruff.lint.mccabe] max-complexity = 10 [tool.setuptools] include-package-data = true -package-dir = {"" = "src"} +package-dir = { "" = "src" } [tool.setuptools.packages.find] where = ["src"] @@ -104,4 +112,4 @@ where = ["src"] mvt = ["ios/data/*.json"] [tool.setuptools.dynamic] -version = {attr = "mvt.common.version.MVT_VERSION"} +version = { attr = "mvt.common.version.MVT_VERSION" } diff --git a/src/mvt/android/artifacts/settings.py b/src/mvt/android/artifacts/settings.py index 06a261e..4649666 100644 --- a/src/mvt/android/artifacts/settings.py +++ b/src/mvt/android/artifacts/settings.py @@ -51,11 +51,6 @@ ANDROID_DANGEROUS_SETTINGS = [ "key": "send_action_app_error", "safe_value": "1", }, - { - "description": "enabled installation of non Google Play apps", - "key": "install_non_market_apps", - "safe_value": "0", - }, { "description": "enabled accessibility services", "key": "accessibility_enabled", diff --git a/src/mvt/android/artifacts/tombstone_crashes.py b/src/mvt/android/artifacts/tombstone_crashes.py index f9f4531..5193ff8 100644 --- a/src/mvt/android/artifacts/tombstone_crashes.py +++ b/src/mvt/android/artifacts/tombstone_crashes.py @@ -53,7 +53,7 @@ class TombstoneCrashResult(pydantic.BaseModel): file_name: str file_timestamp: str # We store the timestamp as a string to avoid timezone issues build_fingerprint: str - revision: int + revision: str arch: Optional[str] = None timestamp: str # We store the timestamp as a string to avoid timezone issues process_uptime: Optional[int] = None @@ -187,7 +187,7 @@ class TombstoneCrashArtifact(AndroidArtifact): raise ValueError(f"Expected key {key}, got {line_key}") value_clean = value.strip().strip("'") - if destination_key in ["uid", "revision"]: + if destination_key == "uid": tombstone[destination_key] = int(value_clean) elif destination_key == "process_uptime": # eg. "Process uptime: 40s" diff --git a/src/mvt/android/modules/adb/packages.py b/src/mvt/android/modules/adb/packages.py index 1d9c821..421ac88 100644 --- a/src/mvt/android/modules/adb/packages.py +++ b/src/mvt/android/modules/adb/packages.py @@ -107,8 +107,7 @@ class Packages(AndroidExtraction): result["matched_indicator"] = ioc self.detected.append(result) - @staticmethod - def check_virustotal(packages: list) -> None: + def check_virustotal(self, packages: list) -> None: hashes = [] for package in packages: for file in package.get("files", []): @@ -143,8 +142,15 @@ class Packages(AndroidExtraction): for package in packages: for file in package.get("files", []): - row = [package["package_name"], file["path"]] - + if "package_name" in package: + row = [package["package_name"], file["path"]] + elif "name" in package: + row = [package["name"], file["path"]] + else: + self.log.error( + f"Package {package} has no name or package_name. packages.json or apks.json is malformed" + ) + continue if file["sha256"] in detections: detection = detections[file["sha256"]] positives = detection.split("/")[0] diff --git a/src/mvt/android/parsers/backup.py b/src/mvt/android/parsers/backup.py index 7c49ded..105b4f2 100644 --- a/src/mvt/android/parsers/backup.py +++ b/src/mvt/android/parsers/backup.py @@ -231,6 +231,7 @@ def parse_sms_file(data): entry.pop("mms_body") body = entry.get("body", None) + message_links = None if body: message_links = check_for_links(entry["body"]) diff --git a/src/mvt/ios/data/ios_versions.json b/src/mvt/ios/data/ios_versions.json index a373595..c5f801f 100644 --- a/src/mvt/ios/data/ios_versions.json +++ b/src/mvt/ios/data/ios_versions.json @@ -895,6 +895,10 @@ "version": "15.8.4", "build": "19H390" }, + { + "version": "15.8.5", + "build": "19H394" + }, { "build": "20A362", "version": "16.0" @@ -1000,6 +1004,10 @@ "version": "16.7.11", "build": "20H360" }, + { + "version": "16.7.12", + "build": "20H364" + }, { "version": "17.0", "build": "21A327" @@ -1131,5 +1139,29 @@ { "version": "18.5", "build": "22F76" + }, + { + "version": "18.6", + "build": "22G86" + }, + { + "version": "18.6.1", + "build": "22G90" + }, + { + "version": "18.6.2", + "build": "22G100" + }, + { + "version": "18.7", + "build": "22H20" + }, + { + "version": "26", + "build": "23A341" + }, + { + "version": "26.0.1", + "build": "23A355" } ] \ No newline at end of file diff --git a/src/mvt/ios/modules/mixed/safari_browserstate.py b/src/mvt/ios/modules/mixed/safari_browserstate.py index 616ea20..f97463d 100644 --- a/src/mvt/ios/modules/mixed/safari_browserstate.py +++ b/src/mvt/ios/modules/mixed/safari_browserstate.py @@ -95,14 +95,17 @@ class SafariBrowserState(IOSExtraction): ) except sqlite3.OperationalError: # Old version iOS <12 likely - cur.execute( + try: + cur.execute( + """ + SELECT + title, url, user_visible_url, last_viewed_time, session_data + FROM tabs + ORDER BY last_viewed_time; """ - SELECT - title, url, user_visible_url, last_viewed_time, session_data - FROM tabs - ORDER BY last_viewed_time; - """ - ) + ) + except sqlite3.OperationalError as e: + self.log.error(f"Error executing query: {e}") for row in cur: session_entries = [] diff --git a/src/mvt/ios/modules/mixed/tcc.py b/src/mvt/ios/modules/mixed/tcc.py index 461e5b3..73d9aa1 100644 --- a/src/mvt/ios/modules/mixed/tcc.py +++ b/src/mvt/ios/modules/mixed/tcc.py @@ -116,13 +116,16 @@ class TCC(IOSExtraction): ) db_version = "v2" except sqlite3.OperationalError: - cur.execute( - """SELECT - service, client, client_type, allowed, - prompt_count - FROM access;""" - ) - db_version = "v1" + try: + cur.execute( + """SELECT + service, client, client_type, allowed, + prompt_count + FROM access;""" + ) + db_version = "v1" + except sqlite3.OperationalError as e: + self.log.error(f"Error parsing TCC database: {e}") for row in cur: service = row[0] diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 57652da..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -requests>=2.31.0 -pytest>=7.4.3 -pytest-cov>=4.1.0 -pytest-github-actions-annotate-failures>=0.2.0 -pytest-mock>=3.14.0 -stix2>=3.0.1 -ruff>=0.1.6 -mypy>=1.7.1 -betterproto[compiler] \ No newline at end of file