mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-15 10:02:43 +00:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71e270fdf8 | ||
|
|
8125f1ba14 | ||
|
|
96e4a9a4a4 | ||
|
|
24d7187303 | ||
|
|
6af6c52f60 | ||
|
|
fdaf2fc760 | ||
|
|
fda621672d | ||
|
|
ce6cc771b4 | ||
|
|
e1e4476bee | ||
|
|
9582778adf | ||
|
|
5e6e4fa8d0 | ||
|
|
9e5a412fe2 | ||
|
|
763cb6e06c | ||
|
|
cbdbf41e1e | ||
|
|
cf630f7c2b | ||
|
|
3d6e01179a | ||
|
|
8260bda308 | ||
|
|
30e00e0707 | ||
|
|
88e2576334 | ||
|
|
076930c2c9 | ||
|
|
8a91e64bb9 | ||
|
|
bdbfe02315 | ||
|
|
54eaf046b0 | ||
|
|
23e4babbc9 | ||
|
|
78b9fcd50c | ||
|
|
4eb7a64614 | ||
|
|
e512e0b72f | ||
|
|
7884c28253 | ||
|
|
8ca7030195 | ||
|
|
f78c671885 | ||
|
|
411ac53522 | ||
|
|
8be60e8a04 | ||
|
|
8a484b3b24 | ||
|
|
0a7512cfb2 | ||
|
|
257f3732e3 | ||
|
|
8d93ab66c9 | ||
|
|
6e19d34700 | ||
|
|
271cdede0f | ||
|
|
88324c7c42 | ||
|
|
ec93c3d8b8 | ||
|
|
1288f8ca53 | ||
|
|
290776a286 | ||
|
|
44b677fdb2 | ||
|
|
3ae822d3ac | ||
|
|
7940fb2879 | ||
|
|
af7bc3ca31 | ||
|
|
d606f9570f | ||
|
|
15c0d71933 | ||
|
|
24c89183a3 | ||
|
|
e5f7727c80 | ||
|
|
7b00f03f03 | ||
|
|
9f696dcb72 | ||
|
|
ef139effdb | ||
|
|
2302c9fb1c | ||
|
|
9bb8ae5187 | ||
|
|
76e6138d77 | ||
|
|
0bc660a2b3 | ||
|
|
7ae9ecbf5a | ||
|
|
1e8278aeec | ||
|
|
995ebc02cf | ||
|
|
12e0f14400 | ||
|
|
6ef5b9d311 | ||
|
|
33e90c1707 | ||
|
|
706c429595 | ||
|
|
f011fd19e8 | ||
|
|
bc48dc2cf5 | ||
|
|
f3c0948283 | ||
|
|
be24680046 | ||
|
|
a3d10c1824 | ||
|
|
b2afce5c79 | ||
|
|
b2e210e91c | ||
|
|
6f83bf5ae1 | ||
|
|
a979b82ec6 | ||
|
|
eaef75d931 | ||
|
|
1650aea248 | ||
|
|
bc3634bf30 | ||
|
|
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 | ||
|
|
2389d5e52d | ||
|
|
ccf0f3f18e | ||
|
|
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
|
||||
*~
|
||||
|
||||
7
AUTHORS
Normal file
7
AUTHORS
Normal file
@@ -0,0 +1,7 @@
|
||||
MVT was originally authored by Claudio Guarnieri <nex@nex.sx>.
|
||||
|
||||
For an up-to-date list of all contributors visit:
|
||||
https://github.com/mvt-project/mvt/graphs/contributors
|
||||
|
||||
Or run:
|
||||
git shortlog -s -n
|
||||
@@ -21,6 +21,7 @@ RUN apt install -y build-essential \
|
||||
libplist-dev \
|
||||
libusbmuxd-dev \
|
||||
libssl-dev \
|
||||
sqlite3 \
|
||||
pkg-config
|
||||
|
||||
# Clean up
|
||||
|
||||
29
README.md
29
README.md
@@ -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.
|
||||
|
||||
@@ -15,37 +15,20 @@ It has been developed and released by the [Amnesty International Security Lab](h
|
||||
|
||||
## Installation
|
||||
|
||||
MVT can be installed from sources or conveniently using:
|
||||
MVT can be installed from sources or from [PyPi](https://pypi.org/project/mvt/) (you will need some dependencies, check the [documentation](https://docs.mvt.re/en/latest/install.html)):
|
||||
|
||||
```
|
||||
pip3 install mvt
|
||||
```
|
||||
|
||||
You will need some dependencies, so please check the [documentation](https://mvt.readthedocs.io/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).
|
||||
|
||||
**Please note:** MVT is best run on Linux or Mac systems. [It does not currently support running natively on Windows.](https://docs.mvt.re/en/latest/install.html#mvt-on-windows)
|
||||
|
||||
## Usage
|
||||
|
||||
MVT provides two commands `mvt-ios` and `mvt-android` with the following subcommands available:
|
||||
|
||||
* `mvt-ios`:
|
||||
* `check-backup`: Extract artifacts from an iTunes backup
|
||||
* `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
|
||||
* `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/).
|
||||
|
||||
MVT provides two commands `mvt-ios` and `mvt-android`. [Check out the documentation to learn how to use them!](https://docs.mvt.re/).
|
||||
|
||||
## License
|
||||
|
||||
The purpose of MVT is to facilitate the ***consensual forensic analysis*** of devices of those who might be targets of sophisticated mobile spyware attacks, especially members of civil society and marginalized communities. We do not want MVT to enable privacy violations of non-consenting individuals. Therefore, the goal of this license is to prohibit the use of MVT (and any other software licensed the same) for the purpose of *adversarial forensics*.
|
||||
|
||||
In order to achieve this, MVT is released under an adaptation of [Mozilla Public License v2.0](https://www.mozilla.org/MPL). This modified license includes a new clause 3.0, "Consensual Use Restriction" which permits the use of the licensed software (and any *"Larger Work"* derived from it) exclusively with the explicit consent of the person/s whose data is being extracted and/or analysed (*"Data Owner"*).
|
||||
|
||||
[Read the LICENSE](https://github.com/mvt-project/mvt/blob/main/LICENSE)
|
||||
The purpose of MVT is to facilitate the ***consensual forensic analysis*** of devices of those who might be targets of sophisticated mobile spyware attacks, especially members of civil society and marginalized communities. We do not want MVT to enable privacy violations of non-consenting individuals. In order to achieve this, MVT is released under its own license. [Read more here.](https://docs.mvt.re/en/latest/license.html)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -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()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -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,8 +1,3 @@
|
||||
# Methodology for Android forensic
|
||||
|
||||
For different technical reasons, it is more complex to do a forensic analysis of an Android phone.
|
||||
|
||||
Currently MVT allows to perform two different checks on an Android phone:
|
||||
|
||||
* Download APKs installed in order to analyze them
|
||||
* Extract Android backup in order to look for suspicious SMS
|
||||
TODO
|
||||
|
||||
@@ -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/).
|
||||
|
||||
@@ -12,6 +12,8 @@ sudo apt install python3 python3-pip libusb-1.0-0 sqlite3
|
||||
|
||||
*libusb-1.0-0* is not required if you intend to only use `mvt-ios` and not `mvt-android`.
|
||||
|
||||
When working with Android devices you should additionally install [Android SDK Platform Tools](https://developer.android.com/studio/releases/platform-tools). If you prefer to install a package made available by your distribution of choice, please make sure the version is recent to ensure compatibility with modern Android devices.
|
||||
|
||||
## Dependencies on Mac
|
||||
|
||||
Running MVT on Mac requires Xcode and [homebrew](https://brew.sh) to be installed.
|
||||
@@ -24,6 +26,20 @@ brew install python3 libusb sqlite3
|
||||
|
||||
*libusb* is not required if you intend to only use `mvt-ios` and not `mvt-android`.
|
||||
|
||||
When working with Android devices you should additionally install Android SDK Platform Tools:
|
||||
|
||||
```bash
|
||||
brew install --cask android-platform-tools
|
||||
```
|
||||
|
||||
Or by downloading the [official binary releases](https://developer.android.com/studio/releases/platform-tools).
|
||||
|
||||
## MVT on Windows
|
||||
|
||||
MVT does not currently officially support running natively on Windows. While most functionality should work out of the box, there are known issues especially with `mvt-android`.
|
||||
|
||||
It is recommended to try installing and running MVT from [Windows Subsystem Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) and follow Linux installation instructions for your distribution of choice.
|
||||
|
||||
## Installing MVT
|
||||
|
||||
If you haven't done so, you can add this to your `.bashrc` or `.zshrc` file in order to add locally installed Pypi binaries to your `$PATH`:
|
||||
@@ -35,7 +51,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
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:
|
||||
|
||||
|
||||
@@ -4,6 +4,16 @@ In this page you can find a (reasonably) up-to-date breakdown of the files creat
|
||||
|
||||
## Records extracted by `check-fs` or `check-backup`
|
||||
|
||||
### `backup_info.json`
|
||||
|
||||
!!! info "Availabiliy"
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-close:
|
||||
|
||||
This JSON file is created by mvt-ios' `BackupInfo` module. The module extracts some details about the backup and the device, such as name, phone number, IMEI, product type and version.
|
||||
|
||||
---
|
||||
|
||||
### `cache_files.json`
|
||||
|
||||
!!! info "Availability"
|
||||
@@ -50,6 +60,16 @@ If indicators a provided through the command-line, they are checked against the
|
||||
|
||||
---
|
||||
|
||||
### `configuration_profiles.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-close:
|
||||
|
||||
This JSON file is created by mvt-ios' `ConfigurationProfiles` module. The module extracts details about iOS configuration profiles that have been installed on the device. These should include both default iOS as well as third-party profiles.
|
||||
|
||||
---
|
||||
|
||||
### `contacts.json`
|
||||
|
||||
!!! info "Availability"
|
||||
@@ -150,6 +170,16 @@ If indicators are provided through the command-line, they are checked against th
|
||||
|
||||
---
|
||||
|
||||
### `profile_events.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-close:
|
||||
|
||||
This JSON file is created by mvt-ios' `ProfileEvents` module. The module extracts a timeline of configuration profile operations. For example, it should indicate when a new profile was installed from the Settings app, or when one was removed.
|
||||
|
||||
---
|
||||
|
||||
### `safari_browser_state.json`
|
||||
|
||||
!!! info "Availability"
|
||||
@@ -242,6 +272,18 @@ If indicators are provided through the command-line, they are checked against th
|
||||
|
||||
---
|
||||
|
||||
### `webkit_resource_load_statistics.json`
|
||||
|
||||
!!! info "Availability"
|
||||
Backup: :material-check:
|
||||
Full filesystem dump: :material-check:
|
||||
|
||||
This JSON file is created by mvt-ios `WebkitResourceLoadStatistics` module. The module extracts records from available WebKit ResourceLoadStatistics *observations.db* SQLite3 databases. These records should indicate domain names contacted by apps, including a timestamp.
|
||||
|
||||
If indicators are provided through the command-line, they are checked against the extracted domain names. Any matches are stored in *webkit_resource_load_statistics_detected.json*.
|
||||
|
||||
---
|
||||
|
||||
### `webkit_safari_view_service.json`
|
||||
|
||||
!!! info "Availability"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .cli import cli
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import click
|
||||
import argparse
|
||||
import logging
|
||||
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
|
||||
@@ -26,7 +27,7 @@ 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"
|
||||
|
||||
SERIAL_HELP_MESSAGE = "Specify a device serial number or HOST:PORT connection string"
|
||||
|
||||
#==============================================================================
|
||||
# Main
|
||||
@@ -40,25 +41,31 @@ def cli():
|
||||
# Download APKs
|
||||
#==============================================================================
|
||||
@cli.command("download-apks", help="Download all or non-safelisted installed APKs installed on the device")
|
||||
@click.option("--serial", "-s", type=str, help=SERIAL_HELP_MESSAGE)
|
||||
@click.option("--all-apks", "-a", is_flag=True,
|
||||
help="Extract all packages installed on the phone, even those marked as safe")
|
||||
@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):
|
||||
def download_apks(all_apks, virustotal, koodous, all_checks, output, from_file, serial):
|
||||
try:
|
||||
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)
|
||||
if serial:
|
||||
download.serial = serial
|
||||
download.run()
|
||||
|
||||
packages = download.packages
|
||||
@@ -80,12 +87,13 @@ def download_apks(all_apks, virustotal, koodous, all_checks, output, from_file):
|
||||
# Checks through ADB
|
||||
#==============================================================================
|
||||
@cli.command("check-adb", help="Check an Android device over adb")
|
||||
@click.option("--serial", "-s", type=str, help=SERIAL_HELP_MESSAGE)
|
||||
@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")
|
||||
def check_adb(iocs, output, list_modules, module):
|
||||
def check_adb(iocs, output, list_modules, module, serial):
|
||||
if list_modules:
|
||||
log.info("Following is the list of available check-adb modules:")
|
||||
for adb_module in ADB_MODULES:
|
||||
@@ -95,6 +103,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)
|
||||
@@ -107,6 +122,8 @@ def check_adb(iocs, output, list_modules, module):
|
||||
continue
|
||||
|
||||
m = adb_module(output_folder=output, log=logging.getLogger(adb_module.__module__))
|
||||
if serial:
|
||||
m.serial = serial
|
||||
|
||||
if iocs:
|
||||
indicators.log = m.log
|
||||
@@ -126,12 +143,20 @@ def check_adb(iocs, output, list_modules, module):
|
||||
# Check ADB backup
|
||||
#==============================================================================
|
||||
@cli.command("check-backup", help="Check an Android Backup")
|
||||
@click.option("--serial", "-s", type=str, help=SERIAL_HELP_MESSAGE)
|
||||
@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):
|
||||
def check_backup(iocs, output, backup_path, serial):
|
||||
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)
|
||||
@@ -149,6 +174,9 @@ def check_backup(iocs, output, backup_path):
|
||||
m = module(base_folder=backup_path, output_folder=output,
|
||||
log=logging.getLogger(module.__module__))
|
||||
|
||||
if serial:
|
||||
m.serial = serial
|
||||
|
||||
if iocs:
|
||||
indicators.log = m.log
|
||||
m.indicators = indicators
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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__)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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__)
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import logging
|
||||
from rich.text import Text
|
||||
from rich.table import Table
|
||||
from rich.progress import track
|
||||
from rich.console import Console
|
||||
from rich.progress import track
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .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,
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import tempfile
|
||||
from adb_shell.adb_device import AdbDeviceUsb
|
||||
import time
|
||||
|
||||
from adb_shell.adb_device import AdbDeviceTcp, 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
|
||||
from mvt.common.module import InsufficientPrivileges, MVTModule
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,17 +29,12 @@ class AndroidExtraction(MVTModule):
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
"""Initialize Android extraction module.
|
||||
:param file_path: Path to the database file to parse
|
||||
:param base_folder: Path to a base folder containing an Android dump
|
||||
:param output_folder: Path to the folder where to store extraction
|
||||
results
|
||||
"""
|
||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
||||
output_folder=output_folder, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
|
||||
self.device = None
|
||||
self.serial = None
|
||||
|
||||
def _adb_check_keys(self):
|
||||
"""Make sure Android adb keys exist.
|
||||
@@ -58,7 +54,19 @@ class AndroidExtraction(MVTModule):
|
||||
priv_key = handle.read()
|
||||
|
||||
signer = PythonRSASigner("", priv_key)
|
||||
self.device = AdbDeviceUsb()
|
||||
|
||||
# If no serial was specified or if the serial does not seem to be
|
||||
# a HOST:PORT definition, we use the USB transport.
|
||||
if not self.serial or ":" not in self.serial:
|
||||
self.device = AdbDeviceUsb(serial=self.serial)
|
||||
# Otherwise we try to use the TCP transport.
|
||||
else:
|
||||
addr = self.serial.split(":")
|
||||
if len(addr) < 2:
|
||||
raise ValueError("TCP serial number must follow the format: `address:port`")
|
||||
|
||||
self.device = AdbDeviceTcp(addr[0], int(addr[1]),
|
||||
default_transport_timeout_s=30.)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@@ -104,7 +112,7 @@ class AndroidExtraction(MVTModule):
|
||||
"""Check if we have a `su` binary, otherwise raise an Exception.
|
||||
"""
|
||||
if not self._adb_check_if_root():
|
||||
raise Exception("The Android device does not seem to have a `su` binary. Cannot run this module.")
|
||||
raise InsufficientPrivileges("This module is optionally available in case the device is already rooted. Do NOT root your own device!")
|
||||
|
||||
def _adb_command_as_root(self, command):
|
||||
"""Execute an adb shell command.
|
||||
@@ -112,6 +120,21 @@ 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
|
||||
"""
|
||||
|
||||
# TODO: Need to support checking files without root privileges as well.
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
import 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
|
||||
|
||||
@@ -19,7 +20,7 @@ class ChromeHistory(AndroidExtraction):
|
||||
"""This module extracts records from Android's Chrome browsing history."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
@@ -51,14 +52,14 @@ class ChromeHistory(AndroidExtraction):
|
||||
""")
|
||||
|
||||
for item in cur:
|
||||
self.results.append(dict(
|
||||
id=item[0],
|
||||
url=item[1],
|
||||
visit_id=item[2],
|
||||
timestamp=item[3],
|
||||
isodate=convert_timestamp_to_iso(convert_chrometime_to_unix(item[3])),
|
||||
redirect_source=item[4],
|
||||
))
|
||||
self.results.append({
|
||||
"id": item[0],
|
||||
"url": item[1],
|
||||
"visit_id": item[2],
|
||||
"timestamp": item[3],
|
||||
"isodate": convert_timestamp_to_iso(convert_chrometime_to_unix[item[3]]),
|
||||
"redirect_source": item[4],
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .base import AndroidExtraction
|
||||
|
||||
@@ -14,7 +14,7 @@ class DumpsysBatterystats(AndroidExtraction):
|
||||
"""This module extracts stats on battery consumption by processes."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .base import AndroidExtraction
|
||||
|
||||
@@ -14,7 +14,7 @@ class DumpsysPackages(AndroidExtraction):
|
||||
"""This module extracts stats on installed packages."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
from .base import AndroidExtraction
|
||||
|
||||
@@ -14,7 +14,7 @@ class DumpsysProcstats(AndroidExtraction):
|
||||
"""This module extracts stats on memory consumption by processes."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from .base import AndroidExtraction
|
||||
@@ -15,7 +16,7 @@ class Packages(AndroidExtraction):
|
||||
"""This module extracts the list of installed packages."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
@@ -75,18 +76,18 @@ class Packages(AndroidExtraction):
|
||||
first_install = dumpsys[1].split("=")[1].strip()
|
||||
last_update = dumpsys[2].split("=")[1].strip()
|
||||
|
||||
self.results.append(dict(
|
||||
package_name=package_name,
|
||||
file_name=file_name,
|
||||
installer=installer,
|
||||
timestamp=timestamp,
|
||||
first_install_time=first_install,
|
||||
last_update_time=last_update,
|
||||
uid=uid,
|
||||
disabled=False,
|
||||
system=False,
|
||||
third_party=False,
|
||||
))
|
||||
self.results.append({
|
||||
"package_name": package_name,
|
||||
"file_name": file_name,
|
||||
"installer": installer,
|
||||
"timestamp": timestamp,
|
||||
"first_install_time": first_install,
|
||||
"last_update_time": last_update,
|
||||
"uid": uid,
|
||||
"disabled": False,
|
||||
"system": False,
|
||||
"third_party": False,
|
||||
})
|
||||
|
||||
cmds = [
|
||||
{"field": "disabled", "arg": "-d"},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
|
||||
@@ -13,7 +13,7 @@ class Processes(AndroidExtraction):
|
||||
"""This module extracts details on running processes."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
@@ -21,7 +21,7 @@ class Processes(AndroidExtraction):
|
||||
def run(self):
|
||||
self._adb_connect()
|
||||
|
||||
output = self._adb_command("ps")
|
||||
output = self._adb_command("ps -e")
|
||||
|
||||
for line in output.split("\n")[1:]:
|
||||
line = line.strip()
|
||||
@@ -29,13 +29,13 @@ class Processes(AndroidExtraction):
|
||||
continue
|
||||
|
||||
fields = line.split()
|
||||
proc = dict(
|
||||
user=fields[0],
|
||||
pid=fields[1],
|
||||
parent_pid=fields[2],
|
||||
vsize=fields[3],
|
||||
rss=fields[4],
|
||||
)
|
||||
proc = {
|
||||
"user": fields[0],
|
||||
"pid": fields[1],
|
||||
"parent_pid": fields[2],
|
||||
"vsize": fields[3],
|
||||
"rss": fields[4],
|
||||
}
|
||||
|
||||
# Sometimes WCHAN is empty, so we need to re-align output fields.
|
||||
if len(fields) == 8:
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from .base import AndroidExtraction
|
||||
@@ -15,7 +16,7 @@ class RootBinaries(AndroidExtraction):
|
||||
"""This module extracts the list of installed packages."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
|
||||
@@ -1,24 +1,49 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import logging
|
||||
import 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."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
@@ -50,24 +75,16 @@ 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:
|
||||
message = dict()
|
||||
message = {}
|
||||
for index, value in enumerate(item):
|
||||
message[names[index]] = value
|
||||
|
||||
@@ -85,7 +102,11 @@ class SMS(AndroidExtraction):
|
||||
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self._adb_process_file(os.path.join("/", SMS_PATH), self._parse_db)
|
||||
except Exception as e:
|
||||
self.log.error(e)
|
||||
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")
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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__)
|
||||
|
||||
@@ -19,7 +20,7 @@ class Whatsapp(AndroidExtraction):
|
||||
"""This module extracts all WhatsApp messages containing links."""
|
||||
|
||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||
fast_mode=False, log=None, results=[]):
|
||||
serial=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)
|
||||
@@ -58,7 +59,7 @@ class Whatsapp(AndroidExtraction):
|
||||
|
||||
messages = []
|
||||
for item in cur:
|
||||
message = dict()
|
||||
message = {}
|
||||
for index, value in enumerate(item):
|
||||
message[names[index]] = value
|
||||
|
||||
@@ -81,7 +82,4 @@ class Whatsapp(AndroidExtraction):
|
||||
self.results = messages
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self._adb_process_file(os.path.join("/", WHATSAPP_PATH), self._parse_db)
|
||||
except Exception as e:
|
||||
self.log.error(e)
|
||||
self._adb_process_file(os.path.join("/", WHATSAPP_PATH), self._parse_db)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .sms import SMS
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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
|
||||
|
||||
|
||||
class SMS(MVTModule):
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import json
|
||||
import os
|
||||
|
||||
from .url import URL
|
||||
|
||||
|
||||
class IndicatorsFileBadFormat(Exception):
|
||||
pass
|
||||
|
||||
class Indicators:
|
||||
"""This class is used to parse indicators from a STIX2 file and provide
|
||||
functions to compare extracted artifacts to the indicators.
|
||||
@@ -16,7 +20,10 @@ class Indicators:
|
||||
def __init__(self, file_path, log=None):
|
||||
self.file_path = file_path
|
||||
with open(self.file_path, "r") as handle:
|
||||
self.data = json.load(handle)
|
||||
try:
|
||||
self.data = json.load(handle)
|
||||
except json.decoder.JSONDecodeError:
|
||||
raise IndicatorsFileBadFormat("Unable to parse STIX2 indicators file, the file seems malformed or in the wrong format")
|
||||
|
||||
self.log = log
|
||||
self.ioc_domains = []
|
||||
@@ -108,10 +115,10 @@ class Indicators:
|
||||
# Then we just check the top level domain.
|
||||
if final_url.top_level.lower() == ioc:
|
||||
if orig_url.is_shortened and orig_url.url != final_url.url:
|
||||
self.log.warning("Found a sub-domain matching a suspicious top level %s shortened as %s",
|
||||
self.log.warning("Found a sub-domain matching a known suspicious top level %s shortened as %s",
|
||||
final_url.url, orig_url.url)
|
||||
else:
|
||||
self.log.warning("Found a sub-domain matching a suspicious top level: %s", final_url.url)
|
||||
self.log.warning("Found a sub-domain matching a known suspicious top level: %s", final_url.url)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import csv
|
||||
import glob
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import csv
|
||||
import glob
|
||||
import logging
|
||||
|
||||
import simplejson as json
|
||||
|
||||
from .indicators import Indicators
|
||||
|
||||
|
||||
class DatabaseNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
class DatabaseCorruptedError(Exception):
|
||||
pass
|
||||
|
||||
class InsufficientPrivileges(Exception):
|
||||
pass
|
||||
|
||||
class MVTModule(object):
|
||||
"""This class provides a base for all extraction modules."""
|
||||
|
||||
@@ -62,13 +66,6 @@ class MVTModule(object):
|
||||
sub = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", self.__class__.__name__)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", sub).lower()
|
||||
|
||||
def _find_paths(self, root_paths):
|
||||
for root_path in root_paths:
|
||||
for found_path in glob.glob(os.path.join(self.base_folder, root_path)):
|
||||
if not os.path.exists(found_path):
|
||||
continue
|
||||
yield found_path
|
||||
|
||||
def load_indicators(self, file_path):
|
||||
self.indicators = Indicators(file_path, self.log)
|
||||
|
||||
@@ -88,9 +85,9 @@ class MVTModule(object):
|
||||
if self.results:
|
||||
results_file_name = f"{name}.json"
|
||||
results_json_path = os.path.join(self.output_folder, results_file_name)
|
||||
with open(results_json_path, "w") as handle:
|
||||
with io.open(results_json_path, "w", encoding="utf-8") as handle:
|
||||
try:
|
||||
json.dump(self.results, handle, indent=4)
|
||||
json.dump(self.results, handle, indent=4, default=str)
|
||||
except Exception as e:
|
||||
self.log.error("Unable to store results of module %s to file %s: %s",
|
||||
self.__class__.__name__, results_file_name, e)
|
||||
@@ -98,8 +95,8 @@ class MVTModule(object):
|
||||
if self.detected:
|
||||
detected_file_name = f"{name}_detected.json"
|
||||
detected_json_path = os.path.join(self.output_folder, detected_file_name)
|
||||
with open(detected_json_path, "w") as handle:
|
||||
json.dump(self.detected, handle, indent=4)
|
||||
with io.open(detected_json_path, "w", encoding="utf-8") as handle:
|
||||
json.dump(self.detected, handle, indent=4, default=str)
|
||||
|
||||
def serialize(self, record):
|
||||
raise NotImplementedError
|
||||
@@ -123,7 +120,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)
|
||||
|
||||
@@ -148,6 +145,8 @@ def run_module(module):
|
||||
except NotImplementedError:
|
||||
module.log.exception("The run() procedure of module %s was not implemented yet!",
|
||||
module.__class__.__name__)
|
||||
except InsufficientPrivileges as e:
|
||||
module.log.info("Insufficient privileges for module %s: %s", module.__class__.__name__, e)
|
||||
except DatabaseNotFoundError as e:
|
||||
module.log.info("There might be no data to extract by module %s: %s",
|
||||
module.__class__.__name__, e)
|
||||
@@ -161,7 +160,13 @@ def run_module(module):
|
||||
try:
|
||||
module.check_indicators()
|
||||
except NotImplementedError:
|
||||
module.log.info("The %s module does not support checking for indicators",
|
||||
module.__class__.__name__)
|
||||
pass
|
||||
else:
|
||||
if module.indicators and not module.detected:
|
||||
module.log.info("The %s module produced no detections!",
|
||||
module.__class__.__name__)
|
||||
|
||||
try:
|
||||
module.to_timeline()
|
||||
@@ -181,8 +186,8 @@ def save_timeline(timeline, timeline_path):
|
||||
csvoutput.writerow(["UTC Timestamp", "Plugin", "Event", "Description"])
|
||||
for event in sorted(timeline, key=lambda x: x["timestamp"] if x["timestamp"] is not None else ""):
|
||||
csvoutput.writerow([
|
||||
event["timestamp"],
|
||||
event["module"],
|
||||
event["event"],
|
||||
event["data"],
|
||||
event.get("timestamp"),
|
||||
event.get("module"),
|
||||
event.get("event"),
|
||||
event.get("data"),
|
||||
])
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
# From: https://gist.github.com/stanchan/bce1c2d030c76fe9223b5ff6ad0f03db
|
||||
|
||||
from click import command, option, Option, UsageError
|
||||
from click import Option, UsageError
|
||||
|
||||
|
||||
class MutuallyExclusiveOption(Option):
|
||||
"""This class extends click to support mutually exclusive options.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import requests
|
||||
from tld import get_tld
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import re
|
||||
import datetime
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
|
||||
def convert_mactime_to_unix(timestamp, from_2001=True):
|
||||
"""Converts Mac Standard Time to a Unix timestamp.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .cli import cli
|
||||
|
||||
146
mvt/ios/cli.py
146
mvt/ios/cli.py
@@ -1,21 +1,23 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import sys
|
||||
import click
|
||||
import tarfile
|
||||
import logging
|
||||
from rich.logging import RichHandler
|
||||
import os
|
||||
|
||||
import click
|
||||
from rich.logging import RichHandler
|
||||
from rich.prompt import Prompt
|
||||
|
||||
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat
|
||||
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
|
||||
from .modules.backup import BACKUP_MODULES
|
||||
from .modules.fs import FS_MODULES
|
||||
from .modules.mixed import MIXED_MODULES
|
||||
|
||||
# Setup logging using Rich.
|
||||
LOG_FORMAT = "[%(name)s] %(message)s"
|
||||
@@ -26,6 +28,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,23 +46,75 @@ 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),
|
||||
help="File containing raw encryption key to use to decrypt the backup",
|
||||
mutually_exclusive=["password"])
|
||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||
def decrypt_backup(destination, password, key_file, backup_path):
|
||||
@click.pass_context
|
||||
def decrypt_backup(ctx, 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("Ignoring environment variable, using --key-file '%s' instead",
|
||||
PASSWD_ENV, key_file)
|
||||
|
||||
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("Ignoring %s environment variable, using --password argument instead",
|
||||
PASSWD_ENV)
|
||||
|
||||
backup.decrypt_with_password(password)
|
||||
elif PASSWD_ENV in os.environ:
|
||||
log.info("Using password from %s environment variable", PASSWD_ENV)
|
||||
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)
|
||||
|
||||
if not backup.can_process():
|
||||
ctx.exit(1)
|
||||
|
||||
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("Ignoring %s environment variable, using --password argument instead",
|
||||
PASSWD_ENV)
|
||||
elif PASSWD_ENV in os.environ:
|
||||
log.info("Using password from %s environment variable", PASSWD_ENV)
|
||||
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,29 +122,41 @@ 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")
|
||||
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
|
||||
def check_backup(iocs, output, fast, backup_path, list_modules, module):
|
||||
@click.pass_context
|
||||
def check_backup(ctx, iocs, output, fast, backup_path, list_modules, module):
|
||||
if list_modules:
|
||||
log.info("Following is the list of available check-backup modules:")
|
||||
for backup_module in BACKUP_MODULES:
|
||||
for backup_module in BACKUP_MODULES + MIXED_MODULES:
|
||||
log.info(" - %s", backup_module.__name__)
|
||||
|
||||
return
|
||||
|
||||
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)
|
||||
ctx.exit(1)
|
||||
|
||||
if iocs:
|
||||
# Pre-load indicators for performance reasons.
|
||||
log.info("Loading indicators from provided file at: %s", iocs)
|
||||
indicators = Indicators(iocs)
|
||||
try:
|
||||
indicators = Indicators(iocs)
|
||||
except IndicatorsFileBadFormat as e:
|
||||
log.critical(e)
|
||||
ctx.exit(1)
|
||||
|
||||
timeline = []
|
||||
timeline_detected = []
|
||||
for backup_module in BACKUP_MODULES:
|
||||
for backup_module in BACKUP_MODULES + MIXED_MODULES:
|
||||
if module and backup_module.__name__ != module:
|
||||
continue
|
||||
|
||||
@@ -116,29 +184,41 @@ 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")
|
||||
@click.argument("DUMP_PATH", type=click.Path(exists=True))
|
||||
def check_fs(iocs, output, fast, dump_path, list_modules, module):
|
||||
@click.pass_context
|
||||
def check_fs(ctx, iocs, output, fast, dump_path, list_modules, module):
|
||||
if list_modules:
|
||||
log.info("Following is the list of available check-fs modules:")
|
||||
for fs_module in FS_MODULES:
|
||||
for fs_module in FS_MODULES + MIXED_MODULES:
|
||||
log.info(" - %s", fs_module.__name__)
|
||||
|
||||
return
|
||||
|
||||
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)
|
||||
ctx.exit(1)
|
||||
|
||||
if iocs:
|
||||
# Pre-load indicators for performance reasons.
|
||||
log.info("Loading indicators from provided file at: %s", iocs)
|
||||
indicators = Indicators(iocs)
|
||||
try:
|
||||
indicators = Indicators(iocs)
|
||||
except IndicatorsFileBadFormat as e:
|
||||
log.critical(e)
|
||||
ctx.exit(1)
|
||||
|
||||
timeline = []
|
||||
timeline_detected = []
|
||||
for fs_module in FS_MODULES:
|
||||
for fs_module in FS_MODULES + MIXED_MODULES:
|
||||
if module and fs_module.__name__ != module:
|
||||
continue
|
||||
|
||||
@@ -171,7 +251,8 @@ def check_fs(iocs, output, fast, dump_path, list_modules, module):
|
||||
@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")
|
||||
@click.argument("FOLDER", type=click.Path(exists=True))
|
||||
def check_iocs(iocs, list_modules, module, folder):
|
||||
@click.pass_context
|
||||
def check_iocs(ctx, iocs, list_modules, module, folder):
|
||||
all_modules = []
|
||||
for entry in BACKUP_MODULES + FS_MODULES:
|
||||
if entry not in all_modules:
|
||||
@@ -188,7 +269,12 @@ def check_iocs(iocs, list_modules, module, folder):
|
||||
|
||||
# Pre-load indicators for performance reasons.
|
||||
log.info("Loading indicators from provided file at: %s", iocs)
|
||||
indicators = Indicators(iocs)
|
||||
|
||||
try:
|
||||
indicators = Indicators(iocs)
|
||||
except IndicatorsFileBadFormat as e:
|
||||
log.critical(e)
|
||||
ctx.exit(1)
|
||||
|
||||
for file_name in os.listdir(folder):
|
||||
name_only, ext = os.path.splitext(file_name)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import binascii
|
||||
import glob
|
||||
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,15 @@ class DecryptBackup:
|
||||
self.backup_path = backup_path
|
||||
self.dest_path = dest_path
|
||||
self._backup = None
|
||||
self._decryption_key = None
|
||||
|
||||
def can_process(self) -> bool:
|
||||
return self._backup is not 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()
|
||||
@@ -62,25 +70,43 @@ class DecryptBackup:
|
||||
except Exception as e:
|
||||
log.error("Failed to decrypt file %s: %s", relative_path, e)
|
||||
|
||||
# Copying over the root plist files as well.
|
||||
for file_name in os.listdir(self.backup_path):
|
||||
if file_name.endswith(".plist"):
|
||||
log.info("Copied plist file %s to %s", file_name, self.dest_path)
|
||||
shutil.copy(os.path.join(self.backup_path, file_name),
|
||||
self.dest_path)
|
||||
|
||||
def decrypt_with_password(self, password):
|
||||
"""Decrypts an encrypted iOS backup.
|
||||
:param password: Password to use to decrypt the original backup
|
||||
"""
|
||||
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)
|
||||
|
||||
if not os.path.exists(os.path.join(self.backup_path, "Manifest.plist")):
|
||||
possible = glob.glob(os.path.join(self.backup_path, "*", "Manifest.plist"))
|
||||
if len(possible) == 1:
|
||||
newpath = os.path.dirname(possible[0])
|
||||
log.warning("No Manifest.plist in %s, using %s instead.",
|
||||
self.backup_path, newpath)
|
||||
self.backup_path = newpath
|
||||
elif len(possible) > 1:
|
||||
log.critical("No Manifest.plist in %s, and %d Manifest.plist files in subdirs. Please choose one!",
|
||||
self.backup_path, len(possible))
|
||||
return
|
||||
try:
|
||||
self._backup = iOSbackup(udid=os.path.basename(self.backup_path),
|
||||
cleartextpassword=password,
|
||||
backuproot=os.path.dirname(self.backup_path))
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.critical("Failed to decrypt backup. Did you provide the correct password?")
|
||||
return
|
||||
else:
|
||||
self._process_backup()
|
||||
if isinstance(e, KeyError) and len(e.args) > 0 and e.args[0] == b"KEY":
|
||||
log.critical("Failed to decrypt backup. Password is probably wrong.")
|
||||
elif isinstance(e, FileNotFoundError) and os.path.basename(e.filename) == "Manifest.plist":
|
||||
log.critical("Failed to find a valid backup at %s. Did you point to the right backup path?",
|
||||
self.backup_path)
|
||||
else:
|
||||
log.exception(e)
|
||||
log.critical("Failed to decrypt backup. Did you provide the correct password? Did you point to the right backup path?")
|
||||
|
||||
def decrypt_with_key_file(self, key_file):
|
||||
"""Decrypts an encrypted iOS backup using a key file.
|
||||
@@ -89,9 +115,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 +131,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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
11
mvt/ios/modules/backup/__init__.py
Normal file
11
mvt/ios/modules/backup/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .backup_info import BackupInfo
|
||||
from .configuration_profiles import ConfigurationProfiles
|
||||
from .manifest import Manifest
|
||||
from .profile_events import ProfileEvents
|
||||
|
||||
BACKUP_MODULES = [BackupInfo, ConfigurationProfiles, Manifest, ProfileEvents]
|
||||
43
mvt/ios/modules/backup/backup_info.py
Normal file
43
mvt/ios/modules/backup/backup_info.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import plistlib
|
||||
|
||||
from mvt.common.module import DatabaseNotFoundError
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
|
||||
class BackupInfo(IOSExtraction):
|
||||
"""This module extracts information about the device and the backup."""
|
||||
|
||||
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)
|
||||
|
||||
self.results = {}
|
||||
|
||||
def run(self):
|
||||
info_path = os.path.join(self.base_folder, "Info.plist")
|
||||
if not os.path.exists(info_path):
|
||||
raise DatabaseNotFoundError("No Info.plist at backup path, unable to extract device information")
|
||||
|
||||
with open(info_path, "rb") as handle:
|
||||
info = plistlib.load(handle)
|
||||
|
||||
fields = ["Build Version", "Device Name", "Display Name", "GUID",
|
||||
"GUID", "ICCID", "IMEI", "MEID", "Installed Applications",
|
||||
"Last Backup Data", "Phone Number", "Product Name",
|
||||
"Product Type", "Product Version", "Serial Number",
|
||||
"Target Identifier", "Target Type", "Unique Identifier",
|
||||
"iTunes Version"]
|
||||
|
||||
for field in fields:
|
||||
value = info.get(field, None)
|
||||
self.log.info("%s: %s", field, value)
|
||||
self.results[field] = value
|
||||
43
mvt/ios/modules/backup/configuration_profiles.py
Normal file
43
mvt/ios/modules/backup/configuration_profiles.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import plistlib
|
||||
from base64 import b64encode
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CONF_PROFILES_DOMAIN = "SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles"
|
||||
|
||||
class ConfigurationProfiles(IOSExtraction):
|
||||
"""This module extracts the full plist data from configuration profiles.
|
||||
"""
|
||||
|
||||
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 run(self):
|
||||
for conf_file in self._get_backup_files_from_manifest(domain=CONF_PROFILES_DOMAIN):
|
||||
conf_file_path = self._get_backup_file_from_id(conf_file["file_id"])
|
||||
if not conf_file_path:
|
||||
continue
|
||||
|
||||
with open(conf_file_path, "rb") as handle:
|
||||
conf_plist = plistlib.load(handle)
|
||||
|
||||
if "SignerCerts" in conf_plist:
|
||||
conf_plist["SignerCerts"] = [b64encode(x) for x in conf_plist["SignerCerts"]]
|
||||
|
||||
self.results.append({
|
||||
"file_id": conf_file["file_id"],
|
||||
"relative_path": conf_file["relative_path"],
|
||||
"domain": conf_file["domain"],
|
||||
"plist": conf_plist,
|
||||
})
|
||||
|
||||
self.log.info("Extracted details about %d configuration profiles", len(self.results))
|
||||
@@ -1,17 +1,19 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import os
|
||||
import biplist
|
||||
import plistlib
|
||||
import sqlite3
|
||||
import datetime
|
||||
|
||||
from mvt.common.module import DatabaseNotFoundError
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
|
||||
class Manifest(IOSExtraction):
|
||||
"""This module extracts information from a backup Manifest.db file."""
|
||||
@@ -23,15 +25,14 @@ class Manifest(IOSExtraction):
|
||||
log=log, results=results)
|
||||
|
||||
def _get_key(self, dictionary, key):
|
||||
"""
|
||||
Unserialized plist objects can have keys which are str or byte types
|
||||
|
||||
"""Unserialized plist objects can have keys which are str or byte types
|
||||
This is a helper to try fetch a key as both a byte or string type.
|
||||
"""
|
||||
return dictionary.get(key.encode("utf-8"), None) or dictionary.get(key, None)
|
||||
|
||||
def _convert_timestamp(self, timestamp_or_unix_time_int):
|
||||
"""Older iOS versions stored the manifest times as unix timestamps."""
|
||||
"""Older iOS versions stored the manifest times as unix timestamps.
|
||||
"""
|
||||
if isinstance(timestamp_or_unix_time_int, datetime.datetime):
|
||||
return convert_timestamp_to_iso(timestamp_or_unix_time_int)
|
||||
else:
|
||||
@@ -40,20 +41,20 @@ class Manifest(IOSExtraction):
|
||||
|
||||
def serialize(self, record):
|
||||
records = []
|
||||
if "modified" not in record or "statusChanged" not in record:
|
||||
if "modified" not in record or "status_changed" not in record:
|
||||
return
|
||||
for ts in set([record["created"], record["modified"], record["statusChanged"]]):
|
||||
for ts in set([record["created"], record["modified"], record["status_changed"]]):
|
||||
macb = ""
|
||||
macb += "M" if ts == record["modified"] else "-"
|
||||
macb += "-"
|
||||
macb += "C" if ts == record["statusChanged"] else "-"
|
||||
macb += "C" if ts == record["status_changed"] else "-"
|
||||
macb += "B" if ts == record["created"] else "-"
|
||||
|
||||
records.append({
|
||||
"timestamp": ts,
|
||||
"module": self.__class__.__name__,
|
||||
"event": macb,
|
||||
"data": f"{record['relativePath']} - {record['domain']}"
|
||||
"data": f"{record['relative_path']} - {record['domain']}"
|
||||
})
|
||||
|
||||
return records
|
||||
@@ -63,23 +64,23 @@ class Manifest(IOSExtraction):
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
if not "relativePath" in result:
|
||||
if not "relative_path" in result:
|
||||
continue
|
||||
if not result["relativePath"]:
|
||||
if not result["relative_path"]:
|
||||
continue
|
||||
|
||||
if result["domain"]:
|
||||
if os.path.basename(result["relativePath"]) == "com.apple.CrashReporter.plist" and result["domain"] == "RootDomain":
|
||||
if os.path.basename(result["relative_path"]) == "com.apple.CrashReporter.plist" and result["domain"] == "RootDomain":
|
||||
self.log.warning("Found a potentially suspicious \"com.apple.CrashReporter.plist\" file created in RootDomain")
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
if self.indicators.check_file(result["relativePath"]):
|
||||
self.log.warning("Found a known malicious file at path: %s", result["relativePath"])
|
||||
if self.indicators.check_file(result["relative_path"]):
|
||||
self.log.warning("Found a known malicious file at path: %s", result["relative_path"])
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
relPath = result["relativePath"].lower()
|
||||
relPath = result["relative_path"].lower()
|
||||
for ioc in self.indicators.ioc_domains:
|
||||
if ioc.lower() in relPath:
|
||||
self.log.warning("Found mention of domain \"%s\" in a backup file with path: %s",
|
||||
@@ -89,7 +90,7 @@ class Manifest(IOSExtraction):
|
||||
def run(self):
|
||||
manifest_db_path = os.path.join(self.base_folder, "Manifest.db")
|
||||
if not os.path.isfile(manifest_db_path):
|
||||
raise FileNotFoundError("Impossible to find the module's database file")
|
||||
raise DatabaseNotFoundError("Impossible to find the module's database file")
|
||||
|
||||
self.log.info("Found Manifest.db database at path: %s", manifest_db_path)
|
||||
|
||||
@@ -100,26 +101,26 @@ class Manifest(IOSExtraction):
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for file_entry in cur:
|
||||
file_data = dict()
|
||||
file_data = {}
|
||||
for index, value in enumerate(file_entry):
|
||||
file_data[names[index]] = value
|
||||
|
||||
cleaned_metadata = {
|
||||
"fileID": file_data["fileID"],
|
||||
"file_id": file_data["fileID"],
|
||||
"domain": file_data["domain"],
|
||||
"relativePath": file_data["relativePath"],
|
||||
"relative_path": file_data["relativePath"],
|
||||
"flags": file_data["flags"],
|
||||
"created": "",
|
||||
}
|
||||
|
||||
if file_data["file"]:
|
||||
try:
|
||||
file_plist = biplist.readPlist(io.BytesIO(file_data["file"]))
|
||||
file_plist = plistlib.load(io.BytesIO(file_data["file"]))
|
||||
file_metadata = self._get_key(file_plist, "$objects")[1]
|
||||
cleaned_metadata.update({
|
||||
"created": self._convert_timestamp(self._get_key(file_metadata, "Birth")),
|
||||
"modified": self._convert_timestamp(self._get_key(file_metadata, "LastModified")),
|
||||
"statusChanged": self._convert_timestamp(self._get_key(file_metadata, "LastStatusChange")),
|
||||
"status_changed": self._convert_timestamp(self._get_key(file_metadata, "LastStatusChange")),
|
||||
"mode": oct(self._get_key(file_metadata, "Mode")),
|
||||
"owner": self._get_key(file_metadata, "UserID"),
|
||||
"size": self._get_key(file_metadata, "Size"),
|
||||
59
mvt/ios/modules/backup/profile_events.py
Normal file
59
mvt/ios/modules/backup/profile_events.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import plistlib
|
||||
from datetime import datetime
|
||||
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CONF_PROFILES_EVENTS_RELPATH = "Library/ConfigurationProfiles/MCProfileEvents.plist"
|
||||
|
||||
class ProfileEvents(IOSExtraction):
|
||||
"""This module extracts events related to the installation of configuration
|
||||
profiles.
|
||||
"""
|
||||
|
||||
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 serialize(self, record):
|
||||
return {
|
||||
"timestamp": record.get("timestamp"),
|
||||
"module": self.__class__.__name__,
|
||||
"event": "profile_operation",
|
||||
"data": f"Process {record.get('process')} started operation {record.get('operation')} of profile {record.get('profile_id')}"
|
||||
}
|
||||
|
||||
def run(self):
|
||||
for events_file in self._get_backup_files_from_manifest(relative_path=CONF_PROFILES_EVENTS_RELPATH):
|
||||
events_file_path = self._get_backup_file_from_id(events_file["file_id"])
|
||||
if not events_file_path:
|
||||
continue
|
||||
|
||||
with open(events_file_path, "rb") as handle:
|
||||
events_plist = plistlib.load(handle)
|
||||
|
||||
if "ProfileEvents" not in events_plist:
|
||||
continue
|
||||
|
||||
for event in events_plist["ProfileEvents"]:
|
||||
key = list(event.keys())[0]
|
||||
self.log.info("On %s process \"%s\" started operation \"%s\" of profile \"%s\"",
|
||||
event[key].get("timestamp"), event[key].get("process"),
|
||||
event[key].get("operation"), key)
|
||||
|
||||
self.results.append({
|
||||
"profile_id": key,
|
||||
"timestamp": convert_timestamp_to_iso(event[key].get("timestamp")),
|
||||
"operation": event[key].get("operation"),
|
||||
"process": event[key].get("process"),
|
||||
})
|
||||
|
||||
self.log.info("Extracted %d profile events", len(self.results))
|
||||
149
mvt/ios/modules/base.py
Normal file
149
mvt/ios/modules/base.py
Normal file
@@ -0,0 +1,149 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
|
||||
from mvt.common.module import (DatabaseCorruptedError, DatabaseNotFoundError,
|
||||
MVTModule)
|
||||
|
||||
|
||||
class IOSExtraction(MVTModule):
|
||||
"""This class provides a base for all iOS filesystem/backup extraction modules."""
|
||||
|
||||
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)
|
||||
|
||||
self.is_backup = False
|
||||
self.is_fs_dump = False
|
||||
self.is_sysdiagnose = False
|
||||
|
||||
def _recover_sqlite_db_if_needed(self, file_path):
|
||||
"""Tries to recover a malformed database by running a .clone command.
|
||||
:param file_path: Path to the malformed database file.
|
||||
"""
|
||||
# TODO: Find a better solution.
|
||||
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()
|
||||
|
||||
if not recover:
|
||||
return
|
||||
|
||||
self.log.info("Database at path %s is malformed. Trying to recover...", file_path)
|
||||
|
||||
if not shutil.which("sqlite3"):
|
||||
raise DatabaseCorruptedError("Unable to recover without sqlite3 binary. Please install sqlite3!")
|
||||
if '"' in file_path:
|
||||
raise DatabaseCorruptedError(f"Database at path '{file_path}' is corrupted. unable to recover because it has a quotation mark (\") in its name.")
|
||||
|
||||
bak_path = f"{file_path}.bak"
|
||||
shutil.move(file_path, bak_path)
|
||||
|
||||
ret = subprocess.call(["sqlite3", bak_path, f".clone \"{file_path}\""],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if ret != 0:
|
||||
raise DatabaseCorruptedError("Recovery of database failed")
|
||||
|
||||
self.log.info("Database at path %s recovered successfully!", file_path)
|
||||
|
||||
def _get_backup_files_from_manifest(self, relative_path=None, domain=None):
|
||||
"""Locate files from Manifest.db.
|
||||
:param relative_path: Relative path to use as filter from Manifest.db.
|
||||
:param domain: Domain to use as filter from Manifest.db.
|
||||
"""
|
||||
manifest_db_path = os.path.join(self.base_folder, "Manifest.db")
|
||||
if not os.path.exists(manifest_db_path):
|
||||
raise Exception("Unable to find backup's Manifest.db")
|
||||
|
||||
base_sql = "SELECT fileID, domain, relativePath FROM Files WHERE "
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(manifest_db_path)
|
||||
cur = conn.cursor()
|
||||
if relative_path and domain:
|
||||
cur.execute(f"{base_sql} relativePath = ? AND domain = ?;",
|
||||
(relative_path, domain))
|
||||
else:
|
||||
if relative_path:
|
||||
cur.execute(f"{base_sql} relativePath = ?;", (relative_path,))
|
||||
elif domain:
|
||||
cur.execute(f"{base_sql} domain = ?;", (domain,))
|
||||
except Exception as e:
|
||||
raise Exception("Query to Manifest.db failed: %s", e)
|
||||
|
||||
for row in cur:
|
||||
yield {
|
||||
"file_id": row[0],
|
||||
"domain": row[1],
|
||||
"relative_path": row[2],
|
||||
}
|
||||
|
||||
def _get_backup_file_from_id(self, file_id):
|
||||
file_path = os.path.join(self.base_folder, file_id[0:2], file_id)
|
||||
if os.path.exists(file_path):
|
||||
return file_path
|
||||
|
||||
return None
|
||||
|
||||
def _get_fs_files_from_patterns(self, root_paths):
|
||||
for root_path in root_paths:
|
||||
for found_path in glob.glob(os.path.join(self.base_folder, root_path)):
|
||||
if not os.path.exists(found_path):
|
||||
continue
|
||||
|
||||
yield found_path
|
||||
|
||||
def _find_ios_database(self, backup_ids=None, root_paths=[]):
|
||||
"""Try to locate a module's database file from either an iTunes
|
||||
backup or a full filesystem dump. This is intended only for
|
||||
modules that expect to work with a single SQLite database.
|
||||
If a module requires to process multiple databases or files,
|
||||
you should use the helper functions above.
|
||||
: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.
|
||||
if not self.file_path:
|
||||
# If not, we first try with backups.
|
||||
# We construct the path to the file according to the iTunes backup
|
||||
# folder structure, if we have a valid ID.
|
||||
if backup_ids:
|
||||
for backup_id in backup_ids:
|
||||
file_path = self._get_backup_file_from_id(backup_id)
|
||||
if file_path:
|
||||
break
|
||||
|
||||
# If this file does not exist we might be processing a full
|
||||
# filesystem dump (checkra1n all the things!).
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
# We reset the file_path.
|
||||
file_path = None
|
||||
for found_path in self._get_fs_files_from_patterns(root_paths):
|
||||
file_path = found_path
|
||||
break
|
||||
|
||||
# If we do not find any, we fail.
|
||||
if file_path:
|
||||
self.file_path = file_path
|
||||
else:
|
||||
raise DatabaseNotFoundError("Unable to find the module's database file")
|
||||
|
||||
self._recover_sqlite_db_if_needed(self.file_path)
|
||||
@@ -1,42 +1,16 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .manifest import Manifest
|
||||
from .contacts import Contacts
|
||||
from .cache_files import CacheFiles
|
||||
from .filesystem import Filesystem
|
||||
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 .version_history import IOSVersionHistory
|
||||
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 .chrome_favicon import ChromeFavicon
|
||||
from .firefox_history import FirefoxHistory
|
||||
from .firefox_favicon import FirefoxFavicon
|
||||
from .version_history import IOSVersionHistory
|
||||
from .idstatuscache import IDStatusCache
|
||||
from .locationd import LocationdClients
|
||||
from .interactionc import InteractionC
|
||||
from .sms import SMS
|
||||
from .sms_attachments import SMSAttachments
|
||||
from .calls import Calls
|
||||
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]
|
||||
|
||||
FS_MODULES = [IOSVersionHistory, SafariHistory, SafariFavicon, SafariBrowserState,
|
||||
WebkitIndexedDB, WebkitLocalStorage, WebkitSafariViewService,
|
||||
WebkitSessionResourceLog, Datausage, Netusage, ChromeHistory,
|
||||
ChromeFavicon, Calls, IDStatusCache, SMS, SMSAttachments,
|
||||
LocationdClients, InteractionC, FirefoxHistory, FirefoxFavicon,
|
||||
Contacts, CacheFiles, Whatsapp, Filesystem]
|
||||
FS_MODULES = [CacheFiles, Filesystem, Netusage, SafariFavicon, IOSVersionHistory,
|
||||
WebkitIndexedDB, WebkitLocalStorage, WebkitSafariViewService,]
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# 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 io
|
||||
import os
|
||||
import glob
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
|
||||
from mvt.common.module import MVTModule
|
||||
from mvt.common.module import DatabaseNotFoundError, DatabaseCorruptedError
|
||||
|
||||
class IOSExtraction(MVTModule):
|
||||
"""This class provides a base for all iOS filesystem/backup extraction modules."""
|
||||
|
||||
is_backup = False
|
||||
is_fs_dump = False
|
||||
is_sysdiagnose = False
|
||||
|
||||
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.
|
||||
"""
|
||||
# TODO: Find a better solution.
|
||||
|
||||
self.log.info("Database at path %s is malformed. Trying to recover...", file_path)
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
return
|
||||
|
||||
if not shutil.which("sqlite3"):
|
||||
raise DatabaseCorruptedError("Unable to recover without sqlite3 binary. Please install sqlite3!")
|
||||
|
||||
bak_path = f"{file_path}.bak"
|
||||
shutil.move(file_path, bak_path)
|
||||
|
||||
cmd = f"sqlite3 {bak_path} '.clone {file_path}'"
|
||||
ret = subprocess.call(cmd, shell=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
if ret != 0:
|
||||
raise DatabaseCorruptedError("Recovery of database failed")
|
||||
|
||||
self.log.info("Database at path %s recovered successfully!", file_path)
|
||||
|
||||
def _find_ios_database(self, backup_ids=None, root_paths=[]):
|
||||
"""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).
|
||||
"""
|
||||
file_path = None
|
||||
# First we check if the was an explicit file path specified.
|
||||
if not self.file_path:
|
||||
# If not, we first try with backups.
|
||||
# We construct the path to the file according to the iTunes backup
|
||||
# folder structure, if we have a valid ID.
|
||||
if backup_ids:
|
||||
for backup_id in backup_ids:
|
||||
file_path = os.path.join(self.base_folder, backup_id[0:2], backup_id)
|
||||
# If we found the correct backup file, then we stop searching.
|
||||
if os.path.exists(file_path):
|
||||
break
|
||||
|
||||
# If this file does not exist we might be processing a full
|
||||
# filesystem dump (checkra1n all the things!).
|
||||
if not file_path or not os.path.exists(file_path):
|
||||
# We reset the file_path.
|
||||
file_path = None
|
||||
for root_path in root_paths:
|
||||
for found_path in glob.glob(os.path.join(self.base_folder, root_path)):
|
||||
# If we find a valid path, we set file_path.
|
||||
if os.path.exists(found_path):
|
||||
file_path = found_path
|
||||
break
|
||||
|
||||
# Otherwise, we reset the file_path again.
|
||||
file_path = None
|
||||
|
||||
# If we do not find any, we fail.
|
||||
if file_path:
|
||||
self.file_path = file_path
|
||||
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:
|
||||
self._recover_database(self.file_path)
|
||||
@@ -1,12 +1,13 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
|
||||
class CacheFiles(IOSExtraction):
|
||||
|
||||
@@ -57,14 +58,14 @@ class CacheFiles(IOSExtraction):
|
||||
self.results[key_name] = []
|
||||
|
||||
for row in cur:
|
||||
self.results[key_name].append(dict(
|
||||
entry_id=row[0],
|
||||
version=row[1],
|
||||
hash_value=row[2],
|
||||
storage_policy=row[3],
|
||||
url=row[4],
|
||||
isodate=row[5],
|
||||
))
|
||||
self.results[key_name].append({
|
||||
"entry_id": row[0],
|
||||
"version": row[1],
|
||||
"hash_value": row[2],
|
||||
"storage_policy": row[3],
|
||||
"url": row[4],
|
||||
"isodate": row[5],
|
||||
})
|
||||
|
||||
def run(self):
|
||||
self.results = {}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
|
||||
class Filesystem(IOSExtraction):
|
||||
"""This module extracts creation and modification date of files from a
|
||||
@@ -24,7 +25,7 @@ class Filesystem(IOSExtraction):
|
||||
return {
|
||||
"timestamp": record["modified"],
|
||||
"module": self.__class__.__name__,
|
||||
"event": f"file_modified",
|
||||
"event": "file_modified",
|
||||
"data": record["file_path"],
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .net_base import NetBase
|
||||
import sqlite3
|
||||
|
||||
from ..net_base import NetBase
|
||||
|
||||
NETUSAGE_ROOT_PATHS = [
|
||||
"private/var/networkd/netusage.sqlite",
|
||||
@@ -21,8 +23,13 @@ class Netusage(NetBase):
|
||||
log=log, results=results)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(root_paths=NETUSAGE_ROOT_PATHS)
|
||||
self.log.info("Found NetUsage database at path: %s", self.file_path)
|
||||
for netusage_path in self._get_fs_files_from_patterns(NETUSAGE_ROOT_PATHS):
|
||||
self.file_path = netusage_path
|
||||
self.log.info("Found NetUsage database at path: %s", self.file_path)
|
||||
try:
|
||||
self._extract_net_data()
|
||||
except sqlite3.OperationalError as e:
|
||||
self.log.info("Skipping this NetUsage database because it seems empty or malformed: %s", e)
|
||||
continue
|
||||
|
||||
self._extract_net_data()
|
||||
self._find_suspicious_processes()
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import sqlite3
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
SAFARI_FAVICON_ROOT_PATHS = [
|
||||
"private/var/mobile/Library/Image Cache/Favicons/Favicons.db",
|
||||
@@ -39,50 +39,57 @@ class SafariFavicon(IOSExtraction):
|
||||
if self.indicators.check_domain(result["url"]) or self.indicators.check_domain(result["icon_url"]):
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(root_paths=SAFARI_FAVICON_ROOT_PATHS)
|
||||
self.log.info("Found Safari favicon cache database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
def _process_favicon_db(self, file_path):
|
||||
conn = sqlite3.connect(file_path)
|
||||
|
||||
# Fetch valid icon cache.
|
||||
cur = conn.cursor()
|
||||
cur.execute("""SELECT
|
||||
cur.execute("""
|
||||
SELECT
|
||||
page_url.url,
|
||||
icon_info.url,
|
||||
icon_info.timestamp
|
||||
FROM page_url
|
||||
JOIN icon_info ON page_url.uuid = icon_info.uuid
|
||||
ORDER BY icon_info.timestamp;""")
|
||||
ORDER BY icon_info.timestamp;
|
||||
""")
|
||||
|
||||
items = []
|
||||
for item in cur:
|
||||
items.append(dict(
|
||||
url=item[0],
|
||||
icon_url=item[1],
|
||||
timestamp=item[2],
|
||||
isodate=convert_timestamp_to_iso(convert_mactime_to_unix(item[2])),
|
||||
type="valid",
|
||||
))
|
||||
for row in cur:
|
||||
self.results.append({
|
||||
"url": row[0],
|
||||
"icon_url": row[1],
|
||||
"timestamp": row[2],
|
||||
"isodate": convert_timestamp_to_iso(convert_mactime_to_unix(row[2])),
|
||||
"type": "valid",
|
||||
"safari_favicon_db_path": file_path,
|
||||
})
|
||||
|
||||
# Fetch icons from the rejected icons table.
|
||||
cur.execute("""SELECT
|
||||
cur.execute("""
|
||||
SELECT
|
||||
page_url,
|
||||
icon_url,
|
||||
timestamp
|
||||
FROM rejected_resources ORDER BY timestamp;""")
|
||||
FROM rejected_resources ORDER BY timestamp;
|
||||
""")
|
||||
|
||||
for item in cur:
|
||||
items.append(dict(
|
||||
url=item[0],
|
||||
icon_url=item[1],
|
||||
timestamp=item[2],
|
||||
isodate=convert_timestamp_to_iso(convert_mactime_to_unix(item[2])),
|
||||
type="rejected",
|
||||
))
|
||||
for row in cur:
|
||||
self.results.append({
|
||||
"url": row[0],
|
||||
"icon_url": row[1],
|
||||
"timestamp": row[2],
|
||||
"isodate": convert_timestamp_to_iso(convert_mactime_to_unix(row[2])),
|
||||
"type": "rejected",
|
||||
"safari_favicon_db_path": file_path,
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted a total of %d favicon records", len(items))
|
||||
self.results = sorted(items, key=lambda item: item["isodate"])
|
||||
def run(self):
|
||||
for file_path in self._get_fs_files_from_patterns(SAFARI_FAVICON_ROOT_PATHS):
|
||||
self.log.info("Found Safari favicon cache database at path: %s", file_path)
|
||||
self._process_favicon_db(file_path)
|
||||
|
||||
self.log.info("Extracted a total of %d favicon records", len(self.results))
|
||||
self.results = sorted(self.results, key=lambda x: x["isodate"])
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import json
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
IOS_ANALYTICS_JOURNAL_PATHS = [
|
||||
"private/var/db/analyticsd/Analytics-Journal-*.ips",
|
||||
@@ -32,7 +32,7 @@ class IOSVersionHistory(IOSExtraction):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
for found_path in self._find_paths(IOS_ANALYTICS_JOURNAL_PATHS):
|
||||
for found_path in self._get_fs_files_from_patterns(IOS_ANALYTICS_JOURNAL_PATHS):
|
||||
with open(found_path, "r") as analytics_log:
|
||||
log_line = json.loads(analytics_log.readline().strip())
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import datetime
|
||||
|
||||
from .base import IOSExtraction
|
||||
import os
|
||||
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
|
||||
class WebkitBase(IOSExtraction):
|
||||
"""This class is a base for other WebKit-related modules."""
|
||||
|
||||
@@ -21,8 +22,8 @@ class WebkitBase(IOSExtraction):
|
||||
if self.indicators.check_domain(item["url"]):
|
||||
self.detected.append(item)
|
||||
|
||||
def _database_from_path(self, root_paths):
|
||||
for found_path in self._find_paths(root_paths):
|
||||
def _process_webkit_folder(self, root_paths):
|
||||
for found_path in self._get_fs_files_from_patterns(root_paths):
|
||||
key = os.path.relpath(found_path, self.base_folder)
|
||||
|
||||
for name in os.listdir(found_path):
|
||||
@@ -33,8 +34,8 @@ class WebkitBase(IOSExtraction):
|
||||
name = name.replace("https_", "https://")
|
||||
url = name.split("_")[0]
|
||||
|
||||
self.results.append(dict(
|
||||
folder=key,
|
||||
url=url,
|
||||
isodate=convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(found_path).st_mtime)),
|
||||
))
|
||||
self.results.append({
|
||||
"folder": key,
|
||||
"url": url,
|
||||
"isodate": convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(os.stat(found_path).st_mtime)),
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .webkit_base import WebkitBase
|
||||
|
||||
@@ -30,6 +30,6 @@ class WebkitIndexedDB(WebkitBase):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
self._database_from_path(WEBKIT_INDEXEDDB_ROOT_PATHS)
|
||||
self._process_webkit_folder(WEBKIT_INDEXEDDB_ROOT_PATHS)
|
||||
self.log.info("Extracted a total of %d WebKit IndexedDB records",
|
||||
len(self.results))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .webkit_base import WebkitBase
|
||||
|
||||
@@ -28,6 +28,6 @@ class WebkitLocalStorage(WebkitBase):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
self._database_from_path(WEBKIT_LOCALSTORAGE_ROOT_PATHS)
|
||||
self._process_webkit_folder(WEBKIT_LOCALSTORAGE_ROOT_PATHS)
|
||||
self.log.info("Extracted a total of %d records from WebKit Local Storages",
|
||||
len(self.results))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .webkit_base import WebkitBase
|
||||
|
||||
@@ -20,4 +20,6 @@ class WebkitSafariViewService(WebkitBase):
|
||||
log=log, results=results)
|
||||
|
||||
def run(self):
|
||||
self._database_from_path(WEBKIT_SAFARIVIEWSERVICE_ROOT_PATHS)
|
||||
self._process_webkit_folder(WEBKIT_SAFARIVIEWSERVICE_ROOT_PATHS)
|
||||
self.log.info("Extracted a total of %d records from WebKit SafariViewService WebsiteData",
|
||||
len(self.results))
|
||||
|
||||
27
mvt/ios/modules/mixed/__init__.py
Normal file
27
mvt/ios/modules/mixed/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .calls import Calls
|
||||
from .chrome_favicon import ChromeFavicon
|
||||
from .chrome_history import ChromeHistory
|
||||
from .contacts import Contacts
|
||||
from .firefox_favicon import FirefoxFavicon
|
||||
from .firefox_history import FirefoxHistory
|
||||
from .idstatuscache import IDStatusCache
|
||||
from .interactionc import InteractionC
|
||||
from .locationd import LocationdClients
|
||||
from .net_datausage import Datausage
|
||||
from .safari_browserstate import SafariBrowserState
|
||||
from .safari_history import SafariHistory
|
||||
from .sms import SMS
|
||||
from .sms_attachments import SMSAttachments
|
||||
from .webkit_resource_load_statistics import WebkitResourceLoadStatistics
|
||||
from .webkit_session_resource_log import WebkitSessionResourceLog
|
||||
from .whatsapp import Whatsapp
|
||||
|
||||
MIXED_MODULES = [Calls, ChromeFavicon, ChromeHistory, Contacts, FirefoxFavicon,
|
||||
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
|
||||
Datausage, SafariBrowserState, SafariHistory, SMS, SMSAttachments,
|
||||
WebkitResourceLoadStatistics, WebkitSessionResourceLog, Whatsapp,]
|
||||
@@ -1,12 +1,13 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import sqlite3
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
from .base import IOSExtraction
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CALLS_BACKUP_IDS = [
|
||||
"5a4935c78a5255723f707230a451d79c540d2741",
|
||||
@@ -33,7 +34,8 @@ class Calls(IOSExtraction):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=CALLS_BACKUP_IDS, root_paths=CALLS_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=CALLS_BACKUP_IDS,
|
||||
root_paths=CALLS_ROOT_PATHS)
|
||||
self.log.info("Found Calls database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
@@ -41,17 +43,17 @@ class Calls(IOSExtraction):
|
||||
cur.execute("""
|
||||
SELECT
|
||||
ZDATE, ZDURATION, ZLOCATION, ZADDRESS, ZSERVICE_PROVIDER
|
||||
FROM ZCALLRECORD;
|
||||
FROM ZCALLRECORD;
|
||||
""")
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for entry in cur:
|
||||
for row in cur:
|
||||
self.results.append({
|
||||
"isodate": convert_timestamp_to_iso(convert_mactime_to_unix(entry[0])),
|
||||
"duration": entry[1],
|
||||
"location": entry[2],
|
||||
"number": entry[3].decode("utf-8") if entry[3] and entry[3] is bytes else entry[3],
|
||||
"provider": entry[4]
|
||||
"isodate": convert_timestamp_to_iso(convert_mactime_to_unix(row[0])),
|
||||
"duration": row[1],
|
||||
"location": row[2],
|
||||
"number": row[3].decode("utf-8") if row[3] and row[3] is bytes else row[3],
|
||||
"provider": row[4]
|
||||
})
|
||||
|
||||
cur.close()
|
||||
@@ -1,13 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CHROME_FAVICON_BACKUP_IDS = [
|
||||
"55680ab883d0fdcffd94f959b1632e5fbbb18c5b"
|
||||
@@ -44,14 +45,16 @@ class ChromeFavicon(IOSExtraction):
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=CHROME_FAVICON_BACKUP_IDS, root_paths=CHROME_FAVICON_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=CHROME_FAVICON_BACKUP_IDS,
|
||||
root_paths=CHROME_FAVICON_ROOT_PATHS)
|
||||
self.log.info("Found Chrome favicon cache database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
|
||||
# Fetch icon cache
|
||||
cur = conn.cursor()
|
||||
cur.execute("""SELECT
|
||||
cur.execute("""
|
||||
SELECT
|
||||
icon_mapping.page_url,
|
||||
favicons.url,
|
||||
favicon_bitmaps.last_updated,
|
||||
@@ -59,20 +62,21 @@ class ChromeFavicon(IOSExtraction):
|
||||
FROM icon_mapping
|
||||
JOIN favicon_bitmaps ON icon_mapping.icon_id = favicon_bitmaps.icon_id
|
||||
JOIN favicons ON icon_mapping.icon_id = favicons.id
|
||||
ORDER BY icon_mapping.id;""")
|
||||
ORDER BY icon_mapping.id;
|
||||
""")
|
||||
|
||||
items = []
|
||||
for item in cur:
|
||||
last_timestamp = int(item[2]) or int(item[3])
|
||||
items.append(dict(
|
||||
url=item[0],
|
||||
icon_url=item[1],
|
||||
timestamp=last_timestamp,
|
||||
isodate=convert_timestamp_to_iso(convert_chrometime_to_unix(last_timestamp)),
|
||||
))
|
||||
records = []
|
||||
for row in cur:
|
||||
last_timestamp = int(row[2]) or int(row[3])
|
||||
records.append({
|
||||
"url": row[0],
|
||||
"icon_url": row[1],
|
||||
"timestamp": last_timestamp,
|
||||
"isodate": convert_timestamp_to_iso(convert_chrometime_to_unix(last_timestamp)),
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted a total of %d favicon records", len(items))
|
||||
self.results = sorted(items, key=lambda item: item["isodate"])
|
||||
self.log.info("Extracted a total of %d favicon records", len(records))
|
||||
self.results = sorted(records, key=lambda row: row["isodate"])
|
||||
@@ -1,13 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CHROME_HISTORY_BACKUP_IDS = [
|
||||
"faf971ce92c3ac508c018dce1bef2a8b8e9838f1",
|
||||
@@ -35,8 +36,17 @@ 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._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)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
@@ -54,14 +64,14 @@ class ChromeHistory(IOSExtraction):
|
||||
""")
|
||||
|
||||
for item in cur:
|
||||
self.results.append(dict(
|
||||
id=item[0],
|
||||
url=item[1],
|
||||
visit_id=item[2],
|
||||
timestamp=item[3],
|
||||
isodate=convert_timestamp_to_iso(convert_chrometime_to_unix(item[3])),
|
||||
redirect_source=item[4],
|
||||
))
|
||||
self.results.append({
|
||||
"id": item[0],
|
||||
"url": item[1],
|
||||
"visit_id": item[2],
|
||||
"timestamp": item[3],
|
||||
"isodate": convert_timestamp_to_iso(convert_chrometime_to_unix(item[3])),
|
||||
"redirect_source": item[4],
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
@@ -1,11 +1,11 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import sqlite3
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CONTACTS_BACKUP_IDS = [
|
||||
"31bb7ba8914766d4ba40d6dfb6113c8b614be442",
|
||||
@@ -39,9 +39,9 @@ class Contacts(IOSExtraction):
|
||||
""")
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for entry in cur:
|
||||
new_contact = dict()
|
||||
for index, value in enumerate(entry):
|
||||
for row in cur:
|
||||
new_contact = {}
|
||||
for index, value in enumerate(row):
|
||||
new_contact[names[index]] = value
|
||||
|
||||
self.results.append(new_contact)
|
||||
@@ -49,4 +49,5 @@ class Contacts(IOSExtraction):
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted a total of %d contacts from the address book", len(self.results))
|
||||
self.log.info("Extracted a total of %d contacts from the address book",
|
||||
len(self.results))
|
||||
@@ -1,15 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import sqlite3
|
||||
|
||||
from datetime import datetime
|
||||
from mvt.common.url import URL
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
FIREFOX_HISTORY_BACKUP_IDS = [
|
||||
"2e57c396a35b0d1bcbc624725002d98bd61d142b",
|
||||
@@ -40,11 +39,13 @@ class FirefoxFavicon(IOSExtraction):
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
if self.indicators.check_domain(result["url"]) or self.indicators.check_domain(result["history_url"]):
|
||||
if (self.indicators.check_domain(result.get("url", "")) or
|
||||
self.indicators.check_domain(result.get("history_url", ""))):
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=FIREFOX_HISTORY_BACKUP_IDS, root_paths=FIREFOX_HISTORY_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=FIREFOX_HISTORY_BACKUP_IDS,
|
||||
root_paths=FIREFOX_HISTORY_ROOT_PATHS)
|
||||
self.log.info("Found Firefox favicon database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
@@ -65,16 +66,16 @@ class FirefoxFavicon(IOSExtraction):
|
||||
""")
|
||||
|
||||
for item in cur:
|
||||
self.results.append(dict(
|
||||
id=item[0],
|
||||
url=item[1],
|
||||
width=item[2],
|
||||
height=item[3],
|
||||
type=item[4],
|
||||
isodate=convert_timestamp_to_iso(datetime.utcfromtimestamp(item[5])),
|
||||
history_id=item[6],
|
||||
history_url=item[7]
|
||||
))
|
||||
self.results.append({
|
||||
"id": item[0],
|
||||
"url": item[1],
|
||||
"width": item[2],
|
||||
"height": item[3],
|
||||
"type": item[4],
|
||||
"isodate": convert_timestamp_to_iso(datetime.utcfromtimestamp(item[5])),
|
||||
"history_id": item[6],
|
||||
"history_url": item[7]
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
@@ -1,15 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import sqlite3
|
||||
|
||||
from datetime import datetime
|
||||
from mvt.common.url import URL
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
FIREFOX_HISTORY_BACKUP_IDS = [
|
||||
"2e57c396a35b0d1bcbc624725002d98bd61d142b",
|
||||
@@ -62,15 +61,15 @@ class FirefoxHistory(IOSExtraction):
|
||||
WHERE visits.siteID = history.id;
|
||||
""")
|
||||
|
||||
for item in cur:
|
||||
self.results.append(dict(
|
||||
id=item[0],
|
||||
isodate=convert_timestamp_to_iso(datetime.utcfromtimestamp(item[1])),
|
||||
url=item[2],
|
||||
title=item[3],
|
||||
i1000000s_local=item[4],
|
||||
type=item[5]
|
||||
))
|
||||
for row in cur:
|
||||
self.results.append({
|
||||
"id": row[0],
|
||||
"isodate": convert_timestamp_to_iso(datetime.utcfromtimestamp(row[1])),
|
||||
"url": row[2],
|
||||
"title": row[3],
|
||||
"i1000000s_local": row[4],
|
||||
"type": row[5]
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
@@ -1,16 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import glob
|
||||
import biplist
|
||||
import collections
|
||||
import plistlib
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
IDSTATUSCACHE_BACKUP_IDS = [
|
||||
"6b97989189901ceaa4e5be9b7f05fb584120e27b",
|
||||
@@ -41,22 +39,24 @@ class IDStatusCache(IOSExtraction):
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
if result["user"].startswith("mailto:"):
|
||||
if result.get("user", "").startswith("mailto:"):
|
||||
email = result["user"][7:].strip("'")
|
||||
if self.indicators.check_email(email):
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
if "\\x00\\x00" in result["user"]:
|
||||
if "\\x00\\x00" in result.get("user", ""):
|
||||
self.log.warning("Found an ID Status Cache entry with suspicious patterns: %s",
|
||||
result["user"])
|
||||
result.get("user"))
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=IDSTATUSCACHE_BACKUP_IDS, root_paths=IDSTATUSCACHE_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=IDSTATUSCACHE_BACKUP_IDS,
|
||||
root_paths=IDSTATUSCACHE_ROOT_PATHS)
|
||||
self.log.info("Found IDStatusCache plist at path: %s", self.file_path)
|
||||
|
||||
file_plist = biplist.readPlist(self.file_path)
|
||||
with open(self.file_path, "rb") as handle:
|
||||
file_plist = plistlib.load(handle)
|
||||
|
||||
id_status_cache_entries = []
|
||||
for app in file_plist:
|
||||
@@ -79,7 +79,7 @@ class IDStatusCache(IOSExtraction):
|
||||
|
||||
entry_counter = collections.Counter([entry["user"] for entry in id_status_cache_entries])
|
||||
for entry in id_status_cache_entries:
|
||||
# Add total count of occurrences to the status cache entry
|
||||
# Add total count of occurrences to the status cache entry.
|
||||
entry["occurrences"] = entry_counter[entry["user"]]
|
||||
self.results.append(entry)
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import sqlite3
|
||||
from base64 import b64encode
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
INTERACTIONC_BACKUP_IDS = [
|
||||
"1f5a521220a3ad80ebfdc196978df8e7a2e49dee",
|
||||
@@ -117,55 +116,56 @@ class InteractionC(IOSExtraction):
|
||||
ZINTERACTIONS.ZGROUPNAME,
|
||||
ZINTERACTIONS.ZDERIVEDINTENTIDENTIFIER,
|
||||
ZINTERACTIONS.Z_PK
|
||||
FROM ZINTERACTIONS
|
||||
LEFT JOIN ZCONTACTS ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
||||
LEFT JOIN Z_1INTERACTIONS ON ZINTERACTIONS.Z_PK == Z_1INTERACTIONS.Z_3INTERACTIONS
|
||||
LEFT JOIN ZATTACHMENT ON Z_1INTERACTIONS.Z_1ATTACHMENTS == ZATTACHMENT.Z_PK
|
||||
LEFT JOIN Z_2INTERACTIONRECIPIENT ON ZINTERACTIONS.Z_PK== Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
||||
LEFT JOIN ZCONTACTS RECEIPIENTCONACT ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS== RECEIPIENTCONACT.Z_PK;
|
||||
FROM ZINTERACTIONS
|
||||
LEFT JOIN ZCONTACTS ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
||||
LEFT JOIN Z_1INTERACTIONS ON ZINTERACTIONS.Z_PK == Z_1INTERACTIONS.Z_3INTERACTIONS
|
||||
LEFT JOIN ZATTACHMENT ON Z_1INTERACTIONS.Z_1ATTACHMENTS == ZATTACHMENT.Z_PK
|
||||
LEFT JOIN Z_2INTERACTIONRECIPIENT ON ZINTERACTIONS.Z_PK== Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
||||
LEFT JOIN ZCONTACTS RECEIPIENTCONACT ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS== RECEIPIENTCONACT.Z_PK;
|
||||
""")
|
||||
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for item in cur:
|
||||
for row in cur:
|
||||
self.results.append({
|
||||
"start_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[0])),
|
||||
"end_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[1])),
|
||||
"bundle_id": item[2],
|
||||
"account": item[3],
|
||||
"target_bundle_id": item[4],
|
||||
"direction": item[5],
|
||||
"sender_display_name": item[6],
|
||||
"sender_identifier": item[7],
|
||||
"sender_personid": item[8],
|
||||
"recipient_display_name": item[9],
|
||||
"recipient_identifier": item[10],
|
||||
"recipient_personid": item[11],
|
||||
"recipient_count": item[12],
|
||||
"domain_identifier": item[13],
|
||||
"is_response": item[14],
|
||||
"content": item[15],
|
||||
"uti": item[16],
|
||||
"content_url": item[17],
|
||||
"size": item[18],
|
||||
"photo_local_id": item[19],
|
||||
"attachment_id": item[20],
|
||||
"cloud_id": item[21],
|
||||
"incoming_recipient_count": item[22],
|
||||
"incoming_sender_count": item[23],
|
||||
"outgoing_recipient_count": item[24],
|
||||
"interactions_creation_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[25])) if item[25] else None,
|
||||
"contacts_creation_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[26])) if item[26] else None,
|
||||
"first_incoming_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[27])) if item[27] else None,
|
||||
"first_incoming_sender_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[28])) if item[28] else None,
|
||||
"first_outgoing_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[29])) if item[29] else None,
|
||||
"last_incoming_sender_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[30])) if item[30] else None,
|
||||
"last_incoming_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[31])) if item[31] else None,
|
||||
"last_outgoing_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(item[32])) if item[32] else None,
|
||||
"custom_id": item[33],
|
||||
"location_uuid": item[35],
|
||||
"group_name": item[36],
|
||||
"derivied_intent_id": item[37],
|
||||
"table_id": item[38]
|
||||
"start_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[0])),
|
||||
"end_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[1])),
|
||||
"bundle_id": row[2],
|
||||
"account": row[3],
|
||||
"target_bundle_id": row[4],
|
||||
"direction": row[5],
|
||||
"sender_display_name": row[6],
|
||||
"sender_identifier": row[7],
|
||||
"sender_personid": row[8],
|
||||
"recipient_display_name": row[9],
|
||||
"recipient_identifier": row[10],
|
||||
"recipient_personid": row[11],
|
||||
"recipient_count": row[12],
|
||||
"domain_identifier": row[13],
|
||||
"is_response": row[14],
|
||||
"content": row[15],
|
||||
"uti": row[16],
|
||||
"content_url": row[17],
|
||||
"size": row[18],
|
||||
"photo_local_id": row[19],
|
||||
"attachment_id": row[20],
|
||||
"cloud_id": row[21],
|
||||
"incoming_recipient_count": row[22],
|
||||
"incoming_sender_count": row[23],
|
||||
"outgoing_recipient_count": row[24],
|
||||
"interactions_creation_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[25])) if row[25] else None,
|
||||
"contacts_creation_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[26])) if row[26] else None,
|
||||
"first_incoming_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[27])) if row[27] else None,
|
||||
"first_incoming_sender_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[28])) if row[28] else None,
|
||||
"first_outgoing_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[29])) if row[29] else None,
|
||||
"last_incoming_sender_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[30])) if row[30] else None,
|
||||
"last_incoming_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[31])) if row[31] else None,
|
||||
"last_outgoing_recipient_date": convert_timestamp_to_iso(convert_mactime_to_unix(row[32])) if row[32] else None,
|
||||
"custom_id": row[33],
|
||||
"location_uuid": row[35],
|
||||
"group_name": row[36],
|
||||
"derivied_intent_id": row[37],
|
||||
"table_id": row[38]
|
||||
})
|
||||
|
||||
cur.close()
|
||||
@@ -1,15 +1,13 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import glob
|
||||
import biplist
|
||||
import plistlib
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
LOCATIOND_BACKUP_IDS = [
|
||||
"a690d7769cce8904ca2b67320b107c8fe5f79412",
|
||||
@@ -26,6 +24,7 @@ class LocationdClients(IOSExtraction):
|
||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
||||
output_folder=output_folder, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
|
||||
self.timestamps = [
|
||||
"ConsumptionPeriodBegin",
|
||||
"ReceivingLocationInformationTimeStopped",
|
||||
@@ -52,9 +51,12 @@ class LocationdClients(IOSExtraction):
|
||||
return records
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=LOCATIOND_BACKUP_IDS, root_paths=LOCATIOND_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=LOCATIOND_BACKUP_IDS,
|
||||
root_paths=LOCATIOND_ROOT_PATHS)
|
||||
self.log.info("Found Locationd Clients plist at path: %s", self.file_path)
|
||||
file_plist = biplist.readPlist(self.file_path)
|
||||
|
||||
with open(self.file_path, "rb") as handle:
|
||||
file_plist = plistlib.load(handle)
|
||||
|
||||
for app in file_plist:
|
||||
if file_plist[app] is dict:
|
||||
@@ -1,9 +1,9 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .net_base import NetBase
|
||||
from ..net_base import NetBase
|
||||
|
||||
DATAUSAGE_BACKUP_IDS = [
|
||||
"0d609c54856a9bb2d56729df1d68f2958a88426b",
|
||||
@@ -23,7 +23,8 @@ class Datausage(NetBase):
|
||||
log=log, results=results)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=DATAUSAGE_BACKUP_IDS, root_paths=DATAUSAGE_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=DATAUSAGE_BACKUP_IDS,
|
||||
root_paths=DATAUSAGE_ROOT_PATHS)
|
||||
self.log.info("Found DataUsage database at path: %s", self.file_path)
|
||||
|
||||
self._extract_net_data()
|
||||
@@ -1,20 +1,22 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import io
|
||||
import biplist
|
||||
import os
|
||||
import plistlib
|
||||
import sqlite3
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
from mvt.common.utils import keys_bytes_to_string
|
||||
from mvt.common.utils import (convert_mactime_to_unix,
|
||||
convert_timestamp_to_iso, keys_bytes_to_string)
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
SAFARI_BROWSER_STATE_BACKUP_IDS = [
|
||||
"3a47b0981ed7c10f3e2800aa66bac96a3b5db28e",
|
||||
]
|
||||
SAFARI_BROWSER_STATE_BACKUP_RELPATH = "Library/Safari/BrowserState.db"
|
||||
SAFARI_BROWSER_STATE_ROOT_PATHS = [
|
||||
"private/var/mobile/Library/Safari/BrowserState.db",
|
||||
"private/var/mobile/Containers/Data/Application/*/Library/Safari/BrowserState.db",
|
||||
@@ -29,6 +31,8 @@ class SafariBrowserState(IOSExtraction):
|
||||
output_folder=output_folder, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
|
||||
self._session_history_count = 0
|
||||
|
||||
def serialize(self, record):
|
||||
return {
|
||||
"timestamp": record["last_viewed_timestamp"],
|
||||
@@ -53,16 +57,12 @@ class SafariBrowserState(IOSExtraction):
|
||||
if "entry_url" in session_entry and self.indicators.check_domain(session_entry["entry_url"]):
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=SAFARI_BROWSER_STATE_BACKUP_IDS,
|
||||
root_paths=SAFARI_BROWSER_STATE_ROOT_PATHS)
|
||||
self.log.info("Found Safari browser state database at path: %s", self.file_path)
|
||||
def _process_browser_state_db(self, db_path):
|
||||
conn = sqlite3.connect(db_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
|
||||
# Fetch valid icon cache.
|
||||
cur = conn.cursor()
|
||||
cur.execute("""SELECT
|
||||
cur.execute("""
|
||||
SELECT
|
||||
tabs.title,
|
||||
tabs.url,
|
||||
tabs.user_visible_url,
|
||||
@@ -70,34 +70,43 @@ class SafariBrowserState(IOSExtraction):
|
||||
tab_sessions.session_data
|
||||
FROM tabs
|
||||
JOIN tab_sessions ON tabs.uuid = tab_sessions.tab_uuid
|
||||
ORDER BY tabs.last_viewed_time;""")
|
||||
ORDER BY tabs.last_viewed_time;
|
||||
""")
|
||||
|
||||
session_history_count = 0
|
||||
for item in cur:
|
||||
for row in cur:
|
||||
session_entries = []
|
||||
|
||||
if item[4]:
|
||||
if row[4]:
|
||||
# Skip a 4 byte header before the plist content.
|
||||
session_plist = item[4][4:]
|
||||
session_data = biplist.readPlist(io.BytesIO(session_plist))
|
||||
session_plist = row[4][4:]
|
||||
session_data = plistlib.load(io.BytesIO(session_plist))
|
||||
session_data = keys_bytes_to_string(session_data)
|
||||
|
||||
if "SessionHistoryEntries" in session_data["SessionHistory"]:
|
||||
for session_entry in session_data["SessionHistory"]["SessionHistoryEntries"]:
|
||||
session_history_count += 1
|
||||
session_entries.append(dict(
|
||||
entry_title=session_entry["SessionHistoryEntryOriginalURL"],
|
||||
entry_url=session_entry["SessionHistoryEntryURL"],
|
||||
data_length=len(session_entry["SessionHistoryEntryData"]) if "SessionHistoryEntryData" in session_entry else 0,
|
||||
))
|
||||
if "SessionHistoryEntries" in session_data.get("SessionHistory", {}):
|
||||
for session_entry in session_data["SessionHistory"].get("SessionHistoryEntries"):
|
||||
self._session_history_count += 1
|
||||
session_entries.append({
|
||||
"entry_title": session_entry.get("SessionHistoryEntryOriginalURL"),
|
||||
"entry_url": session_entry.get("SessionHistoryEntryURL"),
|
||||
"data_length": len(session_entry.get("SessionHistoryEntryData")) if "SessionHistoryEntryData" in session_entry else 0,
|
||||
})
|
||||
|
||||
self.results.append(dict(
|
||||
tab_title=item[0],
|
||||
tab_url=item[1],
|
||||
tab_visible_url=item[2],
|
||||
last_viewed_timestamp=convert_timestamp_to_iso(convert_mactime_to_unix(item[3])),
|
||||
session_data=session_entries,
|
||||
))
|
||||
self.results.append({
|
||||
"tab_title": row[0],
|
||||
"tab_url": row[1],
|
||||
"tab_visible_url": row[2],
|
||||
"last_viewed_timestamp": convert_timestamp_to_iso(convert_mactime_to_unix(row[3])),
|
||||
"session_data": session_entries,
|
||||
"safari_browser_state_db": os.path.relpath(db_path, self.base_folder),
|
||||
})
|
||||
|
||||
def run(self):
|
||||
# TODO: Is there really only one BrowserState.db in a device?
|
||||
self._find_ios_database(backup_ids=SAFARI_BROWSER_STATE_BACKUP_IDS,
|
||||
root_paths=SAFARI_BROWSER_STATE_ROOT_PATHS)
|
||||
self.log.info("Found Safari browser state database at path: %s", self.file_path)
|
||||
|
||||
self._process_browser_state_db(self.file_path)
|
||||
|
||||
self.log.info("Extracted a total of %d tab records and %d session history entries",
|
||||
len(self.results), session_history_count)
|
||||
len(self.results), self._session_history_count)
|
||||
@@ -1,19 +1,17 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from mvt.common.url import URL
|
||||
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
SAFARI_HISTORY_BACKUP_IDS = [
|
||||
"e74113c185fd8297e140cfcf9c99436c5cc06b57",
|
||||
"1a0e7afc19d307da602ccdcece51af33afe92c53",
|
||||
]
|
||||
SAFARI_HISTORY_BACKUP_RELPATH = "Library/Safari/History.db"
|
||||
SAFARI_HISTORY_ROOT_PATHS = [
|
||||
"private/var/mobile/Library/Safari/History.db",
|
||||
"private/var/mobile/Containers/Data/Application/*/Library/Safari/History.db",
|
||||
@@ -81,11 +79,8 @@ class SafariHistory(IOSExtraction):
|
||||
if self.indicators.check_domain(result["url"]):
|
||||
self.detected.append(result)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=SAFARI_HISTORY_BACKUP_IDS, root_paths=SAFARI_HISTORY_ROOT_PATHS)
|
||||
self.log.info("Found Safari history database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
def _process_history_db(self, history_path):
|
||||
conn = sqlite3.connect(history_path)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT
|
||||
@@ -100,20 +95,33 @@ class SafariHistory(IOSExtraction):
|
||||
ORDER BY history_visits.visit_time;
|
||||
""")
|
||||
|
||||
items = []
|
||||
for item in cur:
|
||||
items.append(dict(
|
||||
id=item[0],
|
||||
url=item[1],
|
||||
visit_id=item[2],
|
||||
timestamp=item[3],
|
||||
isodate=convert_timestamp_to_iso(convert_mactime_to_unix(item[3])),
|
||||
redirect_source=item[4],
|
||||
redirect_destination=item[5]
|
||||
))
|
||||
for row in cur:
|
||||
self.results.append({
|
||||
"id": row[0],
|
||||
"url": row[1],
|
||||
"visit_id": row[2],
|
||||
"timestamp": row[3],
|
||||
"isodate": convert_timestamp_to_iso(convert_mactime_to_unix(row[3])),
|
||||
"redirect_source": row[4],
|
||||
"redirect_destination": row[5],
|
||||
"safari_history_db": os.path.relpath(history_path, self.base_folder),
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted a total of %d history items", len(items))
|
||||
self.results = items
|
||||
def run(self):
|
||||
if self.is_backup:
|
||||
for history_file in self._get_backup_files_from_manifest(relative_path=SAFARI_HISTORY_BACKUP_RELPATH):
|
||||
history_path = self._get_backup_file_from_id(history_file["file_id"])
|
||||
if not history_path:
|
||||
continue
|
||||
|
||||
self.log.info("Found Safari history database at path: %s", history_path)
|
||||
self._process_history_db(history_path)
|
||||
elif self.is_fs_dump:
|
||||
for history_path in self._get_fs_files_from_patterns(SAFARI_HISTORY_ROOT_PATHS):
|
||||
self.log.info("Found Safari history database at path: %s", history_path)
|
||||
self._process_history_db(history_path)
|
||||
|
||||
self.log.info("Extracted a total of %d history records", len(self.results))
|
||||
@@ -1,15 +1,15 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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
|
||||
from ..base import IOSExtraction
|
||||
|
||||
SMS_BACKUP_IDS = [
|
||||
"3d0d7e5fb2ce288813306e4d4636395e047a3d28",
|
||||
@@ -41,15 +41,13 @@ class SMS(IOSExtraction):
|
||||
return
|
||||
|
||||
for message in self.results:
|
||||
if not "text" in message:
|
||||
continue
|
||||
|
||||
message_links = check_for_links(message["text"])
|
||||
message_links = check_for_links(message.get("text", ""))
|
||||
if self.indicators.check_domains(message_links):
|
||||
self.detected.append(message)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=SMS_BACKUP_IDS, root_paths=SMS_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=SMS_BACKUP_IDS,
|
||||
root_paths=SMS_ROOT_PATHS)
|
||||
self.log.info("Found SMS database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
@@ -64,7 +62,7 @@ class SMS(IOSExtraction):
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for item in cur:
|
||||
message = dict()
|
||||
message = {}
|
||||
for index, value in enumerate(item):
|
||||
# We base64 escape some of the attributes that could contain
|
||||
# binary data.
|
||||
@@ -78,17 +76,17 @@ class SMS(IOSExtraction):
|
||||
|
||||
# We convert Mac's ridiculous timestamp format.
|
||||
message["isodate"] = convert_timestamp_to_iso(convert_mactime_to_unix(message["date"]))
|
||||
message["direction"] = ("sent" if message["is_from_me"] == 1 else "received")
|
||||
message["direction"] = ("sent" if message.get("is_from_me", 0) == 1 else "received")
|
||||
|
||||
# Sometimes "text" is None instead of empty string.
|
||||
if message["text"] is None:
|
||||
if not message.get("text", None):
|
||||
message["text"] = ""
|
||||
|
||||
# Extract links from the SMS message.
|
||||
message_links = check_for_links(message["text"])
|
||||
message_links = check_for_links(message.get("text", ""))
|
||||
|
||||
# If we find links in the messages or if they are empty we add them to the list.
|
||||
if message_links or message["text"].strip() == "":
|
||||
if message_links or message.get("text", "").strip() == "":
|
||||
self.results.append(message)
|
||||
|
||||
cur.close()
|
||||
@@ -1,15 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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 .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
SMS_BACKUP_IDS = [
|
||||
"3d0d7e5fb2ce288813306e4d4636395e047a3d28",
|
||||
@@ -37,7 +36,8 @@ class SMSAttachments(IOSExtraction):
|
||||
}
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=SMS_BACKUP_IDS, root_paths=SMS_ROOT_PATHS)
|
||||
self._find_ios_database(backup_ids=SMS_BACKUP_IDS,
|
||||
root_paths=SMS_ROOT_PATHS)
|
||||
self.log.info("Found SMS database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
@@ -51,19 +51,20 @@ class SMSAttachments(IOSExtraction):
|
||||
FROM attachment
|
||||
LEFT JOIN message_attachment_join ON message_attachment_join.attachment_id = attachment.ROWID
|
||||
LEFT JOIN message ON message.ROWID = message_attachment_join.message_id
|
||||
LEFT JOIN handle ON handle.ROWID = message.handle_id
|
||||
LEFT JOIN handle ON handle.ROWID = message.handle_id;
|
||||
""")
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for item in cur:
|
||||
attachment = dict()
|
||||
attachment = {}
|
||||
for index, value in enumerate(item):
|
||||
if (names[index] in ["user_info", "sticker_user_info", "attribution_info",
|
||||
"ck_server_change_token_blob", "sr_ck_server_change_token_blob"]) and value:
|
||||
if (names[index] in ["user_info", "sticker_user_info",
|
||||
"attribution_info",
|
||||
"ck_server_change_token_blob",
|
||||
"sr_ck_server_change_token_blob"]) and value:
|
||||
value = b64encode(value).decode()
|
||||
attachment[names[index]] = value
|
||||
|
||||
# We convert Mac's ridiculous timestamp format.
|
||||
attachment["isodate"] = convert_timestamp_to_iso(convert_mactime_to_unix(attachment["created_date"]))
|
||||
attachment["start_date"] = convert_timestamp_to_iso(convert_mactime_to_unix(attachment["start_date"]))
|
||||
attachment["direction"] = ("sent" if attachment["is_outgoing"] == 1 else "received")
|
||||
85
mvt/ios/modules/mixed/webkit_resource_load_statistics.py
Normal file
85
mvt/ios/modules/mixed/webkit_resource_load_statistics.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from mvt.common.utils import 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)
|
||||
|
||||
self.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)
|
||||
|
||||
self._recover_sqlite_db_if_needed(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({
|
||||
"domain_id": row[0],
|
||||
"registrable_domain": row[1],
|
||||
"last_seen": row[2],
|
||||
"had_user_interaction": bool(row[3]),
|
||||
"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):
|
||||
if self.is_backup:
|
||||
try:
|
||||
for backup_file in self._get_backup_files_from_manifest(relative_path=WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH):
|
||||
db_path = os.path.join(self.base_folder, backup_file["file_id"][0:2], backup_file["file_id"])
|
||||
key = f"{backup_file['domain']}/{WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH}"
|
||||
self._process_observations_db(db_path=db_path, key=key)
|
||||
except Exception as e:
|
||||
self.log.info("Unable to search for WebKit observations.db: %s", e)
|
||||
elif self.is_fs_dump:
|
||||
for db_path in self._get_fs_files_from_patterns(WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS):
|
||||
self._process_observations_db(db_path=db_path, key=os.path.relpath(db_path, self.base_folder))
|
||||
@@ -1,20 +1,20 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
import glob
|
||||
import biplist
|
||||
import os
|
||||
import plistlib
|
||||
|
||||
from mvt.common.utils import convert_timestamp_to_iso
|
||||
|
||||
from .base import IOSExtraction
|
||||
from ..base import IOSExtraction
|
||||
|
||||
WEBKIT_SESSION_RESOURCE_LOG_BACKUP_IDS = [
|
||||
"a500ee38053454a02e990957be8a251935e28d3f",
|
||||
]
|
||||
|
||||
WEBKIT_SESSION_RESOURCE_LOG_BACKUP_RELPATH = "Library/WebKit/WebsiteData/ResourceLoadStatistics/full_browsing_session_resourceLog.plist"
|
||||
WEBKIT_SESSION_RESOURCE_LOG_ROOT_PATHS = [
|
||||
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/full_browsing_session_resourceLog.plist",
|
||||
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/ResourceLoadStatistics/full_browsing_session_resourceLog.plist",
|
||||
@@ -32,28 +32,7 @@ class WebkitSessionResourceLog(IOSExtraction):
|
||||
output_folder=output_folder, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
|
||||
def _extract_browsing_stats(self, file_path):
|
||||
items = []
|
||||
|
||||
file_plist = biplist.readPlist(file_path)
|
||||
if "browsingStatistics" not in file_plist:
|
||||
return items
|
||||
|
||||
browsing_stats = file_plist["browsingStatistics"]
|
||||
|
||||
for item in browsing_stats:
|
||||
items.append(dict(
|
||||
origin=item.get("PrevalentResourceOrigin", ""),
|
||||
redirect_source=item.get("topFrameUniqueRedirectsFrom", ""),
|
||||
redirect_destination=item.get("topFrameUniqueRedirectsTo", ""),
|
||||
subframe_under_origin=item.get("subframeUnderTopFrameOrigins", ""),
|
||||
subresource_under_origin=item.get("subresourceUnderTopFrameOrigins", ""),
|
||||
user_interaction=item.get("hadUserInteraction"),
|
||||
most_recent_interaction=convert_timestamp_to_iso(item["mostRecentUserInteraction"]),
|
||||
last_seen=convert_timestamp_to_iso(item["lastSeen"]),
|
||||
))
|
||||
|
||||
return items
|
||||
self.results = {}
|
||||
|
||||
@staticmethod
|
||||
def _extract_domains(entries):
|
||||
@@ -108,32 +87,41 @@ class WebkitSessionResourceLog(IOSExtraction):
|
||||
|
||||
self.log.warning("Found HTTP redirect between suspicious domains: %s", redirect_path)
|
||||
|
||||
def _find_paths(self, root_paths):
|
||||
results = {}
|
||||
for root_path in root_paths:
|
||||
for found_path in glob.glob(os.path.join(self.base_folder, root_path)):
|
||||
if not os.path.exists(found_path):
|
||||
continue
|
||||
def _extract_browsing_stats(self, log_path):
|
||||
items = []
|
||||
|
||||
key = os.path.relpath(found_path, self.base_folder)
|
||||
if key not in results:
|
||||
results[key] = []
|
||||
with open(log_path, "rb") as handle:
|
||||
file_plist = plistlib.load(handle)
|
||||
|
||||
return results
|
||||
if "browsingStatistics" not in file_plist:
|
||||
return items
|
||||
|
||||
browsing_stats = file_plist["browsingStatistics"]
|
||||
|
||||
for item in browsing_stats:
|
||||
items.append({
|
||||
"origin": item.get("PrevalentResourceOrigin", ""),
|
||||
"redirect_source": item.get("topFrameUniqueRedirectsFrom", ""),
|
||||
"redirect_destination": item.get("topFrameUniqueRedirectsTo", ""),
|
||||
"subframe_under_origin": item.get("subframeUnderTopFrameOrigins", ""),
|
||||
"subresource_under_origin": item.get("subresourceUnderTopFrameOrigins", ""),
|
||||
"user_interaction": item.get("hadUserInteraction"),
|
||||
"most_recent_interaction": convert_timestamp_to_iso(item["mostRecentUserInteraction"]),
|
||||
"last_seen": convert_timestamp_to_iso(item["lastSeen"]),
|
||||
})
|
||||
|
||||
return items
|
||||
|
||||
def run(self):
|
||||
self.results = {}
|
||||
if self.is_backup:
|
||||
for log_path in self._get_backup_files_from_manifest(relative_path=WEBKIT_SESSION_RESOURCE_LOG_BACKUP_RELPATH):
|
||||
self.log.info("Found Safari browsing session resource log at path: %s", log_path)
|
||||
self.results[log_path] = self._extract_browsing_stats(log_path)
|
||||
elif self.is_fs_dump:
|
||||
for log_path in self._get_fs_files_from_patterns(WEBKIT_SESSION_RESOURCE_LOG_ROOT_PATHS):
|
||||
self.log.info("Found Safari browsing session resource log at path: %s", log_path)
|
||||
key = os.path.relpath(log_path, self.base_folder)
|
||||
self.results[key] = self._extract_browsing_stats(log_path)
|
||||
|
||||
try:
|
||||
self._find_ios_database(backup_ids=WEBKIT_SESSION_RESOURCE_LOG_BACKUP_IDS)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
else:
|
||||
if self.file_path:
|
||||
self.results[self.file_path] = self._extract_browsing_stats(self.file_path)
|
||||
return
|
||||
|
||||
self.results = self._find_paths(root_paths=WEBKIT_SESSION_RESOURCE_LOG_ROOT_PATHS)
|
||||
for log_file in self.results.keys():
|
||||
self.log.info("Found Safari browsing session resource log at path: %s", log_file)
|
||||
self.results[log_file] = self._extract_browsing_stats(os.path.join(self.base_folder, log_file))
|
||||
self.log.info("Extracted records from %d Safari browsing session resource logs",
|
||||
len(self.results))
|
||||
@@ -1,14 +1,15 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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
|
||||
from ..base import IOSExtraction
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -29,12 +30,12 @@ class Whatsapp(IOSExtraction):
|
||||
log=log, results=results)
|
||||
|
||||
def serialize(self, record):
|
||||
text = record["ZTEXT"].replace("\n", "\\n")
|
||||
text = record.get("ZTEXT", "").replace("\n", "\\n")
|
||||
return {
|
||||
"timestamp": record["isodate"],
|
||||
"timestamp": record.get("isodate"),
|
||||
"module": self.__class__.__name__,
|
||||
"event": "message",
|
||||
"data": f"{text} from {record['ZFROMJID']}"
|
||||
"data": f"{text} from {record.get('ZFROMJID', 'Unknown')}",
|
||||
}
|
||||
|
||||
def check_indicators(self):
|
||||
@@ -42,16 +43,13 @@ class Whatsapp(IOSExtraction):
|
||||
return
|
||||
|
||||
for message in self.results:
|
||||
if not "ZTEXT" in message:
|
||||
continue
|
||||
|
||||
message_links = check_for_links(message["ZTEXT"])
|
||||
message_links = check_for_links(message.get("ZTEXT", ""))
|
||||
if self.indicators.check_domains(message_links):
|
||||
self.detected.append(message)
|
||||
|
||||
def run(self):
|
||||
self._find_ios_database(backup_ids=WHATSAPP_BACKUP_IDS, root_paths=WHATSAPP_ROOT_PATHS)
|
||||
|
||||
self._find_ios_database(backup_ids=WHATSAPP_BACKUP_IDS,
|
||||
root_paths=WHATSAPP_ROOT_PATHS)
|
||||
log.info("Found WhatsApp database at path: %s", self.file_path)
|
||||
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
@@ -60,15 +58,15 @@ class Whatsapp(IOSExtraction):
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for message in cur:
|
||||
new_message = dict()
|
||||
new_message = {}
|
||||
for index, value in enumerate(message):
|
||||
new_message[names[index]] = value
|
||||
|
||||
if not new_message["ZTEXT"]:
|
||||
if not new_message.get("ZTEXT", None):
|
||||
continue
|
||||
|
||||
# We convert Mac's silly timestamp again.
|
||||
new_message["isodate"] = convert_timestamp_to_iso(convert_mactime_to_unix(new_message["ZMESSAGEDATE"]))
|
||||
new_message["isodate"] = convert_timestamp_to_iso(convert_mactime_to_unix(new_message.get("ZMESSAGEDATE")))
|
||||
|
||||
# Extract links from the WhatsApp message.
|
||||
message_links = check_for_links(new_message["ZTEXT"])
|
||||
@@ -1,23 +1,31 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import 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."""
|
||||
|
||||
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 _extract_net_data(self):
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""SELECT
|
||||
cur.execute("""
|
||||
SELECT
|
||||
ZPROCESS.ZFIRSTTIMESTAMP,
|
||||
ZPROCESS.ZTIMESTAMP,
|
||||
ZPROCESS.ZPROCNAME,
|
||||
@@ -31,43 +39,42 @@ class NetBase(IOSExtraction):
|
||||
ZLIVEUSAGE.ZHASPROCESS,
|
||||
ZLIVEUSAGE.ZTIMESTAMP
|
||||
FROM ZLIVEUSAGE
|
||||
LEFT JOIN ZPROCESS ON ZLIVEUSAGE.ZHASPROCESS = ZPROCESS.Z_PK;""")
|
||||
LEFT JOIN ZPROCESS ON ZLIVEUSAGE.ZHASPROCESS = ZPROCESS.Z_PK;
|
||||
""")
|
||||
|
||||
items = []
|
||||
for item in cur:
|
||||
for row in cur:
|
||||
# ZPROCESS records can be missing after the JOIN. Handle NULL timestamps.
|
||||
if item[0] and item[1]:
|
||||
first_isodate = convert_timestamp_to_iso(convert_mactime_to_unix(item[0]))
|
||||
isodate = convert_timestamp_to_iso(convert_mactime_to_unix(item[1]))
|
||||
if row[0] and row[1]:
|
||||
first_isodate = convert_timestamp_to_iso(convert_mactime_to_unix(row[0]))
|
||||
isodate = convert_timestamp_to_iso(convert_mactime_to_unix(row[1]))
|
||||
else:
|
||||
first_isodate = item[0]
|
||||
isodate = item[1]
|
||||
first_isodate = row[0]
|
||||
isodate = row[1]
|
||||
|
||||
if item[11]:
|
||||
live_timestamp = convert_timestamp_to_iso(convert_mactime_to_unix(item[11]))
|
||||
if row[11]:
|
||||
live_timestamp = convert_timestamp_to_iso(convert_mactime_to_unix(row[11]))
|
||||
else:
|
||||
live_timestamp = ""
|
||||
|
||||
items.append(dict(
|
||||
first_isodate=first_isodate,
|
||||
isodate=isodate,
|
||||
proc_name=item[2],
|
||||
bundle_id=item[3],
|
||||
proc_id=item[4],
|
||||
wifi_in=item[5],
|
||||
wifi_out=item[6],
|
||||
wwan_in=item[7],
|
||||
wwan_out=item[8],
|
||||
live_id=item[9],
|
||||
live_proc_id=item[10],
|
||||
live_isodate=live_timestamp,
|
||||
))
|
||||
self.results.append({
|
||||
"first_isodate": first_isodate,
|
||||
"isodate": isodate,
|
||||
"proc_name": row[2],
|
||||
"bundle_id": row[3],
|
||||
"proc_id": row[4],
|
||||
"wifi_in": row[5],
|
||||
"wifi_out": row[6],
|
||||
"wwan_in": row[7],
|
||||
"wwan_out": row[8],
|
||||
"live_id": row[9],
|
||||
"live_proc_id": row[10],
|
||||
"live_isodate": live_timestamp,
|
||||
})
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
self.log.info("Extracted information on %d processes", len(items))
|
||||
self.results = items
|
||||
self.log.info("Extracted information on %d processes", len(self.results))
|
||||
|
||||
def serialize(self, record):
|
||||
record_data = f"{record['proc_name']} (Bundle ID: {record['bundle_id']}, ID: {record['proc_id']})"
|
||||
@@ -97,6 +104,7 @@ class NetBase(IOSExtraction):
|
||||
"data": record_data,
|
||||
}
|
||||
])
|
||||
|
||||
return records
|
||||
|
||||
def _find_suspicious_processes(self):
|
||||
@@ -202,6 +210,15 @@ class NetBase(IOSExtraction):
|
||||
self.results = sorted(self.results, key=operator.itemgetter("first_isodate"))
|
||||
|
||||
def check_indicators(self):
|
||||
# Check for manipulated process records.
|
||||
# TODO: Catching KeyError for live_isodate for retro-compatibility.
|
||||
# This is not very good.
|
||||
try:
|
||||
self.check_manipulated()
|
||||
self.find_deleted()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not self.indicators:
|
||||
return
|
||||
|
||||
@@ -217,12 +234,3 @@ class NetBase(IOSExtraction):
|
||||
|
||||
if self.indicators.check_process(proc_name):
|
||||
self.detected.append(result)
|
||||
|
||||
# Check for manipulated process records.
|
||||
# TODO: Catching KeyError for live_isodate for retro-compatibility.
|
||||
# This is not very good.
|
||||
try:
|
||||
self.check_manipulated()
|
||||
self.find_deleted()
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -1,191 +1,234 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
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 Plus", "identifier": "iPhone8,2"},
|
||||
{"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"},
|
||||
{"build": "18G82", "version": "14.7.1"},
|
||||
]
|
||||
|
||||
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:
|
||||
|
||||
12
setup.py
12
setup.py
@@ -1,13 +1,14 @@
|
||||
# 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
|
||||
# Copyright (c) 2021 The MVT Project Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
__package_name__ = "mvt"
|
||||
__version__ = "1.0.13"
|
||||
__version__ = "1.2.0"
|
||||
__description__ = "Mobile Verification Toolkit"
|
||||
|
||||
this_directory = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -24,7 +25,6 @@ requires = (
|
||||
"requests>=2.26.0",
|
||||
"simplejson>=3.17.3",
|
||||
# iOS dependencies:
|
||||
"biplist>=1.0.3",
|
||||
"iOSbackup>=0.9.912",
|
||||
# Android dependencies:
|
||||
"adb-shell>=0.4.0",
|
||||
|
||||
Reference in New Issue
Block a user