1
mirror of https://github.com/mvt-project/mvt synced 2025-11-13 01:37:36 +01:00

Compare commits

..

62 Commits

Author SHA1 Message Date
besendorf
3a7ace140d Merge branch 'main' into root_binaries 2025-10-23 15:10:50 +02:00
besendorf
5be5ffbf49 add mounts module for androidqf (#710)
* add mounts module for androidqf

* adds test for mounts module
2025-10-23 15:09:37 +02:00
besendorf
2701490501 fix tombstone unpack parsing bug (#711) 2025-10-23 15:08:01 +02:00
besendorf
779842567d Make revision field a string in TombstoneCrash model to fix error where (#702)
there were characters in the revision field
2025-10-09 11:28:47 +02:00
besendorf
d3cc8cf590 Add tzdata dependency (#700)
* Add tzdata dependency

* fix tzdata name
2025-10-05 13:29:54 +02:00
github-actions[bot]
b8a42eaf8f Add new iOS versions and build numbers (#698)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-09-29 20:42:12 +02:00
dependabot[bot]
62b880fbff Bump mkdocstrings from 0.30.0 to 0.30.1 (#697)
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.30.0 to 0.30.1.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.30.0...0.30.1)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-version: 0.30.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 20:03:04 +02:00
besendorf
0778d448df make virustotal check also work with androidqf extractions (#685) 2025-09-19 07:31:17 +02:00
github-actions[bot]
f020655a1a Add new iOS versions and build numbers (#693)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-09-16 15:52:32 +02:00
github-actions[bot]
91c34e6664 Add new iOS versions and build numbers (#692)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-09-15 20:13:40 +02:00
dependabot[bot]
b4a8dd226a Bump mkdocs-material from 9.6.18 to 9.6.20 (#691)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.18 to 9.6.20.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.18...9.6.20)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 19:40:29 +02:00
dependabot[bot]
88213e12c9 Bump mkdocs-autorefs from 1.4.2 to 1.4.3 (#686)
Bumps [mkdocs-autorefs](https://github.com/mkdocstrings/autorefs) from 1.4.2 to 1.4.3.
- [Release notes](https://github.com/mkdocstrings/autorefs/releases)
- [Changelog](https://github.com/mkdocstrings/autorefs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/autorefs/compare/1.4.2...1.4.3)

---
updated-dependencies:
- dependency-name: mkdocs-autorefs
  dependency-version: 1.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 18:30:36 +02:00
r-tx
f75b8e186a add iOS 18.6.2 (#682)
* iOS 18.6.2

* iOS 18.6.2

---------

Co-authored-by: r-tx <r-tx@users.noreply.github.com>
Co-authored-by: Tek <tek@randhome.io>
2025-08-26 13:52:55 +02:00
dependabot[bot]
5babc1fcf3 Bump mkdocs-material from 9.6.17 to 9.6.18 (#683)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.17 to 9.6.18.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.17...9.6.18)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 11:25:22 +02:00
besendorf
b723ebf28e move test dependencies to dev dependency group (#679) 2025-08-21 16:10:03 +02:00
dependabot[bot]
616e870212 Bump mkdocs-material from 9.6.16 to 9.6.17 (#678)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.16 to 9.6.17.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.16...9.6.17)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tek <tek@randhome.io>
2025-08-20 11:13:59 +02:00
Tek
847b0e087b Adds iOS 18.6.1 (#681) 2025-08-20 11:10:20 +02:00
User
b44944aecf fix ruff 2025-08-19 08:51:14 +02:00
User
514c400017 Fix AndroidQF file count test 2025-08-19 08:49:44 +02:00
besendorf
41d22a8fdc Merge branch 'main' into root_binaries 2025-08-15 11:02:34 +02:00
Janik Besendorf
d90654e74a Add root_binaries androidqf module 2025-08-15 11:00:49 +02:00
dependabot[bot]
86a0772eb2 Bump cryptography from 45.0.5 to 45.0.6 (#675)
Bumps [cryptography](https://github.com/pyca/cryptography) from 45.0.5 to 45.0.6.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/45.0.5...45.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 45.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 10:38:19 +02:00
github-actions[bot]
7d0be9db4f Add new iOS versions and build numbers (#673)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-07-31 13:20:34 +02:00
dependabot[bot]
4e120b2640 Bump pydantic-settings from 2.9.1 to 2.10.1 (#655)
Bumps [pydantic-settings](https://github.com/pydantic/pydantic-settings) from 2.9.1 to 2.10.1.
- [Release notes](https://github.com/pydantic/pydantic-settings/releases)
- [Commits](https://github.com/pydantic/pydantic-settings/compare/v2.9.1...2.10.1)

---
updated-dependencies:
- dependency-name: pydantic-settings
  dependency-version: 2.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 22:58:12 +02:00
dependabot[bot]
dbe9e5db9b Bump mkdocstrings from 0.29.1 to 0.30.0 (#671)
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.29.1 to 0.30.0.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.29.1...0.30.0)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-version: 0.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tek <tek@randhome.io>
2025-07-28 22:42:37 +02:00
dependabot[bot]
0b00398729 Bump rich from 14.0.0 to 14.1.0 (#670)
Bumps [rich](https://github.com/Textualize/rich) from 14.0.0 to 14.1.0.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v14.0.0...v14.1.0)

---
updated-dependencies:
- dependency-name: rich
  dependency-version: 14.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 22:37:42 +02:00
dependabot[bot]
87034d2c7a Bump mkdocs-material from 9.6.14 to 9.6.16 (#672)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.14 to 9.6.16.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.14...9.6.16)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 22:29:04 +02:00
besendorf
595a2f6536 Merge pull request #656 from mvt-project/fix/install_non_market_apps
remove deprecated install_non_market_apps permission check
2025-07-22 19:32:05 +02:00
besendorf
8ead44a31e Merge branch 'main' into fix/install_non_market_apps 2025-07-22 19:12:44 +02:00
besendorf
5c19d02a73 Merge pull request #659 from mvt-project/fix/tcc
fix #579 TCC: no such table: access
2025-07-22 19:02:32 +02:00
besendorf
14ebc9ee4e Merge branch 'main' into fix/tcc 2025-07-22 18:56:10 +02:00
besendorf
de53cc07f8 Merge pull request #660 from mvt-project/fix/safari_browserstate
catch sqlite exception in safari_browserstate.py
2025-07-22 18:33:39 +02:00
besendorf
22e066fc4a Merge branch 'main' into fix/safari_browserstate 2025-07-22 18:20:07 +02:00
besendorf
242052b8ec Merge branch 'main' into fix/install_non_market_apps 2025-07-17 11:45:34 +02:00
dependabot[bot]
1df61b5bbf Bump cryptography from 45.0.4 to 45.0.5 (#661)
Bumps [cryptography](https://github.com/pyca/cryptography) from 45.0.4 to 45.0.5.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/45.0.4...45.0.5)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 45.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-07 21:03:08 +02:00
besendorf
b691de2cc0 catch sqlite exception in safari_browserstate.py 2025-07-04 17:52:05 +02:00
besendorf
10915f250c catch tcc error 2025-07-04 17:46:50 +02:00
besendorf
c60cef4009 Merge branch 'main' into fix/install_non_market_apps 2025-07-04 17:04:13 +02:00
besendorf
dda798df8e Merge pull request #658 from mvt-project/fix-mms
initialise message_links in backup parser to fix sms module bug
2025-07-04 15:32:47 +02:00
besendorf
ffe6ad2014 initialise message_links in backup parser to fix sms module bug 2025-07-04 15:29:36 +02:00
dependabot[bot]
a125b20fc5 Bump pydantic from 2.11.5 to 2.11.7 (#651)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.11.5 to 2.11.7.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.11.5...v2.11.7)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-version: 2.11.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-02 20:59:41 +02:00
besendorf
49108e67e2 remove deprecated install_non_market_apps permission check 2025-07-02 10:11:35 +02:00
dependabot[bot]
883b450601 Bump mkdocstrings from 0.23.0 to 0.29.1 (#649)
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.23.0 to 0.29.1.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.23.0...0.29.1)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-version: 0.29.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tek <tek@randhome.io>
2025-06-20 11:29:34 +02:00
dependabot[bot]
ce813568ff Bump mkdocs-autorefs from 1.2.0 to 1.4.2 (#648)
Bumps [mkdocs-autorefs](https://github.com/mkdocstrings/autorefs) from 1.2.0 to 1.4.2.
- [Release notes](https://github.com/mkdocstrings/autorefs/releases)
- [Changelog](https://github.com/mkdocstrings/autorefs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/autorefs/compare/1.2.0...1.4.2)

---
updated-dependencies:
- dependency-name: mkdocs-autorefs
  dependency-version: 1.4.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-20 11:28:24 +02:00
dependabot[bot]
93303f181a Bump mkdocs-material from 9.5.42 to 9.6.14 (#647)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.42 to 9.6.14.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.42...9.6.14)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tek <tek@randhome.io>
2025-06-20 11:24:12 +02:00
dependabot[bot]
bee453a090 Bump cryptography from 45.0.3 to 45.0.4 (#645)
Bumps [cryptography](https://github.com/pyca/cryptography) from 45.0.3 to 45.0.4.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/45.0.3...45.0.4)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 45.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-20 11:22:26 +02:00
dependabot[bot]
42106aa4d6 Bump pyahocorasick from 2.1.0 to 2.2.0 (#646)
Bumps [pyahocorasick](https://github.com/WojciechMula/pyahocorasick) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/WojciechMula/pyahocorasick/releases)
- [Changelog](https://github.com/WojciechMula/pyahocorasick/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/WojciechMula/pyahocorasick/compare/2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: pyahocorasick
  dependency-version: 2.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-20 11:19:57 +02:00
Tek
95076c8f71 Create dependabot.yml (#644) 2025-06-20 11:17:40 +02:00
dependabot[bot]
c9ac12f336 Bump requests from 2.32.2 to 2.32.4 (#642)
Bumps [requests](https://github.com/psf/requests) from 2.32.2 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-19 23:55:42 +02:00
Tek
486e3e7e9b Update global_preferences.py (#641)
Added a warning for lockdown mode when the property is not present
---------

Co-authored-by: makitos666 <makitos666@gmail.com>
2025-06-19 23:51:59 +02:00
besendorf
be1fc3bd8b Update NSKeyedUnarchiver (#636) 2025-06-12 22:42:02 +02:00
Tek
4757cff262 Fixes date parsing issue in tombstones (#635) 2025-06-12 20:49:31 +02:00
Tek
61f51caf31 Freeze versions and bump version (#632)
* Freeze versions and bump version
* Drops support for python below 3.10
2025-06-12 16:33:15 +02:00
besendorf
511063fd0e Update pyproject.toml (#630) 2025-06-04 13:00:04 +02:00
scribblemaniac
88bc5672cb Upgrade main dockerfile runtime to ubuntu:24.04 (#619)
Co-authored-by: Tek <tek@randhome.io>
2025-05-14 11:34:40 +02:00
github-actions[bot]
0fce0acf7a Add new iOS versions and build numbers (#626)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-05-14 11:12:13 +02:00
github-actions[bot]
61f95d07d3 Add new iOS versions and build numbers (#625)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-05-12 22:37:46 +02:00
ping2A
3dedd169c4 Fix issue #574 for a module without IOCs output (#620)
* Fix issue #574 for a module without IOCs output
2025-04-30 10:30:39 +02:00
Tek
e34e03d3a3 Fixes Android Dumpsys ADB parsing issue 2025-04-18 17:43:08 +02:00
github-actions[bot]
34374699ce Add new iOS versions and build numbers (#622)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-04-17 09:46:17 +02:00
github-actions[bot]
cf5aa7c89f Add new iOS versions and build numbers (#618)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-04-01 16:04:06 +02:00
Donncha Ó Cearbhaill
2766739512 Fix bug where default values were dropped when parsing protobuf tombstones (#617) 2025-03-11 14:10:34 +01:00
31 changed files with 907 additions and 148 deletions

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.8', '3.9', '3.10'] # , '3.11']
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4

View File

@@ -103,7 +103,7 @@ RUN git clone https://github.com/libimobiledevice/usbmuxd && cd usbmuxd \
# Create main image
FROM ubuntu:22.04 as main
FROM ubuntu:24.04 as main
LABEL org.opencontainers.image.url="https://mvt.re"
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
@@ -135,8 +135,7 @@ COPY --from=build-usbmuxd /build /
COPY . mvt/
RUN apt-get update \
&& apt-get install -y git python3-pip \
&& PIP_NO_CACHE_DIR=1 pip3 install --upgrade pip \
&& PIP_NO_CACHE_DIR=1 pip3 install ./mvt \
&& PIP_NO_CACHE_DIR=1 pip3 install --break-system-packages ./mvt \
&& apt-get remove -y python3-pip git && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf mvt

View File

@@ -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

View File

@@ -1,5 +1,5 @@
mkdocs==1.6.1
mkdocs-autorefs==1.2.0
mkdocs-material==9.5.42
mkdocs-autorefs==1.4.3
mkdocs-material==9.6.20
mkdocs-material-extensions==1.3.1
mkdocstrings==0.23.0
mkdocstrings==0.30.1

View File

@@ -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 = "Rory Flynn", email = "rory.flynn@amnesty.org" },
]
description = "Mobile Verification Toolkit"
readme = "README.md"
@@ -16,28 +14,30 @@ classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Information Technology",
"Operating System :: OS Independent",
"Programming Language :: Python"
"Programming Language :: Python",
]
dependencies = [
"click >=8.1.3",
"rich >=12.6.0",
"tld >=0.12.6",
"requests >=2.28.1",
"simplejson >=3.17.6",
"packaging >=21.3",
"appdirs >=1.4.4",
"iOSbackup >=0.9.923",
"adb-shell[usb] >=0.4.3",
"libusb1 >=3.0.0",
"cryptography >=42.0.5",
"pyyaml >=6.0",
"pyahocorasick >= 2.0.0",
"betterproto >=1.2.0",
"pydantic >= 2.10.0",
"pydantic-settings >= 2.7.0",
'backports.zoneinfo; python_version < "3.9"',
"click==8.2.1",
"rich==14.1.0",
"tld==0.13.1",
"requests==2.32.4",
"simplejson==3.20.1",
"packaging==25.0",
"appdirs==1.4.4",
"iOSbackup==0.9.925",
"adb-shell[usb]==0.4.4",
"libusb1==3.3.1",
"cryptography==45.0.6",
"PyYAML>=6.0.2",
"pyahocorasick==2.2.0",
"betterproto==1.2.5",
"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.8"
requires-python = ">= 3.10"
[project.urls]
homepage = "https://docs.mvt.re/en/latest/"
@@ -47,14 +47,25 @@ repository = "https://github.com/mvt-project/mvt"
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"
@@ -67,9 +78,7 @@ 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

View File

@@ -4,6 +4,7 @@
# https://license.mvt.re/1.1/
import base64
import binascii
import hashlib
from .artifact import AndroidArtifact
@@ -89,11 +90,16 @@ class DumpsysADBArtifact(AndroidArtifact):
else:
key_base64, user = user_key, b""
try:
key_raw = base64.b64decode(key_base64)
key_fingerprint = hashlib.md5(key_raw).hexdigest().upper()
key_fingerprint_colon = ":".join(
[key_fingerprint[i : i + 2] for i in range(0, len(key_fingerprint), 2)]
)
except binascii.Error:
# Impossible to parse base64
key_fingerprint_colon = ""
return {
"user": user.decode("utf-8"),
"fingerprint": key_fingerprint_colon,

View File

@@ -0,0 +1,186 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from typing import Any
from .artifact import AndroidArtifact
SUSPICIOUS_MOUNT_POINTS = [
"/system",
"/vendor",
"/product",
"/system_ext",
]
SUSPICIOUS_OPTIONS = [
"rw",
"remount",
"noatime",
"nodiratime",
]
ALLOWLIST_NOATIME = [
"/system_dlkm",
"/system_ext",
"/product",
"/vendor",
"/vendor_dlkm",
]
class Mounts(AndroidArtifact):
"""
This artifact parses mount information from /proc/mounts or similar mount data.
It can detect potentially suspicious mount configurations that may indicate
a rooted or compromised device.
"""
def parse(self, entry: str) -> None:
"""
Parse mount information from the provided entry.
Examples:
/dev/block/bootdevice/by-name/system /system ext4 ro,seclabel,relatime 0 0
/dev/block/dm-12 on / type ext4 (ro,seclabel,noatime)
"""
self.results: list[dict[str, Any]] = []
for line in entry.splitlines():
line = line.strip()
if not line:
continue
device = None
mount_point = None
filesystem_type = None
mount_options = ""
if " on " in line and " type " in line:
try:
# Format: device on mount_point type filesystem_type (options)
device_part, rest = line.split(" on ", 1)
device = device_part.strip()
# Split by 'type' to get mount_point and filesystem info
mount_part, fs_part = rest.split(" type ", 1)
mount_point = mount_part.strip()
# Parse filesystem and options
if "(" in fs_part and fs_part.endswith(")"):
# Format: filesystem_type (options)
fs_and_opts = fs_part.strip()
paren_idx = fs_and_opts.find("(")
filesystem_type = fs_and_opts[:paren_idx].strip()
mount_options = fs_and_opts[paren_idx + 1 : -1].strip()
else:
# No options in parentheses, just filesystem type
filesystem_type = fs_part.strip()
mount_options = ""
# Skip if we don't have essential info
if not device or not mount_point or not filesystem_type:
continue
# Parse options into list
options_list = (
[opt.strip() for opt in mount_options.split(",") if opt.strip()]
if mount_options
else []
)
# Check if it's a system partition
is_system_partition = mount_point in SUSPICIOUS_MOUNT_POINTS or any(
mount_point.startswith(sp) for sp in SUSPICIOUS_MOUNT_POINTS
)
# Check if it's mounted read-write
is_read_write = "rw" in options_list
mount_entry = {
"device": device,
"mount_point": mount_point,
"filesystem_type": filesystem_type,
"mount_options": mount_options,
"options_list": options_list,
"is_system_partition": is_system_partition,
"is_read_write": is_read_write,
}
self.results.append(mount_entry)
except ValueError:
# If parsing fails, skip this line
continue
else:
# Skip lines that don't match expected format
continue
def check_indicators(self) -> None:
"""
Check for suspicious mount configurations that may indicate root access
or other security concerns.
"""
system_rw_mounts = []
suspicious_mounts = []
for mount in self.results:
mount_point = mount["mount_point"]
options = mount["options_list"]
# Check for system partitions mounted as read-write
if mount["is_system_partition"] and mount["is_read_write"]:
system_rw_mounts.append(mount)
if mount_point == "/system":
self.log.warning(
"Root detected /system partition is mounted as read-write (rw). "
)
else:
self.log.warning(
"System partition %s is mounted as read-write (rw). This may indicate system modifications.",
mount_point,
)
# Check for other suspicious mount options
suspicious_opts = [opt for opt in options if opt in SUSPICIOUS_OPTIONS]
if suspicious_opts and mount["is_system_partition"]:
if (
"noatime" in mount["mount_options"]
and mount["mount_point"] in ALLOWLIST_NOATIME
):
continue
suspicious_mounts.append(mount)
self.log.warning(
"Suspicious mount options found for %s: %s",
mount_point,
", ".join(suspicious_opts),
)
# Log interesting mount information
if mount_point == "/data" or mount_point.startswith("/sdcard"):
self.log.info(
"Data partition: %s mounted as %s with options: %s",
mount_point,
mount["filesystem_type"],
mount["mount_options"],
)
self.log.info("Parsed %d mount entries", len(self.results))
# Check indicators if available
if not self.indicators:
return
for mount in self.results:
# Check if any mount points match indicators
ioc = self.indicators.check_file_path(mount.get("mount_point", ""))
if ioc:
mount["matched_indicator"] = ioc
self.detected.append(mount)
# Check device paths for indicators
ioc = self.indicators.check_file_path(mount.get("device", ""))
if ioc:
mount["matched_indicator"] = ioc
self.detected.append(mount)

View File

@@ -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",

View File

@@ -8,6 +8,7 @@ from typing import List, Optional, Union
import pydantic
import betterproto
from dateutil import parser
from mvt.common.utils import convert_datetime_to_iso
from mvt.android.parsers.proto.tombstone import Tombstone
@@ -52,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
@@ -69,7 +70,7 @@ class TombstoneCrashResult(pydantic.BaseModel):
class TombstoneCrashArtifact(AndroidArtifact):
""" "
"""
Parser for Android tombstone crash files.
This parser can parse both text and protobuf tombstone crash files.
@@ -120,9 +121,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
def parse_protobuf(
self, file_name: str, file_timestamp: datetime.datetime, data: bytes
) -> None:
"""
Parse Android tombstone crash files from a protobuf object.
"""
"""Parse Android tombstone crash files from a protobuf object."""
tombstone_pb = Tombstone().parse(data)
tombstone_dict = tombstone_pb.to_dict(
betterproto.Casing.SNAKE, include_default_values=True
@@ -143,21 +142,23 @@ class TombstoneCrashArtifact(AndroidArtifact):
def parse(
self, file_name: str, file_timestamp: datetime.datetime, content: bytes
) -> None:
"""
Parse text Android tombstone crash files.
"""
# Split the tombstone file into a dictonary
"""Parse text Android tombstone crash files."""
tombstone_dict = {
"file_name": file_name,
"file_timestamp": convert_datetime_to_iso(file_timestamp),
}
lines = content.decode("utf-8").splitlines()
for line in lines:
for line_num, line in enumerate(lines, 1):
if not line.strip() or TOMBSTONE_DELIMITER in line:
continue
try:
for key, destination_key in TOMBSTONE_TEXT_KEY_MAPPINGS.items():
self._parse_tombstone_line(line, key, destination_key, tombstone_dict)
if self._parse_tombstone_line(
line, key, destination_key, tombstone_dict
):
break
except Exception as e:
raise ValueError(f"Error parsing line {line_num}: {str(e)}")
# Validate the tombstone and add it to the results
tombstone = TombstoneCrashResult.model_validate(tombstone_dict)
@@ -167,7 +168,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
self, line: str, key: str, destination_key: str, tombstone: dict
) -> bool:
if not line.startswith(f"{key}"):
return None
return False
if key == "pid":
return self._load_pid_line(line, tombstone)
@@ -186,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"
@@ -199,51 +200,50 @@ class TombstoneCrashArtifact(AndroidArtifact):
return True
def _load_pid_line(self, line: str, tombstone: dict) -> bool:
pid_part, tid_part, name_part = [part.strip() for part in line.split(",")]
try:
parts = line.split(" >>> ") if " >>> " in line else line.split(">>>")
process_info = parts[0]
pid_key, pid_value = pid_part.split(":", 1)
if pid_key != "pid":
raise ValueError(f"Expected key pid, got {pid_key}")
pid_value = int(pid_value.strip())
# Parse pid, tid, name from process info
info_parts = [p.strip() for p in process_info.split(",")]
for info in info_parts:
key, value = info.split(":", 1)
key = key.strip()
value = value.strip()
tid_key, tid_value = tid_part.split(":", 1)
if tid_key != "tid":
raise ValueError(f"Expected key tid, got {tid_key}")
tid_value = int(tid_value.strip())
if key == "pid":
tombstone["pid"] = int(value)
elif key == "tid":
tombstone["tid"] = int(value)
elif key == "name":
tombstone["process_name"] = value
name_key, name_value = name_part.split(":", 1)
if name_key != "name":
raise ValueError(f"Expected key name, got {name_key}")
name_value = name_value.strip()
process_name, binary_path = self._parse_process_name(name_value, tombstone)
# Extract binary path if it exists
if len(parts) > 1:
tombstone["binary_path"] = parts[1].strip().rstrip(" <")
tombstone["pid"] = pid_value
tombstone["tid"] = tid_value
tombstone["process_name"] = process_name
tombstone["binary_path"] = binary_path
return True
def _parse_process_name(self, process_name_part, tombstone: dict) -> bool:
process_name, process_path = process_name_part.split(">>>")
process_name = process_name.strip()
binary_path = process_path.strip().split(" ")[0]
return process_name, binary_path
except Exception as e:
raise ValueError(f"Failed to parse PID line: {str(e)}")
def _load_signal_line(self, line: str, tombstone: dict) -> bool:
signal, code, _ = [part.strip() for part in line.split(",", 2)]
signal = signal.split("signal ")[1]
signal_code, signal_name = signal.split(" ")
signal_name = signal_name.strip("()")
signal_part, code_part = map(str.strip, line.split(",")[:2])
code_part = code.split("code ")[1]
code_number, code_name = code_part.split(" ")
code_name = code_name.strip("()")
def parse_part(part: str, prefix: str) -> tuple[int, str]:
match = part.split(prefix)[1]
number = int(match.split()[0])
name = match.split("(")[1].split(")")[0] if "(" in match else "UNKNOWN"
return number, name
signal_number, signal_name = parse_part(signal_part, "signal ")
code_number, code_name = parse_part(code_part, "code ")
tombstone["signal_info"] = {
"code": int(code_number),
"code": code_number,
"code_name": code_name,
"name": signal_name,
"number": int(signal_code),
"number": signal_number,
}
return True
@@ -254,13 +254,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
@staticmethod
def _parse_timestamp_string(timestamp: str) -> str:
timestamp_date, timezone = timestamp.split("+")
# Truncate microseconds before parsing
timestamp_without_micro = timestamp_date.split(".")[0] + "+" + timezone
timestamp_parsed = datetime.datetime.strptime(
timestamp_without_micro, "%Y-%m-%d %H:%M:%S%z"
)
timestamp_parsed = parser.parse(timestamp)
# HACK: Swap the local timestamp to UTC, so keep the original time and avoid timezone conversion.
local_timestamp = timestamp_parsed.replace(tzinfo=datetime.timezone.utc)
return convert_datetime_to_iso(local_timestamp)

View File

@@ -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", []):
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]

View File

@@ -19,6 +19,8 @@ from .processes import Processes
from .settings import Settings
from .sms import SMS
from .files import Files
from .root_binaries import RootBinaries
from .mounts import Mounts
ANDROIDQF_MODULES = [
DumpsysActivities,
@@ -37,4 +39,6 @@ ANDROIDQF_MODULES = [
SMS,
DumpsysPackages,
Files,
RootBinaries,
Mounts,
]

View File

@@ -0,0 +1,74 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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 logging
import json
from typing import Optional
from mvt.android.artifacts.mounts import Mounts as MountsArtifact
from .base import AndroidQFModule
class Mounts(MountsArtifact, AndroidQFModule):
"""This module extracts and analyzes mount information from AndroidQF acquisitions."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
self.results = []
def run(self) -> None:
"""
Run the mounts analysis module.
This module looks for mount information files collected by androidqf
and analyzes them for suspicious configurations, particularly focusing
on detecting root access indicators like /system mounted as read-write.
"""
mount_files = self._get_files_by_pattern("*/mounts.json")
if not mount_files:
self.log.info("No mount information file found")
return
self.log.info("Found mount information file: %s", mount_files[0])
try:
data = self._get_file_content(mount_files[0]).decode(
"utf-8", errors="replace"
)
except Exception as exc:
self.log.error("Failed to read mount information file: %s", exc)
return
# Parse the mount data
try:
json_data = json.loads(data)
if isinstance(json_data, list):
# AndroidQF format: array of strings like
# "/dev/block/dm-12 on / type ext4 (ro,seclabel,noatime)"
mount_content = "\n".join(json_data)
self.parse(mount_content)
except Exception as exc:
self.log.error("Failed to parse mount information: %s", exc)
return
self.log.info("Extracted a total of %d mount entries", len(self.results))

View File

@@ -0,0 +1,121 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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 json
import logging
from typing import Optional
from .base import AndroidQFModule
class RootBinaries(AndroidQFModule):
"""This module analyzes root_binaries.json for root binaries found by androidqf."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None,
) -> None:
super().__init__(
file_path=file_path,
target_path=target_path,
results_path=results_path,
module_options=module_options,
log=log,
results=results,
)
def serialize(self, record: dict) -> dict:
return {
"timestamp": record.get("timestamp"),
"module": self.__class__.__name__,
"event": "root_binary_found",
"data": f"Root binary found: {record['path']} (binary: {record['binary_name']})",
}
def check_indicators(self) -> None:
"""Check for indicators of device rooting."""
if not self.results:
return
# All found root binaries are considered indicators of rooting
for result in self.results:
self.log.warning(
'Found root binary "%s" at path "%s"',
result["binary_name"],
result["path"],
)
self.detected.append(result)
if self.detected:
self.log.warning(
"Device shows signs of rooting with %d root binaries found",
len(self.detected),
)
def run(self) -> None:
"""Run the root binaries analysis."""
root_binaries_files = self._get_files_by_pattern("*/root_binaries.json")
if not root_binaries_files:
self.log.info("No root_binaries.json file found")
return
rawdata = self._get_file_content(root_binaries_files[0]).decode(
"utf-8", errors="ignore"
)
try:
root_binary_paths = json.loads(rawdata)
except json.JSONDecodeError as e:
self.log.error("Failed to parse root_binaries.json: %s", e)
return
if not isinstance(root_binary_paths, list):
self.log.error("Expected root_binaries.json to contain a list of paths")
return
# Known root binary names that might be found and their descriptions
# This maps the binary name to a human-readable description
known_root_binaries = {
"su": "SuperUser binary",
"busybox": "BusyBox utilities",
"supersu": "SuperSU root management",
"Superuser.apk": "Superuser app",
"KingoUser.apk": "KingRoot app",
"SuperSu.apk": "SuperSU app",
"magisk": "Magisk root framework",
"magiskhide": "Magisk hide utility",
"magiskinit": "Magisk init binary",
"magiskpolicy": "Magisk policy binary",
}
for path in root_binary_paths:
if not path or not isinstance(path, str):
continue
# Extract binary name from path
binary_name = path.split("/")[-1].lower()
# Check if this matches a known root binary by exact name match
description = "Unknown root binary"
for known_binary in known_root_binaries:
if binary_name == known_binary.lower():
description = known_root_binaries[known_binary]
break
result = {
"path": path.strip(),
"binary_name": binary_name,
"description": description,
}
self.results.append(result)
self.log.info("Found %d root binaries", len(self.results))

View File

@@ -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"])

View File

@@ -65,6 +65,10 @@ class CmdCheckIOCS(Command):
m = iocs_module.from_json(
file_path, log=logging.getLogger(iocs_module.__module__)
)
if not m:
log.warning("No result from this module, skipping it")
continue
if self.iocs.total_ioc_count > 0:
m.indicators = self.iocs
m.indicators.log = m.log

View File

@@ -69,10 +69,14 @@ class MVTModule:
@classmethod
def from_json(cls, json_path: str, log: logging.Logger):
with open(json_path, "r", encoding="utf-8") as handle:
try:
results = json.load(handle)
if log:
log.info('Loaded %d results from "%s"', len(results), json_path)
return cls(results=results, log=log)
except json.decoder.JSONDecodeError as err:
log.error('Error to decode the json "%s" file: "%s"', json_path, err)
return None
@classmethod
def get_slug(cls) -> str:

View File

@@ -3,4 +3,4 @@
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
MVT_VERSION = "2.6.0"
MVT_VERSION = "2.6.1"

View File

@@ -891,6 +891,14 @@
"version": "15.8.2",
"build": "19H384"
},
{
"version": "15.8.4",
"build": "19H390"
},
{
"version": "15.8.5",
"build": "19H394"
},
{
"build": "20A362",
"version": "16.0"
@@ -992,6 +1000,14 @@
"version": "16.7.8",
"build": "20H343"
},
{
"version": "16.7.11",
"build": "20H360"
},
{
"version": "16.7.12",
"build": "20H364"
},
{
"version": "17.0",
"build": "21A327"
@@ -1076,6 +1092,10 @@
"version": "17.6.1",
"build": "21G101"
},
{
"version": "17.7.7",
"build": "21H433"
},
{
"version": "18",
"build": "22A3354"
@@ -1107,5 +1127,41 @@
{
"version": "18.3.1",
"build": "22D72"
},
{
"version": "18.4",
"build": "22E240"
},
{
"version": "18.4.1",
"build": "22E252"
},
{
"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"
}
]

View File

@@ -43,6 +43,8 @@ class GlobalPreferences(IOSExtraction):
self.log.warning("Lockdown mode enabled")
else:
self.log.warning("Lockdown mode disabled")
return
self.log.warning("Lockdown mode disabled")
def process_file(self, file_path: str) -> None:
with open(file_path, "rb") as handle:

View File

@@ -95,6 +95,7 @@ class SafariBrowserState(IOSExtraction):
)
except sqlite3.OperationalError:
# Old version iOS <12 likely
try:
cur.execute(
"""
SELECT
@@ -103,6 +104,8 @@ class SafariBrowserState(IOSExtraction):
ORDER BY last_viewed_time;
"""
)
except sqlite3.OperationalError as e:
self.log.error(f"Error executing query: {e}")
for row in cur:
session_entries = []

View File

@@ -116,6 +116,7 @@ class TCC(IOSExtraction):
)
db_version = "v2"
except sqlite3.OperationalError:
try:
cur.execute(
"""SELECT
service, client, client_type, allowed,
@@ -123,6 +124,8 @@ class TCC(IOSExtraction):
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]

View File

@@ -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]

View File

@@ -64,4 +64,4 @@ class TestTombstoneCrashArtifact:
# We often don't know the time offset for a log entry and so can't convert everything to UTC.
# MVT should output the local time only:
# So original 2023-04-12 12:32:40.518290770+0200 -> 2023-04-12 12:32:40.000000
assert tombstone_result.get("timestamp") == "2023-04-12 12:32:40.000000"
assert tombstone_result.get("timestamp") == "2023-04-12 12:32:40.518290"

View File

@@ -0,0 +1,97 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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 logging
from pathlib import Path
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestAndroidqfMountsArtifact:
def test_parse_mounts_token_checks(self):
"""
Test the artifact-level `parse` method using tolerant token checks.
Different parser variants may place mount tokens into different dict
keys (for example `mount_options`, `pass_num`, `dump_freq`, etc.). To
avoid brittle assertions we concatenate each parsed entry's values and
look for expected tokens (device names, mount points, options) somewhere
in the combined representation.
"""
from mvt.android.artifacts.mounts import Mounts as MountsArtifact
m = MountsArtifact()
mount_lines = [
"/dev/block/dm-12 on / type ext4 (ro,seclabel,noatime)",
"/dev/block/by-name/system on /system type ext4 (rw,seclabel,noatime)",
"/dev/block/by-name/data on /data type f2fs (rw,nosuid,nodev,noatime)",
]
mount_content = "\n".join(mount_lines)
# Parse the mount lines (artifact-level)
m.parse(mount_content)
# Basic sanity: parser should return one entry per input line
assert len(m.results) == 3, f"Expected 3 parsed mounts, got: {m.results}"
# Concatenate each entry's values into a single string so token checks
# are tolerant to which dict keys were used by the parser.
def concat_values(entry):
parts = []
for v in entry.values():
try:
parts.append(str(v))
except Exception:
# Skip values that can't be stringified
continue
return " ".join(parts)
concatenated = [concat_values(e) for e in m.results]
# Token expectations (tolerant):
# - Root line should include 'dm-12' and 'noatime' (and typically 'ro')
assert any("dm-12" in s and "noatime" in s for s in concatenated), (
f"No root-like tokens (dm-12 + noatime) found in parsed results: {concatenated}"
)
# - System line should include '/system' or 'by-name/system' and 'rw'
assert any(
(("by-name/system" in s or "/system" in s) and "rw" in s)
for s in concatenated
), (
f"No system-like tokens (system + rw) found in parsed results: {concatenated}"
)
# - Data line should include '/data' or 'by-name/data' and 'rw'
assert any(
(("by-name/data" in s or "/data" in s) and "rw" in s) for s in concatenated
), f"No data-like tokens (data + rw) found in parsed results: {concatenated}"
class TestAndroidqfMountsModule:
def test_androidqf_module_no_mounts_file(self):
"""
When no `mounts.json` is present in the androidqf dataset, the module
should not produce results nor detections.
"""
from mvt.android.modules.androidqf.mounts import Mounts
data_path = get_android_androidqf()
m = Mounts(target_path=data_path, log=logging)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
# The provided androidqf test dataset does not include mounts.json, so
# results should remain empty.
assert len(m.results) == 0, (
f"Expected no results when mounts.json is absent, got: {m.results}"
)
assert len(m.detected) == 0, f"Expected no detections, got: {m.detected}"

View File

@@ -0,0 +1,116 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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 logging
from pathlib import Path
import pytest
from mvt.android.modules.androidqf.root_binaries import RootBinaries
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
@pytest.fixture()
def data_path():
return get_android_androidqf()
@pytest.fixture()
def parent_data_path(data_path):
return Path(data_path).absolute().parent.as_posix()
@pytest.fixture()
def file_list(data_path):
return list_files(data_path)
@pytest.fixture()
def module(parent_data_path, file_list):
m = RootBinaries(target_path=parent_data_path, log=logging)
m.from_folder(parent_data_path, file_list)
return m
class TestAndroidqfRootBinaries:
def test_root_binaries_detection(self, module):
run_module(module)
# Should find 4 root binaries from the test file
assert len(module.results) == 4
assert len(module.detected) == 4
# Check that all results are detected as indicators
binary_paths = [result["path"] for result in module.results]
assert "/system/bin/su" in binary_paths
assert "/system/xbin/busybox" in binary_paths
assert "/data/local/tmp/magisk" in binary_paths
assert "/system/bin/magiskhide" in binary_paths
def test_root_binaries_descriptions(self, module):
run_module(module)
# Check that binary descriptions are correctly identified
su_result = next((r for r in module.results if "su" in r["binary_name"]), None)
assert su_result is not None
assert "SuperUser binary" in su_result["description"]
busybox_result = next(
(r for r in module.results if "busybox" in r["binary_name"]), None
)
assert busybox_result is not None
assert "BusyBox utilities" in busybox_result["description"]
magisk_result = next(
(r for r in module.results if r["binary_name"] == "magisk"), None
)
assert magisk_result is not None
assert "Magisk root framework" in magisk_result["description"]
magiskhide_result = next(
(r for r in module.results if "magiskhide" in r["binary_name"]), None
)
assert magiskhide_result is not None
assert "Magisk hide utility" in magiskhide_result["description"]
def test_root_binaries_warnings(self, caplog, module):
run_module(module)
# Check that warnings are logged for each root binary found
assert 'Found root binary "su" at path "/system/bin/su"' in caplog.text
assert (
'Found root binary "busybox" at path "/system/xbin/busybox"' in caplog.text
)
assert (
'Found root binary "magisk" at path "/data/local/tmp/magisk"' in caplog.text
)
assert (
'Found root binary "magiskhide" at path "/system/bin/magiskhide"'
in caplog.text
)
assert "Device shows signs of rooting with 4 root binaries found" in caplog.text
def test_serialize_method(self, module):
run_module(module)
# Test that serialize method works correctly
if module.results:
serialized = module.serialize(module.results[0])
assert serialized["module"] == "RootBinaries"
assert serialized["event"] == "root_binary_found"
assert "Root binary found:" in serialized["data"]
def test_no_root_binaries_file(self, parent_data_path):
# Test behavior when no root_binaries.json file is present
empty_file_list = []
m = RootBinaries(target_path=parent_data_path, log=logging)
m.from_folder(parent_data_path, empty_file_list)
run_module(m)
assert len(m.results) == 0
assert len(m.detected) == 0

View File

@@ -9,6 +9,7 @@ from pathlib import Path
from mvt.android.modules.bugreport.appops import Appops
from mvt.android.modules.bugreport.getprop import Getprop
from mvt.android.modules.bugreport.packages import Packages
from mvt.android.modules.bugreport.tombstones import Tombstones
from mvt.common.module import run_module
from ..utils import get_artifact_folder
@@ -54,3 +55,8 @@ class TestBugreportAnalysis:
def test_getprop_module(self):
m = self.launch_bug_report_module(Getprop)
assert len(m.results) == 0
def test_tombstones_modules(self):
m = self.launch_bug_report_module(Tombstones)
assert len(m.results) == 2
assert m.results[1]["pid"] == 3559

View File

@@ -0,0 +1,27 @@
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/a10eea/a10:10/.190711.020/A105:user/release-keys'
Revision: '5'
ABI: 'arm'
Timestamp: 2021-09-29 17:43:49+0200
pid: 9850, tid: 9893, name: UsbFfs-worker >>> /system/bin/adbd <<<
uid: 2000
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'Check failed: payload.size() <= bytes_left (payload.size()=99, bytes_left=51) '
r0 00000000 r1 000026a5 r2 00000006 r3 f11fad98
r4 f11fadac r5 f11fad90 r6 0000267a r7 0000016b
r8 f11fada8 r9 f11fad98 r10 f11fadc8 r11 f11fadb8
ip 000026a5 sp f11fad68 lr f20c23b7 pc f20c23ca
backtrace:
#00 pc 000603ca /apex/com.android.runtime/lib/bionic/libc.so (abort+166) (BuildId: 320fbdc2a1289fadd7dacae7f2eb77a3)
#01 pc 00007e23 /system/lib/libbase.so (android::base::DefaultAborter(char const*)+6) (BuildId: a28585ee446ea17e3e6fcf9c907fff2a)
#02 pc 0000855f /system/lib/libbase.so (android::base::LogMessage::~LogMessage()+406) (BuildId: a28585ee446ea17e3e6fcf9c907fff2a)
#03 pc 000309cf /system/lib/libadbd.so (UsbFfsConnection::ProcessRead(IoBlock*)+814) (BuildId: 3645b175977ae210c156a57b25dfa599)
#04 pc 00030459 /system/lib/libadbd.so (UsbFfsConnection::HandleRead(TransferId, long long)+84) (BuildId: 3645b175977ae210c156a57b25dfa599)
#05 pc 00030349 /system/lib/libadbd.so (UsbFfsConnection::ReadEvents()+92) (BuildId: 3645b175977ae210c156a57b25dfa599)
#06 pc 00030169 /system/lib/libadbd.so (_ZZN16UsbFfsConnection11StartWorkerEvENKUlvE_clEv+504) (BuildId: 3645b175977ae210c156a57b25dfa599)
#07 pc 0002ff53 /system/lib/libadbd.so (_ZNSt3__114__thread_proxyINS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEZN16UsbFfsConnection11StartWorkerEvEUlvE_EEEEEPvSA_+26) (BuildId: 3645b175977ae210c156a57b25dfa599)
#08 pc 000a75b3 /apex/com.android.runtime/lib/bionic/libc.so (__pthread_start(void*)+20) (BuildId: 320fbdc2a1289fadd7dacae7f2eb77a3)
#09 pc 00061b33 /apex/com.android.runtime/lib/bionic/libc.so (__start_thread+30) (BuildId: 320fbdc2a1289fadd7dacae7f2eb77a3)

View File

@@ -0,0 +1,38 @@
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/a10eea/a10:11/RP1A.200720.012/A105:user/release-keys'
Revision: '5'
ABI: 'arm'
Timestamp: 2023-08-21 23:28:59-0400
pid: 3559, tid: 3568, name: tzts_daemon >>> /vendor/bin/tzts_daemon <<<
uid: 1000
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xe8b4d14c
r0 e8b4d14c r1 e8b4d14c r2 0000002b r3 00000004
r4 00000000 r5 e8b4d14c r6 00000000 r7 00000000
r8 e7ef78b0 r9 0000002b r10 e7ef7dad r11 e7ef7400
ip 00000000 sp e7ef7208 lr e89f4b01 pc e89c273a
backtrace:
#00 pc 0005f73a /apex/com.android.runtime/lib/bionic/libc.so (strlen_a15+54) (BuildId: fef5b751123147ea65bf3f4f798c9518)
#01 pc 00091afd /apex/com.android.runtime/lib/bionic/libc.so (__vfprintf+3364) (BuildId: fef5b751123147ea65bf3f4f798c9518)
#02 pc 000a68e5 /apex/com.android.runtime/lib/bionic/libc.so (vsnprintf+152) (BuildId: fef5b751123147ea65bf3f4f798c9518)
#03 pc 000051cf /system/lib/liblog.so (__android_log_vprint+74) (BuildId: 3fcead474cd0ecbdafb529ff176b0d13)
#04 pc 000012e8 /vendor/bin/tzts_daemon
memory near r0:
e8b4d12c -------- -------- -------- -------- ................
e8b4d13c -------- -------- -------- -------- ................
e8b4d14c -------- -------- -------- -------- ................
e8b4d15c -------- -------- -------- -------- ................
e8b4d16c -------- -------- -------- -------- ................
e8b4d17c -------- -------- -------- -------- ................
e8b4d18c -------- -------- -------- -------- ................
e8b4d19c -------- -------- -------- -------- ................
e8b4d1ac -------- -------- -------- -------- ................
e8b4d1bc -------- -------- -------- -------- ................
e8b4d1cc -------- -------- -------- -------- ................
e8b4d1dc -------- -------- -------- -------- ................
e8b4d1ec -------- -------- -------- -------- ................
e8b4d1fc -------- -------- -------- -------- ................
e8b4d20c -------- -------- -------- -------- ................
e8b4d21c -------- -------- -------- -------- ................

View File

@@ -0,0 +1,6 @@
[
"/system/bin/su",
"/system/xbin/busybox",
"/data/local/tmp/magisk",
"/system/bin/magiskhide"
]

View File

@@ -62,7 +62,7 @@ class TestHashes:
def test_hash_from_folder(self):
path = os.path.join(get_artifact_folder(), "androidqf")
hashes = list(generate_hashes_from_path(path, logging))
assert len(hashes) == 7
assert len(hashes) == 8
# Sort the files to have reliable order for tests.
hashes = sorted(hashes, key=lambda x: x["file_path"])
assert hashes[0]["file_path"] == os.path.join(path, "backup.ab")