Compare commits

..

54 Commits

Author SHA1 Message Date
Nex
a979b82ec6 Bumped version 2021-08-01 13:59:59 +02:00
Nex
eaef75d931 Added iPhone models definitions 2021-08-01 13:59:30 +02:00
Nex
1650aea248 pip3 for clarity 2021-07-31 19:48:19 +02:00
Nex
bc3634bf30 Specifying it is a password prompt 2021-07-31 10:27:44 +02:00
Nex
87ffd9e003 Bumped version 2021-07-31 10:23:38 +02:00
Nex
19f355810a Merge branch 'dkg-update-libimobiledevice-docs' 2021-07-31 10:19:46 +02:00
Nex
38b7aa6032 Updated doc on backup 2021-07-31 10:19:38 +02:00
Nex
feb285015a Merge branch 'update-libimobiledevice-docs' of https://github.com/dkg/mvt into dkg-update-libimobiledevice-docs 2021-07-31 10:16:58 +02:00
Nex
933ee65897 Merge branch 'dkg-mvt_decrypt-backup_password_from_env' 2021-07-31 10:13:43 +02:00
Nex
ad9ab1aeba Switched to using rich Prompt 2021-07-31 10:13:18 +02:00
Nex
4debee72cd Merge branch 'mvt_decrypt-backup_password_from_env' of https://github.com/dkg/mvt into dkg-mvt_decrypt-backup_password_from_env 2021-07-31 10:07:14 +02:00
Nex
d7031bd25f Merge branch 'dkg-ioc-docs' 2021-07-31 10:05:55 +02:00
Nex
5b5b065bc4 Updated doc page on IOCs 2021-07-31 10:05:41 +02:00
Daniel Kahn Gillmor
59206fc450 Describe how to use and find IOCs
This offers generic documentation, to show how MVT can be used with
arbitrary STIX-formatted IOCs, while still pointing users at some
known-to-be-useful sample files.
2021-07-31 00:46:36 -04:00
Daniel Kahn Gillmor
7b1b31f7be Update libimobiledevice docs about backup password reset
In this stage, the user is likely to want to run `idevicebackup2` in
interactive mode, so clearly specify the `-i` flag in the right place
(just dropping `-i` at the end of the command does not work as
expected -- i think `idevicebackup2 backup encryption on -i` tries to
set the password to `-i`).

More importantly, note that resetting the password by resetting all
the settings runs a risk of removing some of the forensic information.
Etienne identified a file that he thought was wiped as a result of
this in the call this morning, but I don't remember which file it was.

Maybe `id_status_cache.json` ?  If you have more concrete info, please
add it here too!
2021-07-30 23:49:06 -04:00
Daniel Kahn Gillmor
270e002f1b mvt-ios extract-key: enable pulling password from the environment
This enables automated use of extract-key without requiring a password
to be placed in the command line, where it might leak.
2021-07-30 23:10:54 -04:00
Daniel Kahn Gillmor
53adc05338 mvt-ios decrypt-backup: Enable pulling password from the environment.
Specifying the password on the command line with `--password XXX`
leaves the password itself visible to any process on the machine which
can scan the process table.

On some systems (including common GNU/Linux distributions) this
visibility is possible by default.

This change should make it possible to offer the password without
putting it into the process table; rather, the user puts the password
in the environment, and specifies the name of the environment
variable, like so:

```
$ export MVT_IOS_BACKUP_PASSWORD=WronglySconeRoundnessUnruffled
$ mvt-ios decrypt-backup -d /path/to/dest /path/to/data/XXXXXXXX-YYYYYYYYYYYYYYY/
$ unset MVT_IOS_BACKUP_PASSWORD
```

or you can do so using a prefixed env var, as described in the updated
check.md documentation.
2021-07-30 23:10:54 -04:00
Nex
d7f29a4e88 Updated README 2021-07-30 21:26:48 +02:00
Nex
444e70a6eb Merge branch 'pkirkovsky-extract-key' 2021-07-30 18:47:05 +02:00
Nex
b264ae946d Refactored to include functionality in existing DecryptBackup class 2021-07-30 18:46:45 +02:00
Nex
bfcfb3aa06 Merge branch 'extract-key' of https://github.com/pkirkovsky/mvt into pkirkovsky-extract-key 2021-07-30 18:29:47 +02:00
Nex
3e7d85039a Merge branch 'EmilienCourt-fix_SMS_PATH' 2021-07-30 18:09:13 +02:00
Nex
632409c81d Using consistent constant names 2021-07-30 18:08:52 +02:00
Nex
6df6064370 Merge branch 'fix_SMS_PATH' of https://github.com/EmilienCourt/mvt into EmilienCourt-fix_SMS_PATH 2021-07-30 18:04:16 +02:00
Nex
99e80fd942 Updated documentation links 2021-07-30 17:59:17 +02:00
Nex
9451da4514 Removed duplicate title 2021-07-30 17:56:05 +02:00
Tek
5ac0025470 Merge pull request #137 from opsec-infosec/main
Update Dockerfile missing sqlite3
2021-07-30 14:34:07 +02:00
opsec-infosec
9a6c4d251e Update Dockerfile
Add sqlite3 to Dockerfile for extraction of SMS messages
2021-07-30 16:13:06 +04:00
Nex
eda1976518 Added missing space in workflow file 2021-07-30 11:43:52 +02:00
Nex
c966eea7e6 Sorted imports 2021-07-30 11:40:09 +02:00
Nex
abcbefe359 Added safety checks to workflow 2021-07-30 11:39:43 +02:00
Nex
22d090569c Disabled pytest until unit tests are available 2021-07-30 11:20:59 +02:00
Nex
d490344142 Removed lint 2021-07-30 11:19:51 +02:00
Nex
7f361fb600 Create python-package.yml 2021-07-30 11:19:20 +02:00
Nex
18ed58cbf9 Removed unused dependency 2021-07-30 11:19:15 +02:00
Nex
3a6f57502e Merge branch 'febrezo-master' 2021-07-30 11:08:47 +02:00
Nex
490fb12302 Refactored creation of output folders 2021-07-30 11:08:32 +02:00
Nex
e2d82b0349 Merge branch 'master' of https://github.com/febrezo/mvt into febrezo-master 2021-07-30 10:48:34 +02:00
Nex
1bf7f54c72 Merge pull request #131 from macmade/main
Chrome History - Cheking extracted URLs against indicators.
2021-07-29 13:48:34 +02:00
Nex
60a2dbb860 Added module to parse WebKit ResourceLoadStatistics observations.db (ref: #133) 2021-07-29 13:46:58 +02:00
macmade
5e03c28dbd Chrome History - Cheking extracted URLs against indicators. 2021-07-29 02:33:32 +02:00
Nex
4fb6e204d1 Ordered iOS versions 2021-07-28 08:33:33 +02:00
Pavel Kirkovsky
f4340bd4f9 Merge branch 'mvt-project:main' into extract-key 2021-07-27 17:15:37 -07:00
Nex
7947d413b5 Update lint-python.yml 2021-07-27 21:44:31 +02:00
Nex
45beb6eeda Update lint-python.yml 2021-07-27 21:43:25 +02:00
Nex
ad81d5c450 Delete python-publish.yml 2021-07-27 21:42:21 +02:00
emilien
47df94fa12 fix typo 2021-07-25 15:13:23 +02:00
emilien
e5003b6490 Handle SMS bases in mmssms.db instead of bugle_db 2021-07-25 15:06:22 +02:00
Pavel Kirkovsky
99640ac08c Merge branch 'mvt-project:main' into extract-key 2021-07-23 12:02:02 -07:00
Pavel Kirkovsky
30d0348256 Added extract-key info to main docs 2021-07-23 03:46:48 -07:00
Pavel Kirkovsky
af4826070a Update README with extract-key command 2021-07-22 23:55:08 -07:00
Pavel Kirkovsky
9fbcce4340 Add extract-key command 2021-07-22 23:52:52 -07:00
Pavel Kirkovsky
ece88744ed KeyUtils class for working with decryption keys 2021-07-22 23:52:39 -07:00
febrezo
732db070f2 Add implicit creation of output folders 2021-07-20 03:09:53 +02:00
58 changed files with 777 additions and 395 deletions

View File

@@ -1,21 +0,0 @@
name: lint_python
on: [pull_request, push]
jobs:
lint_python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install bandit black codespell flake8 isort mypy pytest pyupgrade safety
- run: bandit --recursive --skip B108,B112,B404,B602 .
- run: black --check . || true
- run: codespell
- run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- run: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --show-source --statistics
- run: isort --check-only --profile black . || true
- run: pip install -r requirements.txt || true
- run: mypy --install-types --non-interactive . || true
- run: pytest . || true
- run: pytest --doctest-modules . || true
- run: shopt -s globstar && pyupgrade --py36-plus **/*.py || true
- run: safety check

43
.github/workflows/python-package.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python package
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest safety
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Safety checks
run: safety check
# - name: Test with pytest
# run: |
# pytest

View File

@@ -1,36 +0,0 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Upload Python Package
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

2
.gitignore vendored
View File

@@ -129,3 +129,5 @@ dmypy.json
.pyre/
*.pyc
# Temporal files
*~

View File

@@ -21,6 +21,7 @@ RUN apt install -y build-essential \
libplist-dev \
libusbmuxd-dev \
libssl-dev \
sqlite3 \
pkg-config
# Clean up

View File

@@ -5,7 +5,7 @@
# Mobile Verification Toolkit
[![](https://img.shields.io/pypi/v/mvt)](https://pypi.org/project/mvt/)
[![](https://img.shields.io/badge/docs-blue.svg)](https://mvt.readthedocs.io)
[![Documentation Status](https://readthedocs.org/projects/mvt/badge/?version=latest)](https://docs.mvt.re/en/latest/?badge=latest)
Mobile Verification Toolkit (MVT) is a collection of utilities to simplify and automate the process of gathering forensic traces helpful to identify a potential compromise of Android and iOS devices.
@@ -21,9 +21,9 @@ MVT can be installed from sources or conveniently using:
pip3 install mvt
```
You will need some dependencies, so please check the [documentation](https://mvt.readthedocs.io/en/latest/install.html).
You will need some dependencies, so please check the [documentation](https://docs.mvt.re/en/latest/install.html).
Alternatively, you can decide to run MVT and all relevant tools through a [Docker container](https://mvt.readthedocs.io/en/latest/docker.html).
Alternatively, you can decide to run MVT and all relevant tools through a [Docker container](https://docs.mvt.re/en/latest/docker.html).
## Usage
@@ -35,11 +35,12 @@ MVT provides two commands `mvt-ios` and `mvt-android` with the following subcomm
* `check-fs`: Extract artifacts from a full filesystem dump
* `check-iocs`: Compare stored JSON results to provided indicators
* `decrypt-backup`: Decrypt an encrypted iTunes backup
* `extract-key`: Extract decryption key from an iTunes backup
* `mvt-android`:
* `check-backup`: Check an Android Backup
* `download-apks`: Download all or non-safelisted installed APKs
Check out [the documentation to see how to use them](https://mvt.readthedocs.io/en/latest/).
Check out [the documentation to see how to use them](https://docs.mvt.re/).
## License

View File

@@ -10,4 +10,5 @@ import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mvt import android
android.cli()

View File

@@ -10,4 +10,5 @@ import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mvt import ios
ios.cli()

View File

@@ -1,5 +1,3 @@
## Using Docker
Using Docker simplifies having all the required dependencies and tools (including most recent versions of [libimobiledevice](https://libimobiledevice.org)) readily installed.
Install Docker following the [official documentation](https://docs.docker.com/get-docker/).

View File

@@ -35,7 +35,7 @@ export PATH=$PATH:~/.local/bin
Then you can install MVT directly from [pypi](https://pypi.org/project/mvt/)
```bash
pip install mvt
pip3 install mvt
```
Or from the source code:

32
docs/iocs.md Normal file
View File

@@ -0,0 +1,32 @@
# Indicators of Compromise (IOCs)
MVT uses [Structured Threat Information Expression (STIX)](https://oasis-open.github.io/cti-documentation/stix/intro.html) files to identify potential traces of compromise.
These indicators of compromise are contained in a file with a particular structure of [JSON](https://en.wikipedia.org/wiki/JSON) with the `.stix2` or `.json` extensions.
You can indicate a path to a STIX2 indicators file when checking iPhone backups or filesystem dumps. For example:
```bash
mvt-ios check-backup --iocs ~/ios/malware.stix2 --output /path/to/iphone/output /path/to/backup
```
Or, with data from an Android backup:
```bash
mvt-android check-backup --iocs ~/iocs/malware.stix2 /path/to/android/backup/
```
After extracting forensics data from a device, you are also able to compare it with any STIX2 file you indicate:
```bash
mvt-ios check-iocs --iocs ~/iocs/malware.stix2 /path/to/iphone/output/
```
If you're looking for indicators of compromise for a specific piece of malware or adversary, please ask investigators or anti-malware researchers who have the relevant expertise for a STIX file.
## Known repositories of STIX2 IOCs
- The [Amnesty International investigations repository](https://github.com/AmnestyTech/investigations) contains STIX-formatted IOCs for:
- [Pegasus](https://en.wikipedia.org/wiki/Pegasus_(spyware)) ([STIX2](https://raw.githubusercontent.com/AmnestyTech/investigations/master/2021-07-18_nso/pegasus.stix2))
Please [open an issue](https://github.com/mvt-project/mvt/issues/) to suggest new sources of STIX-formatted IOCs.

View File

@@ -2,6 +2,32 @@
The backup might take some time. It is best to make sure the phone remains unlocked during the backup process. Afterwards, a new folder will be created under the path you specified using the UDID of the iPhone you backed up.
## Extracting and saving the decryption key (optional)
If you do not wish to enter a password every time when decrypting a backup, MVT can accept a key file instead. This key can be used with the `decrypt-backup` command.
To generate a key file, you will need your device backup and the backup password:
$ mvt-ios extract-key --help
Usage: mvt-ios extract-key [OPTIONS] BACKUP_PATH
Extract decryption key from an iTunes backup
Options:
-p, --password TEXT Password to use to decrypt the backup [required]
-k, --key-file FILE Key file to be written (if unset, will print to STDOUT)
--help Show this message and exit.
You can specify the password on the command line, or omit the `-p` option to have MVT prompt for a password. The `-k` option specifies where to save the file containing the decryption key. If `-k` is omitted, MVT will display the decryption key without saving.
_Note_: This decryption key is sensitive data! Keep the file safe.
To extract the key and have MVT prompt for a password:
```bash
mvt-ios extract-key -k /path/to/save/key /path/to/backup
```
## Decrypting a backup
In case you have an encrypted backup, you will need to decrypt it first. This can be done with `mvt-ios` as well:
@@ -15,9 +41,10 @@ In case you have an encrypted backup, you will need to decrypt it first. This ca
-d, --destination TEXT Path to the folder where to store the decrypted
backup [required]
-p, --password TEXT Password to use to decrypt the backup NOTE: This
argument is mutually exclusive with arguments:
[key_file].
-p, --password TEXT Password to use to decrypt the backup (or, set
MVT_IOS_BACKUP_PASSWORD environment variable)
NOTE: This argument is mutually exclusive with
arguments: [key_file].
-k, --key-file PATH File containing raw encryption key to use to decrypt
the backup NOTE: This argument is mutually exclusive
@@ -25,10 +52,10 @@ In case you have an encrypted backup, you will need to decrypt it first. This ca
--help Show this message and exit.
You can specify either a password via command-line or pass a key file, and you need to specify a destination path where the decrypted backup will be stored. Following is an example usage of `decrypt-backup`:
You can specify the password in the environment variable `MVT_IOS_BACKUP_PASSWORD`, or via command-line argument, or you can pass a key file. You need to specify a destination path where the decrypted backup will be stored. If a password cannot be found and no key file is specified, MVT will ask for a password. Following is an example usage of `decrypt-backup` sending the password via an environment variable:
```bash
mvt-ios decrypt-backup -p password -d /path/to/decrypted /path/to/backup
MVT_IOS_BACKUP_PASSWORD="mypassword" mvt-ios decrypt-backup -d /path/to/decrypted /path/to/backup
```
## Run `mvt-ios` on a Backup

View File

@@ -3,10 +3,14 @@
If you have correctly [installed libimobiledevice](../install.md) you can easily generate an iTunes backup using the `idevicebackup2` tool included in the suite. First, you might want to ensure that backup encryption is enabled (**note: encrypted backup contain more data than unencrypted backups**):
```bash
idevicebackup2 backup encryption on
idevicebackup2 -i backup encryption on
```
Note that if a backup password was previously set on this device, you might need to use the same or change it. You can try changing password using `idevicebackup2 backup changepw` or resetting the password by resetting only the settings through the iPhone's Settings app.
Note that if a backup password was previously set on this device, you might need to use the same or change it. You can try changing password using `idevicebackup2 -i backup changepw`, or by turning off encryption (`idevicebackup2 -i backup encryption off`) and turning it back on again.
If you are not able to recover or change the password, you should try to disable encryption and obtain an unencrypted backup.
If all else fails, as a *last resort* you can try resetting the password by [resetting all the settings through the iPhone's Settings app](https://support.apple.com/en-us/HT205220), via `Settings » General » Reset » Reset All Settings`. Note that resetting the settings through the iPhone's Settings app will wipe some of the files that contain useful forensic traces, so try the options explained above first.
Once ready, you can proceed performing the backup:

View File

@@ -44,4 +44,5 @@ nav:
- Android Forensic Methodology: "android/methodology.md"
- Check APKs: "android/download_apks.md"
- Check an Android Backup: "android/backup.md"
- Indicators of Compromise: "iocs.md"
- License: "license.md"

View File

@@ -3,15 +3,17 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import sys
import click
import argparse
import logging
import os
import sys
import click
from rich.logging import RichHandler
from mvt.common.module import run_module, save_timeline
from mvt.common.indicators import Indicators
from mvt.common.module import run_module, save_timeline
from .download_apks import DownloadAPKs
from .lookups.koodous import koodous_lookup
from .lookups.virustotal import virustotal_lookup
@@ -45,8 +47,8 @@ def cli():
@click.option("--virustotal", "-v", is_flag=True, help="Check packages on VirusTotal")
@click.option("--koodous", "-k", is_flag=True, help="Check packages on Koodous")
@click.option("--all-checks", "-A", is_flag=True, help="Run all available checks")
@click.option("--output", "-o", type=click.Path(exists=True),
help="Specify a path to a folder where you want to store JSON results")
@click.option("--output", "-o", type=click.Path(exists=False),
help="Specify a path to a folder where you want to store the APKs")
@click.option("--from-file", "-f", type=click.Path(exists=True),
help="Instead of acquiring from phone, load an existing packages.json file for lookups (mainly for debug purposes)")
def download_apks(all_apks, virustotal, koodous, all_checks, output, from_file):
@@ -54,9 +56,12 @@ def download_apks(all_apks, virustotal, koodous, all_checks, output, from_file):
if from_file:
download = DownloadAPKs.from_json(from_file)
else:
if not output:
log.critical("You need to specify an output folder (with --output, -o) when extracting APKs from a device")
sys.exit(-1)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
download = DownloadAPKs(output_folder=output, all_apks=all_apks)
download.run()
@@ -81,7 +86,7 @@ def download_apks(all_apks, virustotal, koodous, all_checks, output, from_file):
#==============================================================================
@cli.command("check-adb", help="Check an Android device over adb")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True),
@click.option("--output", "-o", type=click.Path(exists=False),
help="Specify a path to a folder where you want to store JSON results")
@click.option("--list-modules", "-l", is_flag=True, help="Print list of available modules and exit")
@click.option("--module", "-m", help="Name of a single module you would like to run instead of all")
@@ -95,6 +100,13 @@ def check_adb(iocs, output, list_modules, module):
log.info("Checking Android through adb bridge")
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at %s", iocs)
@@ -127,11 +139,18 @@ def check_adb(iocs, output, list_modules, module):
#==============================================================================
@cli.command("check-backup", help="Check an Android Backup")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True), help=OUTPUT_HELP_MESSAGE)
@click.option("--output", "-o", type=click.Path(exists=False), help=OUTPUT_HELP_MESSAGE)
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
def check_backup(iocs, output, backup_path):
log.info("Checking ADB backup located at: %s", backup_path)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at %s", iocs)

View File

@@ -3,13 +3,15 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import json
import logging
import os
import pkg_resources
from tqdm import tqdm
from mvt.common.utils import get_sha256_from_file_path
from .modules.adb.base import AndroidExtraction
log = logging.getLogger(__name__)

View File

@@ -3,13 +3,13 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import requests
import logging
from rich.text import Text
from rich.table import Table
from rich.progress import track
import requests
from rich.console import Console
from rich.progress import track
from rich.table import Table
from rich.text import Text
log = logging.getLogger(__name__)

View File

@@ -3,12 +3,13 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import requests
import logging
from rich.text import Text
from rich.table import Table
from rich.progress import track
import requests
from rich.console import Console
from rich.progress import track
from rich.table import Table
from rich.text import Text
log = logging.getLogger(__name__)

View File

@@ -7,11 +7,11 @@ from .chrome_history import ChromeHistory
from .dumpsys_batterystats import DumpsysBatterystats
from .dumpsys_packages import DumpsysPackages
from .dumpsys_procstats import DumpsysProcstats
from .packages import Packages
from .processes import Processes
from .rootbinaries import RootBinaries
from .sms import SMS
from .whatsapp import Whatsapp
from .packages import Packages
from .rootbinaries import RootBinaries
ADB_MODULES = [ChromeHistory, SMS, Whatsapp, Processes,
DumpsysBatterystats, DumpsysProcstats,

View File

@@ -3,18 +3,19 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import logging
import os
import random
import string
import sys
import time
import logging
import tempfile
import time
from adb_shell.adb_device import AdbDeviceUsb
from adb_shell.auth.keygen import keygen, write_public_keyfile
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from adb_shell.exceptions import DeviceAuthError, AdbCommandFailureException
from usb1 import USBErrorBusy, USBErrorAccess
from adb_shell.exceptions import AdbCommandFailureException, DeviceAuthError
from usb1 import USBErrorAccess, USBErrorBusy
from mvt.common.module import MVTModule
@@ -112,6 +113,19 @@ class AndroidExtraction(MVTModule):
:returns: Output of command
"""
return self._adb_command(f"su -c {command}")
def _adb_check_file_exists(self, file):
"""Verify that a file exists.
:param file: Path of the file
:returns: Boolean indicating whether the file exists or not
"""
# Connect to the device over adb.
self._adb_connect()
# Check if we have root, if not raise an Exception.
self._adb_root_or_die()
return bool(self._adb_command_as_root(f"[ ! -f {file} ] || echo 1"))
def _adb_download(self, remote_path, local_path, progress_callback=None, retry_root=True):
"""Download a file form the device.

View File

@@ -3,11 +3,12 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import logging
import os
import sqlite3
import logging
from mvt.common.utils import convert_chrometime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (convert_chrometime_to_unix,
convert_timestamp_to_iso)
from .base import AndroidExtraction

View File

@@ -3,8 +3,8 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import logging
import os
from .base import AndroidExtraction

View File

@@ -3,8 +3,8 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import logging
import os
from .base import AndroidExtraction

View File

@@ -3,8 +3,8 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import logging
import os
from .base import AndroidExtraction

View File

@@ -3,8 +3,9 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import logging
import os
import pkg_resources
from .base import AndroidExtraction

View File

@@ -3,8 +3,9 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import logging
import os
import pkg_resources
from .base import AndroidExtraction

View File

@@ -3,16 +3,41 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import logging
import os
import sqlite3
import logging
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
from .base import AndroidExtraction
from mvt.common.utils import convert_timestamp_to_iso, check_for_links
log = logging.getLogger(__name__)
SMS_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
SMS_BUGLE_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
SMS_BUGLE_QUERY = """
SELECT
ppl.normalized_destination AS number,
p.timestamp AS timestamp,
CASE WHEN m.sender_id IN
(SELECT _id FROM participants WHERE contact_id=-1)
THEN 2 ELSE 1 END incoming, p.text AS text
FROM messages m, conversations c, parts p,
participants ppl, conversation_participants cp
WHERE (m.conversation_id = c._id)
AND (m._id = p.message_id)
AND (cp.conversation_id = c._id)
AND (cp.participant_id = ppl._id);
"""
SMS_MMSSMS_PATH = "data/data/com.android.providers.telephony/databases/mmssms.db"
SMS_MMSMS_QUERY = """
SELECT
address AS number,
date_sent AS timestamp,
type as incoming,
body AS text
FROM sms;
"""
class SMS(AndroidExtraction):
"""This module extracts all SMS messages containing links."""
@@ -50,20 +75,12 @@ class SMS(AndroidExtraction):
"""
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("""
SELECT
ppl.normalized_destination AS number,
p.timestamp AS timestamp,
CASE WHEN m.sender_id IN
(SELECT _id FROM participants WHERE contact_id=-1)
THEN 2 ELSE 1 END incoming, p.text AS text
FROM messages m, conversations c, parts p,
participants ppl, conversation_participants cp
WHERE (m.conversation_id = c._id)
AND (m._id = p.message_id)
AND (cp.conversation_id = c._id)
AND (cp.participant_id = ppl._id);
""")
if (self.SMS_DB_TYPE == 1):
cur.execute(SMS_BUGLE_QUERY)
elif (self.SMS_DB_TYPE == 2):
cur.execute(SMS_MMSMS_QUERY)
names = [description[0] for description in cur.description]
for item in cur:
@@ -85,7 +102,15 @@ class SMS(AndroidExtraction):
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
def run(self):
# Checking the SMS database path
try:
self._adb_process_file(os.path.join("/", SMS_PATH), self._parse_db)
if (self._adb_check_file_exists(os.path.join("/", SMS_BUGLE_PATH))):
self.SMS_DB_TYPE = 1
self._adb_process_file(os.path.join("/", SMS_BUGLE_PATH), self._parse_db)
elif (self._adb_check_file_exists(os.path.join("/", SMS_MMSSMS_PATH))):
self.SMS_DB_TYPE = 2
self._adb_process_file(os.path.join("/", SMS_MMSSMS_PATH), self._parse_db)
else:
self.log.error("No SMS database found")
except Exception as e:
self.log.error(e)

View File

@@ -3,13 +3,14 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import base64
import logging
import os
import sqlite3
import logging
import base64
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
from .base import AndroidExtraction
from mvt.common.utils import convert_timestamp_to_iso, check_for_links
log = logging.getLogger(__name__)

View File

@@ -3,13 +3,13 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import json
import os
import zlib
from mvt.common.module import MVTModule
from mvt.common.utils import check_for_links
from mvt.common.utils import convert_timestamp_to_iso
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
class SMS(MVTModule):

View File

@@ -3,11 +3,12 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import json
import os
from .url import URL
class Indicators:
"""This class is used to parse indicators from a STIX2 file and provide
functions to compare extracted artifacts to the indicators.

View File

@@ -3,16 +3,18 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import io
import os
import re
import csv
import glob
import io
import logging
import os
import re
import simplejson as json
from .indicators import Indicators
class DatabaseNotFoundError(Exception):
pass
@@ -123,7 +125,7 @@ class MVTModule(object):
else:
self.timeline_detected.append(record)
# De-duplicate timeline entries
# De-duplicate timeline entries.
self.timeline = self.timeline_deduplicate(self.timeline)
self.timeline_detected = self.timeline_deduplicate(self.timeline_detected)

View File

@@ -5,7 +5,8 @@
# From: https://gist.github.com/stanchan/bce1c2d030c76fe9223b5ff6ad0f03db
from click import command, option, Option, UsageError
from click import Option, UsageError, command, option
class MutuallyExclusiveOption(Option):
"""This class extends click to support mutually exclusive options.

View File

@@ -3,10 +3,11 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import re
import datetime
import hashlib
import os
import re
def convert_mactime_to_unix(timestamp, from_2001=True):
"""Converts Mac Standard Time to a Unix timestamp.

View File

@@ -3,16 +3,19 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import errno
import logging
import os
import sys
import click
import tarfile
import logging
from rich.logging import RichHandler
import click
from rich.logging import RichHandler
from rich.prompt import Prompt
from mvt.common.indicators import Indicators
from mvt.common.module import run_module, save_timeline
from mvt.common.options import MutuallyExclusiveOption
from mvt.common.indicators import Indicators
from .decrypt import DecryptBackup
from .modules.fs import BACKUP_MODULES, FS_MODULES
@@ -26,6 +29,8 @@ log = logging.getLogger(__name__)
# Help messages of repeating options.
OUTPUT_HELP_MESSAGE = "Specify a path to a folder where you want to store JSON results"
# Set this environment variable to a password if needed.
PASSWD_ENV = 'MVT_IOS_BACKUP_PASSWORD'
#==============================================================================
# Main
@@ -42,8 +47,7 @@ def cli():
@click.option("--destination", "-d", required=True,
help="Path to the folder where to store the decrypted backup")
@click.option("--password", "-p", cls=MutuallyExclusiveOption,
help="Password to use to decrypt the backup",
prompt="Enter backup password", hide_input=True, prompt_required=False,
help=f"Password to use to decrypt the backup (or, set {PASSWD_ENV} environment variable)",
mutually_exclusive=["key_file"])
@click.option("--key-file", "-k", cls=MutuallyExclusiveOption,
type=click.Path(exists=True),
@@ -52,13 +56,59 @@ def cli():
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
def decrypt_backup(destination, password, key_file, backup_path):
backup = DecryptBackup(backup_path, destination)
if password:
backup.decrypt_with_password(password)
elif key_file:
if key_file:
if PASSWD_ENV in os.environ:
log.info(f"Ignoring {PASSWD_ENV} environment variable, using --key-file '{key_file}' instead")
backup.decrypt_with_key_file(key_file)
elif password:
log.info("Your password may be visible in the process table because it was supplied on the command line!")
if PASSWD_ENV in os.environ:
log.info(f"Ignoring {PASSWD_ENV} environment variable, using --password argument instead")
backup.decrypt_with_password(password)
elif PASSWD_ENV in os.environ:
log.info(f"Using password from {PASSWD_ENV} environment variable")
backup.decrypt_with_password(os.environ[PASSWD_ENV])
else:
raise click.ClickException("Missing required option. Specify either "
"--password or --key-file.")
sekrit = Prompt.ask("Enter backup password", password=True)
backup.decrypt_with_password(sekrit)
backup.process_backup()
#==============================================================================
# Command: extract-key
#==============================================================================
@cli.command("extract-key", help="Extract decryption key from an iTunes backup")
@click.option("--password", "-p",
help=f"Password to use to decrypt the backup (or, set {PASSWD_ENV} environment variable)")
@click.option("--key-file", "-k",
help="Key file to be written (if unset, will print to STDOUT)",
required=False,
type=click.Path(exists=False, file_okay=True, dir_okay=False, writable=True))
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
def extract_key(password, backup_path, key_file):
backup = DecryptBackup(backup_path)
if password:
log.info("Your password may be visible in the process table because it was supplied on the command line!")
if PASSWD_ENV in os.environ:
log.info(f"Ignoring {PASSWD_ENV} environment variable, using --password argument instead")
elif PASSWD_ENV in os.environ:
log.info(f"Using password from {PASSWD_ENV} environment variable")
password = os.environ[PASSWD_ENV]
else:
password = Prompt.ask("Enter backup password", password=True)
backup.decrypt_with_password(password)
backup.get_key()
if key_file:
backup.write_key(key_file)
#==============================================================================
@@ -66,7 +116,7 @@ def decrypt_backup(destination, password, key_file, backup_path):
#==============================================================================
@cli.command("check-backup", help="Extract artifacts from an iTunes backup")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True), help=OUTPUT_HELP_MESSAGE)
@click.option("--output", "-o", type=click.Path(exists=False), help=OUTPUT_HELP_MESSAGE)
@click.option("--fast", "-f", is_flag=True, help="Avoid running time/resource consuming features")
@click.option("--list-modules", "-l", is_flag=True, help="Print list of available modules and exit")
@click.option("--module", "-m", help="Name of a single module you would like to run instead of all")
@@ -81,6 +131,13 @@ def check_backup(iocs, output, fast, backup_path, list_modules, module):
log.info("Checking iTunes backup located at: %s", backup_path)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at: %s", iocs)
@@ -116,7 +173,7 @@ def check_backup(iocs, output, fast, backup_path, list_modules, module):
#==============================================================================
@cli.command("check-fs", help="Extract artifacts from a full filesystem dump")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True), help=OUTPUT_HELP_MESSAGE)
@click.option("--output", "-o", type=click.Path(exists=False), help=OUTPUT_HELP_MESSAGE)
@click.option("--fast", "-f", is_flag=True, help="Avoid running time/resource consuming features")
@click.option("--list-modules", "-l", is_flag=True, help="Print list of available modules and exit")
@click.option("--module", "-m", help="Name of a single module you would like to run instead of all")
@@ -131,6 +188,13 @@ def check_fs(iocs, output, fast, dump_path, list_modules, module):
log.info("Checking filesystem dump located at: %s", dump_path)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at: %s", iocs)

View File

@@ -3,11 +3,12 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import binascii
import logging
import os
import shutil
import sqlite3
import logging
import binascii
from iOSbackup import iOSbackup
log = logging.getLogger(__name__)
@@ -17,7 +18,7 @@ class DecryptBackup:
using either a password or a key file.
"""
def __init__(self, backup_path, dest_path):
def __init__(self, backup_path, dest_path=None):
"""Decrypts an encrypted iOS backup.
:param backup_path: Path to the encrypted backup folder
:param dest_path: Path to the folder where to store the decrypted backup
@@ -25,8 +26,12 @@ class DecryptBackup:
self.backup_path = backup_path
self.dest_path = dest_path
self._backup = None
self._decryption_key = None
def process_backup(self):
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
def _process_backup(self):
manifest_path = os.path.join(self.dest_path, "Manifest.db")
# We extract a decrypted Manifest.db.
self._backup.getManifestDB()
@@ -68,9 +73,6 @@ class DecryptBackup:
"""
log.info("Decrypting iOS backup at path %s with password", self.backup_path)
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
try:
self._backup = iOSbackup(udid=os.path.basename(self.backup_path),
cleartextpassword=password,
@@ -78,9 +80,6 @@ class DecryptBackup:
except Exception as e:
log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct password?")
return
else:
self._process_backup()
def decrypt_with_key_file(self, key_file):
"""Decrypts an encrypted iOS backup using a key file.
@@ -89,9 +88,6 @@ class DecryptBackup:
log.info("Decrypting iOS backup at path %s with key file %s",
self.backup_path, key_file)
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
with open(key_file, "rb") as handle:
key_bytes = handle.read()
@@ -108,6 +104,31 @@ class DecryptBackup:
except Exception as e:
log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct key file?")
def get_key(self):
"""Retrieve and prints the encryption key.
"""
if not self._backup:
return
self._decryption_key = self._backup.getDecryptionKey()
log.info("Derived decryption key for backup at path %s is: \"%s\"",
self.backup_path, self._decryption_key)
def write_key(self, key_path):
"""Save extracted key to file.
:param key_path: Path to the file where to write the derived decryption key.
"""
if not self._decryption_key:
return
try:
with open(key_path, 'w') as handle:
handle.write(self._decryption_key)
except Exception as e:
log.exception(e)
log.critical("Failed to write key to file: %s", key_path)
return
else:
self._process_backup()
log.info("Wrote decryption key to file: %s. This file is equivalent to a plaintext password. Keep it safe!",
key_path)

View File

@@ -3,40 +3,42 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
from .manifest import Manifest
from .contacts import Contacts
from .net_netusage import Netusage
from .net_datausage import Datausage
from .safari_history import SafariHistory
from .safari_favicon import SafariFavicon
from .safari_browserstate import SafariBrowserState
from .webkit_indexeddb import WebkitIndexedDB
from .webkit_localstorage import WebkitLocalStorage
from .webkit_safariviewservice import WebkitSafariViewService
from .webkit_session_resource_log import WebkitSessionResourceLog
from .chrome_history import ChromeHistory
from .cache_files import CacheFiles
from .calls import Calls
from .chrome_favicon import ChromeFavicon
from .firefox_history import FirefoxHistory
from .chrome_history import ChromeHistory
from .contacts import Contacts
from .filesystem import Filesystem
from .firefox_favicon import FirefoxFavicon
from .version_history import IOSVersionHistory
from .firefox_history import FirefoxHistory
from .idstatuscache import IDStatusCache
from .locationd import LocationdClients
from .interactionc import InteractionC
from .locationd import LocationdClients
from .manifest import Manifest
from .net_datausage import Datausage
from .net_netusage import Netusage
from .safari_browserstate import SafariBrowserState
from .safari_favicon import SafariFavicon
from .safari_history import SafariHistory
from .sms import SMS
from .sms_attachments import SMSAttachments
from .calls import Calls
from .version_history import IOSVersionHistory
from .webkit_indexeddb import WebkitIndexedDB
from .webkit_localstorage import WebkitLocalStorage
from .webkit_resource_load_statistics import WebkitResourceLoadStatistics
from .webkit_safariviewservice import WebkitSafariViewService
from .webkit_session_resource_log import WebkitSessionResourceLog
from .whatsapp import Whatsapp
from .cache_files import CacheFiles
from .filesystem import Filesystem
BACKUP_MODULES = [SafariBrowserState, SafariHistory, Datausage, SMS, SMSAttachments,
ChromeHistory, ChromeFavicon, WebkitSessionResourceLog,
Calls, IDStatusCache, LocationdClients, InteractionC,
FirefoxHistory, FirefoxFavicon, Contacts, Manifest, Whatsapp]
WebkitResourceLoadStatistics, Calls, IDStatusCache, LocationdClients,
InteractionC, FirefoxHistory, FirefoxFavicon, Contacts, Manifest, Whatsapp]
FS_MODULES = [IOSVersionHistory, SafariHistory, SafariFavicon, SafariBrowserState,
WebkitIndexedDB, WebkitLocalStorage, WebkitSafariViewService,
WebkitSessionResourceLog, Datausage, Netusage, ChromeHistory,
WebkitResourceLoadStatistics, WebkitSessionResourceLog,
Datausage, Netusage, ChromeHistory,
ChromeFavicon, Calls, IDStatusCache, SMS, SMSAttachments,
LocationdClients, InteractionC, FirefoxHistory, FirefoxFavicon,
Contacts, CacheFiles, Whatsapp, Filesystem]

View File

@@ -3,15 +3,16 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import glob
import io
import os
import glob
import shutil
import sqlite3
import subprocess
from mvt.common.module import MVTModule
from mvt.common.module import DatabaseNotFoundError, DatabaseCorruptedError
from mvt.common.module import (DatabaseCorruptedError, DatabaseNotFoundError,
MVTModule)
class IOSExtraction(MVTModule):
"""This class provides a base for all iOS filesystem/backup extraction modules."""
@@ -20,6 +21,22 @@ class IOSExtraction(MVTModule):
is_fs_dump = False
is_sysdiagnose = False
def _is_database_malformed(self, file_path):
# Check if the database is malformed.
conn = sqlite3.connect(file_path)
cur = conn.cursor()
try:
recover = False
cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
except sqlite3.DatabaseError as e:
if "database disk image is malformed" in str(e):
recover = True
finally:
conn.close()
return recover
def _recover_database(self, file_path):
"""Tries to recover a malformed database by running a .clone command.
:param file_path: Path to the malformed database file.
@@ -49,6 +66,7 @@ class IOSExtraction(MVTModule):
"""Try to locate the module's database file from either an iTunes
backup or a full filesystem dump.
:param backup_id: iTunes backup database file's ID (or hash).
:param root_paths: Glob patterns for files to seek in filesystem dump.
"""
file_path = None
# First we check if the was an explicit file path specified.
@@ -84,18 +102,5 @@ class IOSExtraction(MVTModule):
else:
raise DatabaseNotFoundError("Unable to find the module's database file")
# Check if the database is corrupted.
conn = sqlite3.connect(self.file_path)
cur = conn.cursor()
try:
recover = False
cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
except sqlite3.DatabaseError as e:
if "database disk image is malformed" in str(e):
recover = True
finally:
conn.close()
if recover:
if self._is_database_malformed(self.file_path):
self._recover_database(self.file_path)

View File

@@ -8,6 +8,7 @@ import sqlite3
from .base import IOSExtraction
class CacheFiles(IOSExtraction):
def __init__(self, file_path=None, base_folder=None, output_folder=None,

View File

@@ -6,6 +6,7 @@
import sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from .base import IOSExtraction
CALLS_BACKUP_IDS = [

View File

@@ -5,7 +5,8 @@
import sqlite3
from mvt.common.utils import convert_chrometime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (convert_chrometime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction

View File

@@ -5,7 +5,8 @@
import sqlite3
from mvt.common.utils import convert_chrometime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (convert_chrometime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction
@@ -35,6 +36,14 @@ class ChromeHistory(IOSExtraction):
"data": f"{record['id']} - {record['url']} (visit ID: {record['visit_id']}, redirect source: {record['redirect_source']})"
}
def check_indicators(self):
if not self.indicators:
return
for result in self.results:
if self.indicators.check_domain(result["url"]):
self.detected.append(result)
def run(self):
self._find_ios_database(backup_ids=CHROME_HISTORY_BACKUP_IDS, root_paths=CHROME_HISTORY_ROOT_PATHS)
self.log.info("Found Chrome history database at path: %s", self.file_path)

View File

@@ -3,13 +3,14 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import datetime
import os
from mvt.common.utils import convert_timestamp_to_iso
from .base import IOSExtraction
class Filesystem(IOSExtraction):
"""This module extracts creation and modification date of files from a
full file-system dump."""

View File

@@ -4,8 +4,8 @@
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import sqlite3
from datetime import datetime
from mvt.common.url import URL
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso

View File

@@ -4,8 +4,8 @@
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import sqlite3
from datetime import datetime
from mvt.common.url import URL
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso

View File

@@ -3,10 +3,11 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import glob
import biplist
import collections
import glob
import os
import biplist
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso

View File

@@ -3,8 +3,9 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import glob
import os
import biplist
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso

View File

@@ -3,16 +3,18 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import datetime
import io
import os
import biplist
import sqlite3
import datetime
import biplist
from mvt.common.utils import convert_timestamp_to_iso
from .base import IOSExtraction
class Manifest(IOSExtraction):
"""This module extracts information from a backup Manifest.db file."""

View File

@@ -3,14 +3,15 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import sqlite3
import operator
import sqlite3
from pathlib import Path
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from .base import IOSExtraction
class NetBase(IOSExtraction):
"""This class provides a base for DataUsage and NetUsage extraction modules."""

View File

@@ -4,11 +4,12 @@
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import io
import biplist
import sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import keys_bytes_to_string
import biplist
from mvt.common.utils import (convert_mactime_to_unix,
convert_timestamp_to_iso, keys_bytes_to_string)
from .base import IOSExtraction

View File

@@ -6,8 +6,8 @@
import sqlite3
from base64 import b64encode
from mvt.common.utils import check_for_links
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (check_for_links, convert_mactime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction

View File

@@ -6,8 +6,8 @@
import sqlite3
from base64 import b64encode
from mvt.common.utils import check_for_links
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (check_for_links, convert_mactime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction

View File

@@ -3,8 +3,8 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import json
import datetime
import json
from mvt.common.utils import convert_timestamp_to_iso

View File

@@ -3,12 +3,13 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import datetime
import os
from mvt.common.utils import convert_timestamp_to_iso
from .base import IOSExtraction
from mvt.common.utils import convert_timestamp_to_iso
class WebkitBase(IOSExtraction):
"""This class is a base for other WebKit-related modules."""

View File

@@ -0,0 +1,98 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import datetime
import os
import sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from .base import IOSExtraction
WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH = "Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db"
WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db",
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/observations.db",
]
class WebkitResourceLoadStatistics(IOSExtraction):
"""This module extracts records from WebKit ResourceLoadStatistics observations.db.
"""
# TODO: Add serialize().
def __init__(self, file_path=None, base_folder=None, output_folder=None,
fast_mode=False, log=None, results=[]):
super().__init__(file_path=file_path, base_folder=base_folder,
output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results)
def check_indicators(self):
if not self.indicators:
return
self.detected = {}
for key, items in self.results.items():
for item in items:
if self.indicators.check_domain(item["registrable_domain"]):
if key not in self.detected:
self.detected[key] = [item,]
else:
self.detected[key].append(item)
def _process_observations_db(self, db_path, key):
self.log.info("Found WebKit ResourceLoadStatistics observations.db file at path %s", db_path)
if self._is_database_malformed(db_path):
self._recover_database(db_path)
conn = sqlite3.connect(db_path)
cur = conn.cursor()
try:
cur.execute("SELECT * from ObservedDomains;")
except sqlite3.OperationalError:
return
if not key in self.results:
self.results[key] = []
for row in cur:
self.results[key].append(dict(
domain_id=row[0],
registrable_domain=row[1],
last_seen=row[2],
had_user_interaction=bool(row[3]),
# TODO: Fix isodate.
last_seen_isodate=convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(int(row[2]))),
))
if len(self.results[key]) > 0:
self.log.info("Extracted a total of %d records from %s", len(self.results[key]), db_path)
def run(self):
self.results = {}
if self.is_backup:
manifest_db_path = os.path.join(self.base_folder, "Manifest.db")
if not os.path.exists(manifest_db_path):
self.log.info("Unable to search for WebKit observations.db files in backup because of missing Manifest.db")
return
try:
conn = sqlite3.connect(manifest_db_path)
cur = conn.cursor()
cur.execute("SELECT fileID, domain FROM Files WHERE relativePath = ?;", (WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH,))
except Exception as e:
self.log.error("Unable to search for WebKit observations.db files in backup because of failed query to Manifest.db: %s", e)
for row in cur:
file_id = row[0]
domain = row[1]
db_path = os.path.join(self.base_folder, file_id[0:2], file_id)
if os.path.exists(db_path):
self._process_observations_db(db_path=db_path, key=f"{domain}/{WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH}")
elif self.is_fs_dump:
for db_path in self._find_paths(WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS):
self._process_observations_db(db_path=db_path, key=os.path.relpath(db_path, self.base_folder))

View File

@@ -3,8 +3,9 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import glob
import os
import biplist
from mvt.common.utils import convert_timestamp_to_iso

View File

@@ -3,10 +3,11 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import sqlite3
import logging
import sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso, check_for_links
from mvt.common.utils import (check_for_links, convert_mactime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction

View File

@@ -3,189 +3,234 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
IPHONE_MODELS = [
{"description": "iPhone 4S", "identifier": "iPhone4,1"},
{"description": "iPhone 5", "identifier": "iPhone5,1"},
{"description": "iPhone 5", "identifier": "iPhone5,2"},
{"description": "iPhone 5c", "identifier": "iPhone5,3"},
{"description": "iPhone 5c", "identifier": "iPhone5,4"},
{"description": "iPhone 5s", "identifier": "iPhone6,1"},
{"description": "iPhone 5s", "identifier": "iPhone6,2"},
{"description": "iPhone 6 Plus", "identifier": "iPhone7,1"},
{"description": "iPhone 6", "identifier": "iPhone7,2"},
{"description": "iPhone 6s", "identifier": "iPhone8,1"},
{"description": "iPhone 6s", "identifier": "iPhone8,1"},
{"description": "iPhone 6s Plus", "identifier": "iPhone8,2"},
{"description": "iPhone 6s Plus", "identifier": "iPhone8,2"},
{"description": "iPhone SE (1st generation)", "identifier": "iPhone8,4"},
{"description": "iPhone SE (1st generation)", "identifier": "iPhone8,4"},
{"description": "iPhone 7", "identifier": "iPhone9,1"},
{"description": "iPhone 7 Plus", "identifier": "iPhone9,2"},
{"description": "iPhone 7", "identifier": "iPhone9,3"},
{"description": "iPhone 7 Plus", "identifier": "iPhone9,4"},
{"description": "iPhone 8", "identifier": "iPhone10,1"},
{"description": "iPhone 8 Plus", "identifier": "iPhone10,2"},
{"description": "iPhone X", "identifier": "iPhone10,3"},
{"description": "iPhone 8", "identifier": "iPhone10,4"},
{"description": "iPhone 8 Plus", "identifier": "iPhone10,5"},
{"description": "iPhone X", "identifier": "iPhone10,6"},
{"description": "iPhone XS", "identifier": "iPhone11,2"},
{"description": "iPhone XS Max", "identifier": "iPhone11,4"},
{"description": "iPhone XS Max", "identifier": "iPhone11,6"},
{"description": "iPhone XR", "identifier": "iPhone11,8"},
{"description": "iPhone 11", "identifier": "iPhone12,1"},
{"description": "iPhone 11 Pro", "identifier": "iPhone12,3"},
{"description": "iPhone 11 Pro Max", "identifier": "iPhone12,5"},
{"description": "iPhone SE (2nd generation)", "identifier": "iPhone12,8"},
{"description": "iPhone 12 mini", "identifier": "iPhone13,1"},
{"description": "iPhone 12", "identifier": "iPhone13,2"},
{"description": "iPhone 12 Pro", "identifier": "iPhone13,3"},
{"description": "iPhone 12 Pro Max", "identifier": "iPhone13,4"},
]
IPHONE_IOS_VERSIONS = [
{"build": "7E18", "version": "3.1.3"},
{"build": "7D11", "version": "3.1.2"},
{"build": "7C144", "version": "3.1"},
{"build": "7A400", "version": "3.0.1"},
{"build": "7A341", "version": "3.0"},
{"build": "5H11", "version": "2.2.1"},
{"build": "5G77", "version": "2.2"},
{"build": "5F136", "version": "2.1"},
{"build": "5C1", "version": "2.0.2"},
{"build": "5B108", "version": "2.0.1"},
{"build": "5A347", "version": "2.0"},
{"build": "4A102", "version": "1.1.4"},
{"build": "4A93", "version": "1.1.3"},
{"build": "3B48b", "version": "1.1.2"},
{"build": "3A109a", "version": "1.1.1"},
{"build": "1C28", "version": "1.0.2"},
{"build": "1C25", "version": "1.0.1"},
{"build": "1A543a", "version": "1.0"},
{"build": "8C148", "version": "4.2"},
{"build": "8B117", "version": "4.1"},
{"build": "8A306", "version": "4.0.1"},
{"build": "1C25", "version": "1.0.1"},
{"build": "1C28", "version": "1.0.2"},
{"build": "3A109a", "version": "1.1.1"},
{"build": "3B48b", "version": "1.1.2"},
{"build": "4A93", "version": "1.1.3"},
{"build": "4A102", "version": "1.1.4"},
{"build": "5A347", "version": "2.0"},
{"build": "5B108", "version": "2.0.1"},
{"build": "5C1", "version": "2.0.2"},
{"build": "5F136", "version": "2.1"},
{"build": "5G77", "version": "2.2"},
{"build": "5H11", "version": "2.2.1"},
{"build": "7A341", "version": "3.0"},
{"build": "7A400", "version": "3.0.1"},
{"build": "7C144", "version": "3.1"},
{"build": "7D11", "version": "3.1.2"},
{"build": "7E18", "version": "3.1.3"},
{"build": "8A293", "version": "4.0"},
{"build": "10B500", "version": "6.1.6"},
{"build": "10B329", "version": "6.1.3"},
{"build": "10B146", "version": "6.1.2"},
{"build": "10B141", "version": "6.1"},
{"build": "10A523", "version": "6.0.1"},
{"build": "10A403", "version": "6.0"},
{"build": "9B206", "version": "5.1.1"},
{"build": "9B176", "version": "5.1"},
{"build": "9A405", "version": "5.0.1"},
{"build": "9A334", "version": "5.0"},
{"build": "8L1", "version": "4.3.5"},
{"build": "8K2", "version": "4.3.4"},
{"build": "8J2", "version": "4.3.3"},
{"build": "8F190", "version": "4.3"},
{"build": "8A306", "version": "4.0.1"},
{"build": "8B117", "version": "4.1"},
{"build": "8C148", "version": "4.2"},
{"build": "8C148a", "version": "4.2.1"},
{"build": "11D257", "version": "7.1.2"},
{"build": "11D201", "version": "7.1.1"},
{"build": "11D169", "version": "7.1"},
{"build": "11B651", "version": "7.0.6"},
{"build": "11B554a", "version": "7.0.4"},
{"build": "11B511", "version": "7.0.3"},
{"build": "10B144", "version": "6.1"},
{"build": "9B208", "version": "5.1.1"},
{"build": "8C148", "version": "4.2.1"},
{"build": "11D167", "version": "7.1"},
{"build": "8E600", "version": "4.2.10"},
{"build": "8E501", "version": "4.2.9"},
{"build": "8E401", "version": "4.2.8"},
{"build": "13G37", "version": "9.3.6"},
{"build": "13G36", "version": "9.3.5"},
{"build": "13G35", "version": "9.3.4"},
{"build": "13G34", "version": "9.3.3"},
{"build": "13F69", "version": "9.3.2"},
{"build": "13E238", "version": "9.3.1"},
{"build": "8E501", "version": "4.2.9"},
{"build": "8F190", "version": "4.3"},
{"build": "8J2", "version": "4.3.3"},
{"build": "8K2", "version": "4.3.4"},
{"build": "8L1", "version": "4.3.5"},
{"build": "9A334", "version": "5.0"},
{"build": "9A405", "version": "5.0.1"},
{"build": "9A406", "version": "5.0.1"},
{"build": "9B176", "version": "5.1"},
{"build": "9B179", "version": "5.1"},
{"build": "9B206", "version": "5.1.1"},
{"build": "9B208", "version": "5.1.1"},
{"build": "10A403", "version": "6.0"},
{"build": "10A405", "version": "6.0"},
{"build": "10A523", "version": "6.0.1"},
{"build": "10A525", "version": "6.0.1"},
{"build": "10A551", "version": "6.0.2"},
{"build": "10B141", "version": "6.1"},
{"build": "10B144", "version": "6.1"},
{"build": "10B142", "version": "6.1"},
{"build": "10B143", "version": "6.1"},
{"build": "10B145", "version": "6.1.1"},
{"build": "10B146", "version": "6.1.2"},
{"build": "10B329", "version": "6.1.3"},
{"build": "10B350", "version": "6.1.4"},
{"build": "10B500", "version": "6.1.6"},
{"build": "11B511", "version": "7.0.3"},
{"build": "11B554a", "version": "7.0.4"},
{"build": "11B601", "version": "7.0.5"},
{"build": "11B651", "version": "7.0.6"},
{"build": "11D169", "version": "7.1"},
{"build": "11D167", "version": "7.1"},
{"build": "11D201", "version": "7.1.1"},
{"build": "11D257", "version": "7.1.2"},
{"build": "12A365", "version": "8.0"},
{"build": "12A366", "version": "8.0"},
{"build": "12A402", "version": "8.0.1"},
{"build": "12A405", "version": "8.0.2"},
{"build": "12B411", "version": "8.1"},
{"build": "12B435", "version": "8.1.1"},
{"build": "12B436", "version": "8.1.1"},
{"build": "12B440", "version": "8.1.2"},
{"build": "12B466", "version": "8.1.3"},
{"build": "12D508", "version": "8.2"},
{"build": "12F70", "version": "8.3"},
{"build": "12H143", "version": "8.4"},
{"build": "12H321", "version": "8.4.1"},
{"build": "13A344", "version": "9.0"},
{"build": "13A342", "version": "9.0"},
{"build": "13A343", "version": "9.0"},
{"build": "13A404", "version": "9.0.1"},
{"build": "13A405", "version": "9.0.1"},
{"build": "13A452", "version": "9.0.2"},
{"build": "13B143", "version": "9.1"},
{"build": "13C75", "version": "9.2"},
{"build": "13D15", "version": "9.2.1"},
{"build": "13D20", "version": "9.2.1"},
{"build": "13E237", "version": "9.3"},
{"build": "13E233", "version": "9.3"},
{"build": "13D15", "version": "9.2.1"},
{"build": "13C75", "version": "9.2"},
{"build": "13B143", "version": "9.1"},
{"build": "13A452", "version": "9.0.2"},
{"build": "13A404", "version": "9.0.1"},
{"build": "13A344", "version": "9.0"},
{"build": "12H321", "version": "8.4.1"},
{"build": "12H143", "version": "8.4"},
{"build": "12F70", "version": "8.3"},
{"build": "12D508", "version": "8.2"},
{"build": "12B466", "version": "8.1.3"},
{"build": "12B440", "version": "8.1.2"},
{"build": "12B435", "version": "8.1.1"},
{"build": "12B411", "version": "8.1"},
{"build": "12A405", "version": "8.0.2"},
{"build": "12A402", "version": "8.0.1"},
{"build": "12A365", "version": "8.0"},
{"build": "10B145", "version": "6.1.1"},
{"build": "10B142", "version": "6.1"},
{"build": "9B179", "version": "5.1"},
{"build": "9A406", "version": "5.0.1"},
{"build": "14G61", "version": "10.3.4"},
{"build": "14G60", "version": "10.3.3"},
{"build": "14F89", "version": "10.3.2"},
{"build": "14E304", "version": "10.3.1"},
{"build": "14E277", "version": "10.3"},
{"build": "14D27", "version": "10.2.1"},
{"build": "14C92", "version": "10.2"},
{"build": "13E234", "version": "9.3"},
{"build": "13E238", "version": "9.3.1"},
{"build": "13F69", "version": "9.3.2"},
{"build": "13G34", "version": "9.3.3"},
{"build": "13G35", "version": "9.3.4"},
{"build": "13G36", "version": "9.3.5"},
{"build": "13G37", "version": "9.3.6"},
{"build": "14A403", "version": "10.0.1"},
{"build": "14A456", "version": "10.0.2"},
{"build": "14A551", "version": "10.0.3"},
{"build": "14B72", "version": "10.1"},
{"build": "14B72c", "version": "10.1"},
{"build": "14B150", "version": "10.1.1"},
{"build": "14B100", "version": "10.1.1"},
{"build": "14B72", "version": "10.1"},
{"build": "14A456", "version": "10.0.2"},
{"build": "14A403", "version": "10.0.1"},
{"build": "10B350", "version": "6.1.4"},
{"build": "10B143", "version": "6.1"},
{"build": "10A551", "version": "6.0.2"},
{"build": "10A525", "version": "6.0.1"},
{"build": "10A405", "version": "6.0"},
{"build": "11B601", "version": "7.0.5"},
{"build": "18F72", "version": "14.6"},
{"build": "18G69", "version": "14.7"},
{"build": "18E199", "version": "14.5"},
{"build": "18E212", "version": "14.5.1"},
{"build": "14C92", "version": "10.2"},
{"build": "14D27", "version": "10.2.1"},
{"build": "14E277", "version": "10.3"},
{"build": "14E304", "version": "10.3.1"},
{"build": "14F89", "version": "10.3.2"},
{"build": "14G60", "version": "10.3.3"},
{"build": "14G61", "version": "10.3.4"},
{"build": "15A372", "version": "11.0"},
{"build": "15A402", "version": "11.0.1"},
{"build": "15A421", "version": "11.0.2"},
{"build": "15A432", "version": "11.0.3"},
{"build": "15B93", "version": "11.1"},
{"build": "15B150", "version": "11.1.1"},
{"build": "15B202", "version": "11.1.2"},
{"build": "15C114", "version": "11.2"},
{"build": "15C153", "version": "11.2.1"},
{"build": "15C202", "version": "11.2.2"},
{"build": "15D60", "version": "11.2.5"},
{"build": "15D100", "version": "11.2.6"},
{"build": "15E216", "version": "11.3"},
{"build": "15E302", "version": "11.3.1"},
{"build": "15F79", "version": "11.4"},
{"build": "15G77", "version": "11.4.1"},
{"build": "16A366", "version": "12.0"},
{"build": "16A404", "version": "12.0.1"},
{"build": "16A405", "version": "12.0.1"},
{"build": "16B92", "version": "12.1"},
{"build": "16B94", "version": "12.1"},
{"build": "16B93", "version": "12.1"},
{"build": "16C50", "version": "12.1.1"},
{"build": "16C104", "version": "12.1.2"},
{"build": "16C101", "version": "12.1.2"},
{"build": "16D39", "version": "12.1.3"},
{"build": "16D40", "version": "12.1.3"},
{"build": "16D57", "version": "12.1.4"},
{"build": "16E227", "version": "12.2"},
{"build": "16F156", "version": "12.3"},
{"build": "16F203", "version": "12.3.1"},
{"build": "16F250", "version": "12.3.2"},
{"build": "16G77", "version": "12.4"},
{"build": "16G102", "version": "12.4.1"},
{"build": "16G114", "version": "12.4.2"},
{"build": "16G130", "version": "12.4.3"},
{"build": "16G161", "version": "12.4.5"},
{"build": "16G183", "version": "12.4.6"},
{"build": "16G192", "version": "12.4.7"},
{"build": "16G201", "version": "12.4.8"},
{"build": "16H5", "version": "12.4.9"},
{"build": "16H20", "version": "12.5"},
{"build": "16H22", "version": "12.5.1"},
{"build": "17A577", "version": "13.0"},
{"build": "17A844", "version": "13.1"},
{"build": "17A854", "version": "13.1.1"},
{"build": "17A860", "version": "13.1.2"},
{"build": "17A861", "version": "13.1.2"},
{"build": "17A878", "version": "13.1.3"},
{"build": "17B84", "version": "13.2"},
{"build": "17B102", "version": "13.2.2"},
{"build": "17B111", "version": "13.2.3"},
{"build": "17C54", "version": "13.3"},
{"build": "17D50", "version": "13.3.1"},
{"build": "17E255", "version": "13.4"},
{"build": "17E262", "version": "13.4.1"},
{"build": "17E8258", "version": "13.4.1"},
{"build": "17F75", "version": "13.5"},
{"build": "17F80", "version": "13.5.1"},
{"build": "17G68", "version": "13.6"},
{"build": "17G80", "version": "13.6.1"},
{"build": "17H35", "version": "13.7"},
{"build": "18A373", "version": "14.0"},
{"build": "18A393", "version": "14.0.1"},
{"build": "18A8395", "version": "14.1"},
{"build": "18B92", "version": "14.2"},
{"build": "18C66", "version": "14.3"},
{"build": "18D52", "version": "14.4"},
{"build": "18D61", "version": "14.4.1"},
{"build": "18D70", "version": "14.4.2"},
{"build": "18C66", "version": "14.3"},
{"build": "18B92", "version": "14.2"},
{"build": "18A8395", "version": "14.1"},
{"build": "18A393", "version": "14.0.1"},
{"build": "18A373", "version": "14.0"},
{"build": "17H35", "version": "13.7"},
{"build": "17G80", "version": "13.6.1"},
{"build": "17G68", "version": "13.6"},
{"build": "17F80", "version": "13.5.1"},
{"build": "17F75", "version": "13.5"},
{"build": "17E262", "version": "13.4.1"},
{"build": "17E255", "version": "13.4"},
{"build": "17C54", "version": "13.3"},
{"build": "17D50", "version": "13.3.1"},
{"build": "17B111", "version": "13.2.3"},
{"build": "17B102", "version": "13.2.2"},
{"build": "17B84", "version": "13.2"},
{"build": "17A878", "version": "13.1.3"},
{"build": "17A860", "version": "13.1.2"},
{"build": "17A854", "version": "13.1.1"},
{"build": "17A844", "version": "13.1"},
{"build": "17A577", "version": "13.0"},
{"build": "16H22", "version": "12.5.1"},
{"build": "16H20", "version": "12.5"},
{"build": "16H5", "version": "12.4.9"},
{"build": "16G201", "version": "12.4.8"},
{"build": "16G192", "version": "12.4.7"},
{"build": "16G183", "version": "12.4.6"},
{"build": "16G161", "version": "12.4.5"},
{"build": "16G130", "version": "12.4.3"},
{"build": "16G114", "version": "12.4.2"},
{"build": "16G102", "version": "12.4.1"},
{"build": "16G77", "version": "12.4"},
{"build": "16F203", "version": "12.3.1"},
{"build": "16F156", "version": "12.3"},
{"build": "16E227", "version": "12.2"},
{"build": "16D57", "version": "12.1.4"},
{"build": "16D39", "version": "12.1.3"},
{"build": "16C104", "version": "12.1.2"},
{"build": "16C101", "version": "12.1.2"},
{"build": "16C50", "version": "12.1.1"},
{"build": "16B92", "version": "12.1"},
{"build": "16A404", "version": "12.0.1"},
{"build": "16A366", "version": "12.0"},
{"build": "15G77", "version": "11.4.1"},
{"build": "15F79", "version": "11.4"},
{"build": "15E302", "version": "11.3.1"},
{"build": "15E216", "version": "11.3"},
{"build": "15D100", "version": "11.2.6"},
{"build": "15D60", "version": "11.2.5"},
{"build": "15C202", "version": "11.2.2"},
{"build": "15C153", "version": "11.2.1"},
{"build": "15C114", "version": "11.2"},
{"build": "15B202", "version": "11.1.2"},
{"build": "15B150", "version": "11.1.1"},
{"build": "15B93", "version": "11.1"},
{"build": "15A432", "version": "11.0.3"},
{"build": "15A421", "version": "11.0.2"},
{"build": "15A402", "version": "11.0.1"},
{"build": "15A372", "version": "11.0"},
{"build": "13D20", "version": "9.2.1"},
{"build": "12B436", "version": "8.1.1"},
{"build": "12A366", "version": "8.0"},
{"build": "13E234", "version": "9.3"},
{"build": "13A405", "version": "9.0.1"},
{"build": "13A342", "version": "9.0"},
{"build": "13A343", "version": "9.0"},
{"build": "14B72c", "version": "10.1"},
{"build": "14A551", "version": "10.0.3"},
{"build": "16F250", "version": "12.3.2"},
{"build": "17A861", "version": "13.1.2"},
{"build": "16D40", "version": "12.1.3"},
{"build": "16A405", "version": "12.0.1"},
{"build": "16B94", "version": "12.1"},
{"build": "16B93", "version": "12.1"},
{"build": "17E8258", "version": "13.4.1"}
{"build": "18E199", "version": "14.5"},
{"build": "18E212", "version": "14.5.1"},
{"build": "18F72", "version": "14.6"},
{"build": "18G69", "version": "14.7"},
]
def get_device_desc_from_id(identifier, devices_list=IPHONE_MODELS):
for model in IPHONE_MODELS:
if identifier == model["identifier"]:
return model["description"]
def find_version_by_build(build):
build = build.upper()
for version in IPHONE_IOS_VERSIONS:

View File

@@ -4,10 +4,11 @@
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
from setuptools import setup, find_packages
from setuptools import find_packages, setup
__package_name__ = "mvt"
__version__ = "1.0.13"
__version__ = "1.0.15"
__description__ = "Mobile Verification Toolkit"
this_directory = os.path.abspath(os.path.dirname(__file__))