1
mirror of https://github.com/mvt-project/mvt synced 2025-10-21 22:42:15 +02:00

Compare commits

..

67 Commits

Author SHA1 Message Date
Nex
fe8c013b0f Bumped version 2021-07-27 21:40:15 +02:00
Nex
caa5d8ee8c Rename lint_python.yml to lint-python.yml 2021-07-27 21:37:26 +02:00
Nex
2baac1f52c Create python-publish.yml 2021-07-27 21:37:06 +02:00
Nex
dec7616a3d Merge pull request #124 from cclauss/patch-1
GitHub Action to lint Python code
2021-07-27 21:30:11 +02:00
Nex
b1ae777621 Fixed variable name 2021-07-27 21:29:14 +02:00
Nex
404edfee9a Merge branch 'main' of github.com:mvt-project/mvt 2021-07-27 21:28:36 +02:00
Nex
3bb0d5020c Fixed variable name 2021-07-27 21:27:43 +02:00
Christian Clauss
b500ee9429 codespell 2021-07-27 12:11:31 +02:00
Christian Clauss
3f2058441a bandit --recursive --skip B108,B112,B404,B602 . 2021-07-27 12:09:52 +02:00
Christian Clauss
9931edccc4 GitHub Action to lint Python code
Output:
2021-07-27 12:06:47 +02:00
tek
9e33ece3e9 Fixes issue with Manifest format 2021-07-27 01:23:22 +02:00
Nex
32aeaaf91c Update README.md 2021-07-26 21:48:55 +02:00
Nex
8b253b5e7c Update README.md 2021-07-26 21:39:49 +02:00
Nex
362bce7c76 Update README.md 2021-07-26 21:38:36 +02:00
Nex
e821421ca7 Update README.md 2021-07-26 21:35:35 +02:00
Nex
95ab269671 Fixed some formatting 2021-07-26 19:33:12 +02:00
Tek
49f592ebe8 Merge pull request #116 from adamstiefel/patch-1
fix: readme grammar
2021-07-26 10:53:24 +02:00
Adam Stiefel
6b436f2057 fix: readme grammar
Changed "evidences" to "evidence". Changed "understanding basics" to "understanding the basics". Changed "command line" to "command-line"
2021-07-25 17:16:26 -04:00
Nex
13ce55f4ac Added some context to error message 2021-07-25 15:51:24 +02:00
Tek
2ca0081833 Merge pull request #110 from EmilienCourt/fix_whatsapp
[ADB] Fix WhatsApp database parsing (thumb_image)
2021-07-25 15:25:39 +02:00
emilien
3d9574682c Fix WhatsApp thumb image 2021-07-25 14:13:10 +02:00
Nex
3dcc24acd5 Added build 18G69 2021-07-25 12:19:45 +02:00
Nex
8f558db60b Fixed version number 2021-07-25 12:07:22 +02:00
Nex
7a02df4592 Merge branch 'main' of github.com:mvt-project/mvt 2021-07-25 12:04:07 +02:00
Nex
a61d4e17eb Snapshotting dependencies 2021-07-25 12:03:45 +02:00
Nex
3fd8d1524f Updated LICENSE 2021-07-25 12:01:23 +02:00
Nex
d8310797ef Merge pull request #109 from U039b/fix-#108
Fix #108
2021-07-25 11:49:12 +02:00
Nex
7fffef77ce Automatically recover malformed sqlite3 databases (closes: #25 #37) 2021-07-25 11:47:05 +02:00
U039b
b7d65e6123 Fix #108 2021-07-25 11:03:28 +02:00
Nex
9d9b77e02e Changing error message to info, to avoid confusion 2021-07-25 10:46:10 +02:00
Nex
6d0ff11540 Restored empty spaces for new line 2021-07-24 14:27:16 +02:00
Nex
97558ec3af Merge pull request #19 from goshawk22/patch-2
Better check for if device has root
2021-07-24 13:56:12 +02:00
Nex
4fdb868216 Merge pull request #76 from bryeetz/patch-1
Typo
2021-07-24 13:54:59 +02:00
Nex
25d6d52557 Merge pull request #98 from Trigus42/main
Fix download of APKs that require root privileges #2
2021-07-24 13:53:43 +02:00
Nex
d172a3fe69 Merge branch 'febrezo-dockerizing' 2021-07-24 13:24:12 +02:00
Nex
d6f49e76d6 Included Docker details in the documentation 2021-07-24 13:23:45 +02:00
Nex
8883306558 Merge branch 'dockerizing' of https://github.com/febrezo/mvt into febrezo-dockerizing 2021-07-24 13:10:04 +02:00
Trigus42
03523a40c0 Fix _adb_process_file & Improve _adb_download_root
- The _adb_download function doesn't need a package_name argument. This broke _adb_process_file and unnecessarily clutters function calls. Also, the function may be used to download other files or folders too. Generating a random filename seems like the best solution to me since it is less likely to get a duplicate filename and thus to replace an existing file.

- The path /sdcard/Download doesn't necessarily exist. Using /sdcard seems more reliable.
2021-07-24 12:09:59 +02:00
Nex
6c496ec3c2 Merge pull request #84 from pkirkovsky/package-versions
Require click >= 8.0.1
2021-07-23 21:08:07 +02:00
Pavel Kirkovsky
143ceafee2 Merge branch 'mvt-project:main' into package-versions 2021-07-23 12:02:11 -07:00
Nex
ba84b3c18d Fixed variable name (closes: #72) 2021-07-23 18:05:51 +02:00
Nex
8e099e5985 Checking for valid indicators before continuing (closes: #35) 2021-07-23 18:04:41 +02:00
goshawk22
ad3faa186b Use command -v instead of which to check for root
`command` is built in, unlike `which`, and is more reliable.
https://github.com/mvt-project/mvt/pull/19#issuecomment-885650430
https://stackoverflow.com/questions/592620/how-can-i-check-if-a-program-exists-from-a-bash-script/677212#677212
2021-07-23 15:35:56 +01:00
Pavel Kirkovsky
8048ed8c3a Require click >= 8.0.1 2021-07-23 02:08:15 -07:00
Bryan Scheetz
fa49203c9b Typo
adversial -> adversarial
2021-07-22 22:49:26 -04:00
tek
e69449a2f0 Fixes typos 2021-07-22 23:21:31 +02:00
febrezo
684aed8d11 Add compilation of libimobiledevice for iOS compatibility
Added considering the feedback reported in the #16 discussion.
2021-07-22 17:44:17 +02:00
tek
b19db5543b Update README 2021-07-21 13:59:54 +02:00
Nex
af7c45ae22 Merge branch 'master' of github.com:mvt-project/mvt 2021-07-21 11:54:13 +02:00
Nex
8d68e7a166 Better handling of special characters when saving a timeline 2021-07-21 11:53:41 +02:00
Nex
3004690fd1 Merge pull request #21 from pkirkovsky/prompt-password
Prompt for password if none is given
2021-07-21 11:20:24 +02:00
Nex
2f05d4b4f9 Fixed typo 2021-07-21 11:07:15 +02:00
tek
f0a9196094 Merge branch 'master' of github.com:mvt-project/mvt 2021-07-21 10:44:43 +02:00
tek
ce46e608de fixes documentation 2021-07-21 10:44:10 +02:00
Tek
791e7db59c Merge pull request #7 from lunakk-PL/patch-1
Update download_apks.md
2021-07-21 10:32:48 +02:00
tek
3e048c4338 updated readme 2021-07-21 10:25:02 +02:00
Tek
a23b890350 Merge pull request #30 from runasand/patch-1
Update README.md
2021-07-21 10:16:29 +02:00
Tek
8fbf95a262 Merge pull request #31 from recurrence/master
[iOS CLI] Remove non-existent SYSDIAGNOSE_MODULES reference
2021-07-21 10:15:56 +02:00
Tyler Kellogg
967eb75e7c [iOS CLI] Remove non-existent SYSDIAGNOSE_MODULES reference 2021-07-20 15:01:09 -07:00
Runa Sandvik
2276df4f1b Update README.md
Use pip3 to install mvt from pypi
2021-07-20 17:55:22 -04:00
Pavel Kirkovsky
695555f26f Prompt for password if none is given 2021-07-20 05:44:36 -07:00
febrezo
1adf3f430b Add welcome message when the terminal is launched 2021-07-20 14:20:27 +02:00
Adam Lawson
9317586851 Better check for if device has root
"which su" will return the path of the su binary, or it will return nothing. 
The python boolean of a string with something in it (such as the path of the su binary), will be True.
An empty string (where there is no su binary) will be False.
2021-07-20 12:55:10 +01:00
Adam Lawson
cb6bde5b8c Fix download of APKs that require root privileges
Some system APKs are stored in directories that require root privileges, such as /system/product.
2021-07-20 12:53:44 +01:00
febrezo
f3afc871cd Create alias for abe instead of custom command 2021-07-20 13:45:55 +02:00
febrezo
8c855b645d Add Dockerfile with Android dependencies solved 2021-07-20 12:10:37 +02:00
lunakk-PL
167f7e3d77 Update download_apks.md
proper Koodous link -> https://koodous.com/
2021-07-19 13:45:47 +02:00
27 changed files with 339 additions and 63 deletions

21
.github/workflows/lint-python.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
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

36
.github/workflows/python-publish.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
# 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 }}

65
Dockerfile Normal file
View File

@@ -0,0 +1,65 @@
FROM ubuntu:20.04
# Ref. https://github.com/mvt-project/mvt
# Fixing major OS dependencies
# ----------------------------
RUN apt update \
&& apt install -y python3 python3-pip libusb-1.0-0-dev \
&& apt install -y wget \
&& apt install -y adb \
&& DEBIAN_FRONTEND=noninteractive apt-get -y install default-jre-headless
# Install build tools for libimobiledevice
# ----------------------------------------
RUN apt install -y build-essential \
checkinstall \
git \
autoconf \
automake \
libtool-bin \
libplist-dev \
libusbmuxd-dev \
libssl-dev \
pkg-config
# Clean up
# --------
RUN apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Build libimobiledevice
# ----------------------
RUN git clone https://github.com/libimobiledevice/libplist
RUN git clone https://github.com/libimobiledevice/libusbmuxd
RUN git clone https://github.com/libimobiledevice/libimobiledevice
RUN git clone https://github.com/libimobiledevice/usbmuxd
RUN cd libplist && ./autogen.sh && make && make install && ldconfig
RUN cd libusbmuxd && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh && make && make install && ldconfig
RUN cd libimobiledevice && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --enable-debug && make && make install && ldconfig
RUN cd usbmuxd && PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./autogen.sh --prefix=/usr --sysconfdir=/etc --localstatedir=/var --runstatedir=/run && make && make install
# Installing MVT
# --------------
RUN pip3 install mvt
# Installing ABE
# --------------
RUN mkdir /opt/abe
RUN wget https://github.com/nelenkov/android-backup-extractor/releases/download/20210709062403-4c55371/abe.jar -O /opt/abe/abe.jar
# Create alias for abe
RUN echo 'alias abe="java -jar /opt/abe/abe.jar"' >> ~/.bashrc
# Setup investigations environment
# --------------------------------
RUN mkdir /home/cases
WORKDIR /home/cases
RUN echo 'echo "Mobile Verification Toolkit @ Docker\n------------------------------------\n\nYou can find information about how to use this image for Android (https://github.com/mvt-project/mvt/tree/master/docs/android) and iOS (https://github.com/mvt-project/mvt/tree/master/docs/ios) in the official docs of the project.\n"' >> ~/.bashrc
RUN echo 'echo "Note that to perform the debug via USB you might need to give the Docker image access to the USB using \"docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb mvt\" or, preferably, the \"--device=\" parameter.\n"' >> ~/.bashrc
CMD /bin/bash

20
LICENSE
View File

@@ -1,4 +1,4 @@
MVT License 1.0
MVT License 1.1
===============
1. Definitions
@@ -35,7 +35,7 @@ MVT License 1.0
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
@@ -89,16 +89,16 @@ MVT License 1.0
a Larger Work.
1.16. "Device Owner" (or "Device Owners")
means an individal or a legal entity with legal ownership of an
means an individual or a legal entity with legal ownership of an
electronic device which is being analysed through the use of
Covered Software or a Larger Work, or from which Data was extracted
for subsequent analysis.
1.17. "Data Owner" (or "Data Owners")
means an individial or group of individuals who made use of the
electronic device from which Data that is extracted and/or analyzed
originated. "Data Owner" might or might not differ from "Device
Owner".
means an individual or group of individuals who made legitimate use
of the electronic device from which Data that is extracted and/or
analyzed originated. "Data Owner" might or might not differ from
"Device Owner".
2. License Grants and Conditions
--------------------------------
@@ -381,8 +381,8 @@ Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the MVT License,
v. 1.0. If a copy of the MVT License was not distributed with this
file, You can obtain one at TODO.
v. 1.1. If a copy of the MVT License was not distributed with this
file, You can obtain one at https://license.mvt.re/1.1/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
@@ -395,7 +395,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the MVT License, v. 1.0.
defined by the MVT License, v. 1.1.
This license is an adaption of Mozilla Public License, v. 2.0.

View File

@@ -5,21 +5,26 @@
# Mobile Verification Toolkit
[![](https://img.shields.io/pypi/v/mvt)](https://pypi.org/project/mvt/)
[![](https://img.shields.io/badge/docs-blue.svg)](https://mvt.readthedocs.io)
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.
[Please check out the documentation.](https://mvt.readthedocs.io/en/latest/)
It has been developed and released by the [Amnesty International Security Lab](https://www.amnesty.org/en/tech/) in July 2021 in the context of the [Pegasus project](https://forbiddenstories.org/about-the-pegasus-project/) along with [a technical forensic methodology and forensic evidence](https://www.amnesty.org/en/latest/research/2021/07/forensic-methodology-report-how-to-catch-nso-groups-pegasus/).
*Warning*: MVT is a forensic research tool intended for technologists and investigators. Using it requires understanding the basics of forensic analysis and using command-line tools. This is not intended for end-user self-assessment. If you are concerned with the security of your device please seek expert assistance.
## Installation
First you need to install dependencies, on Linux `sudo apt install python3 python3-pip libusb-1.0-0` or on MacOS `brew install python3 libusb`.
MVT can be installed from sources or conveniently using:
Then you can install mvt from pypi with `pip install mvt`, or directly form sources:
```bash
git clone https://github.com/mvt-project/mvt.git
cd mvt
pip3 install .
```
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).
## Usage
@@ -36,6 +41,7 @@ MVT provides two commands `mvt-ios` and `mvt-android` with the following subcomm
Check out [the documentation to see how to use them](https://mvt.readthedocs.io/en/latest/).
## 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*.

View File

@@ -25,7 +25,7 @@ tar xvf backup.tar
You can then extract SMSs containing links with MVT:
```bash
$ mvt-android check-backup --output sms .
$ mvt-android check-backup --output . .
16:18:38 INFO [mvt.android.cli] Checking ADB backup located at: .
INFO [mvt.android.modules.backup.sms] Running module SMS...
INFO [mvt.android.modules.backup.sms] Processing SMS backup

View File

@@ -10,7 +10,7 @@ Now you can launch `mvt-android` and specify the `download-apks` command and the
mvt-android download-apks --output /path/to/folder
```
Optionally, you can decide to enable lookups of the SHA256 hash of all the extracted APKs on [VirusTotal](https://www.virustotal.com) and/or [Koodous](https://www.koodous.com). While these lookups do not provide any conclusive assessment on all of the extracted APKs, they might highlight any known malicious ones:
Optionally, you can decide to enable lookups of the SHA256 hash of all the extracted APKs on [VirusTotal](https://www.virustotal.com) and/or [Koodous](https://koodous.com). While these lookups do not provide any conclusive assessment on all of the extracted APKs, they might highlight any known malicious ones:
```bash
mvt-android download-apks --output /path/to/folder --virustotal

35
docs/docker.md Normal file
View File

@@ -0,0 +1,35 @@
## 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/).
Once installed, you can clone MVT's repository and build its Docker image:
```bash
git clone https://github.com/mvt-project/mvt.git
cd mvt
docker build -t mvt .
```
Test if the image was created successfully:
```bash
docker run -it mvt
```
If a prompt is spawned successfully, you can close it with `exit`.
If you wish to use MVT to test an Android device you will need to enable the container's access to the host's USB devices. You can do so by enabling the `--privileged` flag and mounting the USB bus device as a volume:
```bash
docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb mvt
```
**Please note:** the `--privileged` parameter is generally regarded as a security risk. If you want to learn more about this check out [this explainer on container escapes](https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/) as it gives access to the whole system.
Recent versions of Docker provide a `--device` parameter allowing to specify a precise USB device without enabling `--privileged`:
```bash
docker run -it --device=/dev/<your_usb_port> mvt
```

View File

@@ -7,7 +7,7 @@ Before proceeding, please note that mvt requires Python 3.6+ to run. While it sh
First install some basic dependencies that will be necessary to build all required tools:
```bash
sudo apt install python3 python3-pip libusb-1.0-0
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`.
@@ -19,7 +19,7 @@ Running MVT on Mac requires Xcode and [homebrew](https://brew.sh) to be installe
In order to install dependencies use:
```bash
brew install python3 libusb
brew install python3 libusb sqlite3
```
*libusb* is not required if you intend to only use `mvt-ios` and not `mvt-android`.

View File

@@ -14,4 +14,4 @@ Mobile Verification Toolkit (MVT) is a collection of utilities designed to facil
While MVT is capable of extracting and processing various types of very personal records typically found on a mobile phone (such as calls history, SMS and WhatsApp messages, etc.), this is intended to help identify potential attack vectors such as malicious SMS messages leading to exploitation.
MVT's purpose is not to facilitate adversial forensics of non-consenting individuals' devices. The use of MVT and derivative products to extract and/or analyse data originating from devices used by individuals not consenting to the procedure is explicitly prohibited in the [license](license.md).
MVT's purpose is not to facilitate adversarial forensics of non-consenting individuals' devices. The use of MVT and derivative products to extract and/or analyse data originating from devices used by individuals not consenting to the procedure is explicitly prohibited in the [license](license.md).

View File

@@ -1,6 +1,6 @@
# Dumping the filesystem
While iTunes backup provide a lot of very useful databases and diagnistic data, in some cases you might want to jailbreak the device and perform a full filesystem dump. In that case, you should take a look at [checkra1n](https://checkra.in/), which provides an easy way to obtain root on most recent iPhone models.
While iTunes backup provide a lot of very useful databases and diagnostic data, in some cases you might want to jailbreak the device and perform a full filesystem dump. In that case, you should take a look at [checkra1n](https://checkra.in/), which provides an easy way to obtain root on most recent iPhone models.
!!! warning
Before you checkra1n any device, make sure you take a full backup, and that you are prepared to do a full factory reset before restoring it. Even after using checkra1n's "Restore System", some traces of the jailbreak are still left on the device and [apps with anti-jailbreaks will be able to detect them](https://github.com/checkra1n/BugTracker/issues/279) and stop functioning.

View File

@@ -6,7 +6,7 @@ Before jumping into acquiring and analyzing data from an iOS device, you should
You will need to decide whether to attempt to jailbreak the device and obtain a full filesystem dump, or not.
While access the full filesystem allows to extact data that would otherwise be unavailable, it might not always be possible to jailbreak a certain iPhone model or version of iOS. In addition, depending on the type of jailbreak available, doing so might compromise some important records, pollute others, or potentially cause unintended malfunctioning of the device later in case it is used again.
While access the full filesystem allows to extract data that would otherwise be unavailable, it might not always be possible to jailbreak a certain iPhone model or version of iOS. In addition, depending on the type of jailbreak available, doing so might compromise some important records, pollute others, or potentially cause unintended malfunctioning of the device later in case it is used again.
If you are not expected to return the phone, you might want to consider to attempt a jailbreak after having exhausted all other options, including a backup.

View File

@@ -236,7 +236,7 @@ If indicators are provided through the command-line, they are checked against th
Backup: :material-close:
Full filesystem dump: :material-check:
This JSON file is created by mvt-ios' `WebkitLocalStorage` module. The module extracts a lsit of file and folder names located at the following path */private/var/mobile/Containers/Data/Application/\*/Library/WebKit/WebsiteData/LocalStorage/*, which contains local storage files created by any app installed on the device.
This JSON file is created by mvt-ios' `WebkitLocalStorage` module. The module extracts a list of file and folder names located at the following path */private/var/mobile/Containers/Data/Application/\*/Library/WebKit/WebsiteData/LocalStorage/*, which contains local storage files created by any app installed on the device.
If indicators are provided through the command-line, they are checked against the extracted names. Any matches are stored in *webkit_local_storage_detected.json*.

View File

@@ -28,6 +28,7 @@ nav:
- Welcome: "index.md"
- Introduction: "introduction.md"
- Installation: "install.md"
- Using Docker: "docker.md"
- MVT for iOS:
- iOS Forensic Methodology: "ios/methodology.md"
- Install libimobiledevice: "ios/install.md"

View File

@@ -16,7 +16,7 @@ log = logging.getLogger(__name__)
# TODO: Would be better to replace tqdm with rich.progress to reduce
# the number of dependencies. Need to investigate whether
# it's possible to have a simialr callback system.
# it's possible to have a similar callback system.
class PullProgress(tqdm):
"""PullProgress is a tqdm update system for APK downloads."""
@@ -42,7 +42,7 @@ class DownloadAPKs(AndroidExtraction):
"""Initialize module.
:param output_folder: Path to the folder where data should be stored
:param all_apks: Boolean indicating whether to download all packages
or filter known-goods
or filter known-goods
:param packages: Provided list of packages, typically for JSON checks
"""
super().__init__(file_path=None, base_folder=None,

View File

@@ -4,6 +4,8 @@
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import os
import random
import string
import sys
import time
import logging
@@ -96,7 +98,7 @@ class AndroidExtraction(MVTModule):
"""Check if we have a `su` binary on the Android device.
:returns: Boolean indicating whether a `su` binary is present or not
"""
return bool(self._adb_command("[ ! -f /sbin/su ] || echo 1"))
return bool(self._adb_command("command -v su"))
def _adb_root_or_die(self):
"""Check if we have a `su` binary, otherwise raise an Exception.
@@ -111,7 +113,7 @@ class AndroidExtraction(MVTModule):
"""
return self._adb_command(f"su -c {command}")
def _adb_download(self, remote_path, local_path, progress_callback=None):
def _adb_download(self, remote_path, local_path, progress_callback=None, retry_root=True):
"""Download a file form the device.
:param remote_path: Path to download from the device
:param local_path: Path to where to locally store the copy of the file
@@ -119,6 +121,37 @@ class AndroidExtraction(MVTModule):
"""
try:
self.device.pull(remote_path, local_path, progress_callback)
except AdbCommandFailureException as e:
if retry_root:
self._adb_download_root(remote_path, local_path, progress_callback)
else:
raise Exception(f"Unable to download file {remote_path}: {e}")
def _adb_download_root(self, remote_path, local_path, progress_callback=None):
try:
# Check if we have root, if not raise an Exception.
self._adb_root_or_die()
# We generate a random temporary filename.
tmp_filename = "tmp_" + ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + string.digits, k=10))
# We create a temporary local file.
new_remote_path = f"/sdcard/{tmp_filename}"
# We copy the file from the data folder to /sdcard/.
cp = self._adb_command_as_root(f"cp {remote_path} {new_remote_path}")
if cp.startswith("cp: ") and "No such file or directory" in cp:
raise Exception(f"Unable to process file {remote_path}: File not found")
elif cp.startswith("cp: ") and "Permission denied" in cp:
raise Exception(f"Unable to process file {remote_path}: Permission denied")
# We download from /sdcard/ to the local temporary file.
# If it doesn't work now, don't try again (retry_root=False)
self._adb_download(new_remote_path, local_path, retry_root=False)
# Delete the copy on /sdcard/.
self._adb_command(f"rm -rf {new_remote_path}")
except AdbCommandFailureException as e:
raise Exception(f"Unable to download file {remote_path}: {e}")

View File

@@ -6,6 +6,7 @@
import os
import sqlite3
import logging
import base64
from .base import AndroidExtraction
from mvt.common.utils import convert_timestamp_to_iso, check_for_links
@@ -69,6 +70,8 @@ class Whatsapp(AndroidExtraction):
# If we find links in the messages or if they are empty we add them to the list.
if check_for_links(message["data"]) or message["data"].strip() == "":
if (message.get('thumb_image') is not None):
message['thumb_image'] = base64.b64encode(message['thumb_image'])
messages.append(message)
cur.close()

View File

@@ -3,6 +3,7 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import io
import os
import re
import csv
@@ -12,6 +13,12 @@ import simplejson as json
from .indicators import Indicators
class DatabaseNotFoundError(Exception):
pass
class DatabaseCorruptedError(Exception):
pass
class MVTModule(object):
"""This class provides a base for all extraction modules."""
@@ -66,7 +73,7 @@ class MVTModule(object):
self.indicators = Indicators(file_path, self.log)
def check_indicators(self):
"""Check the results of this module againt a provided list of
"""Check the results of this module against a provided list of
indicators."""
raise NotImplementedError
@@ -82,7 +89,11 @@ class MVTModule(object):
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:
json.dump(self.results, handle, indent=4)
try:
json.dump(self.results, handle, indent=4)
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)
if self.detected:
detected_file_name = f"{name}_detected.json"
@@ -98,17 +109,19 @@ class MVTModule(object):
"""
for result in self.results:
record = self.serialize(result)
if type(record) == list:
self.timeline.extend(record)
else:
self.timeline.append(record)
if record:
if type(record) == list:
self.timeline.extend(record)
else:
self.timeline.append(record)
for detected in self.detected:
record = self.serialize(detected)
if type(record) == list:
self.timeline_detected.extend(record)
else:
self.timeline_detected.append(record)
if record:
if type(record) == list:
self.timeline_detected.extend(record)
else:
self.timeline_detected.append(record)
# De-duplicate timeline entries
self.timeline = self.timeline_deduplicate(self.timeline)
@@ -135,8 +148,11 @@ def run_module(module):
except NotImplementedError:
module.log.exception("The run() procedure of module %s was not implemented yet!",
module.__class__.__name__)
except FileNotFoundError as e:
module.log.error("There might be no data to extract by module %s: %s",
except DatabaseNotFoundError as e:
module.log.info("There might be no data to extract by module %s: %s",
module.__class__.__name__, e)
except DatabaseCorruptedError as e:
module.log.error("The %s module database seems to be corrupted and recovery failed: %s",
module.__class__.__name__, e)
except Exception as e:
module.log.exception("Error in running extraction from module %s: %s",
@@ -160,7 +176,7 @@ def save_timeline(timeline, timeline_path):
:param timeline: List of records to order and store.
:param timeline_path: Path to the csv file to store the timeline to.
"""
with open(timeline_path, "a+") as handle:
with io.open(timeline_path, "a+", encoding="utf-8") as handle:
csvoutput = csv.writer(handle, delimiter=",", quotechar="\"")
csvoutput.writerow(["UTC Timestamp", "Plugin", "Event", "Description"])
for event in sorted(timeline, key=lambda x: x["timestamp"] if x["timestamp"] is not None else ""):

View File

@@ -43,6 +43,7 @@ def cli():
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,
mutually_exclusive=["key_file"])
@click.option("--key-file", "-k", cls=MutuallyExclusiveOption,
type=click.Path(exists=True),
@@ -172,7 +173,7 @@ def check_fs(iocs, output, fast, dump_path, list_modules, module):
@click.argument("FOLDER", type=click.Path(exists=True))
def check_iocs(iocs, list_modules, module, folder):
all_modules = []
for entry in BACKUP_MODULES + FS_MODULES + SYSDIAGNOSE_MODULES:
for entry in BACKUP_MODULES + FS_MODULES:
if entry not in all_modules:
all_modules.append(entry)

View File

@@ -3,10 +3,15 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
import 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."""
@@ -15,6 +20,31 @@ class IOSExtraction(MVTModule):
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.
@@ -52,4 +82,20 @@ class IOSExtraction(MVTModule):
if file_path:
self.file_path = file_path
else:
raise FileNotFoundError("Unable to find the module's database file")
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)

View File

@@ -43,10 +43,10 @@ class LocationdClients(IOSExtraction):
for ts in self.timestamps:
if ts in record.keys():
records.append({
"timestamp": entry[ts],
"timestamp": record[ts],
"module": self.__class__.__name__,
"event": ts,
"data": f"{ts} from {entry['package']}"
"data": f"{ts} from {record['package']}"
})
return records
@@ -62,7 +62,7 @@ class LocationdClients(IOSExtraction):
result["package"] = app
for ts in self.timestamps:
if ts in result.keys():
result[ts] = convert_timestamp_to_iso(convert_mactime_to_unix(result[date]))
result[ts] = convert_timestamp_to_iso(convert_mactime_to_unix(result[ts]))
self.results.append(result)

View File

@@ -40,6 +40,8 @@ class Manifest(IOSExtraction):
def serialize(self, record):
records = []
if "modified" not in record or "statusChanged" not in record:
return
for ts in set([record["created"], record["modified"], record["statusChanged"]]):
macb = ""
macb += "M" if ts == record["modified"] else "-"
@@ -63,12 +65,15 @@ class Manifest(IOSExtraction):
for result in self.results:
if not "relativePath" in result:
continue
if os.path.basename(result["relativePath"]) == "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)
if not result["relativePath"]:
continue
if result["domain"]:
if os.path.basename(result["relativePath"]) == "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"])
self.detected.append(result)

View File

@@ -167,6 +167,10 @@ class NetBase(IOSExtraction):
results_by_proc = {proc["proc_id"]: proc for proc in self.results if proc["proc_id"]}
all_proc_id = sorted(results_by_proc.keys())
# Fix issue #108
if not all_proc_id:
return
missing_procs, last_proc_id = {}, None
for proc_id in range(min(all_proc_id), max(all_proc_id)):
if proc_id not in all_proc_id:

View File

@@ -70,6 +70,9 @@ class WebkitSessionResourceLog(IOSExtraction):
return domains
def check_indicators(self):
if not self.indicators:
return
for key, entries in self.results.items():
for entry in entries:
source_domains = self._extract_domains(entry["redirect_source"])

View File

@@ -73,7 +73,7 @@ class Whatsapp(IOSExtraction):
# Extract links from the WhatsApp message.
message_links = check_for_links(new_message["ZTEXT"])
# If we find mesages, or if there's an empty message we add it to the list.
# If we find messages, or if there's an empty message we add it to the list.
if new_message["ZTEXT"] and (message_links or new_message["ZTEXT"].strip() == ""):
self.results.append(new_message)

View File

@@ -102,6 +102,7 @@ IPHONE_IOS_VERSIONS = [
{"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": "18D52", "version": "14.4"},

View File

@@ -7,7 +7,7 @@ import os
from setuptools import setup, find_packages
__package_name__ = "mvt"
__version__ = "1.0.11"
__version__ = "1.0.13"
__description__ = "Mobile Verification Toolkit"
this_directory = os.path.abspath(os.path.dirname(__file__))
@@ -17,18 +17,18 @@ with open(readme_path, encoding="utf-8") as handle:
requires = (
# Base dependencies:
"click",
"rich",
"tld",
"tqdm",
"requests",
"simplejson",
"click>=8.0.1",
"rich>=10.6.0",
"tld>=0.12.6",
"tqdm>=4.61.2",
"requests>=2.26.0",
"simplejson>=3.17.3",
# iOS dependencies:
"biplist",
"iOSbackup",
"biplist>=1.0.3",
"iOSbackup>=0.9.912",
# Android dependencies:
"adb-shell",
"libusb1",
"adb-shell>=0.4.0",
"libusb1>=1.9.3",
)
def get_package_data(package):