mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-15 10:02:43 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87ffd9e003 | ||
|
|
19f355810a | ||
|
|
38b7aa6032 | ||
|
|
feb285015a | ||
|
|
933ee65897 | ||
|
|
ad9ab1aeba | ||
|
|
4debee72cd | ||
|
|
d7031bd25f | ||
|
|
5b5b065bc4 | ||
|
|
59206fc450 | ||
|
|
7b1b31f7be | ||
|
|
270e002f1b | ||
|
|
53adc05338 | ||
|
|
d7f29a4e88 | ||
|
|
444e70a6eb | ||
|
|
b264ae946d | ||
|
|
bfcfb3aa06 | ||
|
|
3e7d85039a | ||
|
|
632409c81d | ||
|
|
6df6064370 | ||
|
|
99e80fd942 | ||
|
|
9451da4514 | ||
|
|
5ac0025470 | ||
|
|
9a6c4d251e | ||
|
|
eda1976518 | ||
|
|
c966eea7e6 | ||
|
|
abcbefe359 | ||
|
|
22d090569c | ||
|
|
d490344142 | ||
|
|
7f361fb600 | ||
|
|
18ed58cbf9 | ||
|
|
3a6f57502e | ||
|
|
490fb12302 | ||
|
|
e2d82b0349 | ||
|
|
1bf7f54c72 | ||
|
|
60a2dbb860 | ||
|
|
5e03c28dbd | ||
|
|
4fb6e204d1 | ||
|
|
f4340bd4f9 | ||
|
|
7947d413b5 | ||
|
|
45beb6eeda | ||
|
|
ad81d5c450 | ||
|
|
47df94fa12 | ||
|
|
e5003b6490 | ||
|
|
99640ac08c | ||
|
|
30d0348256 | ||
|
|
af4826070a | ||
|
|
9fbcce4340 | ||
|
|
ece88744ed | ||
|
|
732db070f2 |
21
.github/workflows/lint-python.yml
vendored
21
.github/workflows/lint-python.yml
vendored
@@ -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
43
.github/workflows/python-package.yml
vendored
Normal 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
|
||||
36
.github/workflows/python-publish.yml
vendored
36
.github/workflows/python-publish.yml
vendored
@@ -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
2
.gitignore
vendored
@@ -129,3 +129,5 @@ dmypy.json
|
||||
.pyre/
|
||||
*.pyc
|
||||
|
||||
# Temporal files
|
||||
*~
|
||||
|
||||
@@ -21,6 +21,7 @@ RUN apt install -y build-essential \
|
||||
libplist-dev \
|
||||
libusbmuxd-dev \
|
||||
libssl-dev \
|
||||
sqlite3 \
|
||||
pkg-config
|
||||
|
||||
# Clean up
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Mobile Verification Toolkit
|
||||
|
||||
[](https://pypi.org/project/mvt/)
|
||||
[](https://mvt.readthedocs.io)
|
||||
[](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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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/).
|
||||
|
||||
32
docs/iocs.md
Normal file
32
docs/iocs.md
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
98
mvt/ios/modules/fs/webkit_resource_load_statistics.py
Normal file
98
mvt/ios/modules/fs/webkit_resource_load_statistics.py
Normal 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))
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -4,186 +4,186 @@
|
||||
# https://github.com/mvt-project/mvt/blob/main/LICENSE
|
||||
|
||||
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 find_version_by_build(build):
|
||||
|
||||
5
setup.py
5
setup.py
@@ -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.14"
|
||||
__description__ = "Mobile Verification Toolkit"
|
||||
|
||||
this_directory = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
Reference in New Issue
Block a user